OverviewFilesCommitsIssuesDocsReleasesPeers

blender-bridge / test / test_page_table.cpp

230 lines · cpp ref:
#include "../src/page_table.h"

#include <cassert>
#include <cstdio>

static int tests_passed = 0;

#define TEST(name)                             \
	do {                                       \
		printf("  %-40s", #name "...");        \
	} while (0)
#define PASS()                                 \
	do {                                       \
		printf("PASS\n");                      \
		tests_passed++;                        \
	} while (0)

void test_init() {
	TEST(init);
	PageTable pt;
	pt.init(8, 128);
	assert(pt.mip_levels() == 8);
	assert(pt.base_tiles() == 128);
	assert(pt.tiles_at_mip(0) == 128);
	assert(pt.tiles_at_mip(1) == 64);
	assert(pt.tiles_at_mip(7) == 1);
	PASS();
}

void test_map_lookup() {
	TEST(map_lookup);
	PageTable pt;
	pt.init(8, 128);

	PhysicalLocation loc = { 10, 20 };
	pt.map(0, 5, 3, loc);

	PageLookup result = pt.lookup(0, 5, 3);
	assert(result.valid);
	assert(result.location.x == 10);
	assert(result.location.y == 20);

	// Unmapped → invalid
	PageLookup miss = pt.lookup(0, 6, 3);
	assert(!miss.valid);
	PASS();
}

void test_evict() {
	TEST(evict);
	PageTable pt;
	pt.init(4, 16);

	PhysicalLocation loc = { 5, 5 };
	pt.map(0, 3, 3, loc);
	assert(pt.lookup(0, 3, 3).valid);

	pt.evict(0, 3, 3);
	assert(!pt.lookup(0, 3, 3).valid);
	PASS();
}

void test_map_with_fallback() {
	TEST(map_with_fallback);
	PageTable pt;
	pt.init(4, 16);

	// Map at mip 2 (4×4 tiles)
	PhysicalLocation parent_loc = { 30, 40 };
	pt.map_with_fallback(2, 1, 1, parent_loc);

	// Parent should be mapped
	PageLookup parent = pt.lookup(2, 1, 1);
	assert(parent.valid);
	assert(parent.location.x == 30);

	// Children at mip 1 (8×8): (2,2), (2,3), (3,2), (3,3)
	for (int dy = 0; dy < 2; dy++) {
		for (int dx = 0; dx < 2; dx++) {
			PageLookup child = pt.lookup(1, 2 + dx, 2 + dy);
			assert(child.valid);
			assert(child.location.x == 30);
			assert(child.location.y == 40);
		}
	}

	// Grandchildren at mip 0 (16×16): 4..7 in each dimension
	for (int dy = 0; dy < 4; dy++) {
		for (int dx = 0; dx < 4; dx++) {
			PageLookup gc = pt.lookup(0, 4 + dx, 4 + dy);
			assert(gc.valid);
			assert(gc.location.x == 30);
		}
	}
	PASS();
}

void test_fallback_doesnt_overwrite() {
	TEST(fallback_doesnt_overwrite);
	PageTable pt;
	pt.init(4, 16);

	// Map a child first
	PhysicalLocation child_loc = { 50, 60 };
	pt.map(1, 2, 2, child_loc);

	// Now map parent with fallback — should NOT overwrite existing child
	PhysicalLocation parent_loc = { 30, 40 };
	pt.map_with_fallback(2, 1, 1, parent_loc);

	// Child at (1,2,2) should still have its own mapping
	PageLookup child = pt.lookup(1, 2, 2);
	assert(child.valid);
	assert(child.location.x == 50);
	assert(child.location.y == 60);

	// Other children should have parent's fallback
	PageLookup other = pt.lookup(1, 3, 3);
	assert(other.valid);
	assert(other.location.x == 30);
	PASS();
}

void test_invalidate_region() {
	TEST(invalidate_region);
	PageTable pt;
	pt.init(4, 16);

	// Map some tiles at various mips
	pt.map(2, 1, 1, { 10, 10 });
	pt.map(1, 2, 2, { 20, 20 });
	pt.map(1, 3, 3, { 21, 21 });
	pt.map(0, 4, 4, { 30, 30 });
	pt.map(0, 5, 5, { 31, 31 });

	// Invalidate at mip 2, tile (1,1), 1×1 — should cascade to finer mips
	pt.invalidate_region(2, 1, 1, 1, 1);

	// Mip 2 (1,1) should be evicted
	assert(!pt.lookup(2, 1, 1).valid);

	// Mip 1: (2,2)..(3,3) should be evicted
	assert(!pt.lookup(1, 2, 2).valid);
	assert(!pt.lookup(1, 3, 3).valid);

	// Mip 0: (4,4)..(7,7) should be evicted
	assert(!pt.lookup(0, 4, 4).valid);
	assert(!pt.lookup(0, 5, 5).valid);
	PASS();
}

void test_clear() {
	TEST(clear);
	PageTable pt;
	pt.init(4, 16);

	for (int i = 0; i < 16; i++) {
		pt.map(0, i, 0, { (uint8_t)i, 0 });
	}
	assert(pt.lookup(0, 5, 0).valid);

	pt.clear();
	for (int i = 0; i < 16; i++) {
		assert(!pt.lookup(0, i, 0).valid);
	}
	PASS();
}

void test_gpu_data() {
	TEST(gpu_data);
	PageTable pt;
	pt.init(4, 16);

	pt.map(0, 0, 0, { 10, 20 });
	pt.map(0, 1, 0, { 11, 21 });

	uint8_t buf[16 * 16 * 4];
	int written = pt.get_gpu_data(0, buf, sizeof(buf));
	assert(written == 16 * 16 * 4);

	// Entry (0,0): x=10, y=20, valid=1
	assert(buf[0] == 10);
	assert(buf[1] == 20);
	assert(buf[2] == 1);

	// Entry (1,0): x=11, y=21, valid=1
	assert(buf[4] == 11);
	assert(buf[5] == 21);
	assert(buf[6] == 1);

	// Entry (2,0): unmapped, valid=0
	assert(buf[8] == 0);
	assert(buf[9] == 0);
	assert(buf[10] == 0);
	PASS();
}

void test_out_of_bounds() {
	TEST(out_of_bounds);
	PageTable pt;
	pt.init(4, 16);

	// Out-of-bounds operations should not crash
	pt.map(5, 0, 0, { 1, 1 }); // mip 5 doesn't exist
	pt.map(0, 100, 100, { 1, 1 }); // coords too large
	PageLookup r = pt.lookup(5, 0, 0);
	assert(!r.valid);
	PageLookup r2 = pt.lookup(0, 100, 100);
	assert(!r2.valid);
	pt.evict(5, 0, 0); // should not crash
	PASS();
}

int main() {
	printf("=== Page Table Tests ===\n");

	test_init();
	test_map_lookup();
	test_evict();
	test_map_with_fallback();
	test_fallback_doesnt_overwrite();
	test_invalidate_region();
	test_clear();
	test_gpu_data();
	test_out_of_bounds();

	printf("\nAll %d tests passed.\n", tests_passed);
	return 0;
}