diff --git a/src/editor/build_cache.cpp b/src/editor/build_cache.cpp index 2348442..feb209b 100644 --- a/src/editor/build_cache.cpp +++ b/src/editor/build_cache.cpp @@ -375,7 +375,10 @@ bool validate_build_generation(const fs::path& directory, const std::string& pac return false; for (const auto* shader : {"vertexMain", "fragmentMain", "shadowMain", "gpuVertexMain", "gpuShadowMain", "gpuCullMain", - "gpuHzbMain", "gpuPostCullMain"}) + "gpuHzbMain", "gpuPostCullMain", "lightTileMain", + "temporalResolveMain", "temporalCompositeVertexMain", + "temporalCompositeFragmentMain", "temporalVertexMain", + "temporalFragmentMain", "gpuTemporalVertexMain"}) for (const auto* extension : {".spv", ".reflection.json"}) if (!expected.contains("shaders/" + std::string(shader) + extension)) return false; diff --git a/src/editor/build_service.cpp b/src/editor/build_service.cpp index 4bf6974..458afbe 100644 --- a/src/editor/build_service.cpp +++ b/src/editor/build_service.cpp @@ -920,7 +920,12 @@ struct BuildService::Impl { {"required_features", {"dynamicRendering", "synchronization2"}}, {"materials", {"base-color factor and texture", "metallic and roughness factors"}}, {"texture_sampling", "linear clamp, one mip level"}, - {"shadow_map", {{"resolution", 1024}, {"world_extent", 40}}}, + {"shadow_atlases", {{"sun_cascade_limit", 4}, + {"local_face_limit", 16}, + {"caster_draw_limit", 4096}, + {"preferred_resolution", 2048}, + {"fallback_resolution", 1024}, + {"sampled_d32_required", true}}}, {"unsupported_material_features", {"normal maps", "metallic-roughness maps", "emissive and occlusion maps", "alpha mode selection", "unlit mode", "per-material face culling"}}}; diff --git a/tests/build_schema_tests.cpp b/tests/build_schema_tests.cpp index a353243..361738d 100644 --- a/tests/build_schema_tests.cpp +++ b/tests/build_schema_tests.cpp @@ -1,9 +1,11 @@ #include #include #include +#include #include #include #include +#include #include #include @@ -202,19 +204,39 @@ int test_main(int argc, char** argv) { read_text(config.project_root / "schema-export-count.txt") == "2", "Corrupt schema cannot be a cache hit"); atomic_write(path_from_utf8(first.result.at("directory").get()) / - "shaders/vertexMain.spv", + "shaders/temporalResolveMain.spv", "corrupt"); first = builds.wait(builds.start_build()); check(first.state == "succeeded" && first.result.at("schema_cache_hit") == false && read_text(config.project_root / "schema-export-count.txt") == "3", - "Corrupt shader cannot be a cache hit"); + "Corrupt temporal shader cannot be a cache hit"); const auto directory = path_from_utf8(first.result.at("directory").get()); for (const auto* entry : {"lightTileMain", "gpuVertexMain", "gpuShadowMain", "gpuCullMain", - "gpuHzbMain", "gpuPostCullMain"}) + "gpuHzbMain", "gpuPostCullMain", "temporalResolveMain", + "temporalCompositeVertexMain", "temporalCompositeFragmentMain", + "temporalVertexMain", "temporalFragmentMain", + "gpuTemporalVertexMain"}) for (const auto* extension : {".spv", ".reflection.json"}) check(fs::is_regular_file(directory / "shaders" / (std::string(entry) + extension)), - "Published Player contains every checked P2 shader artifact"); + "Published Player contains every checked P2/P3 shader artifact"); + const auto published_manifest = read_json(directory / "manifest.json"); + const auto package_key = published_manifest.at("package_key").get(); + check(editor::validate_build_generation(directory, package_key), + "Complete P3 shader bundle validates as a reusable generation"); + const auto missing_temporal = root / "missing-temporal-generation"; + fs::copy(directory, missing_temporal, fs::copy_options::recursive); + fs::remove(missing_temporal / "shaders/temporalResolveMain.spv"); + fs::remove(missing_temporal / "shaders/temporalResolveMain.reflection.json"); + auto incomplete_manifest = read_json(missing_temporal / "manifest.json"); + auto& listed_files = incomplete_manifest.at("files").get_ref(); + std::erase_if(listed_files, [](const Json& item) { + return item.at("path") == "shaders/temporalResolveMain.spv" || + item.at("path") == "shaders/temporalResolveMain.reflection.json"; + }); + atomic_write_json(missing_temporal / "manifest.json", incomplete_manifest); + check(!editor::validate_build_generation(missing_temporal, package_key), + "Generation missing temporal shaders cannot be reused even with matching hashes"); const auto player = path_from_utf8(first.result.at("player").get()); const auto schema = path_from_utf8(first.result.at("schema").get()); const auto last_build = builds.config().cache_root / "last_build.json"; diff --git a/tests/build_schema_tool.cpp b/tests/build_schema_tool.cpp index 016c812..ba4c5ca 100644 --- a/tests/build_schema_tool.cpp +++ b/tests/build_schema_tool.cpp @@ -92,7 +92,10 @@ int tool_main(int argc, char** argv) { fs::copy_options::overwrite_existing); for (const auto* entry : {"vertexMain", "fragmentMain", "shadowMain", "lightTileMain", "gpuVertexMain", "gpuShadowMain", "gpuCullMain", - "gpuHzbMain", "gpuPostCullMain"}) + "gpuHzbMain", "gpuPostCullMain", "temporalResolveMain", + "temporalCompositeVertexMain", "temporalCompositeFragmentMain", + "temporalVertexMain", "temporalFragmentMain", + "gpuTemporalVertexMain"}) for (const auto* extension : {".spv", ".reflection.json"}) atomic_write(build / "shaders" / (std::string(entry) + extension), "fixture\n"); return 0; diff --git a/tests/build_service_tests.cpp b/tests/build_service_tests.cpp index 5562076..c051065 100644 --- a/tests/build_service_tests.cpp +++ b/tests/build_service_tests.cpp @@ -310,8 +310,17 @@ int integration(const fs::path& root) { require(path_from_utf8(result.result.at("build_directory").get()) == config.build_directory / "Release", "Export has a separate CMake directory"); - require(read_json(directory / "manifest.json").at("configuration") == "Release", + const auto export_manifest = read_json(directory / "manifest.json"); + require(export_manifest.at("configuration") == "Release", "Export manifest records the actual profile"); + const auto& renderer_profile = export_manifest.at("renderer_profile"); + require(!renderer_profile.contains("shadow_map") && + renderer_profile.at("shadow_atlases").at("sun_cascade_limit") == 4 && + renderer_profile.at("shadow_atlases").at("local_face_limit") == 16 && + renderer_profile.at("shadow_atlases").at("caster_draw_limit") == 4096 && + renderer_profile.at("shadow_atlases").at("preferred_resolution") == 2048 && + renderer_profile.at("shadow_atlases").at("fallback_resolution") == 1024, + "Export manifest describes the actual bounded P3 shadow-atlas policy"); for (const auto* entry : {"temporalResolveMain", "temporalCompositeVertexMain", "temporalCompositeFragmentMain", "temporalVertexMain", "temporalFragmentMain", "gpuTemporalVertexMain"}) {