Skip to content

Commit 4f8556e

Browse files
committed
Refactor culling state flags and update tests
1 parent 46b75f1 commit 4f8556e

4 files changed

Lines changed: 233 additions & 29 deletions

File tree

src/vrhi_state.cpp

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -467,21 +467,17 @@ nvrhi::RasterState vhTranslateRasterState( uint64_t stateFlags )
467467
{
468468
nvrhi::RasterState rasterState;
469469

470+
// Extract cull mode
470471
uint32_t cullMode = ( uint32_t ) ( ( stateFlags & VRHI_STATE_CULL_MASK ) >> VRHI_STATE_CULL_SHIFT );
471-
if ( cullMode == ( VRHI_STATE_CULL_CW >> VRHI_STATE_CULL_SHIFT ) )
472-
{
473-
rasterState.cullMode = nvrhi::RasterCullMode::Back;
474-
}
475-
else if ( cullMode == ( VRHI_STATE_CULL_CCW >> VRHI_STATE_CULL_SHIFT ) )
476-
{
477-
rasterState.cullMode = nvrhi::RasterCullMode::Front;
478-
}
479-
else
480-
{
481-
rasterState.cullMode = nvrhi::RasterCullMode::None;
482-
}
472+
473+
// Map to NVRHI: 0 = None, 1 = Back, 2 = Front
474+
rasterState.cullMode = (cullMode == 1) ? nvrhi::RasterCullMode::Back :
475+
(cullMode == 2) ? nvrhi::RasterCullMode::Front :
476+
nvrhi::RasterCullMode::None;
483477

484-
rasterState.frontCounterClockwise = ( stateFlags & VRHI_STATE_FRONT_CCW ) != 0;
478+
// Front face winding (default CCW = front, override to CW if flag set)
479+
rasterState.frontCounterClockwise = !( stateFlags & VRHI_STATE_FRONT_CW );
480+
485481
rasterState.multisampleEnable = ( stateFlags & VRHI_STATE_MSAA ) != 0;
486482
rasterState.antialiasedLineEnable = ( stateFlags & VRHI_STATE_LINEAA ) != 0;
487483
rasterState.conservativeRasterEnable = ( stateFlags & VRHI_STATE_CONSERVATIVE_RASTER ) != 0;

test/test_gfx.cpp

Lines changed: 162 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
651806
UTEST_F( Graphics, ScissorTest )
652807
{
653808
vhTexture rt = CreateTestTexture( 64, 64, nvrhi::Format::RGBA8_UNORM );

test/test_shader.cpp

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,11 @@ UTEST( ShaderInternal, StateToDesc )
6565
EXPECT_EQ( vhTranslatePrimitiveType( VRHI_STATE_PT_TRIANGLES ), nvrhi::PrimitiveType::TriangleList );
6666
EXPECT_EQ( vhTranslatePrimitiveType( VRHI_STATE_PT_TRISTRIP ), nvrhi::PrimitiveType::TriangleStrip );
6767

68-
// Test Default (Depth Test Less, Write All, Cull CW)
68+
// Test Default (Depth Test Less, Write All, Cull Back with CCW=front)
6969
{
7070
nvrhi::RasterState rs = vhTranslateRasterState( VRHI_STATE_DEFAULT );
7171
EXPECT_EQ( rs.cullMode, nvrhi::RasterCullMode::Back );
72+
EXPECT_TRUE( rs.frontCounterClockwise ); // Default: CCW = front
7273

7374
nvrhi::DepthStencilState ds = vhTranslateDepthStencilState( VRHI_STATE_DEFAULT, VRHI_STENCIL_NONE );
7475
EXPECT_TRUE( ds.depthTestEnable );
@@ -83,6 +84,41 @@ UTEST( ShaderInternal, StateToDesc )
8384
EXPECT_EQ( bs.targets[0].destBlend, nvrhi::BlendFactor::One );
8485
}
8586

87+
// Test Cull Back (Default CCW=front)
88+
{
89+
nvrhi::RasterState rs = vhTranslateRasterState( VRHI_STATE_CULL_BACK | VRHI_STATE_WRITE_RGB );
90+
EXPECT_EQ( rs.cullMode, nvrhi::RasterCullMode::Back );
91+
EXPECT_TRUE( rs.frontCounterClockwise ); // CCW = front
92+
}
93+
94+
// Test Cull Front (Default CCW=front)
95+
{
96+
nvrhi::RasterState rs = vhTranslateRasterState( VRHI_STATE_CULL_FRONT | VRHI_STATE_WRITE_RGB );
97+
EXPECT_EQ( rs.cullMode, nvrhi::RasterCullMode::Front );
98+
EXPECT_TRUE( rs.frontCounterClockwise ); // CCW = front
99+
}
100+
101+
// Test Cull None
102+
{
103+
nvrhi::RasterState rs = vhTranslateRasterState( VRHI_STATE_CULL_NONE | VRHI_STATE_WRITE_RGB );
104+
EXPECT_EQ( rs.cullMode, nvrhi::RasterCullMode::None );
105+
EXPECT_TRUE( rs.frontCounterClockwise ); // CCW = front (default)
106+
}
107+
108+
// Test Cull Back + CW Override (CW=front)
109+
{
110+
nvrhi::RasterState rs = vhTranslateRasterState( VRHI_STATE_CULL_BACK | VRHI_STATE_FRONT_CW | VRHI_STATE_WRITE_RGB );
111+
EXPECT_EQ( rs.cullMode, nvrhi::RasterCullMode::Back );
112+
EXPECT_FALSE( rs.frontCounterClockwise ); // CW = front
113+
}
114+
115+
// Test Cull Front + CW Override (CW=front)
116+
{
117+
nvrhi::RasterState rs = vhTranslateRasterState( VRHI_STATE_CULL_FRONT | VRHI_STATE_FRONT_CW | VRHI_STATE_WRITE_RGB );
118+
EXPECT_EQ( rs.cullMode, nvrhi::RasterCullMode::Front );
119+
EXPECT_FALSE( rs.frontCounterClockwise ); // CW = front
120+
}
121+
86122
// Test Depth Always
87123
{
88124
nvrhi::DepthStencilState ds = vhTranslateDepthStencilState( VRHI_STATE_DEPTH_TEST_ALWAYS, VRHI_STENCIL_NONE );

vrhi_defines.h

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -268,12 +268,29 @@
268268

269269
/**
270270
* Cull state. When `VRHI_STATE_CULL_*` is not specified culling will be disabled.
271+
*
272+
* Culling Convention
273+
* ------------------
274+
* Default: Counter-clockwise (CCW) wound triangles are front faces (OpenGL/DX9-11 standard)
275+
* - VRHI_STATE_CULL_NONE: No culling
276+
* - VRHI_STATE_CULL_BACK: Cull back faces (CW triangles when using default CCW=front)
277+
* - VRHI_STATE_CULL_FRONT: Cull front faces (CCW triangles when using default CCW=front)
278+
* - VRHI_STATE_FRONT_CW: Override to make CW triangles = front faces (rare)
271279
*
280+
* Example: VRHI_STATE_CULL_BACK culls clockwise-wound triangles (standard backface culling)
272281
*/
273-
#define VRHI_STATE_CULL_CW UINT64_C(0x0000001000000000) //!< Cull clockwise triangles.
274-
#define VRHI_STATE_CULL_CCW UINT64_C(0x0000002000000000) //!< Cull counter-clockwise triangles.
275-
#define VRHI_STATE_CULL_SHIFT 36 //!< Culling mode bit shift
276-
#define VRHI_STATE_CULL_MASK UINT64_C(0x0000003000000000) //!< Culling mode bit mask
282+
#define VRHI_STATE_CULL_NONE UINT64_C(0x0000000000000000) //!< No culling
283+
#define VRHI_STATE_CULL_BACK UINT64_C(0x0000000000000100) //!< Cull back faces (default CW triangles)
284+
#define VRHI_STATE_CULL_FRONT UINT64_C(0x0000000000000200) //!< Cull front faces (default CCW triangles)
285+
#define VRHI_STATE_CULL_SHIFT 8 //!< Culling mode bit shift
286+
#define VRHI_STATE_CULL_MASK UINT64_C(0x0000000000000300) //!< Culling mode bit mask
287+
288+
// Front Face Winding Override (bit 10) - rare, only when you need CW = front
289+
#define VRHI_STATE_FRONT_CW UINT64_C(0x0000000000000400) //!< Override: CW triangles = front faces
290+
291+
// Deprecated: Use VRHI_STATE_CULL_BACK/FRONT instead
292+
#define VRHI_STATE_CULL_CW VRHI_STATE_CULL_BACK //!< Old: cull CW (back) faces - deprecated, use VRHI_STATE_CULL_BACK
293+
#define VRHI_STATE_CULL_CCW VRHI_STATE_CULL_FRONT //!< Old: cull CCW (front) faces - deprecated, use VRHI_STATE_CULL_FRONT
277294

278295
#define VRHI_STATE_PT_TRIANGLES UINT64_C(0x0000000000000000) //!< Triangles. ( Not needed, just for completeness )
279296
#define VRHI_STATE_PT_TRISTRIP UINT64_C(0x0001000000000000) //!< Tristrip.
@@ -292,19 +309,19 @@
292309
#define VRHI_STATE_LINEAA UINT64_C(0x0200000000000000) //!< Enable line AA rasterization.
293310
#define VRHI_STATE_CONSERVATIVE_RASTER UINT64_C(0x0400000000000000) //!< Enable conservative rasterization.
294311
#define VRHI_STATE_NONE UINT64_C(0x0000000000000000) //!< No state.
295-
#define VRHI_STATE_FRONT_CCW UINT64_C(0x0000008000000000) //!< Front counter-clockwise ( default is clockwise ).
296312
#define VRHI_STATE_BLEND_INDEPENDENT UINT64_C(0x0000000400000000) //!< Enable blend independent.
297313
#define VRHI_STATE_BLEND_ALPHA_TO_COVERAGE UINT64_C(0x0000000800000000) //!< Enable alpha to coverage.
298314
#define VRHI_STATE_DEPTH_CLIP UINT64_C(0x0001000000000000) //!< Enable depth clipping.
299315
#define VRHI_STATE_DEPTH_TEST_ENABLE UINT64_C(0x0002000000000000) //!< Explicit depth test enable.
300-
/// Default state is write to RGB, alpha, and depth with depth test less enabled, with clockwise
301-
/// culling and MSAA (when writing into MSAA frame buffer, otherwise this flag is ignored).
316+
/// Default state is write to RGB, alpha, and depth with depth test less enabled, with backface
317+
/// culling (culls clockwise-wound triangles using standard CCW=front convention) and MSAA
318+
/// (when writing into MSAA frame buffer, otherwise this flag is ignored).
302319
#define VRHI_STATE_DEFAULT (0 \
303320
| VRHI_STATE_WRITE_RGB \
304321
| VRHI_STATE_WRITE_A \
305322
| VRHI_STATE_WRITE_Z \
306323
| VRHI_STATE_DEPTH_TEST_LESS \
307-
| VRHI_STATE_CULL_CW \
324+
| VRHI_STATE_CULL_BACK \
308325
| VRHI_STATE_MSAA \
309326
)
310327

0 commit comments

Comments
 (0)