@@ -614,12 +614,14 @@ UTEST_F( Graphics, Culling )
614614 vhState state;
615615 state.SetColourAttachment ( 0 , rt )
616616 .SetViewRect ( glm::vec4 ( 0 , 0 , 64 , 64 ) )
617- .SetProgram ( vhCreateGfxProgram ( vs, ps ) );
617+ .SetStateFlags ( VRHI_STATE_WRITE_MASK );
618618
619- // Test 1: Cull CCW. CW should be visible (Red).
619+ // Test 1: Cull Front (CCW). CW should be visible (Red).
620+ vhProgram programCW = vhCreateGfxProgram ( vs, ps );
620621 state.SetViewClear ( VRHI_CLEAR_COLOR , glm::vec4 ( 0 .0f , 0 .0f , 0 .0f , 1 .0f ) )
621- .SetStateFlags ( VRHI_STATE_WRITE_RGB | VRHI_STATE_CULL_CCW )
622- .SetVertexBuffer ( vbCW, 0 );
622+ .SetStateFlags ( VRHI_STATE_WRITE_MASK | VRHI_STATE_CULL_FRONT )
623+ .SetVertexBuffer ( vbCW, 0 )
624+ .SetProgram ( programCW );
623625
624626 vhStateId sidCW = 600 ;
625627 vhSetState ( sidCW, state );
@@ -628,10 +630,12 @@ UTEST_F( Graphics, Culling )
628630 vhFinish ();
629631 EXPECT_TRUE ( VerifyPixel ( rt, 16 , 16 , 0xFF0000FF ) ); // Red
630632
631- // Test 2: Cull CW. CCW should be visible (Green).
633+ // Test 2: Cull Back (CW). CCW should be visible (Green).
634+ vhProgram programCCW = vhCreateGfxProgram ( vs, ps );
632635 state.SetViewClear ( VRHI_CLEAR_COLOR , glm::vec4 ( 0 .0f , 0 .0f , 0 .0f , 1 .0f ) )
633- .SetStateFlags ( VRHI_STATE_WRITE_RGB | VRHI_STATE_CULL_CW )
634- .SetVertexBuffer ( vbCCW, 0 );
636+ .SetStateFlags ( VRHI_STATE_WRITE_MASK | VRHI_STATE_CULL_BACK )
637+ .SetVertexBuffer ( vbCCW, 0 )
638+ .SetProgram ( programCCW );
635639
636640 vhStateId sidCCW = 601 ;
637641 vhSetState ( sidCCW, state.DirtyAll () );
@@ -648,6 +652,157 @@ UTEST_F( Graphics, Culling )
648652 vhFinish ();
649653}
650654
655+ UTEST_F ( Graphics, CullingExtensive )
656+ {
657+ vhTexture rt = CreateTestTexture ( 64 , 64 , nvrhi::Format::RGBA8_UNORM );
658+
659+ struct Vertex { glm::vec3 pos; glm::vec4 colour; };
660+
661+ // CCW-wound quad (left half of screen, red)
662+ Vertex vertsCCW[6 ] =
663+ {
664+ { { -1 .0f , -1 .0f , 0 .0f }, { 1 .0f , 0 .0f , 0 .0f , 1 .0f } },
665+ { { 0 .0f , -1 .0f , 0 .0f }, { 1 .0f , 0 .0f , 0 .0f , 1 .0f } },
666+ { { -1 .0f , 1 .0f , 0 .0f }, { 1 .0f , 0 .0f , 0 .0f , 1 .0f } },
667+ { { -1 .0f , 1 .0f , 0 .0f }, { 1 .0f , 0 .0f , 0 .0f , 1 .0f } },
668+ { { 0 .0f , -1 .0f , 0 .0f }, { 1 .0f , 0 .0f , 0 .0f , 1 .0f } },
669+ { { 0 .0f , 1 .0f , 0 .0f }, { 1 .0f , 0 .0f , 0 .0f , 1 .0f } }
670+ };
671+
672+ // CW-wound quad (right half of screen, green)
673+ Vertex vertsCW[6 ] =
674+ {
675+ { { 0 .0f , -1 .0f , 0 .0f }, { 0 .0f , 1 .0f , 0 .0f , 1 .0f } },
676+ { { 0 .0f , 1 .0f , 0 .0f }, { 0 .0f , 1 .0f , 0 .0f , 1 .0f } },
677+ { { 1 .0f , -1 .0f , 0 .0f }, { 0 .0f , 1 .0f , 0 .0f , 1 .0f } },
678+ { { 0 .0f , 1 .0f , 0 .0f }, { 0 .0f , 1 .0f , 0 .0f , 1 .0f } },
679+ { { 1 .0f , 1 .0f , 0 .0f }, { 0 .0f , 1 .0f , 0 .0f , 1 .0f } },
680+ { { 1 .0f , -1 .0f , 0 .0f }, { 0 .0f , 1 .0f , 0 .0f , 1 .0f } }
681+ };
682+
683+ vhBuffer vbCCW = CreateTestVB ( " float3 float4" , vertsCCW, sizeof ( vertsCCW ) );
684+ vhBuffer vbCW = CreateTestVB ( " float3 float4" , vertsCW, sizeof ( vertsCW ) );
685+ vhShader vs = CreateTestShader ( g_simpleVS, VRHI_SHADER_STAGE_VERTEX );
686+ vhShader ps = CreateTestShader ( g_solidPS, VRHI_SHADER_STAGE_PIXEL );
687+
688+ vhState state;
689+ state.SetColourAttachment ( 0 , rt )
690+ .SetViewRect ( glm::vec4 ( 0 , 0 , 64 , 64 ) )
691+ .SetStateFlags ( VRHI_STATE_WRITE_MASK );
692+
693+ // Test 1: No Culling - Both triangles visible
694+ {
695+ vhProgram program1 = vhCreateGfxProgram ( vs, ps );
696+ state.SetViewClear ( VRHI_CLEAR_COLOR , glm::vec4 ( 0 .0f , 0 .0f , 0 .0f , 1 .0f ) )
697+ .SetStateFlags ( VRHI_STATE_WRITE_MASK | VRHI_STATE_CULL_NONE )
698+ .SetProgram ( program1 );
699+
700+ vhStateId sid = 610 ;
701+ state.SetVertexBuffer ( vbCCW, 0 );
702+ vhSetState ( sid, state );
703+ vhClear ( sid, VRHI_CLEAR_COLOR );
704+ vhDraw ( sid, 6 );
705+ state.SetVertexBuffer ( vbCW, 0 );
706+ vhSetState ( sid, state.DirtyAll () );
707+ vhDraw ( sid, 6 );
708+ vhFinish ();
709+
710+ EXPECT_TRUE ( VerifyPixel ( rt, 16 , 32 , 0xFF0000FF ) ); // Left pixel (CCW) = red
711+ EXPECT_TRUE ( VerifyPixel ( rt, 48 , 32 , 0xFF00FF00 ) ); // Right pixel (CW) = green
712+ }
713+
714+ // Test 2: Cull Back (Default CCW=Front) - CCW visible, CW culled
715+ {
716+ vhProgram program2 = vhCreateGfxProgram ( vs, ps );
717+ state.SetViewClear ( VRHI_CLEAR_COLOR , glm::vec4 ( 0 .0f , 0 .0f , 0 .0f , 1 .0f ) )
718+ .SetStateFlags ( VRHI_STATE_WRITE_MASK | VRHI_STATE_CULL_BACK )
719+ .SetProgram ( program2 );
720+
721+ vhStateId sid = 611 ;
722+ state.SetVertexBuffer ( vbCCW, 0 );
723+ vhSetState ( sid, state );
724+ vhClear ( sid, VRHI_CLEAR_COLOR );
725+ vhDraw ( sid, 6 );
726+ state.SetVertexBuffer ( vbCW, 0 );
727+ vhSetState ( sid, state.DirtyAll () );
728+ vhDraw ( sid, 6 );
729+ vhFinish ();
730+
731+ EXPECT_TRUE ( VerifyPixel ( rt, 16 , 32 , 0xFF0000FF ) ); // Left pixel (CCW) = red
732+ EXPECT_TRUE ( VerifyPixel ( rt, 48 , 32 , 0xFF000000 ) ); // Right pixel (CW) = black (culled)
733+ }
734+
735+ // Test 3: Cull Front (Default CCW=Front) - CW visible, CCW culled
736+ {
737+ vhProgram program3 = vhCreateGfxProgram ( vs, ps );
738+ state.SetViewClear ( VRHI_CLEAR_COLOR , glm::vec4 ( 0 .0f , 0 .0f , 0 .0f , 1 .0f ) )
739+ .SetStateFlags ( VRHI_STATE_WRITE_MASK | VRHI_STATE_CULL_FRONT )
740+ .SetProgram ( program3 );
741+
742+ vhStateId sid = 612 ;
743+ state.SetVertexBuffer ( vbCCW, 0 );
744+ vhSetState ( sid, state );
745+ vhClear ( sid, VRHI_CLEAR_COLOR );
746+ vhDraw ( sid, 6 );
747+ state.SetVertexBuffer ( vbCW, 0 );
748+ vhSetState ( sid, state.DirtyAll () );
749+ vhDraw ( sid, 6 );
750+ vhFinish ();
751+
752+ EXPECT_TRUE ( VerifyPixel ( rt, 16 , 32 , 0xFF000000 ) ); // Left pixel (CCW) = black (culled)
753+ EXPECT_TRUE ( VerifyPixel ( rt, 48 , 32 , 0xFF00FF00 ) ); // Right pixel (CW) = green
754+ }
755+
756+ // Test 4: Cull Back + CW Override - CW=front, so back=CCW, CW visible, CCW culled
757+ {
758+ vhProgram program4 = vhCreateGfxProgram ( vs, ps );
759+ state.SetViewClear ( VRHI_CLEAR_COLOR , glm::vec4 ( 0 .0f , 0 .0f , 0 .0f , 1 .0f ) )
760+ .SetStateFlags ( VRHI_STATE_WRITE_MASK | VRHI_STATE_CULL_BACK | VRHI_STATE_FRONT_CW )
761+ .SetProgram ( program4 );
762+
763+ vhStateId sid = 613 ;
764+ state.SetVertexBuffer ( vbCCW, 0 );
765+ vhSetState ( sid, state );
766+ vhClear ( sid, VRHI_CLEAR_COLOR );
767+ vhDraw ( sid, 6 );
768+ state.SetVertexBuffer ( vbCW, 0 );
769+ vhSetState ( sid, state.DirtyAll () );
770+ vhDraw ( sid, 6 );
771+ vhFinish ();
772+
773+ EXPECT_TRUE ( VerifyPixel ( rt, 16 , 32 , 0xFF000000 ) ); // Left pixel (CCW) = black (culled)
774+ EXPECT_TRUE ( VerifyPixel ( rt, 48 , 32 , 0xFF00FF00 ) ); // Right pixel (CW) = green
775+ }
776+
777+ // Test 5: Cull Front + CW Override - CW=front, cull front faces, CCW visible, CW culled
778+ {
779+ vhProgram program5 = vhCreateGfxProgram ( vs, ps );
780+ state.SetViewClear ( VRHI_CLEAR_COLOR , glm::vec4 ( 0 .0f , 0 .0f , 0 .0f , 1 .0f ) )
781+ .SetStateFlags ( VRHI_STATE_WRITE_MASK | VRHI_STATE_CULL_FRONT | VRHI_STATE_FRONT_CW )
782+ .SetProgram ( program5 );
783+
784+ vhStateId sid = 614 ;
785+ state.SetVertexBuffer ( vbCCW, 0 );
786+ vhSetState ( sid, state );
787+ vhClear ( sid, VRHI_CLEAR_COLOR );
788+ vhDraw ( sid, 6 );
789+ state.SetVertexBuffer ( vbCW, 0 );
790+ vhSetState ( sid, state.DirtyAll () );
791+ vhDraw ( sid, 6 );
792+ vhFinish ();
793+
794+ EXPECT_TRUE ( VerifyPixel ( rt, 16 , 32 , 0xFF0000FF ) ); // Left pixel (CCW) = red
795+ EXPECT_TRUE ( VerifyPixel ( rt, 48 , 32 , 0xFF000000 ) ); // Right pixel (CW) = black (culled)
796+ }
797+
798+ vhDestroyTexture ( rt );
799+ vhDestroyBuffer ( vbCCW );
800+ vhDestroyBuffer ( vbCW );
801+ vhDestroyShader ( vs );
802+ vhDestroyShader ( ps );
803+ vhFinish ();
804+ }
805+
651806UTEST_F ( Graphics, ScissorTest )
652807{
653808 vhTexture rt = CreateTestTexture ( 64 , 64 , nvrhi::Format::RGBA8_UNORM );
0 commit comments