@@ -46,14 +46,12 @@ struct ZoneParams
4646cbuffer ColorBlindnessNGParams : register (b0)
4747{
4848 // Global parameters (32 bytes)
49- float2 ViewportSize;
49+ float2 MousePosition; // Mouse position in screen pixels
50+ float2 ViewportSize; // Viewport size in pixels
5051 float SplitModeValue; // 0=Full, 1=SplitV, 2=SplitH, 3=Quad
5152 float SplitPosition; // Horizontal split (0-1)
52-
5353 float SplitPositionV; // Vertical split (0-1)
54- float ComparisonMode; // 0=off, 1=on (zone 0 shows original)
55- float _globalPad1;
56- float _globalPad2;
54+ float ComparisonMode; // 0=off, 1=on (screen duplication mode)
5755
5856 // Per-zone parameters (64 bytes each × 4 = 256 bytes)
5957 ZoneParams Zone0;
@@ -669,6 +667,176 @@ float GetSeparatorAlpha(float2 screenPos)
669667 return 0.0 ;
670668}
671669
670+ // ============================================================================
671+ // Comparison Mode - Screen Duplication
672+ // ============================================================================
673+
674+ // Get zone bounds in screen coordinates
675+ void GetZoneBounds (int zoneIndex, out float2 zoneMin, out float2 zoneMax)
676+ {
677+ float splitX = ViewportSize.x * SplitPosition;
678+ float splitY = ViewportSize.y * SplitPositionV;
679+
680+ if (SplitModeValue > 2.5 ) // Quadrants (3)
681+ {
682+ if (zoneIndex == 0 ) // Top-Left
683+ {
684+ zoneMin = float2 (0 , 0 );
685+ zoneMax = float2 (splitX, splitY);
686+ }
687+ else if (zoneIndex == 1 ) // Top-Right
688+ {
689+ zoneMin = float2 (splitX, 0 );
690+ zoneMax = float2 (ViewportSize.x, splitY);
691+ }
692+ else if (zoneIndex == 2 ) // Bottom-Left
693+ {
694+ zoneMin = float2 (0 , splitY);
695+ zoneMax = float2 (splitX, ViewportSize.y);
696+ }
697+ else // Bottom-Right (3)
698+ {
699+ zoneMin = float2 (splitX, splitY);
700+ zoneMax = ViewportSize;
701+ }
702+ }
703+ else if (SplitModeValue > 1.5 ) // Split Horizontal (2)
704+ {
705+ if (zoneIndex == 0 ) // Top
706+ {
707+ zoneMin = float2 (0 , 0 );
708+ zoneMax = float2 (ViewportSize.x, splitY);
709+ }
710+ else // Bottom
711+ {
712+ zoneMin = float2 (0 , splitY);
713+ zoneMax = ViewportSize;
714+ }
715+ }
716+ else // Split Vertical (1)
717+ {
718+ if (zoneIndex == 0 ) // Left
719+ {
720+ zoneMin = float2 (0 , 0 );
721+ zoneMax = float2 (splitX, ViewportSize.y);
722+ }
723+ else // Right
724+ {
725+ zoneMin = float2 (splitX, 0 );
726+ zoneMax = ViewportSize;
727+ }
728+ }
729+ }
730+
731+ // Get UV coordinates for comparison mode with aspect ratio preservation
732+ // Returns (-1, -1) if pixel is in letterbox/pillarbox area (black bars)
733+ float2 GetComparisonUV (float2 screenPos, int zoneIndex)
734+ {
735+ float2 zoneMin, zoneMax;
736+ GetZoneBounds (zoneIndex, zoneMin, zoneMax);
737+ float2 zoneSize = zoneMax - zoneMin;
738+ float2 zoneCenter = (zoneMin + zoneMax) * 0.5 ;
739+
740+ if (SplitModeValue > 2.5 ) // Quadrants - stretch to fit
741+ {
742+ return (screenPos - zoneMin) / zoneSize;
743+ }
744+
745+ // Split modes - preserve aspect ratio with letterboxing/pillarboxing
746+ float screenAspect = ViewportSize.x / ViewportSize.y;
747+ float zoneAspect = zoneSize.x / zoneSize.y;
748+
749+ float2 scaledSize;
750+ if (screenAspect > zoneAspect)
751+ {
752+ // Screen is wider than zone - fit to zone width, letterbox
753+ scaledSize.x = zoneSize.x;
754+ scaledSize.y = zoneSize.x / screenAspect;
755+ }
756+ else
757+ {
758+ // Screen is taller than zone - fit to zone height, pillarbox
759+ scaledSize.y = zoneSize.y;
760+ scaledSize.x = zoneSize.y * screenAspect;
761+ }
762+
763+ float2 contentMin = zoneCenter - scaledSize * 0.5 ;
764+ float2 contentMax = zoneCenter + scaledSize * 0.5 ;
765+
766+ // Check if pixel is outside content area (in black bars)
767+ if (screenPos.x < contentMin.x || screenPos.x > contentMax.x ||
768+ screenPos.y < contentMin.y || screenPos.y > contentMax.y)
769+ {
770+ return float2 (-1 , -1 ); // Signal to render black
771+ }
772+
773+ return (screenPos - contentMin) / scaledSize;
774+ }
775+
776+ // Get the transformed mouse position within a zone for comparison mode
777+ float2 GetTransformedMousePos (int zoneIndex)
778+ {
779+ float2 zoneMin, zoneMax;
780+ GetZoneBounds (zoneIndex, zoneMin, zoneMax);
781+ float2 zoneSize = zoneMax - zoneMin;
782+ float2 zoneCenter = (zoneMin + zoneMax) * 0.5 ;
783+
784+ // Normalize mouse position to 0-1
785+ float2 normalizedMouse = MousePosition / ViewportSize;
786+
787+ if (SplitModeValue > 2.5 ) // Quadrants - stretch to fit
788+ {
789+ return zoneMin + normalizedMouse * zoneSize;
790+ }
791+
792+ // Split modes - preserve aspect ratio
793+ float screenAspect = ViewportSize.x / ViewportSize.y;
794+ float zoneAspect = zoneSize.x / zoneSize.y;
795+
796+ float2 scaledSize;
797+ if (screenAspect > zoneAspect)
798+ {
799+ scaledSize.x = zoneSize.x;
800+ scaledSize.y = zoneSize.x / screenAspect;
801+ }
802+ else
803+ {
804+ scaledSize.y = zoneSize.y;
805+ scaledSize.x = zoneSize.y * screenAspect;
806+ }
807+
808+ float2 contentMin = zoneCenter - scaledSize * 0.5 ;
809+ return contentMin + normalizedMouse * scaledSize;
810+ }
811+
812+ // Draw a crosshair cursor indicator
813+ float DrawCursor (float2 screenPos, float2 cursorPos, float size)
814+ {
815+ float2 delta = screenPos - cursorPos;
816+ float dist = length (delta);
817+
818+ // Crosshair parameters
819+ float lineWidth = 2.0 ;
820+ float innerRadius = 4.0 ;
821+ float outerRadius = size;
822+
823+ // Horizontal line
824+ bool onHLine = abs (delta.y) < lineWidth && abs (delta.x) > innerRadius && abs (delta.x) < outerRadius;
825+ // Vertical line
826+ bool onVLine = abs (delta.x) < lineWidth && abs (delta.y) > innerRadius && abs (delta.y) < outerRadius;
827+ // Center dot
828+ bool onCenter = dist < 3.0 ;
829+ // Outer circle (thin)
830+ bool onCircle = abs (dist - outerRadius) < 1.5 ;
831+
832+ if (onHLine || onVLine || onCenter || onCircle)
833+ {
834+ return 1.0 ;
835+ }
836+
837+ return 0.0 ;
838+ }
839+
672840// ============================================================================
673841// Pixel Shader
674842// ============================================================================
@@ -678,36 +846,88 @@ float4 PSMain(PSInput input) : SV_TARGET
678846 float2 uv = input.TexCoord;
679847 float2 screenPos = uv * ViewportSize;
680848
681- // Sample original screen color
682- float4 screenColor = ScreenTexture.Sample (LinearSampler, uv);
683- float3 color = screenColor.rgb;
684-
685849 // Get zone index
686850 int zoneIndex = GetZoneIndex (screenPos);
687851
688- float3 finalColor;
689-
690- // In comparison mode, zone 0 always shows original
691- bool forceOriginal = (ComparisonMode > 0.5 && zoneIndex == 0 );
852+ // Handle comparison mode - full screen duplication in each zone
853+ float2 sampleUV = uv;
854+ bool isBlackBar = false ;
692855
693- if (forceOriginal )
856+ if (ComparisonMode > 0.5 && SplitModeValue > 0.5 )
694857 {
695- finalColor = color;
858+ // In comparison mode, duplicate the full screen into each zone
859+ float2 comparisonUV = GetComparisonUV (screenPos, zoneIndex);
860+ if (comparisonUV.x < 0 )
861+ {
862+ isBlackBar = true ;
863+ }
864+ else
865+ {
866+ sampleUV = comparisonUV;
867+ }
696868 }
697- else
869+
870+ // Return black for letterbox/pillarbox areas
871+ if (isBlackBar)
698872 {
699- // Get zone parameters and process
700- ZoneParams zone;
701- if (zoneIndex == 0 )
702- zone = Zone0;
703- else if (zoneIndex == 1 )
704- zone = Zone1;
705- else if (zoneIndex == 2 )
706- zone = Zone2;
707- else
708- zone = Zone3;
873+ return float4 (0.0 , 0.0 , 0.0 , 1.0 );
874+ }
709875
710- finalColor = ProcessZone (color, zone, zoneIndex);
876+ // Sample original screen color at the (possibly remapped) UV
877+ float4 screenColor = ScreenTexture.Sample (LinearSampler, sampleUV);
878+ float3 color = screenColor.rgb;
879+
880+ float3 finalColor;
881+
882+ // Get zone parameters and process
883+ ZoneParams zone;
884+ if (zoneIndex == 0 )
885+ zone = Zone0;
886+ else if (zoneIndex == 1 )
887+ zone = Zone1;
888+ else if (zoneIndex == 2 )
889+ zone = Zone2;
890+ else
891+ zone = Zone3;
892+
893+ finalColor = ProcessZone (color, zone, zoneIndex);
894+
895+ // Draw virtual cursor in comparison mode
896+ if (ComparisonMode > 0.5 && SplitModeValue > 0.5 )
897+ {
898+ float2 transformedMouse = GetTransformedMousePos (zoneIndex);
899+ float cursorSize = 15.0 ;
900+
901+ // Scale cursor size based on zone size (smaller for quadrants)
902+ if (SplitModeValue > 2.5 ) // Quadrants
903+ {
904+ cursorSize = 12.0 ;
905+ }
906+
907+ float cursorAlpha = DrawCursor (screenPos, transformedMouse, cursorSize);
908+
909+ if (cursorAlpha > 0.5 )
910+ {
911+ // Draw cursor with contrasting color (inverted luminance)
912+ float lum = dot (finalColor, float3 (0.299 , 0.587 , 0.114 ));
913+ float3 cursorColor = lum > 0.5 ? float3 (0.0 , 0.0 , 0.0 ) : float3 (1.0 , 1.0 , 1.0 );
914+
915+ // Add colored accent for better visibility
916+ float2 delta = screenPos - transformedMouse;
917+ float dist = length (delta);
918+
919+ // Center dot and outer ring - use accent color (cyan/magenta based on zone)
920+ if (dist < 3.0 || abs (dist - cursorSize) < 1.5 )
921+ {
922+ float3 accentColor = (zoneIndex % 2 == 0 ) ? float3 (0.0 , 1.0 , 1.0 ) : float3 (1.0 , 0.0 , 1.0 );
923+ finalColor = lerp (finalColor, accentColor, 0.9 );
924+ }
925+ else
926+ {
927+ // Crosshair lines - use contrasting color
928+ finalColor = lerp (finalColor, cursorColor, 0.95 );
929+ }
930+ }
711931 }
712932
713933 // Draw separator line
0 commit comments