Harbor

branch main
showing the latest snapshot on main
shadow_test.odin 4.3 KB · Plain text
tests/shadow_test.odin 0644 Raw
package gpu_tests

import "../renderer"
import "core:math"
import glsl "core:math/linalg/glsl"
import "core:testing"

@(test)
test_shadow_ubo_size :: proc(t: ^testing.T) {
	// Shadow UBO: 256 light_matrices + 16 shadow_count + 16 shadow_params
	// + 16 shadow_light_indices + 64 atlas_regions = 368 bytes data,
	// padded by Odin's struct alignment (mat4x4 array -> 64-byte align)
	ubo_size := size_of(renderer.Shadow_UBO_Data)
	// Must be >= 368 bytes (may be larger due to alignment)
	testing.expect(t, ubo_size >= 368, "shadow UBO must be at least 368 bytes")
}

@(test)
test_shadow_push_constants_size :: proc(t: ^testing.T) {
	// Shadow push constants: 2x mat4 = 128 bytes
	testing.expect_value(t, size_of(renderer.Push_Constants_Shadow), 128)
}

@(test)
test_light_matrix_computation :: proc(t: ^testing.T) {
	// Test that compute_light_matrix produces valid matrices
	light_dir := glsl.vec3{0, -1, 0} // sun straight down
	camera_target := glsl.vec3{0, 0, 0}
	half_extent: f32 = 10.0
	depth: f32 = 20.0

	mat := renderer.compute_light_matrix(light_dir, camera_target, half_extent, depth)

	// Result should be non-zero (valid matrix)
	all_zero := true
	for r in 0 ..< 4 {
		for c in 0 ..< 4 {
			if mat[r, c] != 0 {
				all_zero = false
			}
		}
	}
	testing.expect(t, !all_zero, "light matrix should not be all zeros")

	// The matrix should transform the camera target to somewhere near the center
	target_h := glsl.vec4{0, 0, 0, 1}
	result := mat * target_h
	// After projection, the result should be finite
	testing.expect(t, !math.is_nan(result.x), "projected x should be finite")
	testing.expect(t, !math.is_nan(result.y), "projected y should be finite")
	testing.expect(t, !math.is_nan(result.z), "projected z should be finite")
}

@(test)
test_light_matrix_directional :: proc(t: ^testing.T) {
	// A directional light pointing down -Y should create a top-down view
	light_dir := glsl.vec3{0, -1, 0}
	camera_target := glsl.vec3{5, 0, 5}
	half_extent: f32 = 15.0
	depth: f32 = 40.0

	mat := renderer.compute_light_matrix(light_dir, camera_target, half_extent, depth)

	// Camera target should map near the center in clip space (near 0,0)
	target_h := glsl.vec4{5, 0, 5, 1}
	result := mat * target_h
	ndc := glsl.vec3{result.x / result.w, result.y / result.w, result.z / result.w}

	testing.expect(t, math.abs(ndc.x) < 0.5, "target should be near center X in clip space")
	testing.expect(t, math.abs(ndc.y) < 0.5, "target should be near center Y in clip space")
}

@(test)
test_shadow_caster_max :: proc(t: ^testing.T) {
	// Ensure max shadow casters is reasonable
	testing.expect_value(t, renderer.MAX_SHADOW_CASTERS, 4)
}

@(test)
test_light_data_casts_shadow :: proc(t: ^testing.T) {
	// Verify Light_Data has casts_shadow field
	ld := renderer.Light_Data {
		type         = 0,
		enabled      = true,
		position     = {0, -1, 0},
		casts_shadow = true,
	}
	testing.expect(t, ld.casts_shadow, "casts_shadow should be true")

	ld2 := renderer.Light_Data{}
	testing.expect(t, !ld2.casts_shadow, "casts_shadow should default to false")
}

@(test)
test_atlas_region_single :: proc(t: ^testing.T) {
	// Single shadow caster gets the full atlas
	region := renderer.compute_atlas_uv_region(0, 1)
	testing.expect_value(t, region, [4]f32{0, 0, 1, 1})
}

@(test)
test_atlas_region_multiple :: proc(t: ^testing.T) {
	// Multiple shadow casters subdivide into quadrants
	r0 := renderer.compute_atlas_uv_region(0, 4)
	r1 := renderer.compute_atlas_uv_region(1, 4)
	r2 := renderer.compute_atlas_uv_region(2, 4)
	r3 := renderer.compute_atlas_uv_region(3, 4)

	testing.expect_value(t, r0, [4]f32{0, 0, 0.5, 0.5})
	testing.expect_value(t, r1, [4]f32{0.5, 0, 0.5, 0.5})
	testing.expect_value(t, r2, [4]f32{0, 0.5, 0.5, 0.5})
	testing.expect_value(t, r3, [4]f32{0.5, 0.5, 0.5, 0.5})
}

@(test)
test_atlas_pixel_region :: proc(t: ^testing.T) {
	// Single caster gets full atlas in pixel space
	region := renderer.compute_atlas_region(0, 1, 4096)
	testing.expect_value(t, region.x, i32(0))
	testing.expect_value(t, region.y, i32(0))
	testing.expect_value(t, region.w, u32(4096))
	testing.expect_value(t, region.h, u32(4096))

	// Multiple casters get quadrants
	r1 := renderer.compute_atlas_region(1, 4, 4096)
	testing.expect_value(t, r1.x, i32(2048))
	testing.expect_value(t, r1.y, i32(0))
	testing.expect_value(t, r1.w, u32(2048))
	testing.expect_value(t, r1.h, u32(2048))
}