Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2652,6 +2652,14 @@ if(BUILD_UNIT_TESTS)
target_include_directories(unit_vm_native_module PRIVATE ${CMAKE_SOURCE_DIR}/src)
add_test(NAME unit_vm_native_module COMMAND unit_vm_native_module)
set_tests_properties(unit_vm_native_module PROPERTIES LABELS "unit;validation")

add_executable(unit_vk_rtx_world
tests/unit/test_vk_rtx_world.c
src/renderers/vulkan/vk_rtx_world.c)
target_include_directories(unit_vk_rtx_world PRIVATE ${CMAKE_SOURCE_DIR}/src)
target_compile_definitions(unit_vk_rtx_world PRIVATE USE_VULKAN_RTX=1)
add_test(NAME unit_vk_rtx_world COMMAND unit_vk_rtx_world)
set_tests_properties(unit_vk_rtx_world PROPERTIES LABELS "unit;validation;renderer")
endif()

# Example mod pack (config-only .pk3); optional - see examples/demo_game/README.md
Expand Down
2 changes: 1 addition & 1 deletion tests/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Tests

- **Unit** (`BUILD_UNIT_TESTS=ON`): `unit_macros`, `unit_qmath`, `unit_surfaceflags`, `unit_qhelpers`, `unit_crc`, `unit_pathutil`, `unit_msg`, `unit_info`, `unit_cm_bounds`, `unit_parse`, `unit_endian` (CRC, COM path, `Info_*`, `COM_Parse*`, and endian tests use the same minimal `stub_qcommon_min.c` + `q_shared.c` + `q_math.c` link as `unit_qhelpers`; `unit_msg` links `msg.c` + `huffman_static.c` with `stub_qcommon_min.c`, `stub_msg_cvar.c`, and `-DDEDICATED`; `unit_cm_bounds` links `cm_bounds.c` + `q_math.c` only) — run `ctest -R unit_` or `./unit_*` from the build directory.
- **Unit** (`BUILD_UNIT_TESTS=ON`): `unit_macros`, `unit_qmath`, `unit_surfaceflags`, `unit_qhelpers`, `unit_crc`, `unit_pathutil`, `unit_msg`, `unit_info`, `unit_cm_bounds`, `unit_parse`, `unit_endian`, `unit_vm_native_module`, `unit_vk_rtx_world` (CRC, COM path, `Info_*`, `COM_Parse*`, and endian tests use the same minimal `stub_qcommon_min.c` + `q_shared.c` + `q_math.c` link as `unit_qhelpers`; `unit_msg` links `msg.c` + `huffman_static.c` with `stub_qcommon_min.c`, `stub_msg_cvar.c`, and `-DDEDICATED`; `unit_cm_bounds` links `cm_bounds.c` + `q_math.c` only; `unit_vk_rtx_world` exercises Vulkan RTX world BLAS counting/packing with in-memory surface fixtures) — run `ctest -R unit_` or `./unit_*` from the build directory.
- **Script regression tests**: `test_botlib_bounded_strings` (botlib string invariants). Run with `ctest -R test_botlib_bounded_strings` from the build directory.
- **Validation**: `smoke_test`, `renderer_regression_check`, `check_artifacts`, `test_run_vulkan_script`, `test_compile_engine_lto`, `test_demo_game_pk3`, `test_vk_vegetation_dispatch_order`, `test_vulkan_mesh_shader_opt_in`, `test_vulkan_runtime_regressions`, `test_botlib_chat_message_bounds`, `test_vulkan_renderer_guards`, `test_vulkan_regression_source_guards`, `test_gltf_opengl_regressions`, `test_botlib_bounded_strings` — see `scripts/`, `tests/scripts/`, and `docs/RENDERER_CONFIDENCE.md`.
200 changes: 200 additions & 0 deletions tests/unit/test_vk_rtx_world.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
/*
* Unit tests: Vulkan RTX world BLAS geometry extraction.
* Exercises deterministic CPU-side packing without requiring Vulkan hardware.
*/
#include <stddef.h>
#include <stdio.h>
#include <string.h>

#include "renderers/vulkan/vk_rtx_world.h"

#define ASSERT(cond, msg) do { \
if (!(cond)) { \
fprintf(stderr, "FAIL: %s\n", msg); \
return 1; \
} \
} while (0)

typedef struct {
srfSurfaceFace_t face;
float extraPoints[3][VERTEXSIZE];
unsigned indices[6];
} face_fixture_t;

static void set_point( float points[][VERTEXSIZE], int index, float x, float y, float z )
{
memset( points[index], 0, sizeof( points[index] ) );
points[index][0] = x;
points[index][1] = y;
points[index][2] = z;
}

static void init_face_fixture( face_fixture_t *fixture )
{
float ( *points )[VERTEXSIZE];

memset( fixture, 0, sizeof( *fixture ) );
fixture->face.surfaceType = SF_FACE;
fixture->face.numPoints = 4;
fixture->face.numIndices = 6;
fixture->face.ofsIndices = (int)offsetof( face_fixture_t, indices );
fixture->indices[0] = 0;
fixture->indices[1] = 1;
fixture->indices[2] = 2;
fixture->indices[3] = 0;
fixture->indices[4] = 2;
fixture->indices[5] = 3;

points = fixture->face.points;
set_point( points, 0, 10.0f, 11.0f, 12.0f );
set_point( points, 1, 20.0f, 21.0f, 22.0f );
set_point( points, 2, 30.0f, 31.0f, 32.0f );
set_point( points, 3, 40.0f, 41.0f, 42.0f );
}

static void set_vert( srfVert_t *vert, float x, float y, float z )
{
memset( vert, 0, sizeof( *vert ) );
vert->xyz[0] = x;
vert->xyz[1] = y;
vert->xyz[2] = z;
}

static void init_triangles_fixture( srfTriangles_t *tri, srfVert_t verts[3], int indexes[3] )
{
memset( tri, 0, sizeof( *tri ) );
tri->surfaceType = SF_TRIANGLES;
tri->numIndexes = 3;
tri->indexes = indexes;
tri->numVerts = 3;
tri->verts = verts;
indexes[0] = 2;
indexes[1] = 1;
indexes[2] = 0;
set_vert( &verts[0], 100.0f, 101.0f, 102.0f );
set_vert( &verts[1], 110.0f, 111.0f, 112.0f );
set_vert( &verts[2], 120.0f, 121.0f, 122.0f );
}

static void init_world( world_t *world, bmodel_t *bmodels, msurface_t *surfaces, int numSurfaces )
{
memset( world, 0, sizeof( *world ) );
memset( bmodels, 0, sizeof( *bmodels ) );
world->bmodels = bmodels;
world->numBModels = 1;
world->surfaces = surfaces;
world->numsurfaces = numSurfaces;
bmodels[0].firstSurface = surfaces;
bmodels[0].numSurfaces = numSurfaces;
}

static int test_count_skips_invalid_inputs( void )
{
world_t world;
bmodel_t bmodel;
msurface_t surfaces[2];
face_fixture_t face;
surfaceType_t skippedType = SF_GRID;

init_face_fixture( &face );
memset( surfaces, 0, sizeof( surfaces ) );
surfaces[0].data = NULL;
surfaces[1].data = &skippedType;
init_world( &world, &bmodel, surfaces, 2 );

ASSERT( vk_rtx_world_count_primitives( NULL, 8u ) == 0u, "NULL world has no primitives" );
ASSERT( vk_rtx_world_pack( NULL, 8u, NULL, NULL ) == 0u, "NULL world packs no primitives" );
ASSERT( vk_rtx_world_count_primitives( &world, 8u ) == 0u, "unsupported/null surfaces are skipped" );

surfaces[0].data = &face.face.surfaceType;
face.face.numIndices = 5;
ASSERT( vk_rtx_world_count_primitives( &world, 8u ) == 0u, "malformed face index count is skipped" );
return 0;
}

static int test_count_honors_primitive_cap( void )
{
world_t world;
bmodel_t bmodel;
msurface_t surfaces[1];
face_fixture_t face;

init_face_fixture( &face );
memset( surfaces, 0, sizeof( surfaces ) );
surfaces[0].data = &face.face.surfaceType;
init_world( &world, &bmodel, surfaces, 1 );

ASSERT( vk_rtx_world_count_primitives( &world, 8u ) == 2u, "face fixture has two triangles" );
ASSERT( vk_rtx_world_count_primitives( &world, 1u ) == 1u, "primitive count is capped" );
return 0;
}

static int test_pack_face_and_triangles( void )
{
world_t world;
bmodel_t bmodel;
msurface_t surfaces[2];
face_fixture_t face;
srfTriangles_t tri;
srfVert_t verts[3];
int triIndexes[3];
float positions[9 * 3];
uint32_t indices[9];

init_face_fixture( &face );
init_triangles_fixture( &tri, verts, triIndexes );
memset( surfaces, 0, sizeof( surfaces ) );
surfaces[0].data = &face.face.surfaceType;
surfaces[1].data = &tri.surfaceType;
init_world( &world, &bmodel, surfaces, 2 );
memset( positions, 0, sizeof( positions ) );
memset( indices, 0, sizeof( indices ) );

ASSERT( vk_rtx_world_count_primitives( &world, 8u ) == 3u, "face plus trisoup primitive count" );
ASSERT( vk_rtx_world_pack( &world, 8u, positions, indices ) == 3u, "face plus trisoup packed count" );

ASSERT( positions[0] == 10.0f && positions[1] == 11.0f && positions[2] == 12.0f, "face tri 0 vertex 0" );
ASSERT( positions[3] == 20.0f && positions[4] == 21.0f && positions[5] == 22.0f, "face tri 0 vertex 1" );
ASSERT( positions[6] == 30.0f && positions[7] == 31.0f && positions[8] == 32.0f, "face tri 0 vertex 2" );
ASSERT( indices[0] == 0u && indices[1] == 1u && indices[2] == 2u, "face tri 0 indices" );

ASSERT( positions[9] == 10.0f && positions[10] == 11.0f && positions[11] == 12.0f, "face tri 1 reuses face point 0" );
ASSERT( positions[12] == 30.0f && positions[13] == 31.0f && positions[14] == 32.0f, "face tri 1 reuses face point 2" );
ASSERT( positions[15] == 40.0f && positions[16] == 41.0f && positions[17] == 42.0f, "face tri 1 uses face point 3" );
ASSERT( positions[18] == 120.0f && positions[19] == 121.0f && positions[20] == 122.0f, "trisoup honors source index order" );
ASSERT( indices[6] == 6u && indices[7] == 7u && indices[8] == 8u, "trisoup indices continue after face vertices" );
return 0;
}

static int test_pack_stops_at_cap( void )
{
world_t world;
bmodel_t bmodel;
msurface_t surfaces[1];
face_fixture_t face;
float positions[3 * 3];
uint32_t indices[3];

init_face_fixture( &face );
memset( surfaces, 0, sizeof( surfaces ) );
surfaces[0].data = &face.face.surfaceType;
init_world( &world, &bmodel, surfaces, 1 );
memset( positions, 0, sizeof( positions ) );
memset( indices, 0, sizeof( indices ) );

ASSERT( vk_rtx_world_pack( &world, 1u, positions, indices ) == 1u, "packing stops at primitive cap" );
ASSERT( positions[0] == 10.0f && positions[6] == 30.0f, "first face triangle packed under cap" );
ASSERT( indices[0] == 0u && indices[1] == 1u && indices[2] == 2u, "capped face indices" );
return 0;
}

int main( void )
{
if ( test_count_skips_invalid_inputs() ) return 1;
if ( test_count_honors_primitive_cap() ) return 1;
if ( test_pack_face_and_triangles() ) return 1;
if ( test_pack_stops_at_cap() ) return 1;

printf( "PASS: unit_vk_rtx_world\n" );
return 0;
}
Loading