From 8d0e3f054c4864b80bbb0c6cb9b7805f1b58de93 Mon Sep 17 00:00:00 2001 From: Jan Laupetin Date: Fri, 20 Mar 2026 20:58:14 +0100 Subject: [PATCH 1/4] chore: add unit tests for t5 techsets --- .../T5/Techset/TechniqueCompilerT5Test.cpp | 558 ++++++++++++++++++ .../Game/T5/Techset/TechsetCompilerT5Test.cpp | 149 +++++ .../T5/Techset/VertexDeclCompilerT5Test.cpp | 63 ++ .../Game/T5/Techset/ps_advanced.hlsl.cso | Bin 0 -> 4060 bytes .../Game/T5/Techset/ps_simple.hlsl.cso | Bin 0 -> 140 bytes .../Game/T5/Techset/vs_advanced.hlsl.cso | Bin 0 -> 2480 bytes .../Game/T5/Techset/vs_simple.hlsl.cso | Bin 0 -> 392 bytes .../Game/T5/Techset/TechsetDumperT5Test.cpp | 549 +++++++++++++++++ .../Game/T5/Techset/ps_advanced.hlsl.cso | Bin 0 -> 4060 bytes .../Game/T5/Techset/ps_simple.hlsl.cso | Bin 0 -> 140 bytes .../Game/T5/Techset/vs_advanced.hlsl.cso | Bin 0 -> 2480 bytes .../Game/T5/Techset/vs_simple.hlsl.cso | Bin 0 -> 392 bytes 12 files changed, 1319 insertions(+) create mode 100644 test/ObjCompilingTests/Game/T5/Techset/TechniqueCompilerT5Test.cpp create mode 100644 test/ObjCompilingTests/Game/T5/Techset/TechsetCompilerT5Test.cpp create mode 100644 test/ObjCompilingTests/Game/T5/Techset/VertexDeclCompilerT5Test.cpp create mode 100644 test/ObjCompilingTests/Game/T5/Techset/ps_advanced.hlsl.cso create mode 100644 test/ObjCompilingTests/Game/T5/Techset/ps_simple.hlsl.cso create mode 100644 test/ObjCompilingTests/Game/T5/Techset/vs_advanced.hlsl.cso create mode 100644 test/ObjCompilingTests/Game/T5/Techset/vs_simple.hlsl.cso create mode 100644 test/ObjWritingTests/Game/T5/Techset/TechsetDumperT5Test.cpp create mode 100644 test/ObjWritingTests/Game/T5/Techset/ps_advanced.hlsl.cso create mode 100644 test/ObjWritingTests/Game/T5/Techset/ps_simple.hlsl.cso create mode 100644 test/ObjWritingTests/Game/T5/Techset/vs_advanced.hlsl.cso create mode 100644 test/ObjWritingTests/Game/T5/Techset/vs_simple.hlsl.cso diff --git a/test/ObjCompilingTests/Game/T5/Techset/TechniqueCompilerT5Test.cpp b/test/ObjCompilingTests/Game/T5/Techset/TechniqueCompilerT5Test.cpp new file mode 100644 index 00000000..eaad7588 --- /dev/null +++ b/test/ObjCompilingTests/Game/T5/Techset/TechniqueCompilerT5Test.cpp @@ -0,0 +1,558 @@ +#include "Game/T5/Techset/TechniqueCompilerT5.h" + +#include "Game/T5/T5.h" +#include "Game/T5/Techset/PixelShaderLoaderT5.h" +#include "Game/T5/Techset/VertexDeclCompilerT5.h" +#include "Game/T5/Techset/VertexShaderLoaderT5.h" +#include "OatTestPaths.h" +#include "SearchPath/MockSearchPath.h" +#include "Shader/ShaderCommon.h" +#include "Utils/MemoryManager.h" +#include "catch2/generators/catch_generators.hpp" + +#include +#include +#include +#include + +using namespace T5; +using namespace Catch; +using namespace std::literals; +namespace fs = std::filesystem; + +namespace +{ + void GivenVertexShaderFile(const std::string& name, MockSearchPath& searchPath) + { + const auto filePath = oat::paths::GetTestDirectory() / "ObjCompilingTests/Game/T5/Techset" / std::format("vs_{}.cso", name); + const auto fileSize = static_cast(fs::file_size(filePath)); + + std::ifstream file(filePath, std::ios::binary); + REQUIRE(file.is_open()); + + std::string data(fileSize, '\0'); + file.read(data.data(), fileSize); + REQUIRE(file.gcount() == static_cast(fileSize)); + + searchPath.AddFileData(shader::GetFileNameForVertexShaderAssetName(name), std::move(data)); + } + + void GivenPixelShaderFile(const std::string& name, MockSearchPath& searchPath) + { + const auto filePath = oat::paths::GetTestDirectory() / "ObjCompilingTests/Game/T5/Techset" / std::format("ps_{}.cso", name); + const auto fileSize = static_cast(fs::file_size(filePath)); + + std::ifstream file(filePath, std::ios::binary); + REQUIRE(file.is_open()); + + std::string data(fileSize, '\0'); + file.read(data.data(), fileSize); + REQUIRE(file.gcount() == static_cast(fileSize)); + + searchPath.AddFileData(shader::GetFileNameForPixelShaderAssetName(name), std::move(data)); + } +} // namespace + +TEST_CASE("TechniqueCompilerT5", "[t5][techset][compiler]") +{ + + Zone zone("MockZone", 0, GameId::T6, GamePlatform::PC); + zone.Register(); + + MemoryManager memory; + AssetCreatorCollection creatorCollection(zone); + IgnoredAssetLookup ignoredAssetLookup; + AssetCreationContext context(zone, &creatorCollection, &ignoredAssetLookup); + MockSearchPath searchPath; + + creatorCollection.AddSubAssetCreator(techset::CreateVertexDeclCompilerT5(memory)); + creatorCollection.AddSubAssetCreator(techset::CreateVertexShaderLoaderT5(memory, searchPath)); + creatorCollection.AddSubAssetCreator(techset::CreatePixelShaderLoaderT5(memory, searchPath)); + + auto loader = techset::CreateTechniqueCompilerT5(memory, zone, searchPath); + + SECTION("Can compile simple technique") + { + const auto [inputName, inputData] = GENERATE(Catch::Generators::table({ + {"auto-create args", R"TECHNIQUE( +{ + stateMap "passthrough"; + + vertexShader 3.0 "simple.hlsl" + { + } + + pixelShader 3.0 "simple.hlsl" + { + } + + vertex.position = code.position; +} +)TECHNIQUE"}, + {"manual args", R"TECHNIQUE( +{ + stateMap "passthrough"; + + vertexShader 3.0 "simple.hlsl" + { + worldMatrix = constant.worldMatrix; + viewProjectionMatrix = constant.viewProjectionMatrix; + } + + pixelShader 3.0 "simple.hlsl" + { + } + + vertex.position = code.position; +} +)TECHNIQUE"}, + })); + + CAPTURE(inputName); + searchPath.AddFileData("techniques/pimp_technique_zprepass_example.tech", inputData); + + GivenVertexShaderFile("simple.hlsl", searchPath); + GivenPixelShaderFile("simple.hlsl", searchPath); + + auto result = loader->CreateSubAsset("pimp_technique_zprepass_example", context); + REQUIRE(result.HasBeenSuccessful()); + + const auto* assetInfo = reinterpret_cast*>(result.GetAssetInfo()); + const auto* technique = assetInfo->Asset(); + + CHECK(technique->name == "pimp_technique_zprepass_example"s); + + // Usually would be 0x80 set as well, but that's only set when postprocessing with materials + CHECK(technique->flags == 0x04); + + REQUIRE(technique->passCount == 1); + auto& pass = technique->passArray[0]; + CHECK(pass.customSamplerFlags == 0); + + REQUIRE(pass.vertexShader); + CHECK(pass.vertexShader->name == "simple.hlsl"s); + REQUIRE(pass.pixelShader); + CHECK(pass.pixelShader->name == "simple.hlsl"s); + + REQUIRE(pass.vertexDecl); + auto& vertexDecl = *pass.vertexDecl; + CHECK(vertexDecl.hasOptionalSource == false); + CHECK(vertexDecl.isLoaded == false); + REQUIRE(vertexDecl.streamCount == 1); + CHECK(vertexDecl.routing.data[0].source == STREAM_SRC_POSITION); + CHECK(vertexDecl.routing.data[0].dest == STREAM_DST_POSITION); + + REQUIRE(pass.perPrimArgCount == 1); + REQUIRE(pass.perObjArgCount == 1); + REQUIRE(pass.stableArgCount == 0); + CHECK(pass.args[0].type == MTL_ARG_CODE_VERTEX_CONST); + CHECK(pass.args[0].dest == 4); + CHECK(pass.args[0].u.codeConst.index == CONST_SRC_CODE_TRANSPOSE_WORLD_MATRIX); + CHECK(pass.args[0].u.codeConst.firstRow == 0); + CHECK(pass.args[0].u.codeConst.rowCount == 4); + + CHECK(pass.args[1].type == MTL_ARG_CODE_VERTEX_CONST); + CHECK(pass.args[1].dest == 0); + CHECK(pass.args[1].u.codeConst.index == CONST_SRC_CODE_TRANSPOSE_VIEW_PROJECTION_MATRIX); + CHECK(pass.args[1].u.codeConst.firstRow == 0); + CHECK(pass.args[1].u.codeConst.rowCount == 4); + } + + SECTION("Can compile advanced technique") + { + const auto [inputName, inputData] = GENERATE(Catch::Generators::table({ + {"auto-create args", R"TECHNIQUE( +{ + stateMap "passthrough"; + + vertexShader 3.0 "advanced.hlsl" + { + Flicker_Min = material.Flicker_Min; + Flicker_Max = material.Flicker_Max; + Seed_Value = material.Seed_Value; + } + + pixelShader 3.0 "advanced.hlsl" + { + Diffuse_MapSampler = material.Diffuse_MapSampler; + Diffuse_Map_Damage = material.Diffuse_Map_Damage; + Normal_Map_Cracked = material.Normal_Map_Cracked; + Reveal_Map = material.Reveal_Map; + Specular_Map = material.Specular_Map; + Heat_Map = material.Heat_Map; + Ember_Scale = material.Ember_Scale; + Heat_Direction = material.Heat_Direction; + Ember_Direction = material.Ember_Direction; + Heat_Scale = material.Heat_Scale; + } + + vertex.position = code.position; + vertex.texcoord[0] = code.texcoord[0]; + vertex.normal = code.normal; + vertex.texcoord[2] = code.tangent; +} +)TECHNIQUE"}, + {"manual args", R"TECHNIQUE( +{ + stateMap "passthrough"; + + vertexShader 3.0 "advanced.hlsl" + { + worldMatrix = constant.worldMatrix; + baseLightingCoords = constant.baseLightingCoords; + viewProjectionMatrix = constant.viewProjectionMatrix; + shadowLookupMatrix = constant.shadowLookupMatrix; + Flicker_Min = material.Flicker_Min; + Flicker_Max = material.Flicker_Max; + Seed_Value = material.Seed_Value; + fogConsts = constant.fogConsts; + fogConsts2 = constant.fogConsts2; + gameTime = constant.gameTime; + sunFogDir = constant.sunFogDir; + sunFogColor = constant.sunFogColor; + sunFog = constant.sunFog; + fogColor = constant.fogColor; + } + + pixelShader 3.0 "advanced.hlsl" + { + Diffuse_MapSampler = material.Diffuse_MapSampler; + Diffuse_Map_Damage = material.Diffuse_Map_Damage; + Normal_Map_Cracked = material.Normal_Map_Cracked; + Reveal_Map = material.Reveal_Map; + Specular_Map = material.Specular_Map; + Heat_Map = material.Heat_Map; + shadowmapSamplerSpot = sampler.shadowmapSamplerSpot; + modelLightingSampler = sampler.modelLightingSampler; + lightingLookupScale = constant.lightingLookupScale; + glightPosXs = constant.glightPosXs; + glightPosYs = constant.glightPosYs; + glightPosZs = constant.glightPosZs; + glightFallOffs = constant.glightFallOffs; + glightReds = constant.glightReds; + glightGreens = constant.glightGreens; + glightBlues = constant.glightBlues; + lightPosition = constant.lightPosition; + lightDiffuse = constant.lightDiffuse; + lightHeroScale = constant.lightHeroScale; + lightSpotDir = constant.lightSpotDir; + lightSpotFactors = constant.lightSpotFactors; + lightAttenuation = constant.lightAttenuation; + lightFallOffA = constant.lightFallOffA; + spotShadowmapPixelAdjust = constant.spotShadowmapPixelAdjust; + gameTime = constant.gameTime; + hdrControl0 = constant.hdrControl0; + heroLightingR = constant.heroLightingR; + heroLightingG = constant.heroLightingG; + heroLightingB = constant.heroLightingB; + Ember_Scale = material.Ember_Scale; + Heat_Direction = material.Heat_Direction; + Ember_Direction = material.Ember_Direction; + Heat_Scale = material.Heat_Scale; + } + + vertex.position = code.position; + vertex.texcoord[0] = code.texcoord[0]; + vertex.normal = code.normal; + vertex.texcoord[2] = code.tangent; +} +)TECHNIQUE"}, + })); + + CAPTURE(inputName); + searchPath.AddFileData("techniques/example_lit_omni_shadow_glight.tech", inputData); + + GivenVertexShaderFile("advanced.hlsl", searchPath); + GivenPixelShaderFile("advanced.hlsl", searchPath); + + auto result = loader->CreateSubAsset("example_lit_omni_shadow_glight", context); + REQUIRE(result.HasBeenSuccessful()); + + const auto* assetInfo = reinterpret_cast*>(result.GetAssetInfo()); + const auto* technique = assetInfo->Asset(); + + CHECK(technique->name == "example_lit_omni_shadow_glight"s); + + // Usually would be 0x80 set as well, but that's only set when postprocessing with materials + CHECK(technique->flags == 0x10); + + REQUIRE(technique->passCount == 1); + auto& pass = technique->passArray[0]; + CHECK(pass.customSamplerFlags == 1); + + REQUIRE(pass.vertexShader); + CHECK(pass.vertexShader->name == "advanced.hlsl"s); + REQUIRE(pass.pixelShader); + CHECK(pass.pixelShader->name == "advanced.hlsl"s); + + REQUIRE(pass.vertexDecl); + auto& vertexDecl = *pass.vertexDecl; + CHECK(vertexDecl.hasOptionalSource == false); + CHECK(vertexDecl.isLoaded == false); + REQUIRE(vertexDecl.streamCount == 4); + CHECK(vertexDecl.routing.data[0].source == STREAM_SRC_POSITION); + CHECK(vertexDecl.routing.data[0].dest == STREAM_DST_POSITION); + CHECK(vertexDecl.routing.data[1].source == STREAM_SRC_TEXCOORD_0); + CHECK(vertexDecl.routing.data[1].dest == STREAM_DST_TEXCOORD_0); + CHECK(vertexDecl.routing.data[2].source == STREAM_SRC_NORMAL); + CHECK(vertexDecl.routing.data[2].dest == STREAM_DST_NORMAL); + CHECK(vertexDecl.routing.data[3].source == STREAM_SRC_TANGENT); + CHECK(vertexDecl.routing.data[3].dest == STREAM_DST_TEXCOORD_2); + + REQUIRE(pass.perPrimArgCount == 2); + REQUIRE(pass.perObjArgCount == 2); + REQUIRE(pass.stableArgCount == 43); + + CHECK(pass.args[0].type == MTL_ARG_CODE_VERTEX_CONST); + CHECK(pass.args[0].dest == 4); + CHECK(pass.args[0].u.codeConst.index == CONST_SRC_CODE_TRANSPOSE_WORLD_MATRIX); + CHECK(pass.args[0].u.codeConst.firstRow == 0); + CHECK(pass.args[0].u.codeConst.rowCount == 3); + + CHECK(pass.args[1].type == MTL_ARG_CODE_VERTEX_CONST); + CHECK(pass.args[1].dest == 8); + CHECK(pass.args[1].u.codeConst.index == CONST_SRC_CODE_BASE_LIGHTING_COORDS); + CHECK(pass.args[1].u.codeConst.firstRow == 0); + CHECK(pass.args[1].u.codeConst.rowCount == 1); + + CHECK(pass.args[2].type == MTL_ARG_CODE_VERTEX_CONST); + CHECK(pass.args[2].dest == 0); + CHECK(pass.args[2].u.codeConst.index == CONST_SRC_CODE_TRANSPOSE_VIEW_PROJECTION_MATRIX); + CHECK(pass.args[2].u.codeConst.firstRow == 0); + CHECK(pass.args[2].u.codeConst.rowCount == 4); + + CHECK(pass.args[3].type == MTL_ARG_CODE_VERTEX_CONST); + CHECK(pass.args[3].dest == 24); + CHECK(pass.args[3].u.codeConst.index == CONST_SRC_CODE_TRANSPOSE_SHADOW_LOOKUP_MATRIX); + CHECK(pass.args[3].u.codeConst.firstRow == 0); + CHECK(pass.args[3].u.codeConst.rowCount == 4); + + CHECK(pass.args[4].type == MTL_ARG_MATERIAL_VERTEX_CONST); + CHECK(pass.args[4].dest == 36); + CHECK(pass.args[4].u.nameHash == 0x2DDF50E9); + + CHECK(pass.args[5].type == MTL_ARG_MATERIAL_VERTEX_CONST); + CHECK(pass.args[5].dest == 23); + CHECK(pass.args[5].u.nameHash == 0x2DDF51F7); + + CHECK(pass.args[6].type == MTL_ARG_MATERIAL_VERTEX_CONST); + CHECK(pass.args[6].dest == 37); + CHECK(pass.args[6].u.nameHash == 0x9CC05A63); + + CHECK(pass.args[7].type == MTL_ARG_MATERIAL_PIXEL_SAMPLER); + CHECK(pass.args[7].dest == 5); + CHECK(pass.args[7].u.nameHash == 0x25D709F9); + + CHECK(pass.args[8].type == MTL_ARG_MATERIAL_PIXEL_SAMPLER); + CHECK(pass.args[8].dest == 7); + CHECK(pass.args[8].u.nameHash == 0x96F1B7B9); + + CHECK(pass.args[9].type == MTL_ARG_MATERIAL_PIXEL_SAMPLER); + CHECK(pass.args[9].dest == 3); + CHECK(pass.args[9].u.nameHash == 0xCFBF1DD6); + + CHECK(pass.args[10].type == MTL_ARG_MATERIAL_PIXEL_SAMPLER); + CHECK(pass.args[10].dest == 2); + CHECK(pass.args[10].u.nameHash == 0xCFED92EA); + + CHECK(pass.args[11].type == MTL_ARG_MATERIAL_PIXEL_SAMPLER); + CHECK(pass.args[11].dest == 4); + CHECK(pass.args[11].u.nameHash == 0xD28C20CC); + + CHECK(pass.args[12].type == MTL_ARG_MATERIAL_PIXEL_SAMPLER); + CHECK(pass.args[12].dest == 6); + CHECK(pass.args[12].u.nameHash == 0xE4B9BF3B); + + CHECK(pass.args[13].type == MTL_ARG_CODE_VERTEX_CONST); + CHECK(pass.args[13].dest == 20); + CHECK(pass.args[13].u.codeConst.index == CONST_SRC_CODE_FOG); + CHECK(pass.args[13].u.codeConst.firstRow == 0); + CHECK(pass.args[13].u.codeConst.rowCount == 1); + + CHECK(pass.args[14].type == MTL_ARG_CODE_VERTEX_CONST); + CHECK(pass.args[14].dest == 21); + CHECK(pass.args[14].u.codeConst.index == CONST_SRC_CODE_FOG2); + CHECK(pass.args[14].u.codeConst.firstRow == 0); + CHECK(pass.args[14].u.codeConst.rowCount == 1); + + CHECK(pass.args[15].type == MTL_ARG_CODE_VERTEX_CONST); + CHECK(pass.args[15].dest == 22); + CHECK(pass.args[15].u.codeConst.index == CONST_SRC_CODE_GAMETIME); + CHECK(pass.args[15].u.codeConst.firstRow == 0); + CHECK(pass.args[15].u.codeConst.rowCount == 1); + + CHECK(pass.args[16].type == MTL_ARG_CODE_VERTEX_CONST); + CHECK(pass.args[16].dest == 64); + CHECK(pass.args[16].u.codeConst.index == CONST_SRC_CODE_SUN_FOG_DIR); + CHECK(pass.args[16].u.codeConst.firstRow == 0); + CHECK(pass.args[16].u.codeConst.rowCount == 1); + + CHECK(pass.args[17].type == MTL_ARG_CODE_VERTEX_CONST); + CHECK(pass.args[17].dest == 65); + CHECK(pass.args[17].u.codeConst.index == CONST_SRC_CODE_SUN_FOG_COLOR); + CHECK(pass.args[17].u.codeConst.firstRow == 0); + CHECK(pass.args[17].u.codeConst.rowCount == 1); + + CHECK(pass.args[18].type == MTL_ARG_CODE_VERTEX_CONST); + CHECK(pass.args[18].dest == 66); + CHECK(pass.args[18].u.codeConst.index == CONST_SRC_CODE_SUN_FOG); + CHECK(pass.args[18].u.codeConst.firstRow == 0); + CHECK(pass.args[18].u.codeConst.rowCount == 1); + + CHECK(pass.args[19].type == MTL_ARG_CODE_VERTEX_CONST); + CHECK(pass.args[19].dest == 67); + CHECK(pass.args[19].u.codeConst.index == CONST_SRC_CODE_FOG_COLOR); + CHECK(pass.args[19].u.codeConst.firstRow == 0); + CHECK(pass.args[19].u.codeConst.rowCount == 1); + + CHECK(pass.args[20].type == MTL_ARG_CODE_PIXEL_SAMPLER); + CHECK(pass.args[20].dest == 1); + CHECK(pass.args[20].u.codeSampler == TEXTURE_SRC_CODE_SHADOWMAP_SPOT); + + CHECK(pass.args[21].type == MTL_ARG_CODE_PIXEL_SAMPLER); + CHECK(pass.args[21].dest == 11); + CHECK(pass.args[21].u.codeSampler == TEXTURE_SRC_CODE_MODEL_LIGHTING); + + CHECK(pass.args[22].type == MTL_ARG_CODE_PIXEL_CONST); + CHECK(pass.args[22].dest == 5); + CHECK(pass.args[22].u.codeConst.index == CONST_SRC_CODE_LIGHTING_LOOKUP_SCALE); + CHECK(pass.args[22].u.codeConst.firstRow == 0); + CHECK(pass.args[22].u.codeConst.rowCount == 1); + + CHECK(pass.args[23].type == MTL_ARG_CODE_PIXEL_CONST); + CHECK(pass.args[23].dest == 6); + CHECK(pass.args[23].u.codeConst.index == CONST_SRC_CODE_GLIGHT_POSXS); + CHECK(pass.args[23].u.codeConst.firstRow == 0); + CHECK(pass.args[23].u.codeConst.rowCount == 1); + + CHECK(pass.args[24].type == MTL_ARG_CODE_PIXEL_CONST); + CHECK(pass.args[24].dest == 7); + CHECK(pass.args[24].u.codeConst.index == CONST_SRC_CODE_GLIGHT_POSYS); + CHECK(pass.args[24].u.codeConst.firstRow == 0); + CHECK(pass.args[24].u.codeConst.rowCount == 1); + + CHECK(pass.args[25].type == MTL_ARG_CODE_PIXEL_CONST); + CHECK(pass.args[25].dest == 8); + CHECK(pass.args[25].u.codeConst.index == CONST_SRC_CODE_GLIGHT_POSZS); + CHECK(pass.args[25].u.codeConst.firstRow == 0); + CHECK(pass.args[25].u.codeConst.rowCount == 1); + + CHECK(pass.args[26].type == MTL_ARG_CODE_PIXEL_CONST); + CHECK(pass.args[26].dest == 9); + CHECK(pass.args[26].u.codeConst.index == CONST_SRC_CODE_GLIGHT_FALLOFFS); + CHECK(pass.args[26].u.codeConst.firstRow == 0); + CHECK(pass.args[26].u.codeConst.rowCount == 1); + + CHECK(pass.args[27].type == MTL_ARG_CODE_PIXEL_CONST); + CHECK(pass.args[27].dest == 10); + CHECK(pass.args[27].u.codeConst.index == CONST_SRC_CODE_GLIGHT_REDS); + CHECK(pass.args[27].u.codeConst.firstRow == 0); + CHECK(pass.args[27].u.codeConst.rowCount == 1); + + CHECK(pass.args[28].type == MTL_ARG_CODE_PIXEL_CONST); + CHECK(pass.args[28].dest == 11); + CHECK(pass.args[28].u.codeConst.index == CONST_SRC_CODE_GLIGHT_GREENS); + CHECK(pass.args[28].u.codeConst.firstRow == 0); + CHECK(pass.args[28].u.codeConst.rowCount == 1); + + CHECK(pass.args[29].type == MTL_ARG_CODE_PIXEL_CONST); + CHECK(pass.args[29].dest == 20); + CHECK(pass.args[29].u.codeConst.index == CONST_SRC_CODE_GLIGHT_BLUES); + CHECK(pass.args[29].u.codeConst.firstRow == 0); + CHECK(pass.args[29].u.codeConst.rowCount == 1); + + CHECK(pass.args[30].type == MTL_ARG_CODE_PIXEL_CONST); + CHECK(pass.args[30].dest == 21); + CHECK(pass.args[30].u.codeConst.index == CONST_SRC_CODE_LIGHT_POSITION); + CHECK(pass.args[30].u.codeConst.firstRow == 0); + CHECK(pass.args[30].u.codeConst.rowCount == 1); + + CHECK(pass.args[31].type == MTL_ARG_CODE_PIXEL_CONST); + CHECK(pass.args[31].dest == 22); + CHECK(pass.args[31].u.codeConst.index == CONST_SRC_CODE_LIGHT_DIFFUSE); + CHECK(pass.args[31].u.codeConst.firstRow == 0); + CHECK(pass.args[31].u.codeConst.rowCount == 1); + + CHECK(pass.args[32].type == MTL_ARG_CODE_PIXEL_CONST); + CHECK(pass.args[32].dest == 23); + CHECK(pass.args[32].u.codeConst.index == CONST_SRC_CODE_LIGHT_HERO_SCALE); + CHECK(pass.args[32].u.codeConst.firstRow == 0); + CHECK(pass.args[32].u.codeConst.rowCount == 1); + + CHECK(pass.args[33].type == MTL_ARG_CODE_PIXEL_CONST); + CHECK(pass.args[33].dest == 24); + CHECK(pass.args[33].u.codeConst.index == CONST_SRC_CODE_LIGHT_SPOTDIR); + CHECK(pass.args[33].u.codeConst.firstRow == 0); + CHECK(pass.args[33].u.codeConst.rowCount == 1); + + CHECK(pass.args[34].type == MTL_ARG_CODE_PIXEL_CONST); + CHECK(pass.args[34].dest == 25); + CHECK(pass.args[34].u.codeConst.index == CONST_SRC_CODE_LIGHT_SPOTFACTORS); + CHECK(pass.args[34].u.codeConst.firstRow == 0); + CHECK(pass.args[34].u.codeConst.rowCount == 1); + + CHECK(pass.args[35].type == MTL_ARG_CODE_PIXEL_CONST); + CHECK(pass.args[35].dest == 26); + CHECK(pass.args[35].u.codeConst.index == CONST_SRC_CODE_LIGHT_ATTENUATION); + CHECK(pass.args[35].u.codeConst.firstRow == 0); + CHECK(pass.args[35].u.codeConst.rowCount == 1); + + CHECK(pass.args[36].type == MTL_ARG_CODE_PIXEL_CONST); + CHECK(pass.args[36].dest == 27); + CHECK(pass.args[36].u.codeConst.index == CONST_SRC_CODE_LIGHT_FALLOFF_A); + CHECK(pass.args[36].u.codeConst.firstRow == 0); + CHECK(pass.args[36].u.codeConst.rowCount == 1); + + CHECK(pass.args[37].type == MTL_ARG_CODE_PIXEL_CONST); + CHECK(pass.args[37].dest == 28); + CHECK(pass.args[37].u.codeConst.index == CONST_SRC_CODE_SPOT_SHADOWMAP_PIXEL_ADJUST); + CHECK(pass.args[37].u.codeConst.firstRow == 0); + CHECK(pass.args[37].u.codeConst.rowCount == 1); + + CHECK(pass.args[38].type == MTL_ARG_CODE_PIXEL_CONST); + CHECK(pass.args[38].dest == 29); + CHECK(pass.args[38].u.codeConst.index == CONST_SRC_CODE_GAMETIME); + CHECK(pass.args[38].u.codeConst.firstRow == 0); + CHECK(pass.args[38].u.codeConst.rowCount == 1); + + CHECK(pass.args[39].type == MTL_ARG_CODE_PIXEL_CONST); + CHECK(pass.args[39].dest == 30); + CHECK(pass.args[39].u.codeConst.index == CONST_SRC_CODE_HDRCONTROL_0); + CHECK(pass.args[39].u.codeConst.firstRow == 0); + CHECK(pass.args[39].u.codeConst.rowCount == 1); + + CHECK(pass.args[40].type == MTL_ARG_CODE_PIXEL_CONST); + CHECK(pass.args[40].dest == 31); + CHECK(pass.args[40].u.codeConst.index == CONST_SRC_CODE_HERO_LIGHTING_R); + CHECK(pass.args[40].u.codeConst.firstRow == 0); + CHECK(pass.args[40].u.codeConst.rowCount == 1); + + CHECK(pass.args[41].type == MTL_ARG_CODE_PIXEL_CONST); + CHECK(pass.args[41].dest == 32); + CHECK(pass.args[41].u.codeConst.index == CONST_SRC_CODE_HERO_LIGHTING_G); + CHECK(pass.args[41].u.codeConst.firstRow == 0); + CHECK(pass.args[41].u.codeConst.rowCount == 1); + + CHECK(pass.args[42].type == MTL_ARG_CODE_PIXEL_CONST); + CHECK(pass.args[42].dest == 33); + CHECK(pass.args[42].u.codeConst.index == CONST_SRC_CODE_HERO_LIGHTING_B); + CHECK(pass.args[42].u.codeConst.firstRow == 0); + CHECK(pass.args[42].u.codeConst.rowCount == 1); + + CHECK(pass.args[43].type == MTL_ARG_MATERIAL_PIXEL_CONST); + CHECK(pass.args[43].dest == 34); + CHECK(pass.args[43].u.nameHash == 0x349EB03A); + + CHECK(pass.args[44].type == MTL_ARG_MATERIAL_PIXEL_CONST); + CHECK(pass.args[44].dest == 36); + CHECK(pass.args[44].u.nameHash == 0x978B6822); + + CHECK(pass.args[45].type == MTL_ARG_MATERIAL_PIXEL_CONST); + CHECK(pass.args[45].dest == 35); + CHECK(pass.args[45].u.nameHash == 0xF6410F67); + + CHECK(pass.args[46].type == MTL_ARG_MATERIAL_PIXEL_CONST); + CHECK(pass.args[46].dest == 37); + CHECK(pass.args[46].u.nameHash == 0xFA98347F); + } +} diff --git a/test/ObjCompilingTests/Game/T5/Techset/TechsetCompilerT5Test.cpp b/test/ObjCompilingTests/Game/T5/Techset/TechsetCompilerT5Test.cpp new file mode 100644 index 00000000..1da90e49 --- /dev/null +++ b/test/ObjCompilingTests/Game/T5/Techset/TechsetCompilerT5Test.cpp @@ -0,0 +1,149 @@ +#include "Game/T5/Techset/TechsetCompilerT5.h" + +#include "Game/T5/T5.h" +#include "SearchPath/MockSearchPath.h" +#include "Techset/TechsetCommon.h" +#include "Utils/TestMemoryManager.h" + +#include +#include +#include +#include + +using namespace T5; +using namespace std::string_literals; + +namespace +{ + MaterialTechnique* GivenTechnique(const std::string& name, AssetCreationContext& context, MemoryManager& memory) + { + auto* technique = memory.Alloc(); + technique->name = memory.Dup(name.c_str()); + + context.AddSubAsset(name, technique); + + return technique; + } +} // namespace + +TEST_CASE("TechsetCompilerT5", "[techset][t5][compiler]") +{ + Zone zone("test", 0, GameId::T5, GamePlatform::PC); + AssetCreatorCollection creators(zone); + IgnoredAssetLookup ignoredAssets; + AssetCreationContext context(zone, &creators, &ignoredAssets); + MockSearchPath searchPath; + TestMemoryManager memory; + const auto sut = techset::CreateTechsetCompilerT5(memory, searchPath); + + SECTION("Sets correct worldVertFormat") + { + const auto [techsetName, expectedWorldVertFormat] = GENERATE(Catch::Generators::table({ + {"default", MTL_WORLDVERT_TEX_1_NRM_1}, + {"effect_zeqqz943", MTL_WORLDVERT_TEX_1_NRM_1}, + {"lit_r0c0_t1c1n1", MTL_WORLDVERT_TEX_2_NRM_1}, + {"lit_r0c0n0x0_b1c1n1s1v1", MTL_WORLDVERT_TEX_2_NRM_2}, + {"lit_r0c0n0x0_b1c1n1s1v1_b2c2n2x2", MTL_WORLDVERT_TEX_3_NRM_3}, + {"lit_sm_b0c0_b1c1_b2c2", MTL_WORLDVERT_TEX_3_NRM_1}, + {"lit_sm_b0c0_b1c1n1x1_b2c2n2v2", MTL_WORLDVERT_TEX_3_NRM_2}, + {"lit_sm_r0c0_b1c1_b2c2_b3c3", MTL_WORLDVERT_TEX_4_NRM_1}, + {"lit_sm_r0c0n0_b1c1_b2c2n2v2_m3c3", MTL_WORLDVERT_TEX_4_NRM_2}, + {"lit_sm_r0c0n0_b1c1n1s1", MTL_WORLDVERT_TEX_2_NRM_2}, + {"lit_sm_r0c0n0s0_b1c1n1s1_b2c2n2", MTL_WORLDVERT_TEX_3_NRM_3}, + {"lit_sm_r0c0n0x0_b1c1_b2c2n2s2", MTL_WORLDVERT_TEX_3_NRM_2}, + {"lit_sm_r0c0n0x0_b1c1n1_b2c2n2s2v2", MTL_WORLDVERT_TEX_3_NRM_3}, + {"lit_sm_r0c0n0x0_b1c1s1v1_m2c2_m3c3", MTL_WORLDVERT_TEX_4_NRM_1}, + {"lit_sm_r0c0n0x0_b1c1v1_b2c2n2s2_m3c3", MTL_WORLDVERT_TEX_4_NRM_2}, + {"lit_sm_r0c0s0_b1c1n1s1_m2c2", MTL_WORLDVERT_TEX_3_NRM_1}, + {"lit_sm_r0c0x0_b1c1", MTL_WORLDVERT_TEX_2_NRM_1}, + {"lit_sm_r0c0x0_b1c1n1s1_b2c2n2s2", MTL_WORLDVERT_TEX_3_NRM_2}, + {"lit_sm_r0c0x0_m1c1_m2c2_m3c3", MTL_WORLDVERT_TEX_4_NRM_1}, + {"lit_sm_t0c0n0_b1c1n1v1", MTL_WORLDVERT_TEX_2_NRM_2}, + {"lit_sm_t0c0n0s0_b1c1n1_b2c2n2s2v2", MTL_WORLDVERT_TEX_3_NRM_3}, + {"mc_lit_sm_r0c0d0_2213939z", MTL_WORLDVERT_TEX_1_NRM_1}, + {"wpc_lit_sm_b0c0s0_3f3q946z", MTL_WORLDVERT_TEX_1_NRM_1}, + {"wpc_lit_sm_r0c0n0s0o0_qj92q1f8", MTL_WORLDVERT_TEX_1_NRM_1}, + {"wpc_sw4_3d_burning_embers_nuketown_74j6971w", MTL_WORLDVERT_TEX_1_NRM_1}, + {"wpc_unlitdecalblend_add_j26wq580", MTL_WORLDVERT_TEX_1_NRM_1}, + })); + + CAPTURE(techsetName); + searchPath.AddFileData(techset::GetFileNameForTechsetName(techsetName), ""); + + const auto result = sut->CreateAsset(techsetName, context); + REQUIRE(result.HasBeenSuccessful()); + + const auto* techset = static_cast(result.GetAssetInfo()->m_ptr); + CHECK(techset->worldVertFormat == expectedWorldVertFormat); + } + + SECTION("Can parse simple techset") + { + searchPath.AddFileData(techset::GetFileNameForTechsetName("simple"), R"TECHSET( +"depth prepass": + example_zprepass; + +"lit omni shadow glight": + example_lit_omni_shadow_glight; +)TECHSET"); + + auto* exampleZPrepass = GivenTechnique("example_zprepass", context, memory); + auto* exampleLLitOmniShadowGlight = GivenTechnique("example_lit_omni_shadow_glight", context, memory); + + const auto result = sut->CreateAsset("simple", context); + REQUIRE(result.HasBeenSuccessful()); + + const auto* techset = static_cast(result.GetAssetInfo()->m_ptr); + CHECK(techset->name == "simple"s); + CHECK(techset->worldVertFormat == MTL_WORLDVERT_TEX_1_NRM_1); + + size_t techniqueCount = 0; + for (auto* technique : techset->techniques) + { + if (technique) + techniqueCount++; + } + + CHECK(techniqueCount == 2); + CHECK(techset->techniques[TECHNIQUE_DEPTH_PREPASS] == exampleZPrepass); + CHECK(techset->techniques[TECHNIQUE_LIT_OMNI_SHADOW_GLIGHT] == exampleLLitOmniShadowGlight); + } + + SECTION("Can parse techset with same technique used multiple times") + { + searchPath.AddFileData(techset::GetFileNameForTechsetName("simple"), R"TECHSET( +"depth prepass": +"build shadowmap depth": + example_zprepass; + +"lit": +"lit sun": +"lit sun shadow": + example_lit_sun_shadow; +)TECHSET"); + + auto* exampleZPrepass = GivenTechnique("example_zprepass", context, memory); + auto* exampleLitSunShadow = GivenTechnique("example_lit_sun_shadow", context, memory); + + const auto result = sut->CreateAsset("simple", context); + REQUIRE(result.HasBeenSuccessful()); + + const auto* techset = static_cast(result.GetAssetInfo()->m_ptr); + CHECK(techset->name == "simple"s); + CHECK(techset->worldVertFormat == MTL_WORLDVERT_TEX_1_NRM_1); + + size_t techniqueCount = 0; + for (auto* technique : techset->techniques) + { + if (technique) + techniqueCount++; + } + + CHECK(techniqueCount == 5); + CHECK(techset->techniques[TECHNIQUE_DEPTH_PREPASS] == exampleZPrepass); + CHECK(techset->techniques[TECHNIQUE_BUILD_SHADOWMAP_DEPTH] == exampleZPrepass); + CHECK(techset->techniques[TECHNIQUE_LIT] == exampleLitSunShadow); + CHECK(techset->techniques[TECHNIQUE_LIT_SUN] == exampleLitSunShadow); + CHECK(techset->techniques[TECHNIQUE_LIT_SUN_SHADOW] == exampleLitSunShadow); + } +} diff --git a/test/ObjCompilingTests/Game/T5/Techset/VertexDeclCompilerT5Test.cpp b/test/ObjCompilingTests/Game/T5/Techset/VertexDeclCompilerT5Test.cpp new file mode 100644 index 00000000..a622cbbc --- /dev/null +++ b/test/ObjCompilingTests/Game/T5/Techset/VertexDeclCompilerT5Test.cpp @@ -0,0 +1,63 @@ +#include "Game/T5/Techset/VertexDeclCompilerT5.h" + +#include "Game/T5/T5.h" +#include "Utils/MemoryManager.h" + +#include + +using namespace T5; +using namespace Catch; +using namespace std::literals; + +TEST_CASE("VertexDeclCompilerT5", "[t5][techset][compiler]") +{ + Zone zone("MockZone", 0, GameId::T5, GamePlatform::PC); + zone.Register(); + + MemoryManager memory; + AssetCreatorCollection creatorCollection(zone); + IgnoredAssetLookup ignoredAssetLookup; + AssetCreationContext context(zone, &creatorCollection, &ignoredAssetLookup); + + auto loader = techset::CreateVertexDeclCompilerT5(memory); + + SECTION("Can create simple vertex decl") + { + auto result = loader->CreateSubAsset("pp", context); + REQUIRE(result.HasBeenSuccessful()); + + const auto* assetInfo = reinterpret_cast*>(result.GetAssetInfo()); + const auto* decl = assetInfo->Asset(); + + CHECK(decl->hasOptionalSource == false); + CHECK(decl->isLoaded == false); + + REQUIRE(decl->streamCount == 1); + CHECK(decl->routing.data[0].source == STREAM_SRC_POSITION); + CHECK(decl->routing.data[0].dest == STREAM_DST_POSITION); + } + + SECTION("Can create advanced vertex decl") + { + auto result = loader->CreateSubAsset("pbcc1tt10t1t1n1n", context); + REQUIRE(result.HasBeenSuccessful()); + + const auto* assetInfo = reinterpret_cast*>(result.GetAssetInfo()); + const auto* decl = assetInfo->Asset(); + + CHECK(decl->hasOptionalSource == true); + CHECK(decl->isLoaded == false); + + REQUIRE(decl->streamCount == 5); + CHECK(decl->routing.data[0].source == STREAM_SRC_POSITION); + CHECK(decl->routing.data[0].dest == STREAM_DST_BLENDWEIGHT); + CHECK(decl->routing.data[1].source == STREAM_SRC_COLOR); + CHECK(decl->routing.data[1].dest == STREAM_DST_COLOR_1); + CHECK(decl->routing.data[2].source == STREAM_SRC_TANGENT); + CHECK(decl->routing.data[2].dest == STREAM_DST_TEXCOORD_10); + CHECK(decl->routing.data[3].source == STREAM_SRC_TEXCOORD_1); + CHECK(decl->routing.data[3].dest == STREAM_DST_TEXCOORD_1); + CHECK(decl->routing.data[4].source == STREAM_SRC_NORMAL_TRANSFORM_1); + CHECK(decl->routing.data[4].dest == STREAM_DST_NORMAL); + } +} diff --git a/test/ObjCompilingTests/Game/T5/Techset/ps_advanced.hlsl.cso b/test/ObjCompilingTests/Game/T5/Techset/ps_advanced.hlsl.cso new file mode 100644 index 0000000000000000000000000000000000000000..a374c3cb1eabf3b4a6ae3ba2c9e9e6f2016863ed GIT binary patch literal 4060 zcmaJ^O>7ip7=CAG_J_7mL<)rhEq~F2F(h0(AaqLwOi+-v28pCu+G)G6+iiBYO5$Zo zyqKD>Bp$dqiHV2p(SsgHYz!9?4~7dIJaE`VjY;D{6N!oA^Ss}D(;XmAGVJ?4@Bh#D zO%XSW{*8R+^ki{tpNL$|;vZm#@IyZ1$jJM^?q>i?RvciDLG$@lv3v^q2Utc9E7s@2 z2j184X5^4!Z-73B_p)L~61@fRH}SfXOZi(-y`w4H&sEP&>3ykKCWW;X^HSKi>Tkbd z>!A7kq}a|B_OoJJQrPc`^`@}D6*D32#?6Q$}> z#jnenCw>!q0+ z{;XW`Kk^ME6HETga-~$KT;@wvf3jTlkpc5C`%d5UmGb;TbF8xLH)8rssZzNxH)q+i zb>CmK%*9&cau@wU7k$Ojm;Bj=EX>wV*A|=gTIHB5`1RU(Mkz1OkEOV?DejVJF2!ck zUtBI}ixr4RVWe?(MZ=wgYtswWM0({-yNRV*6Vo!9WX_aknzeev07&M1t#)I1$=FwG zvwp>9nQX-x_F`*lZgqdIV&;FbUc2Tenx1S+=`|Kgv$c<_$%Zk$H1N-a6&uO%EH$Q&Pal&DOa5Z9(eRs-wOXYyGU+#(Bh<%&zW7;NdrxrTN`DCXy2$7#e(?k+ zi93qhJ$o_=!71Vy;2++;HTvoHTch6{$gUR-dh5h-o9BW`zS|EP=NJA9!G4KC)P&pGMO{&FSUO#NCpaV$;`-rj zggQDc@*CWxQGr1jo zCO7gnH|&SzmP2mLmANqg4ET)l4(25*=tHRW6$X)a1e=IBU)XF52Bd|&T6YTI*{_T| z)faU^rJ(vb(70ig&c=4EkG{yelEFTRzE`xDgc8^?F$Wt9)1#v$1Vja?tG zaebMiiy6>HU?VaW>k!*eoHjNm#QFv#&_iQu>Y|Lc=x>+I0dI1^{v+l_5)_w-Q*Iu#SDebE# zd8H}qRsW1VwdzTGl{~)Y%w^S;Hu}jvd$dzq+yRhvK$~31vkvOw7rmx^zhb@U^$yJ5 z(|SaAG#k&-&e}_6yH)4BOTGv>dsnu?XD8S#qG!PLy3=yulRd~{E?N17HDdl7T^P@B zgc(OE4E+jGzxvK0XHOo&|AQ8N8U6wIhb`>O3gZ(44J>Mr&*D5~(Z8IuV9WTdo9Chf z`O^erPfedY!4dS1e%K##1_#wAXGxztqpm#Bx-AcTVC|4|V2^T`OD>)Z_X6`+<$2&- zDCfDSt&2F!*Up7}yhFrvF7^y?F4l%Rd|8vphxJXNr{7TmH5j=X#InkcG9);7;w|mnc$FjRcUFK_f>T}QV4bs|dJ$7cM51abW z^2jBzXKh{_@Bi#=f6RPZh}#Ox*~w!EpQ5IAXmxPsf^Xr!In-kxv`4^9pUI;?v0aC> zevk6lWk;s&(1v%u{T@-yIiL>6<&KGC?laDgIZ=oGE2vI8(J{RbHa6Hqg8y zrefLy-HvH~Drg$&d8T0hXp0oe-!C&s4IX@DbJJ(Ts7&eB_ zC_j?$p7ICPr?rh*yx;J*6J+dHhdO#+i0sx_2hh8`#-e}03}IU^^d@d*01d8^;?k_!p$+ITjbEmW1Tz z=M*c1q!yPb1ZO0sq!xiyVc9}s_LxDvTD z_?og@-x+R|+;J=8elXk|xwmq?pA0uo?t0GsVz^V}-p;vS4R;zYZsJeMa+R33jT-gJ z)>Bd!uSLUE$rbasFB+~w&bF3Qy*CU8`m=L??-P`v~teHiNy(L2z$(=Srj7X+FSk=+KwTPnLJM z+rAWRWijmk@ES&SE;c)~*IcA_z3w;ty|qqo*T2d5S!|cGdt(9IBodcrp4<1!62tP%h_%&KA z^Zq*TE5erzkMrbA&lkS=d0F^rdNi;+SO0CYcI%((GZyJi|kOgQn>&-vU3@cGa&=sNc0P^L_d{{ekwbnpUQ6J z=5iq_sF^j%qJVh4NuDf~46#?jNvf$<}eSqSxK zjBH`tQ+e@=A-@{ z*am0*`GgoO{<-IDl^3m2)}9$Zy=od;`?=IF!q$^VW00nv>|35z~j*>MnSQ_fJxh zy-)Lrjn7Pniju>A=3nC{gCn2uN98nM3oiQrmiH+gE`TqEng zSd`6K57g6`ny1v2>1f}ice=}9?xH`(x-%WYGMhQ(qrFfa?VHL{yVhxNPq5fqD2>Fv ziiFQw%=}^7C`oPmvM2l(9qH_KZ%VKY=Df`wmEamoofMZ0cFazIA!j&~Y~~U>j!vocB2$LKTjAw7~@_3ZD7Guipcvp*)zbUtFId5kB{ zv}e)_@wvp<*^ZFwEf7o2^8VBO>PAQQM13U>Igh?#nQ7AJ_u)kU$<=#D=je%_BYUYC zJ<)f}d}L41lU$4GJWi+mE<)$=OpbLP&*oU?@f@+%#CIt$73=A@{tL2cN_E zXB$JH1ONW>&Fl=j2+q~Hdct62(QPk*a(O7Wc0>qsccO1HL+&F(X` zYxAAOcJsc-(s+-uECU4oCfE7c*)&-;?X8=S=1=(eF`J2VCX1|1+%PgO8%|e?rAeEa zf5yFEKu;qqfPCET5B{E;1L2{Q9~HkK5VHPo(0DF1?uuLf7!bL@DOWf(EIs#HR-XGU cYtJ_=iz7HsUFSV>XT!>~Y*~8_$W;Yg0AMOjz5oCK literal 0 HcmV?d00001 diff --git a/test/ObjWritingTests/Game/T5/Techset/TechsetDumperT5Test.cpp b/test/ObjWritingTests/Game/T5/Techset/TechsetDumperT5Test.cpp new file mode 100644 index 00000000..a259fca4 --- /dev/null +++ b/test/ObjWritingTests/Game/T5/Techset/TechsetDumperT5Test.cpp @@ -0,0 +1,549 @@ +#include "Game/T5/Techset/TechsetDumperT5.h" + +#include "Asset/AssetRegistration.h" +#include "Game/T5/GameT5.h" +#include "OatTestPaths.h" +#include "SearchPath/MockOutputPath.h" +#include "SearchPath/MockSearchPath.h" +#include "Utils/MemoryManager.h" + +#include +#include +#include +#include +#include + +using namespace T5; +using namespace Catch; +using namespace std::literals; +namespace fs = std::filesystem; + +namespace +{ + std::string Trimmed(const std::string& input) + { + auto start = input.find_first_not_of(" \r\n"); + if (start == std::string::npos) + start = 0; + + auto end = input.find_last_not_of(" \r\n"); + if (end == std::string::npos) + end = input.length(); + else + end = end + 1; + + return input.substr(start, end - start); + } + + MaterialVertexShader* GivenVertexShader(const std::string& name, MemoryManager& memory) + { + const auto filePath = oat::paths::GetTestDirectory() / "ObjWritingTests/Game/T5/Techset" / std::format("vs_{}.cso", name); + const auto fileSize = static_cast(fs::file_size(filePath)); + + std::ifstream file(filePath, std::ios::binary); + REQUIRE(file.is_open()); + + auto* shader = memory.Alloc(); + shader->name = memory.Dup(name.c_str()); + shader->prog.loadDef.program = reinterpret_cast(memory.Alloc(fileSize)); + shader->prog.loadDef.programSize = static_cast(fileSize / 4); + file.read(reinterpret_cast(shader->prog.loadDef.program), fileSize); + REQUIRE(file.gcount() == static_cast(fileSize)); + + return shader; + } + + MaterialPixelShader* GivenPixelShader(const std::string& name, MemoryManager& memory) + { + const auto filePath = oat::paths::GetTestDirectory() / "ObjWritingTests/Game/T5/Techset" / std::format("ps_{}.cso", name); + const auto fileSize = static_cast(fs::file_size(filePath)); + + std::ifstream file(filePath, std::ios::binary); + REQUIRE(file.is_open()); + + auto* shader = memory.Alloc(); + shader->name = memory.Dup(name.c_str()); + shader->prog.loadDef.program = reinterpret_cast(memory.Alloc(fileSize)); + shader->prog.loadDef.programSize = static_cast(fileSize / 4); + file.read(reinterpret_cast(shader->prog.loadDef.program), fileSize); + REQUIRE(file.gcount() == static_cast(fileSize)); + + return shader; + } + + MaterialTechnique* GivenDepthPrepassTechnique(MemoryManager& memory) + { + auto* technique = memory.Alloc(); + technique->name = "example_zprepass"; + technique->flags = 0x84; + technique->passCount = 1; + + auto& pass = technique->passArray[0]; + pass.perPrimArgCount = 1; + pass.perObjArgCount = 1; + pass.stableArgCount = 0; + pass.customSamplerFlags = 0; + + pass.vertexShader = GivenVertexShader("simple.hlsl", memory); + pass.pixelShader = GivenPixelShader("simple.hlsl", memory); + + pass.vertexDecl = memory.Alloc(); + auto& vertexDecl = *pass.vertexDecl; + vertexDecl.streamCount = 1; + vertexDecl.hasOptionalSource = false; + vertexDecl.isLoaded = false; + vertexDecl.routing.data[0].source = STREAM_SRC_POSITION; + vertexDecl.routing.data[0].dest = STREAM_DST_POSITION; + + pass.args = memory.Alloc(2); + pass.args[0].type = MTL_ARG_CODE_VERTEX_CONST; + pass.args[0].dest = 4; + pass.args[0].u.codeConst.index = CONST_SRC_CODE_TRANSPOSE_WORLD_MATRIX; + pass.args[0].u.codeConst.firstRow = 0; + pass.args[0].u.codeConst.rowCount = 4; + + pass.args[1].type = MTL_ARG_CODE_VERTEX_CONST; + pass.args[1].dest = 0; + pass.args[1].u.codeConst.index = CONST_SRC_CODE_TRANSPOSE_VIEW_PROJECTION_MATRIX; + pass.args[1].u.codeConst.firstRow = 0; + pass.args[1].u.codeConst.rowCount = 4; + + return technique; + } + + MaterialTechnique* GivenLitOmniShadowGlightTechnique(MemoryManager& memory) + { + auto* technique = memory.Alloc(); + technique->name = "example_lit_omni_shadow_glight"; + technique->flags = 0x90; + technique->passCount = 1; + + auto& pass = technique->passArray[0]; + pass.perPrimArgCount = 2; + pass.perObjArgCount = 2; + pass.stableArgCount = 43; + pass.customSamplerFlags = 1; + + pass.vertexShader = GivenVertexShader("advanced.hlsl", memory); + pass.pixelShader = GivenPixelShader("advanced.hlsl", memory); + + pass.vertexDecl = memory.Alloc(); + auto& vertexDecl = *pass.vertexDecl; + vertexDecl.streamCount = 4; + vertexDecl.hasOptionalSource = false; + vertexDecl.isLoaded = false; + vertexDecl.routing.data[0].source = STREAM_SRC_POSITION; + vertexDecl.routing.data[0].dest = STREAM_DST_POSITION; + vertexDecl.routing.data[1].source = STREAM_SRC_TEXCOORD_0; + vertexDecl.routing.data[1].dest = STREAM_DST_TEXCOORD_0; + vertexDecl.routing.data[2].source = STREAM_SRC_NORMAL; + vertexDecl.routing.data[2].dest = STREAM_DST_NORMAL; + vertexDecl.routing.data[3].source = STREAM_SRC_TANGENT; + vertexDecl.routing.data[3].dest = STREAM_DST_TEXCOORD_2; + + pass.args = memory.Alloc(47); + pass.args[0].type = MTL_ARG_CODE_VERTEX_CONST; + pass.args[0].dest = 4; + pass.args[0].u.codeConst.index = CONST_SRC_CODE_TRANSPOSE_WORLD_MATRIX; + pass.args[0].u.codeConst.firstRow = 0; + pass.args[0].u.codeConst.rowCount = 3; + + pass.args[1].type = MTL_ARG_CODE_VERTEX_CONST; + pass.args[1].dest = 8; + pass.args[1].u.codeConst.index = CONST_SRC_CODE_BASE_LIGHTING_COORDS; + pass.args[1].u.codeConst.firstRow = 0; + pass.args[1].u.codeConst.rowCount = 1; + + pass.args[2].type = MTL_ARG_CODE_VERTEX_CONST; + pass.args[2].dest = 0; + pass.args[2].u.codeConst.index = CONST_SRC_CODE_TRANSPOSE_VIEW_PROJECTION_MATRIX; + pass.args[2].u.codeConst.firstRow = 0; + pass.args[2].u.codeConst.rowCount = 4; + + pass.args[3].type = MTL_ARG_CODE_VERTEX_CONST; + pass.args[3].dest = 24; + pass.args[3].u.codeConst.index = CONST_SRC_CODE_TRANSPOSE_SHADOW_LOOKUP_MATRIX; + pass.args[3].u.codeConst.firstRow = 0; + pass.args[3].u.codeConst.rowCount = 4; + + pass.args[4].type = MTL_ARG_MATERIAL_VERTEX_CONST; + pass.args[4].dest = 36; + pass.args[4].u.nameHash = 0x2DDF50E9; + + pass.args[5].type = MTL_ARG_MATERIAL_VERTEX_CONST; + pass.args[5].dest = 23; + pass.args[5].u.nameHash = 0x2DDF51F7; + + pass.args[6].type = MTL_ARG_MATERIAL_VERTEX_CONST; + pass.args[6].dest = 37; + pass.args[6].u.nameHash = 0x9CC05A63; + + pass.args[7].type = MTL_ARG_MATERIAL_PIXEL_SAMPLER; + pass.args[7].dest = 5; + pass.args[7].u.nameHash = 0x25D709F9; + + pass.args[8].type = MTL_ARG_MATERIAL_PIXEL_SAMPLER; + pass.args[8].dest = 7; + pass.args[8].u.nameHash = 0x96F1B7B9; + + pass.args[9].type = MTL_ARG_MATERIAL_PIXEL_SAMPLER; + pass.args[9].dest = 3; + pass.args[9].u.nameHash = 0xCFBF1DD6; + + pass.args[10].type = MTL_ARG_MATERIAL_PIXEL_SAMPLER; + pass.args[10].dest = 2; + pass.args[10].u.nameHash = 0xCFED92EA; + + pass.args[11].type = MTL_ARG_MATERIAL_PIXEL_SAMPLER; + pass.args[11].dest = 4; + pass.args[11].u.nameHash = 0xD28C20CC; + + pass.args[12].type = MTL_ARG_MATERIAL_PIXEL_SAMPLER; + pass.args[12].dest = 6; + pass.args[12].u.nameHash = 0xE4B9BF3B; + + pass.args[13].type = MTL_ARG_CODE_VERTEX_CONST; + pass.args[13].dest = 20; + pass.args[13].u.codeConst.index = CONST_SRC_CODE_FOG; + pass.args[13].u.codeConst.firstRow = 0; + pass.args[13].u.codeConst.rowCount = 1; + + pass.args[14].type = MTL_ARG_CODE_VERTEX_CONST; + pass.args[14].dest = 21; + pass.args[14].u.codeConst.index = CONST_SRC_CODE_FOG2; + pass.args[14].u.codeConst.firstRow = 0; + pass.args[14].u.codeConst.rowCount = 1; + + pass.args[15].type = MTL_ARG_CODE_VERTEX_CONST; + pass.args[15].dest = 22; + pass.args[15].u.codeConst.index = CONST_SRC_CODE_GAMETIME; + pass.args[15].u.codeConst.firstRow = 0; + pass.args[15].u.codeConst.rowCount = 1; + + pass.args[16].type = MTL_ARG_CODE_VERTEX_CONST; + pass.args[16].dest = 64; + pass.args[16].u.codeConst.index = CONST_SRC_CODE_SUN_FOG_DIR; + pass.args[16].u.codeConst.firstRow = 0; + pass.args[16].u.codeConst.rowCount = 1; + + pass.args[17].type = MTL_ARG_CODE_VERTEX_CONST; + pass.args[17].dest = 65; + pass.args[17].u.codeConst.index = CONST_SRC_CODE_SUN_FOG_COLOR; + pass.args[17].u.codeConst.firstRow = 0; + pass.args[17].u.codeConst.rowCount = 1; + + pass.args[18].type = MTL_ARG_CODE_VERTEX_CONST; + pass.args[18].dest = 66; + pass.args[18].u.codeConst.index = CONST_SRC_CODE_SUN_FOG; + pass.args[18].u.codeConst.firstRow = 0; + pass.args[18].u.codeConst.rowCount = 1; + + pass.args[19].type = MTL_ARG_CODE_VERTEX_CONST; + pass.args[19].dest = 67; + pass.args[19].u.codeConst.index = CONST_SRC_CODE_FOG_COLOR; + pass.args[19].u.codeConst.firstRow = 0; + pass.args[19].u.codeConst.rowCount = 1; + + pass.args[20].type = MTL_ARG_CODE_PIXEL_SAMPLER; + pass.args[20].dest = 1; + pass.args[20].u.codeSampler = TEXTURE_SRC_CODE_SHADOWMAP_SPOT; + + pass.args[21].type = MTL_ARG_CODE_PIXEL_SAMPLER; + pass.args[21].dest = 11; + pass.args[21].u.codeSampler = TEXTURE_SRC_CODE_MODEL_LIGHTING; + + pass.args[22].type = MTL_ARG_CODE_PIXEL_CONST; + pass.args[22].dest = 5; + pass.args[22].u.codeConst.index = CONST_SRC_CODE_LIGHTING_LOOKUP_SCALE; + pass.args[22].u.codeConst.firstRow = 0; + pass.args[22].u.codeConst.rowCount = 1; + + pass.args[23].type = MTL_ARG_CODE_PIXEL_CONST; + pass.args[23].dest = 6; + pass.args[23].u.codeConst.index = CONST_SRC_CODE_GLIGHT_POSXS; + pass.args[23].u.codeConst.firstRow = 0; + pass.args[23].u.codeConst.rowCount = 1; + + pass.args[24].type = MTL_ARG_CODE_PIXEL_CONST; + pass.args[24].dest = 7; + pass.args[24].u.codeConst.index = CONST_SRC_CODE_GLIGHT_POSYS; + pass.args[24].u.codeConst.firstRow = 0; + pass.args[24].u.codeConst.rowCount = 1; + + pass.args[25].type = MTL_ARG_CODE_PIXEL_CONST; + pass.args[25].dest = 8; + pass.args[25].u.codeConst.index = CONST_SRC_CODE_GLIGHT_POSZS; + pass.args[25].u.codeConst.firstRow = 0; + pass.args[25].u.codeConst.rowCount = 1; + + pass.args[26].type = MTL_ARG_CODE_PIXEL_CONST; + pass.args[26].dest = 9; + pass.args[26].u.codeConst.index = CONST_SRC_CODE_GLIGHT_FALLOFFS; + pass.args[26].u.codeConst.firstRow = 0; + pass.args[26].u.codeConst.rowCount = 1; + + pass.args[27].type = MTL_ARG_CODE_PIXEL_CONST; + pass.args[27].dest = 10; + pass.args[27].u.codeConst.index = CONST_SRC_CODE_GLIGHT_REDS; + pass.args[27].u.codeConst.firstRow = 0; + pass.args[27].u.codeConst.rowCount = 1; + + pass.args[28].type = MTL_ARG_CODE_PIXEL_CONST; + pass.args[28].dest = 11; + pass.args[28].u.codeConst.index = CONST_SRC_CODE_GLIGHT_GREENS; + pass.args[28].u.codeConst.firstRow = 0; + pass.args[28].u.codeConst.rowCount = 1; + + pass.args[29].type = MTL_ARG_CODE_PIXEL_CONST; + pass.args[29].dest = 20; + pass.args[29].u.codeConst.index = CONST_SRC_CODE_GLIGHT_BLUES; + pass.args[29].u.codeConst.firstRow = 0; + pass.args[29].u.codeConst.rowCount = 1; + + pass.args[30].type = MTL_ARG_CODE_PIXEL_CONST; + pass.args[30].dest = 21; + pass.args[30].u.codeConst.index = CONST_SRC_CODE_LIGHT_POSITION; + pass.args[30].u.codeConst.firstRow = 0; + pass.args[30].u.codeConst.rowCount = 1; + + pass.args[31].type = MTL_ARG_CODE_PIXEL_CONST; + pass.args[31].dest = 22; + pass.args[31].u.codeConst.index = CONST_SRC_CODE_LIGHT_DIFFUSE; + pass.args[31].u.codeConst.firstRow = 0; + pass.args[31].u.codeConst.rowCount = 1; + + pass.args[32].type = MTL_ARG_CODE_PIXEL_CONST; + pass.args[32].dest = 23; + pass.args[32].u.codeConst.index = CONST_SRC_CODE_LIGHT_HERO_SCALE; + pass.args[32].u.codeConst.firstRow = 0; + pass.args[32].u.codeConst.rowCount = 1; + + pass.args[33].type = MTL_ARG_CODE_PIXEL_CONST; + pass.args[33].dest = 24; + pass.args[33].u.codeConst.index = CONST_SRC_CODE_LIGHT_SPOTDIR; + pass.args[33].u.codeConst.firstRow = 0; + pass.args[33].u.codeConst.rowCount = 1; + + pass.args[34].type = MTL_ARG_CODE_PIXEL_CONST; + pass.args[34].dest = 25; + pass.args[34].u.codeConst.index = CONST_SRC_CODE_LIGHT_SPOTFACTORS; + pass.args[34].u.codeConst.firstRow = 0; + pass.args[34].u.codeConst.rowCount = 1; + + pass.args[35].type = MTL_ARG_CODE_PIXEL_CONST; + pass.args[35].dest = 26; + pass.args[35].u.codeConst.index = CONST_SRC_CODE_LIGHT_ATTENUATION; + pass.args[35].u.codeConst.firstRow = 0; + pass.args[35].u.codeConst.rowCount = 1; + + pass.args[36].type = MTL_ARG_CODE_PIXEL_CONST; + pass.args[36].dest = 27; + pass.args[36].u.codeConst.index = CONST_SRC_CODE_LIGHT_FALLOFF_A; + pass.args[36].u.codeConst.firstRow = 0; + pass.args[36].u.codeConst.rowCount = 1; + + pass.args[37].type = MTL_ARG_CODE_PIXEL_CONST; + pass.args[37].dest = 28; + pass.args[37].u.codeConst.index = CONST_SRC_CODE_SPOT_SHADOWMAP_PIXEL_ADJUST; + pass.args[37].u.codeConst.firstRow = 0; + pass.args[37].u.codeConst.rowCount = 1; + + pass.args[38].type = MTL_ARG_CODE_PIXEL_CONST; + pass.args[38].dest = 29; + pass.args[38].u.codeConst.index = CONST_SRC_CODE_GAMETIME; + pass.args[38].u.codeConst.firstRow = 0; + pass.args[38].u.codeConst.rowCount = 1; + + pass.args[39].type = MTL_ARG_CODE_PIXEL_CONST; + pass.args[39].dest = 30; + pass.args[39].u.codeConst.index = CONST_SRC_CODE_HDRCONTROL_0; + pass.args[39].u.codeConst.firstRow = 0; + pass.args[39].u.codeConst.rowCount = 1; + + pass.args[40].type = MTL_ARG_CODE_PIXEL_CONST; + pass.args[40].dest = 31; + pass.args[40].u.codeConst.index = CONST_SRC_CODE_HERO_LIGHTING_R; + pass.args[40].u.codeConst.firstRow = 0; + pass.args[40].u.codeConst.rowCount = 1; + + pass.args[41].type = MTL_ARG_CODE_PIXEL_CONST; + pass.args[41].dest = 32; + pass.args[41].u.codeConst.index = CONST_SRC_CODE_HERO_LIGHTING_G; + pass.args[41].u.codeConst.firstRow = 0; + pass.args[41].u.codeConst.rowCount = 1; + + pass.args[42].type = MTL_ARG_CODE_PIXEL_CONST; + pass.args[42].dest = 33; + pass.args[42].u.codeConst.index = CONST_SRC_CODE_HERO_LIGHTING_B; + pass.args[42].u.codeConst.firstRow = 0; + pass.args[42].u.codeConst.rowCount = 1; + + pass.args[43].type = MTL_ARG_MATERIAL_PIXEL_CONST; + pass.args[43].dest = 34; + pass.args[43].u.nameHash = 0x349EB03A; + + pass.args[44].type = MTL_ARG_MATERIAL_PIXEL_CONST; + pass.args[44].dest = 36; + pass.args[44].u.nameHash = 0x978B6822; + + pass.args[45].type = MTL_ARG_MATERIAL_PIXEL_CONST; + pass.args[45].dest = 35; + pass.args[45].u.nameHash = 0xF6410F67; + + pass.args[46].type = MTL_ARG_MATERIAL_PIXEL_CONST; + pass.args[46].dest = 37; + pass.args[46].u.nameHash = 0xFA98347F; + + return technique; + } + + MaterialTechniqueSet* GivenTechset(Zone& zone, MemoryManager& memory) + { + auto* techset = memory.Alloc(); + techset->name = "example_techset"; + techset->worldVertFormat = MTL_WORLDVERT_TEX_4_NRM_2; + + techset->techniques[TECHNIQUE_DEPTH_PREPASS] = GivenDepthPrepassTechnique(memory); + techset->techniques[TECHNIQUE_LIT_OMNI_SHADOW_GLIGHT] = GivenLitOmniShadowGlightTechnique(memory); + + zone.m_pools.AddAsset(std::make_unique>(ASSET_TYPE_TECHNIQUE_SET, techset->name, techset)); + return techset; + } +} // namespace + +TEST_CASE("TechsetDumperT5", "[t5][techset][dumper]") +{ + Zone zone("MockZone", 0, GameId::T6, GamePlatform::PC); + zone.Register(); + + MemoryManager memory; + MockSearchPath mockObjPath; + MockOutputPath mockOutput; + AssetDumpingContext context(zone, "", mockOutput, mockObjPath, std::nullopt); + + GivenTechset(zone, memory); + + techset::DumperT5 dumper(true); + + SECTION("Can dump techset") + { + std::string expected(R"TECHSET( +"depth prepass": + example_zprepass; + +"lit omni shadow glight": + example_lit_omni_shadow_glight; +)TECHSET"); + + dumper.Dump(context); + + const auto* file = mockOutput.GetMockedFile("techsets/example_techset.techset"); + REQUIRE(file); + REQUIRE(Trimmed(file->AsString()) == Trimmed(expected)); + } + + SECTION("Can dump simple technique") + { + std::string expected(R"TECHNIQUE( +// TECHNIQUE FLAGS: 0x4 +// TECHNIQUE FLAGS: 0x80 +{ + stateMap "passthrough"; // TODO + + vertexShader 3.0 "simple.hlsl" + { + // Omitted due to matching accessors: worldMatrix = constant.worldMatrix; + // Omitted due to matching accessors: viewProjectionMatrix = constant.viewProjectionMatrix; + } + + pixelShader 3.0 "simple.hlsl" + { + } + + vertex.position = code.position; +} +)TECHNIQUE"); + dumper.Dump(context); + + const auto* file = mockOutput.GetMockedFile("techniques/example_zprepass.tech"); + REQUIRE(file); + REQUIRE(Trimmed(file->AsString()) == Trimmed(expected)); + } + + SECTION("Can dump advanced technique") + { + std::string expected(R"TECHNIQUE( +// TECHNIQUE FLAGS: 0x10 +// TECHNIQUE FLAGS: 0x80 +{ + // CUSTOM SAMPLER FLAGS: 0x1 + stateMap "passthrough"; // TODO + + vertexShader 3.0 "advanced.hlsl" + { + // Omitted due to matching accessors: worldMatrix = constant.worldMatrix; + // Omitted due to matching accessors: baseLightingCoords = constant.baseLightingCoords; + // Omitted due to matching accessors: viewProjectionMatrix = constant.viewProjectionMatrix; + // Omitted due to matching accessors: shadowLookupMatrix = constant.shadowLookupMatrix; + Flicker_Min = material.Flicker_Min; + Flicker_Max = material.Flicker_Max; + Seed_Value = material.Seed_Value; + // Omitted due to matching accessors: fogConsts = constant.fogConsts; + // Omitted due to matching accessors: fogConsts2 = constant.fogConsts2; + // Omitted due to matching accessors: gameTime = constant.gameTime; + // Omitted due to matching accessors: sunFogDir = constant.sunFogDir; + // Omitted due to matching accessors: sunFogColor = constant.sunFogColor; + // Omitted due to matching accessors: sunFog = constant.sunFog; + // Omitted due to matching accessors: fogColor = constant.fogColor; + } + + pixelShader 3.0 "advanced.hlsl" + { + Diffuse_MapSampler = material.Diffuse_MapSampler; + Diffuse_Map_Damage = material.Diffuse_Map_Damage; + Normal_Map_Cracked = material.Normal_Map_Cracked; + Reveal_Map = material.Reveal_Map; + Specular_Map = material.Specular_Map; + Heat_Map = material.Heat_Map; + // Omitted due to matching accessors: shadowmapSamplerSpot = sampler.shadowmapSamplerSpot; + // Omitted due to matching accessors: modelLightingSampler = sampler.modelLightingSampler; + // Omitted due to matching accessors: lightingLookupScale = constant.lightingLookupScale; + // Omitted due to matching accessors: glightPosXs = constant.glightPosXs; + // Omitted due to matching accessors: glightPosYs = constant.glightPosYs; + // Omitted due to matching accessors: glightPosZs = constant.glightPosZs; + // Omitted due to matching accessors: glightFallOffs = constant.glightFallOffs; + // Omitted due to matching accessors: glightReds = constant.glightReds; + // Omitted due to matching accessors: glightGreens = constant.glightGreens; + // Omitted due to matching accessors: glightBlues = constant.glightBlues; + // Omitted due to matching accessors: lightPosition = constant.lightPosition; + // Omitted due to matching accessors: lightDiffuse = constant.lightDiffuse; + // Omitted due to matching accessors: lightHeroScale = constant.lightHeroScale; + // Omitted due to matching accessors: lightSpotDir = constant.lightSpotDir; + // Omitted due to matching accessors: lightSpotFactors = constant.lightSpotFactors; + // Omitted due to matching accessors: lightAttenuation = constant.lightAttenuation; + // Omitted due to matching accessors: lightFallOffA = constant.lightFallOffA; + // Omitted due to matching accessors: spotShadowmapPixelAdjust = constant.spotShadowmapPixelAdjust; + // Omitted due to matching accessors: gameTime = constant.gameTime; + // Omitted due to matching accessors: hdrControl0 = constant.hdrControl0; + // Omitted due to matching accessors: heroLightingR = constant.heroLightingR; + // Omitted due to matching accessors: heroLightingG = constant.heroLightingG; + // Omitted due to matching accessors: heroLightingB = constant.heroLightingB; + Ember_Scale = material.Ember_Scale; + Heat_Direction = material.Heat_Direction; + Ember_Direction = material.Ember_Direction; + Heat_Scale = material.Heat_Scale; + } + + vertex.position = code.position; + vertex.texcoord[0] = code.texcoord[0]; + vertex.normal = code.normal; + vertex.texcoord[2] = code.tangent; +} +)TECHNIQUE"); + dumper.Dump(context); + + const auto* file = mockOutput.GetMockedFile("techniques/example_lit_omni_shadow_glight.tech"); + REQUIRE(file); + REQUIRE(Trimmed(file->AsString()) == Trimmed(expected)); + } +} diff --git a/test/ObjWritingTests/Game/T5/Techset/ps_advanced.hlsl.cso b/test/ObjWritingTests/Game/T5/Techset/ps_advanced.hlsl.cso new file mode 100644 index 0000000000000000000000000000000000000000..a374c3cb1eabf3b4a6ae3ba2c9e9e6f2016863ed GIT binary patch literal 4060 zcmaJ^O>7ip7=CAG_J_7mL<)rhEq~F2F(h0(AaqLwOi+-v28pCu+G)G6+iiBYO5$Zo zyqKD>Bp$dqiHV2p(SsgHYz!9?4~7dIJaE`VjY;D{6N!oA^Ss}D(;XmAGVJ?4@Bh#D zO%XSW{*8R+^ki{tpNL$|;vZm#@IyZ1$jJM^?q>i?RvciDLG$@lv3v^q2Utc9E7s@2 z2j184X5^4!Z-73B_p)L~61@fRH}SfXOZi(-y`w4H&sEP&>3ykKCWW;X^HSKi>Tkbd z>!A7kq}a|B_OoJJQrPc`^`@}D6*D32#?6Q$}> z#jnenCw>!q0+ z{;XW`Kk^ME6HETga-~$KT;@wvf3jTlkpc5C`%d5UmGb;TbF8xLH)8rssZzNxH)q+i zb>CmK%*9&cau@wU7k$Ojm;Bj=EX>wV*A|=gTIHB5`1RU(Mkz1OkEOV?DejVJF2!ck zUtBI}ixr4RVWe?(MZ=wgYtswWM0({-yNRV*6Vo!9WX_aknzeev07&M1t#)I1$=FwG zvwp>9nQX-x_F`*lZgqdIV&;FbUc2Tenx1S+=`|Kgv$c<_$%Zk$H1N-a6&uO%EH$Q&Pal&DOa5Z9(eRs-wOXYyGU+#(Bh<%&zW7;NdrxrTN`DCXy2$7#e(?k+ zi93qhJ$o_=!71Vy;2++;HTvoHTch6{$gUR-dh5h-o9BW`zS|EP=NJA9!G4KC)P&pGMO{&FSUO#NCpaV$;`-rj zggQDc@*CWxQGr1jo zCO7gnH|&SzmP2mLmANqg4ET)l4(25*=tHRW6$X)a1e=IBU)XF52Bd|&T6YTI*{_T| z)faU^rJ(vb(70ig&c=4EkG{yelEFTRzE`xDgc8^?F$Wt9)1#v$1Vja?tG zaebMiiy6>HU?VaW>k!*eoHjNm#QFv#&_iQu>Y|Lc=x>+I0dI1^{v+l_5)_w-Q*Iu#SDebE# zd8H}qRsW1VwdzTGl{~)Y%w^S;Hu}jvd$dzq+yRhvK$~31vkvOw7rmx^zhb@U^$yJ5 z(|SaAG#k&-&e}_6yH)4BOTGv>dsnu?XD8S#qG!PLy3=yulRd~{E?N17HDdl7T^P@B zgc(OE4E+jGzxvK0XHOo&|AQ8N8U6wIhb`>O3gZ(44J>Mr&*D5~(Z8IuV9WTdo9Chf z`O^erPfedY!4dS1e%K##1_#wAXGxztqpm#Bx-AcTVC|4|V2^T`OD>)Z_X6`+<$2&- zDCfDSt&2F!*Up7}yhFrvF7^y?F4l%Rd|8vphxJXNr{7TmH5j=X#InkcG9);7;w|mnc$FjRcUFK_f>T}QV4bs|dJ$7cM51abW z^2jBzXKh{_@Bi#=f6RPZh}#Ox*~w!EpQ5IAXmxPsf^Xr!In-kxv`4^9pUI;?v0aC> zevk6lWk;s&(1v%u{T@-yIiL>6<&KGC?laDgIZ=oGE2vI8(J{RbHa6Hqg8y zrefLy-HvH~Drg$&d8T0hXp0oe-!C&s4IX@DbJJ(Ts7&eB_ zC_j?$p7ICPr?rh*yx;J*6J+dHhdO#+i0sx_2hh8`#-e}03}IU^^d@d*01d8^;?k_!p$+ITjbEmW1Tz z=M*c1q!yPb1ZO0sq!xiyVc9}s_LxDvTD z_?og@-x+R|+;J=8elXk|xwmq?pA0uo?t0GsVz^V}-p;vS4R;zYZsJeMa+R33jT-gJ z)>Bd!uSLUE$rbasFB+~w&bF3Qy*CU8`m=L??-P`v~teHiNy(L2z$(=Srj7X+FSk=+KwTPnLJM z+rAWRWijmk@ES&SE;c)~*IcA_z3w;ty|qqo*T2d5S!|cGdt(9IBodcrp4<1!62tP%h_%&KA z^Zq*TE5erzkMrbA&lkS=d0F^rdNi;+SO0CYcI%((GZyJi|kOgQn>&-vU3@cGa&=sNc0P^L_d{{ekwbnpUQ6J z=5iq_sF^j%qJVh4NuDf~46#?jNvf$<}eSqSxK zjBH`tQ+e@=A-@{ z*am0*`GgoO{<-IDl^3m2)}9$Zy=od;`?=IF!q$^VW00nv>|35z~j*>MnSQ_fJxh zy-)Lrjn7Pniju>A=3nC{gCn2uN98nM3oiQrmiH+gE`TqEng zSd`6K57g6`ny1v2>1f}ice=}9?xH`(x-%WYGMhQ(qrFfa?VHL{yVhxNPq5fqD2>Fv ziiFQw%=}^7C`oPmvM2l(9qH_KZ%VKY=Df`wmEamoofMZ0cFazIA!j&~Y~~U>j!vocB2$LKTjAw7~@_3ZD7Guipcvp*)zbUtFId5kB{ zv}e)_@wvp<*^ZFwEf7o2^8VBO>PAQQM13U>Igh?#nQ7AJ_u)kU$<=#D=je%_BYUYC zJ<)f}d}L41lU$4GJWi+mE<)$=OpbLP&*oU?@f@+%#CIt$73=A@{tL2cN_E zXB$JH1ONW>&Fl=j2+q~Hdct62(QPk*a(O7Wc0>qsccO1HL+&F(X` zYxAAOcJsc-(s+-uECU4oCfE7c*)&-;?X8=S=1=(eF`J2VCX1|1+%PgO8%|e?rAeEa zf5yFEKu;qqfPCET5B{E;1L2{Q9~HkK5VHPo(0DF1?uuLf7!bL@DOWf(EIs#HR-XGU cYtJ_=iz7HsUFSV>XT!>~Y*~8_$W;Yg0AMOjz5oCK literal 0 HcmV?d00001 From 3b0416fa999242a64799752b42afd7ee8f2f739f Mon Sep 17 00:00:00 2001 From: Jan Laupetin Date: Fri, 20 Mar 2026 22:20:20 +0100 Subject: [PATCH 2/4] chore: add unit tests for iw5 techsets --- .../IW5/Techset/TechniqueCompilerIW5Test.cpp | 386 ++++++++++++++++++ .../IW5/Techset/TechsetCompilerIW5Test.cpp | 149 +++++++ .../IW5/Techset/VertexDeclCompilerIW5Test.cpp | 63 +++ .../Game/IW5/Techset/ps_advanced.hlsl.cso | Bin 0 -> 1872 bytes .../Game/IW5/Techset/ps_simple.hlsl.cso | Bin 0 -> 136 bytes .../Game/IW5/Techset/vs_advanced.hlsl.cso | Bin 0 -> 1200 bytes .../Game/IW5/Techset/vs_simple.hlsl.cso | Bin 0 -> 388 bytes .../Game/IW5/Techset/TechsetDumperIW5Test.cpp | 375 +++++++++++++++++ .../Game/IW5/Techset/ps_advanced.hlsl.cso | Bin 0 -> 1872 bytes .../Game/IW5/Techset/ps_simple.hlsl.cso | Bin 0 -> 136 bytes .../Game/IW5/Techset/vs_advanced.hlsl.cso | Bin 0 -> 1200 bytes .../Game/IW5/Techset/vs_simple.hlsl.cso | Bin 0 -> 388 bytes 12 files changed, 973 insertions(+) create mode 100644 test/ObjCompilingTests/Game/IW5/Techset/TechniqueCompilerIW5Test.cpp create mode 100644 test/ObjCompilingTests/Game/IW5/Techset/TechsetCompilerIW5Test.cpp create mode 100644 test/ObjCompilingTests/Game/IW5/Techset/VertexDeclCompilerIW5Test.cpp create mode 100644 test/ObjCompilingTests/Game/IW5/Techset/ps_advanced.hlsl.cso create mode 100644 test/ObjCompilingTests/Game/IW5/Techset/ps_simple.hlsl.cso create mode 100644 test/ObjCompilingTests/Game/IW5/Techset/vs_advanced.hlsl.cso create mode 100644 test/ObjCompilingTests/Game/IW5/Techset/vs_simple.hlsl.cso create mode 100644 test/ObjWritingTests/Game/IW5/Techset/TechsetDumperIW5Test.cpp create mode 100644 test/ObjWritingTests/Game/IW5/Techset/ps_advanced.hlsl.cso create mode 100644 test/ObjWritingTests/Game/IW5/Techset/ps_simple.hlsl.cso create mode 100644 test/ObjWritingTests/Game/IW5/Techset/vs_advanced.hlsl.cso create mode 100644 test/ObjWritingTests/Game/IW5/Techset/vs_simple.hlsl.cso diff --git a/test/ObjCompilingTests/Game/IW5/Techset/TechniqueCompilerIW5Test.cpp b/test/ObjCompilingTests/Game/IW5/Techset/TechniqueCompilerIW5Test.cpp new file mode 100644 index 00000000..d5a5e34e --- /dev/null +++ b/test/ObjCompilingTests/Game/IW5/Techset/TechniqueCompilerIW5Test.cpp @@ -0,0 +1,386 @@ +#include "Game/IW5/Techset/TechniqueCompilerIW5.h" + +#include "Game/IW5/IW5.h" +#include "Game/IW5/Techset/PixelShaderLoaderIW5.h" +#include "Game/IW5/Techset/VertexDeclCompilerIW5.h" +#include "Game/IW5/Techset/VertexShaderLoaderIW5.h" +#include "OatTestPaths.h" +#include "SearchPath/MockSearchPath.h" +#include "Shader/ShaderCommon.h" +#include "Utils/MemoryManager.h" +#include "catch2/generators/catch_generators.hpp" + +#include +#include +#include +#include + +using namespace IW5; +using namespace Catch; +using namespace std::literals; +namespace fs = std::filesystem; + +namespace +{ + void GivenVertexShaderFile(const std::string& name, MockSearchPath& searchPath) + { + const auto filePath = oat::paths::GetTestDirectory() / "ObjCompilingTests/Game/IW5/Techset" / std::format("vs_{}.cso", name); + const auto fileSize = static_cast(fs::file_size(filePath)); + + std::ifstream file(filePath, std::ios::binary); + REQUIRE(file.is_open()); + + std::string data(fileSize, '\0'); + file.read(data.data(), fileSize); + REQUIRE(file.gcount() == static_cast(fileSize)); + + searchPath.AddFileData(shader::GetFileNameForVertexShaderAssetName(name), std::move(data)); + } + + void GivenPixelShaderFile(const std::string& name, MockSearchPath& searchPath) + { + const auto filePath = oat::paths::GetTestDirectory() / "ObjCompilingTests/Game/IW5/Techset" / std::format("ps_{}.cso", name); + const auto fileSize = static_cast(fs::file_size(filePath)); + + std::ifstream file(filePath, std::ios::binary); + REQUIRE(file.is_open()); + + std::string data(fileSize, '\0'); + file.read(data.data(), fileSize); + REQUIRE(file.gcount() == static_cast(fileSize)); + + searchPath.AddFileData(shader::GetFileNameForPixelShaderAssetName(name), std::move(data)); + } +} // namespace + +TEST_CASE("TechniqueCompilerIW5", "[iw5][techset][compiler]") +{ + + Zone zone("MockZone", 0, GameId::IW5, GamePlatform::PC); + zone.Register(); + + MemoryManager memory; + AssetCreatorCollection creatorCollection(zone); + IgnoredAssetLookup ignoredAssetLookup; + AssetCreationContext context(zone, &creatorCollection, &ignoredAssetLookup); + MockSearchPath searchPath; + + creatorCollection.AddAssetCreator(techset::CreateVertexDeclCompilerIW5(memory)); + creatorCollection.AddAssetCreator(techset::CreateVertexShaderLoaderIW5(memory, searchPath)); + creatorCollection.AddAssetCreator(techset::CreatePixelShaderLoaderIW5(memory, searchPath)); + + auto loader = techset::CreateTechniqueCompilerIW5(memory, zone, searchPath); + + SECTION("Can compile simple technique") + { + const auto [inputName, inputData] = GENERATE(Catch::Generators::table({ + {"auto-create args", R"TECHNIQUE( +{ + stateMap "passthrough"; + + vertexShader 3.0 "simple.hlsl" + { + } + + pixelShader 3.0 "simple.hlsl" + { + } + + vertex.position = code.position; +} +)TECHNIQUE"}, + {"manual args", R"TECHNIQUE( +{ + stateMap "passthrough"; + + vertexShader 3.0 "simple.hlsl" + { + worldMatrix = constant.worldMatrix; + viewProjectionMatrix = constant.viewProjectionMatrix; + } + + pixelShader 3.0 "simple.hlsl" + { + } + + vertex.position = code.position; +} +)TECHNIQUE"}, + })); + + CAPTURE(inputName); + searchPath.AddFileData("techniques/zprepass.tech", inputData); + + GivenVertexShaderFile("simple.hlsl", searchPath); + GivenPixelShaderFile("simple.hlsl", searchPath); + + auto result = loader->CreateSubAsset("zprepass", context); + REQUIRE(result.HasBeenSuccessful()); + + const auto* assetInfo = reinterpret_cast*>(result.GetAssetInfo()); + const auto* technique = assetInfo->Asset(); + + CHECK(technique->name == "zprepass"s); + + CHECK(technique->flags == 0x204); + + REQUIRE(technique->passCount == 1); + auto& pass = technique->passArray[0]; + CHECK(pass.customSamplerFlags == 0); + + REQUIRE(pass.vertexShader); + CHECK(pass.vertexShader->name == "simple.hlsl"s); + REQUIRE(pass.pixelShader); + CHECK(pass.pixelShader->name == "simple.hlsl"s); + + REQUIRE(pass.vertexDecl); + auto& vertexDecl = *pass.vertexDecl; + CHECK(vertexDecl.name == "pp"s); + CHECK(vertexDecl.hasOptionalSource == false); + REQUIRE(vertexDecl.streamCount == 1); + CHECK(vertexDecl.routing.data[0].source == STREAM_SRC_POSITION); + CHECK(vertexDecl.routing.data[0].dest == STREAM_DST_POSITION); + + REQUIRE(pass.perPrimArgCount == 1); + REQUIRE(pass.perObjArgCount == 1); + REQUIRE(pass.stableArgCount == 0); + CHECK(pass.args[0].type == MTL_ARG_CODE_VERTEX_CONST); + CHECK(pass.args[0].dest == 4); + CHECK(pass.args[0].u.codeConst.index == CONST_SRC_CODE_TRANSPOSE_WORLD_MATRIX0); + CHECK(pass.args[0].u.codeConst.firstRow == 0); + CHECK(pass.args[0].u.codeConst.rowCount == 4); + + CHECK(pass.args[1].type == MTL_ARG_CODE_VERTEX_CONST); + CHECK(pass.args[1].dest == 0); + CHECK(pass.args[1].u.codeConst.index == CONST_SRC_CODE_TRANSPOSE_VIEW_PROJECTION_MATRIX); + CHECK(pass.args[1].u.codeConst.firstRow == 0); + CHECK(pass.args[1].u.codeConst.rowCount == 4); + } + + SECTION("Can compile advanced technique") + { + const auto [inputName, inputData] = GENERATE(Catch::Generators::table({ + {"auto-create args", R"TECHNIQUE( +{ + stateMap "passthrough"; + + vertexShader 3.0 "advanced.hlsl" + { + oceanUVAnimParmOctave0 = material.oceanUVAnimParmOctave0; + oceanUVAnimParmOctave1 = material.oceanUVAnimParmOctave1; + oceanAmplitude = material.oceanAmplitude; + oceanDisplacementSampler = material.oceanDisplacementMap; + } + + pixelShader 3.0 "advanced.hlsl" + { + oceanEnvSampler = material.oceanEnvMap; + oceanDetailNormalSampler = material.oceanDetailNormalMap; + oceanHeightNormalSampler = material.oceanHeightNormalMap; + oceanFoamSampler = material.oceanFoamMap; + oceanUVAnimParmFoam = material.oceanUVAnimParmFoam; + envMapParms = material.envMapParms; + oceanFoamParms = material.oceanFoamParms; + oceanUVAnimParmOctave0 = material.oceanUVAnimParmOctave0; + oceanUVAnimParmOctave1 = material.oceanUVAnimParmOctave1; + oceanUVAnimParmDetail1 = material.oceanUVAnimParmDetail1; + oceanUVAnimParmDetail0 = material.oceanUVAnimParmDetail0; + oceanAmplitude = material.oceanAmplitude; + oceanMiscParms = material.oceanMiscParms; + } + + vertex.position = code.position; + vertex.color[0] = code.color; +} +)TECHNIQUE"}, + {"manual args", R"TECHNIQUE( +{ + stateMap "passthrough"; + + vertexShader 3.0 "advanced.hlsl" + { + worldMatrix = constant.worldMatrix; + viewProjectionMatrix = constant.viewProjectionMatrix; + oceanUVAnimParmOctave0 = material.oceanUVAnimParmOctave0; + oceanUVAnimParmOctave1 = material.oceanUVAnimParmOctave1; + oceanAmplitude = material.oceanAmplitude; + oceanDisplacementSampler = material.oceanDisplacementMap; + eyeOffset = constant.eyeOffset; + fogConsts = constant.fogConsts; + gameTime = constant.gameTime; + } + + pixelShader 3.0 "advanced.hlsl" + { + oceanEnvSampler = material.oceanEnvMap; + oceanDetailNormalSampler = material.oceanDetailNormalMap; + oceanHeightNormalSampler = material.oceanHeightNormalMap; + oceanFoamSampler = material.oceanFoamMap; + fogColorLinear = constant.fogColorLinear; + gameTime = constant.gameTime; + oceanUVAnimParmFoam = material.oceanUVAnimParmFoam; + envMapParms = material.envMapParms; + oceanFoamParms = material.oceanFoamParms; + oceanUVAnimParmOctave0 = material.oceanUVAnimParmOctave0; + oceanUVAnimParmOctave1 = material.oceanUVAnimParmOctave1; + oceanUVAnimParmDetail1 = material.oceanUVAnimParmDetail1; + oceanUVAnimParmDetail0 = material.oceanUVAnimParmDetail0; + oceanAmplitude = material.oceanAmplitude; + oceanMiscParms = material.oceanMiscParms; + } + + vertex.position = code.position; + vertex.color[0] = code.color; +} +)TECHNIQUE"}, + })); + + CAPTURE(inputName); + searchPath.AddFileData("techniques/example_lit_sun_shadow.tech", inputData); + + GivenVertexShaderFile("advanced.hlsl", searchPath); + GivenPixelShaderFile("advanced.hlsl", searchPath); + + auto result = loader->CreateSubAsset("example_lit_sun_shadow", context); + REQUIRE(result.HasBeenSuccessful()); + + const auto* assetInfo = reinterpret_cast*>(result.GetAssetInfo()); + const auto* technique = assetInfo->Asset(); + + CHECK(technique->name == "example_lit_sun_shadow"s); + + CHECK(technique->flags == 0); + + REQUIRE(technique->passCount == 1); + auto& pass = technique->passArray[0]; + CHECK(pass.customSamplerFlags == 0); + + REQUIRE(pass.vertexShader); + CHECK(pass.vertexShader->name == "advanced.hlsl"s); + REQUIRE(pass.pixelShader); + CHECK(pass.pixelShader->name == "advanced.hlsl"s); + + REQUIRE(pass.vertexDecl); + auto& vertexDecl = *pass.vertexDecl; + CHECK(vertexDecl.hasOptionalSource == false); + REQUIRE(vertexDecl.streamCount == 2); + CHECK(vertexDecl.routing.data[0].source == STREAM_SRC_POSITION); + CHECK(vertexDecl.routing.data[0].dest == STREAM_DST_POSITION); + CHECK(vertexDecl.routing.data[1].source == STREAM_SRC_COLOR); + CHECK(vertexDecl.routing.data[1].dest == STREAM_DST_COLOR_0); + + REQUIRE(pass.perPrimArgCount == 1); + REQUIRE(pass.perObjArgCount == 1); + REQUIRE(pass.stableArgCount == 22); + + CHECK(pass.args[0].type == MTL_ARG_CODE_VERTEX_CONST); + CHECK(pass.args[0].dest == 4); + CHECK(pass.args[0].u.codeConst.index == CONST_SRC_CODE_TRANSPOSE_WORLD_MATRIX0); + CHECK(pass.args[0].u.codeConst.firstRow == 0); + CHECK(pass.args[0].u.codeConst.rowCount == 4); + + CHECK(pass.args[1].type == MTL_ARG_CODE_VERTEX_CONST); + CHECK(pass.args[1].dest == 0); + CHECK(pass.args[1].u.codeConst.index == CONST_SRC_CODE_TRANSPOSE_VIEW_PROJECTION_MATRIX); + CHECK(pass.args[1].u.codeConst.firstRow == 0); + CHECK(pass.args[1].u.codeConst.rowCount == 4); + + CHECK(pass.args[2].type == MTL_ARG_MATERIAL_VERTEX_CONST); + CHECK(pass.args[2].dest == 59); + CHECK(pass.args[2].u.nameHash == 0x470F6C9A); + + CHECK(pass.args[3].type == MTL_ARG_MATERIAL_VERTEX_CONST); + CHECK(pass.args[3].dest == 60); + CHECK(pass.args[3].u.nameHash == 0x470F6C9B); + + CHECK(pass.args[4].type == MTL_ARG_MATERIAL_VERTEX_CONST); + CHECK(pass.args[4].dest == 58); + CHECK(pass.args[4].u.nameHash == 0x9D5408FF); + + CHECK(pass.args[5].type == MTL_ARG_MATERIAL_VERTEX_SAMPLER); + CHECK(pass.args[5].dest == 2); + CHECK(pass.args[5].u.nameHash == 0x29F357AD); + + CHECK(pass.args[6].type == MTL_ARG_MATERIAL_PIXEL_SAMPLER); + CHECK(pass.args[6].dest == 5); + CHECK(pass.args[6].u.nameHash == 0x7D392967); + + CHECK(pass.args[7].type == MTL_ARG_MATERIAL_PIXEL_SAMPLER); + CHECK(pass.args[7].dest == 7); + CHECK(pass.args[7].u.nameHash == 0x88792E38); + + CHECK(pass.args[8].type == MTL_ARG_MATERIAL_PIXEL_SAMPLER); + CHECK(pass.args[8].dest == 4); + CHECK(pass.args[8].u.nameHash == 0x8CB95536); + + CHECK(pass.args[9].type == MTL_ARG_MATERIAL_PIXEL_SAMPLER); + CHECK(pass.args[9].dest == 6); + CHECK(pass.args[9].u.nameHash == 0xC096573F); + + CHECK(pass.args[10].type == MTL_ARG_CODE_VERTEX_CONST); + CHECK(pass.args[10].dest == 10); + CHECK(pass.args[10].u.codeConst.index == CONST_SRC_CODE_EYEOFFSET); + CHECK(pass.args[10].u.codeConst.firstRow == 0); + CHECK(pass.args[10].u.codeConst.rowCount == 1); + + CHECK(pass.args[11].type == MTL_ARG_CODE_VERTEX_CONST); + CHECK(pass.args[11].dest == 21); + CHECK(pass.args[11].u.codeConst.index == CONST_SRC_CODE_FOG); + CHECK(pass.args[11].u.codeConst.firstRow == 0); + CHECK(pass.args[11].u.codeConst.rowCount == 1); + + CHECK(pass.args[12].type == MTL_ARG_CODE_VERTEX_CONST); + CHECK(pass.args[12].dest == 22); + CHECK(pass.args[12].u.codeConst.index == CONST_SRC_CODE_GAMETIME); + CHECK(pass.args[12].u.codeConst.firstRow == 0); + CHECK(pass.args[12].u.codeConst.rowCount == 1); + + CHECK(pass.args[13].type == MTL_ARG_CODE_PIXEL_CONST); + CHECK(pass.args[13].dest == 0); + CHECK(pass.args[13].u.codeConst.index == CONST_SRC_CODE_FOG_COLOR_LINEAR); + CHECK(pass.args[13].u.codeConst.firstRow == 0); + CHECK(pass.args[13].u.codeConst.rowCount == 1); + + CHECK(pass.args[14].type == MTL_ARG_CODE_PIXEL_CONST); + CHECK(pass.args[14].dest == 3); + CHECK(pass.args[14].u.codeConst.index == CONST_SRC_CODE_GAMETIME); + CHECK(pass.args[14].u.codeConst.firstRow == 0); + CHECK(pass.args[14].u.codeConst.rowCount == 1); + + CHECK(pass.args[15].type == MTL_ARG_MATERIAL_PIXEL_CONST); + CHECK(pass.args[15].dest == 9); + CHECK(pass.args[15].u.nameHash == 0x64E3AE5); + + CHECK(pass.args[16].type == MTL_ARG_MATERIAL_PIXEL_CONST); + CHECK(pass.args[16].dest == 5); + CHECK(pass.args[16].u.nameHash == 0x3D9994DC); + + CHECK(pass.args[17].type == MTL_ARG_MATERIAL_PIXEL_CONST); + CHECK(pass.args[17].dest == 22); + CHECK(pass.args[17].u.nameHash == 0x3FC0F1DE); + + CHECK(pass.args[18].type == MTL_ARG_MATERIAL_PIXEL_CONST); + CHECK(pass.args[18].dest == 7); + CHECK(pass.args[18].u.nameHash == 0x470F6C9A); + + CHECK(pass.args[19].type == MTL_ARG_MATERIAL_PIXEL_CONST); + CHECK(pass.args[19].dest == 8); + CHECK(pass.args[19].u.nameHash == 0x470F6C9B); + + CHECK(pass.args[20].type == MTL_ARG_MATERIAL_PIXEL_CONST); + CHECK(pass.args[20].dest == 20); + CHECK(pass.args[20].u.nameHash == 0x6373ABA0); + + CHECK(pass.args[21].type == MTL_ARG_MATERIAL_PIXEL_CONST); + CHECK(pass.args[21].dest == 11); + CHECK(pass.args[21].u.nameHash == 0x6373ABA1); + + CHECK(pass.args[22].type == MTL_ARG_MATERIAL_PIXEL_CONST); + CHECK(pass.args[22].dest == 6); + CHECK(pass.args[22].u.nameHash == 0x9D5408FF); + + CHECK(pass.args[23].type == MTL_ARG_MATERIAL_PIXEL_CONST); + CHECK(pass.args[23].dest == 21); + CHECK(pass.args[23].u.nameHash == 0xAA2E7C4F); + } +} diff --git a/test/ObjCompilingTests/Game/IW5/Techset/TechsetCompilerIW5Test.cpp b/test/ObjCompilingTests/Game/IW5/Techset/TechsetCompilerIW5Test.cpp new file mode 100644 index 00000000..b1b70f73 --- /dev/null +++ b/test/ObjCompilingTests/Game/IW5/Techset/TechsetCompilerIW5Test.cpp @@ -0,0 +1,149 @@ +#include "Game/IW5/Techset/TechsetCompilerIW5.h" + +#include "Game/IW5/IW5.h" +#include "SearchPath/MockSearchPath.h" +#include "Techset/TechsetCommon.h" +#include "Utils/TestMemoryManager.h" + +#include +#include +#include +#include + +using namespace IW5; +using namespace std::string_literals; + +namespace +{ + MaterialTechnique* GivenTechnique(const std::string& name, AssetCreationContext& context, MemoryManager& memory) + { + auto* technique = memory.Alloc(); + technique->name = memory.Dup(name.c_str()); + + context.AddSubAsset(name, technique); + + return technique; + } +} // namespace + +TEST_CASE("TechsetCompilerIW5", "[techset][iw5][compiler]") +{ + Zone zone("test", 0, GameId::IW5, GamePlatform::PC); + AssetCreatorCollection creators(zone); + IgnoredAssetLookup ignoredAssets; + AssetCreationContext context(zone, &creators, &ignoredAssets); + MockSearchPath searchPath; + TestMemoryManager memory; + const auto sut = techset::CreateTechsetCompilerIW5(memory, searchPath); + + SECTION("Sets correct worldVertFormat") + { + const auto [techsetName, expectedWorldVertFormat] = GENERATE(Catch::Generators::table({ + {"default", MTL_WORLDVERT_TEX_1_NRM_1}, + {"effect_zeqqz943", MTL_WORLDVERT_TEX_1_NRM_1}, + {"lit_r0c0_t1c1n1", MTL_WORLDVERT_TEX_2_NRM_1}, + {"lit_r0c0n0x0_b1c1n1s1v1", MTL_WORLDVERT_TEX_2_NRM_2}, + {"lit_r0c0n0x0_b1c1n1s1v1_b2c2n2x2", MTL_WORLDVERT_TEX_3_NRM_3}, + {"lit_sm_b0c0_b1c1_b2c2", MTL_WORLDVERT_TEX_3_NRM_1}, + {"lit_sm_b0c0_b1c1n1x1_b2c2n2v2", MTL_WORLDVERT_TEX_3_NRM_2}, + {"lit_sm_r0c0_b1c1_b2c2_b3c3", MTL_WORLDVERT_TEX_4_NRM_1}, + {"lit_sm_r0c0n0_b1c1_b2c2n2v2_m3c3", MTL_WORLDVERT_TEX_4_NRM_2}, + {"lit_sm_r0c0n0_b1c1n1s1", MTL_WORLDVERT_TEX_2_NRM_2}, + {"lit_sm_r0c0n0s0_b1c1n1s1_b2c2n2", MTL_WORLDVERT_TEX_3_NRM_3}, + {"lit_sm_r0c0n0x0_b1c1_b2c2n2s2", MTL_WORLDVERT_TEX_3_NRM_2}, + {"lit_sm_r0c0n0x0_b1c1n1_b2c2n2s2v2", MTL_WORLDVERT_TEX_3_NRM_3}, + {"lit_sm_r0c0n0x0_b1c1s1v1_m2c2_m3c3", MTL_WORLDVERT_TEX_4_NRM_1}, + {"lit_sm_r0c0n0x0_b1c1v1_b2c2n2s2_m3c3", MTL_WORLDVERT_TEX_4_NRM_2}, + {"lit_sm_r0c0s0_b1c1n1s1_m2c2", MTL_WORLDVERT_TEX_3_NRM_1}, + {"lit_sm_r0c0x0_b1c1", MTL_WORLDVERT_TEX_2_NRM_1}, + {"lit_sm_r0c0x0_b1c1n1s1_b2c2n2s2", MTL_WORLDVERT_TEX_3_NRM_2}, + {"lit_sm_r0c0x0_m1c1_m2c2_m3c3", MTL_WORLDVERT_TEX_4_NRM_1}, + {"lit_sm_t0c0n0_b1c1n1v1", MTL_WORLDVERT_TEX_2_NRM_2}, + {"lit_sm_t0c0n0s0_b1c1n1_b2c2n2s2v2", MTL_WORLDVERT_TEX_3_NRM_3}, + {"mc_lit_sm_r0c0d0_2213939z", MTL_WORLDVERT_TEX_1_NRM_1}, + {"wpc_lit_sm_b0c0s0_3f3q946z", MTL_WORLDVERT_TEX_1_NRM_1}, + {"wpc_lit_sm_r0c0n0s0o0_qj92q1f8", MTL_WORLDVERT_TEX_1_NRM_1}, + {"wpc_sw4_3d_burning_embers_nuketown_74j6971w", MTL_WORLDVERT_TEX_1_NRM_1}, + {"wpc_unlitdecalblend_add_j26wq580", MTL_WORLDVERT_TEX_1_NRM_1}, + })); + + CAPTURE(techsetName); + searchPath.AddFileData(techset::GetFileNameForTechsetName(techsetName), ""); + + const auto result = sut->CreateAsset(techsetName, context); + REQUIRE(result.HasBeenSuccessful()); + + const auto* techset = static_cast(result.GetAssetInfo()->m_ptr); + CHECK(techset->worldVertFormat == expectedWorldVertFormat); + } + + SECTION("Can parse simple techset") + { + searchPath.AddFileData(techset::GetFileNameForTechsetName("simple"), R"TECHSET( +"depth prepass": + example_zprepass; + +"lit": + example_lit; +)TECHSET"); + + auto* exampleZPrepass = GivenTechnique("example_zprepass", context, memory); + auto* exampleLit = GivenTechnique("example_lit", context, memory); + + const auto result = sut->CreateAsset("simple", context); + REQUIRE(result.HasBeenSuccessful()); + + const auto* techset = static_cast(result.GetAssetInfo()->m_ptr); + CHECK(techset->name == "simple"s); + CHECK(techset->worldVertFormat == MTL_WORLDVERT_TEX_1_NRM_1); + + size_t techniqueCount = 0; + for (auto* technique : techset->techniques) + { + if (technique) + techniqueCount++; + } + + CHECK(techniqueCount == 2); + CHECK(techset->techniques[TECHNIQUE_DEPTH_PREPASS] == exampleZPrepass); + CHECK(techset->techniques[TECHNIQUE_LIT] == exampleLit); + } + + SECTION("Can parse techset with same technique used multiple times") + { + searchPath.AddFileData(techset::GetFileNameForTechsetName("simple"), R"TECHSET( +"depth prepass": +"build shadowmap depth": + example_zprepass; + +"lit": +"lit sun": +"lit instanced spot shadow cucoloris dfog": + example_lit_sun; +)TECHSET"); + + auto* exampleZPrepass = GivenTechnique("example_zprepass", context, memory); + auto* exampleLitSun = GivenTechnique("example_lit_sun", context, memory); + + const auto result = sut->CreateAsset("simple", context); + REQUIRE(result.HasBeenSuccessful()); + + const auto* techset = static_cast(result.GetAssetInfo()->m_ptr); + CHECK(techset->name == "simple"s); + CHECK(techset->worldVertFormat == MTL_WORLDVERT_TEX_1_NRM_1); + + size_t techniqueCount = 0; + for (auto* technique : techset->techniques) + { + if (technique) + techniqueCount++; + } + + CHECK(techniqueCount == 5); + CHECK(techset->techniques[TECHNIQUE_DEPTH_PREPASS] == exampleZPrepass); + CHECK(techset->techniques[TECHNIQUE_BUILD_SHADOWMAP_DEPTH] == exampleZPrepass); + CHECK(techset->techniques[TECHNIQUE_LIT] == exampleLitSun); + CHECK(techset->techniques[TECHNIQUE_LIT_SUN] == exampleLitSun); + CHECK(techset->techniques[TECHNIQUE_LIT_INSTANCED_SPOT_SHADOW_CUCOLORIS_DFOG] == exampleLitSun); + } +} diff --git a/test/ObjCompilingTests/Game/IW5/Techset/VertexDeclCompilerIW5Test.cpp b/test/ObjCompilingTests/Game/IW5/Techset/VertexDeclCompilerIW5Test.cpp new file mode 100644 index 00000000..bfe11f19 --- /dev/null +++ b/test/ObjCompilingTests/Game/IW5/Techset/VertexDeclCompilerIW5Test.cpp @@ -0,0 +1,63 @@ +#include "Game/IW5/Techset/VertexDeclCompilerIW5.h" + +#include "Game/IW5/IW5.h" +#include "Utils/MemoryManager.h" + +#include + +using namespace IW5; +using namespace Catch; +using namespace std::literals; + +TEST_CASE("VertexDeclCompilerIW5", "[iw5][techset][compiler]") +{ + Zone zone("MockZone", 0, GameId::IW5, GamePlatform::PC); + zone.Register(); + + MemoryManager memory; + AssetCreatorCollection creatorCollection(zone); + IgnoredAssetLookup ignoredAssetLookup; + AssetCreationContext context(zone, &creatorCollection, &ignoredAssetLookup); + + auto loader = techset::CreateVertexDeclCompilerIW5(memory); + + SECTION("Can create simple vertex decl") + { + auto result = loader->CreateAsset("pp", context); + REQUIRE(result.HasBeenSuccessful()); + + const auto* assetInfo = reinterpret_cast*>(result.GetAssetInfo()); + const auto* decl = assetInfo->Asset(); + + CHECK(decl->name == "pp"s); + CHECK(decl->hasOptionalSource == false); + + REQUIRE(decl->streamCount == 1); + CHECK(decl->routing.data[0].source == STREAM_SRC_POSITION); + CHECK(decl->routing.data[0].dest == STREAM_DST_POSITION); + } + + SECTION("Can create advanced vertex decl") + { + auto result = loader->CreateAsset("pdcc1tt7t1t1n1n", context); + REQUIRE(result.HasBeenSuccessful()); + + const auto* assetInfo = reinterpret_cast*>(result.GetAssetInfo()); + const auto* decl = assetInfo->Asset(); + + CHECK(decl->name == "pdcc1tt7t1t1n1n"s); + CHECK(decl->hasOptionalSource == true); + + REQUIRE(decl->streamCount == 5); + CHECK(decl->routing.data[0].source == STREAM_SRC_POSITION); + CHECK(decl->routing.data[0].dest == STREAM_DST_DEPTH); + CHECK(decl->routing.data[1].source == STREAM_SRC_COLOR); + CHECK(decl->routing.data[1].dest == STREAM_DST_COLOR_1); + CHECK(decl->routing.data[2].source == STREAM_SRC_TANGENT); + CHECK(decl->routing.data[2].dest == STREAM_DST_TEXCOORD_7); + CHECK(decl->routing.data[3].source == STREAM_SRC_TEXCOORD_1); + CHECK(decl->routing.data[3].dest == STREAM_DST_TEXCOORD_1); + CHECK(decl->routing.data[4].source == STREAM_SRC_NORMAL_TRANSFORM_1); + CHECK(decl->routing.data[4].dest == STREAM_DST_NORMAL); + } +} diff --git a/test/ObjCompilingTests/Game/IW5/Techset/ps_advanced.hlsl.cso b/test/ObjCompilingTests/Game/IW5/Techset/ps_advanced.hlsl.cso new file mode 100644 index 0000000000000000000000000000000000000000..1ab1e54b2d8e2bb0ce47c6923d325c4d0e1101ca GIT binary patch literal 1872 zcmZ`(&rcIk5T5y9JkS-<$8veDlq`p=8AI-}r=9D&^(tMD$(9 z7qAH|Y|~Yu6JV3u8^%n-jOzgT~;~8v6utElVE!bEFdoP&MoYwY9 zFgxStHul$?=2NFt4}fzolrGk} z2Z7tJ_A+vSJ1o7qa#;F$!#cHZ znWv1K%Ad~6FH0%yO^h3+H^d}%)ZPlgD|`5KOnV|?9QzN}VBg6N?eo~n$der!6h{`1 zH5uTH!vg(^Od@<{VD~x&>P1+?WjJVbhp{kk_xe5>{S0;Z=!hHe^uo!67qcn6;|Z^p z@Qx!!8{*}C%F8~(yr~}cnWV1h;an-V-RI_9y^ehWx0z_>8kh(5BY#yi>xssE!lUe7 zCogrW+I7UC&M@Uk?JSS-$$^_Sr@3;-!?CPcM{T^HLLGbuI_^M2uT+iqc#j&ZM>aIn=MQplPlONrbfkK;D4bJCts2dV&3TH*Z}j5{;YG|t7w3%I zAv6gm!H07i^jl&y%8SkMe1`n!IoDPcob|94{3>DXCa*e&ji2f`nwIn1a%Q0y*U&GH zH>irbikfs_iLCUc7$tEOyD3Nv{s9ab z+3=kF0e(h*07HgI6ofi3Af}pgpB)n+Ui9L7&buGy-uGZ#C)ddt4qA<8cL01c_($#r zKZ3CZd?a^4(?l5t9^i}cTtB!?RLLFZ+%L`Tkb9MLiRSjneb2c~=Cl}XFz0=#$@Q3D z6E$*IxnEOrb#gy*uBSP`ruX|S_JihZa@#ql-l=A<7pM^Pxxs1%PcwJ){L=$}7=!^% z-GOR(0|mdU++kxd>U-hYNd}*L!Km+c)j$oyBbR4tjJb^s=8<2zbYT0qG4uw9?s(Ab zhVDe|rTPA)A7bLE^TV0>N*Gr(_pb`Vo6Y;3cKI`vY88bE^v8kdOKfMuc-S&T0eOAS)`yiRP8?~t%{-DPAtORY)8g&5QB#yHw zErd($bY`MY<|11E>XTd-Yx;_;ztmb%eQoBxD~{bl~_V#28Fn;g4DS$n4iYb0`r>`)^Ac^D5m>LdX0Nmf dU&So0W^ZNX>yX=A^S{X&CT=fM|8|@tz#r2B=qdmJ literal 0 HcmV?d00001 diff --git a/test/ObjCompilingTests/Game/IW5/Techset/vs_simple.hlsl.cso b/test/ObjCompilingTests/Game/IW5/Techset/vs_simple.hlsl.cso new file mode 100644 index 0000000000000000000000000000000000000000..ed20a21735d86662320c524b9a1c96d6826d6df3 GIT binary patch literal 388 zcmZ9H%}T>S6otBbm^4XC9%CkbPdNi#|u= z$#n?DfjeiubLY$t;zN6AZ}60kA1?v?us|=Obc{1#LwRO(p62{P?MBXu=xChx-YT=} zx59ndd6~K8%d9N24VtEj5z9^`Nnq`Yd=c6!KfRmY;`Q4u$KKmAbuRa#)cSHX`J63m z5i%SXuZ?j^O4ZM-WojA1{d`yf2*MA?JJFa;7EATvlc$- e*$58<18S$3=NXdc^}_T5YvHJ8BOH_47Wf5S$V=S- literal 0 HcmV?d00001 diff --git a/test/ObjWritingTests/Game/IW5/Techset/TechsetDumperIW5Test.cpp b/test/ObjWritingTests/Game/IW5/Techset/TechsetDumperIW5Test.cpp new file mode 100644 index 00000000..dd8053e9 --- /dev/null +++ b/test/ObjWritingTests/Game/IW5/Techset/TechsetDumperIW5Test.cpp @@ -0,0 +1,375 @@ +#include "Game/IW5/Techset/TechsetDumperIW5.h" + +#include "Asset/AssetRegistration.h" +#include "Game/IW5/IW5.h" +#include "OatTestPaths.h" +#include "SearchPath/MockOutputPath.h" +#include "SearchPath/MockSearchPath.h" +#include "Utils/MemoryManager.h" + +#include +#include +#include +#include +#include + +using namespace IW5; +using namespace Catch; +using namespace std::literals; +namespace fs = std::filesystem; + +namespace +{ + std::string Trimmed(const std::string& input) + { + auto start = input.find_first_not_of(" \r\n"); + if (start == std::string::npos) + start = 0; + + auto end = input.find_last_not_of(" \r\n"); + if (end == std::string::npos) + end = input.length(); + else + end = end + 1; + + return input.substr(start, end - start); + } + + MaterialVertexShader* GivenVertexShader(const std::string& name, MemoryManager& memory) + { + const auto filePath = oat::paths::GetTestDirectory() / "ObjWritingTests/Game/IW5/Techset" / std::format("vs_{}.cso", name); + const auto fileSize = static_cast(fs::file_size(filePath)); + + std::ifstream file(filePath, std::ios::binary); + REQUIRE(file.is_open()); + + auto* shader = memory.Alloc(); + shader->name = memory.Dup(name.c_str()); + shader->prog.loadDef.program = reinterpret_cast(memory.Alloc(fileSize)); + shader->prog.loadDef.programSize = static_cast(fileSize / 4u); + file.read(reinterpret_cast(shader->prog.loadDef.program), fileSize); + REQUIRE(file.gcount() == static_cast(fileSize)); + + return shader; + } + + MaterialPixelShader* GivenPixelShader(const std::string& name, MemoryManager& memory) + { + const auto filePath = oat::paths::GetTestDirectory() / "ObjWritingTests/Game/IW5/Techset" / std::format("ps_{}.cso", name); + const auto fileSize = static_cast(fs::file_size(filePath)); + + std::ifstream file(filePath, std::ios::binary); + REQUIRE(file.is_open()); + + auto* shader = memory.Alloc(); + shader->name = memory.Dup(name.c_str()); + shader->prog.loadDef.program = reinterpret_cast(memory.Alloc(fileSize)); + shader->prog.loadDef.programSize = static_cast(fileSize / 4u); + file.read(reinterpret_cast(shader->prog.loadDef.program), fileSize); + REQUIRE(file.gcount() == static_cast(fileSize)); + + return shader; + } + + MaterialTechnique* GivenDepthPrepassTechnique(MemoryManager& memory) + { + auto* technique = memory.Alloc(); + technique->name = "example_zprepass"; + technique->flags = 0x204; + technique->passCount = 1; + + auto& pass = technique->passArray[0]; + pass.perPrimArgCount = 1; + pass.perObjArgCount = 1; + pass.stableArgCount = 0; + pass.customSamplerFlags = 0; + + pass.vertexShader = GivenVertexShader("simple.hlsl", memory); + pass.pixelShader = GivenPixelShader("simple.hlsl", memory); + + pass.vertexDecl = memory.Alloc(); + auto& vertexDecl = *pass.vertexDecl; + vertexDecl.name = "pp"; + vertexDecl.streamCount = 1; + vertexDecl.hasOptionalSource = false; + vertexDecl.routing.data[0].source = STREAM_SRC_POSITION; + vertexDecl.routing.data[0].dest = STREAM_DST_POSITION; + + pass.args = memory.Alloc(2); + pass.args[0].type = MTL_ARG_CODE_VERTEX_CONST; + pass.args[0].dest = 4; + pass.args[0].u.codeConst.index = CONST_SRC_CODE_TRANSPOSE_WORLD_MATRIX0; + pass.args[0].u.codeConst.firstRow = 0; + pass.args[0].u.codeConst.rowCount = 4; + + pass.args[1].type = MTL_ARG_CODE_VERTEX_CONST; + pass.args[1].dest = 0; + pass.args[1].u.codeConst.index = CONST_SRC_CODE_TRANSPOSE_VIEW_PROJECTION_MATRIX; + pass.args[1].u.codeConst.firstRow = 0; + pass.args[1].u.codeConst.rowCount = 4; + + return technique; + } + + MaterialTechnique* GivenLitTechnique(MemoryManager& memory) + { + auto* technique = memory.Alloc(); + technique->name = "example_lit"; + technique->flags = 0; + technique->passCount = 1; + + auto& pass = technique->passArray[0]; + pass.perPrimArgCount = 1; + pass.perObjArgCount = 1; + pass.stableArgCount = 22; + pass.customSamplerFlags = 0; + + pass.vertexShader = GivenVertexShader("advanced.hlsl", memory); + pass.pixelShader = GivenPixelShader("advanced.hlsl", memory); + + pass.vertexDecl = memory.Alloc(); + auto& vertexDecl = *pass.vertexDecl; + vertexDecl.name = "ppcc0"; + vertexDecl.streamCount = 2; + vertexDecl.hasOptionalSource = false; + vertexDecl.routing.data[0].source = STREAM_SRC_POSITION; + vertexDecl.routing.data[0].dest = STREAM_DST_POSITION; + vertexDecl.routing.data[1].source = STREAM_SRC_COLOR; + vertexDecl.routing.data[1].dest = STREAM_DST_COLOR_0; + + pass.args = memory.Alloc(24); + pass.args[0].type = MTL_ARG_CODE_VERTEX_CONST; + pass.args[0].dest = 4; + pass.args[0].u.codeConst.index = CONST_SRC_CODE_TRANSPOSE_WORLD_MATRIX0; + pass.args[0].u.codeConst.firstRow = 0; + pass.args[0].u.codeConst.rowCount = 4; + + pass.args[1].type = MTL_ARG_CODE_VERTEX_CONST; + pass.args[1].dest = 0; + pass.args[1].u.codeConst.index = CONST_SRC_CODE_TRANSPOSE_VIEW_PROJECTION_MATRIX; + pass.args[1].u.codeConst.firstRow = 0; + pass.args[1].u.codeConst.rowCount = 4; + + pass.args[2].type = MTL_ARG_MATERIAL_VERTEX_CONST; + pass.args[2].dest = 59; + pass.args[2].u.nameHash = 0x470F6C9A; + + pass.args[3].type = MTL_ARG_MATERIAL_VERTEX_CONST; + pass.args[3].dest = 60; + pass.args[3].u.nameHash = 0x470F6C9B; + + pass.args[4].type = MTL_ARG_MATERIAL_VERTEX_CONST; + pass.args[4].dest = 58; + pass.args[4].u.nameHash = 0x9D5408FF; + + pass.args[5].type = MTL_ARG_MATERIAL_VERTEX_SAMPLER; + pass.args[5].dest = 2; + pass.args[5].u.nameHash = 0x29F357AD; + + pass.args[6].type = MTL_ARG_MATERIAL_PIXEL_SAMPLER; + pass.args[6].dest = 5; + pass.args[6].u.nameHash = 0x7D392967; + + pass.args[7].type = MTL_ARG_MATERIAL_PIXEL_SAMPLER; + pass.args[7].dest = 7; + pass.args[7].u.nameHash = 0x88792E38; + + pass.args[8].type = MTL_ARG_MATERIAL_PIXEL_SAMPLER; + pass.args[8].dest = 4; + pass.args[8].u.nameHash = 0x8CB95536; + + pass.args[9].type = MTL_ARG_MATERIAL_PIXEL_SAMPLER; + pass.args[9].dest = 6; + pass.args[9].u.nameHash = 0xC096573F; + + pass.args[10].type = MTL_ARG_CODE_VERTEX_CONST; + pass.args[10].dest = 10; + pass.args[10].u.codeConst.index = CONST_SRC_CODE_EYEOFFSET; + pass.args[10].u.codeConst.firstRow = 0; + pass.args[10].u.codeConst.rowCount = 1; + + pass.args[11].type = MTL_ARG_CODE_VERTEX_CONST; + pass.args[11].dest = 21; + pass.args[11].u.codeConst.index = CONST_SRC_CODE_FOG; + pass.args[11].u.codeConst.firstRow = 0; + pass.args[11].u.codeConst.rowCount = 1; + + pass.args[12].type = MTL_ARG_CODE_VERTEX_CONST; + pass.args[12].dest = 22; + pass.args[12].u.codeConst.index = CONST_SRC_CODE_GAMETIME; + pass.args[12].u.codeConst.firstRow = 0; + pass.args[12].u.codeConst.rowCount = 1; + + pass.args[13].type = MTL_ARG_CODE_PIXEL_CONST; + pass.args[13].dest = 0; + pass.args[13].u.codeConst.index = CONST_SRC_CODE_FOG_COLOR_LINEAR; + pass.args[13].u.codeConst.firstRow = 0; + pass.args[13].u.codeConst.rowCount = 1; + + pass.args[14].type = MTL_ARG_CODE_PIXEL_CONST; + pass.args[14].dest = 3; + pass.args[14].u.codeConst.index = CONST_SRC_CODE_GAMETIME; + pass.args[14].u.codeConst.firstRow = 0; + pass.args[14].u.codeConst.rowCount = 1; + + pass.args[15].type = MTL_ARG_MATERIAL_PIXEL_CONST; + pass.args[15].dest = 9; + pass.args[15].u.nameHash = 0x64E3AE5; + + pass.args[16].type = MTL_ARG_MATERIAL_PIXEL_CONST; + pass.args[16].dest = 5; + pass.args[16].u.nameHash = 0x3D9994DC; + + pass.args[17].type = MTL_ARG_MATERIAL_PIXEL_CONST; + pass.args[17].dest = 22; + pass.args[17].u.nameHash = 0x3FC0F1DE; + + pass.args[18].type = MTL_ARG_MATERIAL_PIXEL_CONST; + pass.args[18].dest = 7; + pass.args[18].u.nameHash = 0x470F6C9A; + + pass.args[19].type = MTL_ARG_MATERIAL_PIXEL_CONST; + pass.args[19].dest = 8; + pass.args[19].u.nameHash = 0x470F6C9B; + + pass.args[20].type = MTL_ARG_MATERIAL_PIXEL_CONST; + pass.args[20].dest = 20; + pass.args[20].u.nameHash = 0x6373ABA0; + + pass.args[21].type = MTL_ARG_MATERIAL_PIXEL_CONST; + pass.args[21].dest = 11; + pass.args[21].u.nameHash = 0x6373ABA1; + + pass.args[22].type = MTL_ARG_MATERIAL_PIXEL_CONST; + pass.args[22].dest = 6; + pass.args[22].u.nameHash = 0x9D5408FF; + + pass.args[23].type = MTL_ARG_MATERIAL_PIXEL_CONST; + pass.args[23].dest = 21; + pass.args[23].u.nameHash = 0xAA2E7C4F; + + return technique; + } + + MaterialTechniqueSet* GivenTechset(Zone& zone, MemoryManager& memory) + { + auto* techset = memory.Alloc(); + techset->name = "example_techset"; + techset->worldVertFormat = MTL_WORLDVERT_TEX_4_NRM_2; + + techset->techniques[TECHNIQUE_DEPTH_PREPASS] = GivenDepthPrepassTechnique(memory); + techset->techniques[TECHNIQUE_LIT] = GivenLitTechnique(memory); + + zone.m_pools.AddAsset(std::make_unique>(ASSET_TYPE_TECHNIQUE_SET, techset->name, techset)); + return techset; + } +} // namespace + +TEST_CASE("TechsetDumperIW5", "[iw5][techset][dumper]") +{ + Zone zone("MockZone", 0, GameId::IW5, GamePlatform::PC); + zone.Register(); + + MemoryManager memory; + MockSearchPath mockObjPath; + MockOutputPath mockOutput; + AssetDumpingContext context(zone, "", mockOutput, mockObjPath, std::nullopt); + + GivenTechset(zone, memory); + + techset::DumperIW5 dumper(true); + + SECTION("Can dump techset") + { + std::string expected(R"TECHSET( +"depth prepass": + example_zprepass; + +"lit": + example_lit; +)TECHSET"); + + dumper.Dump(context); + + const auto* file = mockOutput.GetMockedFile("techsets/example_techset.techset"); + REQUIRE(file); + REQUIRE(Trimmed(file->AsString()) == Trimmed(expected)); + } + + SECTION("Can dump simple technique") + { + std::string expected(R"TECHNIQUE( +// TECHNIQUE FLAGS: 0x4 +// TECHNIQUE FLAGS: 0x200 +{ + stateMap "passthrough"; // TODO + + vertexShader 3.0 "simple.hlsl" + { + // Omitted due to matching accessors: worldMatrix = constant.worldMatrix; + // Omitted due to matching accessors: viewProjectionMatrix = constant.viewProjectionMatrix; + } + + pixelShader 3.0 "simple.hlsl" + { + } + + vertex.position = code.position; +} +)TECHNIQUE"); + dumper.Dump(context); + + const auto* file = mockOutput.GetMockedFile("techniques/example_zprepass.tech"); + REQUIRE(file); + REQUIRE(Trimmed(file->AsString()) == Trimmed(expected)); + } + + SECTION("Can dump advanced technique") + { + std::string expected(R"TECHNIQUE( +{ + stateMap "passthrough"; // TODO + + vertexShader 3.0 "advanced.hlsl" + { + // Omitted due to matching accessors: worldMatrix = constant.worldMatrix; + // Omitted due to matching accessors: viewProjectionMatrix = constant.viewProjectionMatrix; + oceanUVAnimParmOctave0 = material.oceanUVAnimParmOctave0; + oceanUVAnimParmOctave1 = material.oceanUVAnimParmOctave1; + oceanAmplitude = material.oceanAmplitude; + oceanDisplacementSampler = material.oceanDisplacementMap; + // Omitted due to matching accessors: eyeOffset = constant.eyeOffset; + // Omitted due to matching accessors: fogConsts = constant.fogConsts; + // Omitted due to matching accessors: gameTime = constant.gameTime; + } + + pixelShader 3.0 "advanced.hlsl" + { + oceanEnvSampler = material.oceanEnvMap; + oceanDetailNormalSampler = material.oceanDetailNormalMap; + oceanHeightNormalSampler = material.oceanHeightNormalMap; + oceanFoamSampler = material.oceanFoamMap; + // Omitted due to matching accessors: fogColorLinear = constant.fogColorLinear; + // Omitted due to matching accessors: gameTime = constant.gameTime; + oceanUVAnimParmFoam = material.oceanUVAnimParmFoam; + envMapParms = material.envMapParms; + oceanFoamParms = material.oceanFoamParms; + oceanUVAnimParmOctave0 = material.oceanUVAnimParmOctave0; + oceanUVAnimParmOctave1 = material.oceanUVAnimParmOctave1; + oceanUVAnimParmDetail1 = material.oceanUVAnimParmDetail1; + oceanUVAnimParmDetail0 = material.oceanUVAnimParmDetail0; + oceanAmplitude = material.oceanAmplitude; + oceanMiscParms = material.oceanMiscParms; + } + + vertex.position = code.position; + vertex.color[0] = code.color; +} +)TECHNIQUE"); + dumper.Dump(context); + + const auto* file = mockOutput.GetMockedFile("techniques/example_lit.tech"); + REQUIRE(file); + REQUIRE(Trimmed(file->AsString()) == Trimmed(expected)); + } +} diff --git a/test/ObjWritingTests/Game/IW5/Techset/ps_advanced.hlsl.cso b/test/ObjWritingTests/Game/IW5/Techset/ps_advanced.hlsl.cso new file mode 100644 index 0000000000000000000000000000000000000000..1ab1e54b2d8e2bb0ce47c6923d325c4d0e1101ca GIT binary patch literal 1872 zcmZ`(&rcIk5T5y9JkS-<$8veDlq`p=8AI-}r=9D&^(tMD$(9 z7qAH|Y|~Yu6JV3u8^%n-jOzgT~;~8v6utElVE!bEFdoP&MoYwY9 zFgxStHul$?=2NFt4}fzolrGk} z2Z7tJ_A+vSJ1o7qa#;F$!#cHZ znWv1K%Ad~6FH0%yO^h3+H^d}%)ZPlgD|`5KOnV|?9QzN}VBg6N?eo~n$der!6h{`1 zH5uTH!vg(^Od@<{VD~x&>P1+?WjJVbhp{kk_xe5>{S0;Z=!hHe^uo!67qcn6;|Z^p z@Qx!!8{*}C%F8~(yr~}cnWV1h;an-V-RI_9y^ehWx0z_>8kh(5BY#yi>xssE!lUe7 zCogrW+I7UC&M@Uk?JSS-$$^_Sr@3;-!?CPcM{T^HLLGbuI_^M2uT+iqc#j&ZM>aIn=MQplPlONrbfkK;D4bJCts2dV&3TH*Z}j5{;YG|t7w3%I zAv6gm!H07i^jl&y%8SkMe1`n!IoDPcob|94{3>DXCa*e&ji2f`nwIn1a%Q0y*U&GH zH>irbikfs_iLCUc7$tEOyD3Nv{s9ab z+3=kF0e(h*07HgI6ofi3Af}pgpB)n+Ui9L7&buGy-uGZ#C)ddt4qA<8cL01c_($#r zKZ3CZd?a^4(?l5t9^i}cTtB!?RLLFZ+%L`Tkb9MLiRSjneb2c~=Cl}XFz0=#$@Q3D z6E$*IxnEOrb#gy*uBSP`ruX|S_JihZa@#ql-l=A<7pM^Pxxs1%PcwJ){L=$}7=!^% z-GOR(0|mdU++kxd>U-hYNd}*L!Km+c)j$oyBbR4tjJb^s=8<2zbYT0qG4uw9?s(Ab zhVDe|rTPA)A7bLE^TV0>N*Gr(_pb`Vo6Y;3cKI`vY88bE^v8kdOKfMuc-S&T0eOAS)`yiRP8?~t%{-DPAtORY)8g&5QB#yHw zErd($bY`MY<|11E>XTd-Yx;_;ztmb%eQoBxD~{bl~_V#28Fn;g4DS$n4iYb0`r>`)^Ac^D5m>LdX0Nmf dU&So0W^ZNX>yX=A^S{X&CT=fM|8|@tz#r2B=qdmJ literal 0 HcmV?d00001 diff --git a/test/ObjWritingTests/Game/IW5/Techset/vs_simple.hlsl.cso b/test/ObjWritingTests/Game/IW5/Techset/vs_simple.hlsl.cso new file mode 100644 index 0000000000000000000000000000000000000000..ed20a21735d86662320c524b9a1c96d6826d6df3 GIT binary patch literal 388 zcmZ9H%}T>S6otBbm^4XC9%CkbPdNi#|u= z$#n?DfjeiubLY$t;zN6AZ}60kA1?v?us|=Obc{1#LwRO(p62{P?MBXu=xChx-YT=} zx59ndd6~K8%d9N24VtEj5z9^`Nnq`Yd=c6!KfRmY;`Q4u$KKmAbuRa#)cSHX`J63m z5i%SXuZ?j^O4ZM-WojA1{d`yf2*MA?JJFa;7EATvlc$- e*$58<18S$3=NXdc^}_T5YvHJ8BOH_47Wf5S$V=S- literal 0 HcmV?d00001 From 7bb9cfcbfa670ed91f12be85c0da89e2b3672173 Mon Sep 17 00:00:00 2001 From: Jan Laupetin Date: Fri, 20 Mar 2026 23:03:02 +0100 Subject: [PATCH 3/4] chore: add unit tests for iw4 techsets --- .../IW4/Techset/TechniqueCompilerIW4Test.cpp | 308 +++++++++++++++++ .../IW4/Techset/TechsetCompilerIW4Test.cpp | 149 +++++++++ .../IW4/Techset/VertexDeclCompilerIW4Test.cpp | 63 ++++ .../Game/IW4/Techset/ps_advanced.hlsl.cso | Bin 0 -> 860 bytes .../Game/IW4/Techset/ps_simple.hlsl.cso | Bin 0 -> 124 bytes .../Game/IW4/Techset/vs_advanced.hlsl.cso | Bin 0 -> 968 bytes .../Game/IW4/Techset/vs_simple.hlsl.cso | Bin 0 -> 376 bytes .../Game/IW4/Techset/TechsetDumperIW4Test.cpp | 310 ++++++++++++++++++ .../Game/IW4/Techset/ps_advanced.hlsl.cso | Bin 0 -> 860 bytes .../Game/IW4/Techset/ps_simple.hlsl.cso | Bin 0 -> 124 bytes .../Game/IW4/Techset/vs_advanced.hlsl.cso | Bin 0 -> 968 bytes .../Game/IW4/Techset/vs_simple.hlsl.cso | Bin 0 -> 376 bytes 12 files changed, 830 insertions(+) create mode 100644 test/ObjCompilingTests/Game/IW4/Techset/TechniqueCompilerIW4Test.cpp create mode 100644 test/ObjCompilingTests/Game/IW4/Techset/TechsetCompilerIW4Test.cpp create mode 100644 test/ObjCompilingTests/Game/IW4/Techset/VertexDeclCompilerIW4Test.cpp create mode 100644 test/ObjCompilingTests/Game/IW4/Techset/ps_advanced.hlsl.cso create mode 100644 test/ObjCompilingTests/Game/IW4/Techset/ps_simple.hlsl.cso create mode 100644 test/ObjCompilingTests/Game/IW4/Techset/vs_advanced.hlsl.cso create mode 100644 test/ObjCompilingTests/Game/IW4/Techset/vs_simple.hlsl.cso create mode 100644 test/ObjWritingTests/Game/IW4/Techset/TechsetDumperIW4Test.cpp create mode 100644 test/ObjWritingTests/Game/IW4/Techset/ps_advanced.hlsl.cso create mode 100644 test/ObjWritingTests/Game/IW4/Techset/ps_simple.hlsl.cso create mode 100644 test/ObjWritingTests/Game/IW4/Techset/vs_advanced.hlsl.cso create mode 100644 test/ObjWritingTests/Game/IW4/Techset/vs_simple.hlsl.cso diff --git a/test/ObjCompilingTests/Game/IW4/Techset/TechniqueCompilerIW4Test.cpp b/test/ObjCompilingTests/Game/IW4/Techset/TechniqueCompilerIW4Test.cpp new file mode 100644 index 00000000..cfa17d11 --- /dev/null +++ b/test/ObjCompilingTests/Game/IW4/Techset/TechniqueCompilerIW4Test.cpp @@ -0,0 +1,308 @@ +#include "Game/IW4/Techset/TechniqueCompilerIW4.h" + +#include "Game/IW4/IW4.h" +#include "Game/IW4/Techset/PixelShaderLoaderIW4.h" +#include "Game/IW4/Techset/VertexDeclCompilerIW4.h" +#include "Game/IW4/Techset/VertexShaderLoaderIW4.h" +#include "OatTestPaths.h" +#include "SearchPath/MockSearchPath.h" +#include "Shader/ShaderCommon.h" +#include "Utils/MemoryManager.h" +#include "catch2/generators/catch_generators.hpp" + +#include +#include +#include +#include + +using namespace IW4; +using namespace Catch; +using namespace std::literals; +namespace fs = std::filesystem; + +namespace +{ + void GivenVertexShaderFile(const std::string& name, MockSearchPath& searchPath) + { + const auto filePath = oat::paths::GetTestDirectory() / "ObjCompilingTests/Game/IW4/Techset" / std::format("vs_{}.cso", name); + const auto fileSize = static_cast(fs::file_size(filePath)); + + std::ifstream file(filePath, std::ios::binary); + REQUIRE(file.is_open()); + + std::string data(fileSize, '\0'); + file.read(data.data(), fileSize); + REQUIRE(file.gcount() == static_cast(fileSize)); + + searchPath.AddFileData(shader::GetFileNameForVertexShaderAssetName(name), std::move(data)); + } + + void GivenPixelShaderFile(const std::string& name, MockSearchPath& searchPath) + { + const auto filePath = oat::paths::GetTestDirectory() / "ObjCompilingTests/Game/IW4/Techset" / std::format("ps_{}.cso", name); + const auto fileSize = static_cast(fs::file_size(filePath)); + + std::ifstream file(filePath, std::ios::binary); + REQUIRE(file.is_open()); + + std::string data(fileSize, '\0'); + file.read(data.data(), fileSize); + REQUIRE(file.gcount() == static_cast(fileSize)); + + searchPath.AddFileData(shader::GetFileNameForPixelShaderAssetName(name), std::move(data)); + } +} // namespace + +TEST_CASE("TechniqueCompilerIW4", "[iw4][techset][compiler]") +{ + + Zone zone("MockZone", 0, GameId::IW4, GamePlatform::PC); + zone.Register(); + + MemoryManager memory; + AssetCreatorCollection creatorCollection(zone); + IgnoredAssetLookup ignoredAssetLookup; + AssetCreationContext context(zone, &creatorCollection, &ignoredAssetLookup); + MockSearchPath searchPath; + + creatorCollection.AddAssetCreator(techset::CreateVertexDeclCompilerIW4(memory)); + creatorCollection.AddAssetCreator(techset::CreateVertexShaderLoaderIW4(memory, searchPath)); + creatorCollection.AddAssetCreator(techset::CreatePixelShaderLoaderIW4(memory, searchPath)); + + auto loader = techset::CreateTechniqueCompilerIW4(memory, zone, searchPath); + + SECTION("Can compile simple technique") + { + const auto [inputName, inputData] = GENERATE(Catch::Generators::table({ + {"auto-create args", R"TECHNIQUE( +{ + stateMap "passthrough"; + + vertexShader 3.0 "simple.hlsl" + { + } + + pixelShader 3.0 "simple.hlsl" + { + } + + vertex.position = code.position; +} +)TECHNIQUE"}, + {"manual args", R"TECHNIQUE( +{ + stateMap "passthrough"; + + vertexShader 3.0 "simple.hlsl" + { + worldMatrix = constant.worldMatrix; + viewProjectionMatrix = constant.viewProjectionMatrix; + } + + pixelShader 3.0 "simple.hlsl" + { + } + + vertex.position = code.position; +} +)TECHNIQUE"}, + })); + + CAPTURE(inputName); + searchPath.AddFileData("techniques/zprepass.tech", inputData); + + GivenVertexShaderFile("simple.hlsl", searchPath); + GivenPixelShaderFile("simple.hlsl", searchPath); + + auto result = loader->CreateSubAsset("zprepass", context); + REQUIRE(result.HasBeenSuccessful()); + + const auto* assetInfo = reinterpret_cast*>(result.GetAssetInfo()); + const auto* technique = assetInfo->Asset(); + + CHECK(technique->name == "zprepass"s); + + CHECK(technique->flags == 0x204); + + REQUIRE(technique->passCount == 1); + auto& pass = technique->passArray[0]; + CHECK(pass.customSamplerFlags == 0); + + REQUIRE(pass.vertexShader); + CHECK(pass.vertexShader->name == "simple.hlsl"s); + REQUIRE(pass.pixelShader); + CHECK(pass.pixelShader->name == "simple.hlsl"s); + + REQUIRE(pass.vertexDecl); + auto& vertexDecl = *pass.vertexDecl; + CHECK(vertexDecl.name == "pp"s); + CHECK(vertexDecl.hasOptionalSource == false); + REQUIRE(vertexDecl.streamCount == 1); + CHECK(vertexDecl.routing.data[0].source == STREAM_SRC_POSITION); + CHECK(vertexDecl.routing.data[0].dest == STREAM_DST_POSITION); + + REQUIRE(pass.perPrimArgCount == 1); + REQUIRE(pass.perObjArgCount == 1); + REQUIRE(pass.stableArgCount == 0); + CHECK(pass.args[0].type == MTL_ARG_CODE_VERTEX_CONST); + CHECK(pass.args[0].dest == 4); + CHECK(pass.args[0].u.codeConst.index == CONST_SRC_CODE_TRANSPOSE_WORLD_MATRIX0); + CHECK(pass.args[0].u.codeConst.firstRow == 0); + CHECK(pass.args[0].u.codeConst.rowCount == 4); + + CHECK(pass.args[1].type == MTL_ARG_CODE_VERTEX_CONST); + CHECK(pass.args[1].dest == 0); + CHECK(pass.args[1].u.codeConst.index == CONST_SRC_CODE_TRANSPOSE_VIEW_PROJECTION_MATRIX); + CHECK(pass.args[1].u.codeConst.firstRow == 0); + CHECK(pass.args[1].u.codeConst.rowCount == 4); + } + + SECTION("Can compile advanced technique") + { + const auto [inputName, inputData] = GENERATE(Catch::Generators::table({ + {"auto-create args", R"TECHNIQUE( +{ + stateMap "passthrough"; + + vertexShader 3.0 "advanced.hlsl" + { + } + + pixelShader 3.0 "advanced.hlsl" + { + normalMapSampler = material.normalMap; + colorMapSampler = material.colorMap; + fogColorLinear = float4(0, 0, 0, 0); + } + + vertex.position = code.position; + vertex.color[0] = code.color; + vertex.texcoord[0] = code.texcoord[0]; + vertex.normal = code.normal; + vertex.texcoord[2] = code.tangent; +} +)TECHNIQUE"}, + {"manual args", R"TECHNIQUE( +{ + stateMap "passthrough"; + + vertexShader 3.0 "advanced.hlsl" + { + worldMatrix = constant.worldMatrix; + viewProjectionMatrix = constant.viewProjectionMatrix; + fogConsts = constant.fogConsts; + } + + pixelShader 3.0 "advanced.hlsl" + { + attenuationSampler = sampler.attenuationSampler; + normalMapSampler = material.normalMap; + colorMapSampler = material.colorMap; + lightPosition = constant.lightPosition; + lightDiffuse = constant.lightDiffuse; + fogColorLinear = float4(0, 0, 0, 0); + } + + vertex.position = code.position; + vertex.color[0] = code.color; + vertex.texcoord[0] = code.texcoord[0]; + vertex.normal = code.normal; + vertex.texcoord[2] = code.tangent; +} +)TECHNIQUE"}, + })); + + CAPTURE(inputName); + searchPath.AddFileData("techniques/example_lit_omni.tech", inputData); + + GivenVertexShaderFile("advanced.hlsl", searchPath); + GivenPixelShaderFile("advanced.hlsl", searchPath); + + auto result = loader->CreateSubAsset("example_lit_omni", context); + REQUIRE(result.HasBeenSuccessful()); + + const auto* assetInfo = reinterpret_cast*>(result.GetAssetInfo()); + const auto* technique = assetInfo->Asset(); + + CHECK(technique->name == "example_lit_omni"s); + + CHECK(technique->flags == 0); + + REQUIRE(technique->passCount == 1); + auto& pass = technique->passArray[0]; + CHECK(pass.customSamplerFlags == 0); + + REQUIRE(pass.vertexShader); + CHECK(pass.vertexShader->name == "advanced.hlsl"s); + REQUIRE(pass.pixelShader); + CHECK(pass.pixelShader->name == "advanced.hlsl"s); + + REQUIRE(pass.vertexDecl); + auto& vertexDecl = *pass.vertexDecl; + CHECK(vertexDecl.hasOptionalSource == false); + REQUIRE(vertexDecl.streamCount == 5); + CHECK(vertexDecl.routing.data[0].source == STREAM_SRC_POSITION); + CHECK(vertexDecl.routing.data[0].dest == STREAM_DST_POSITION); + CHECK(vertexDecl.routing.data[1].source == STREAM_SRC_COLOR); + CHECK(vertexDecl.routing.data[1].dest == STREAM_DST_COLOR_0); + CHECK(vertexDecl.routing.data[2].source == STREAM_SRC_TEXCOORD_0); + CHECK(vertexDecl.routing.data[2].dest == STREAM_DST_TEXCOORD_0); + CHECK(vertexDecl.routing.data[3].source == STREAM_SRC_NORMAL); + CHECK(vertexDecl.routing.data[3].dest == STREAM_DST_NORMAL); + CHECK(vertexDecl.routing.data[4].source == STREAM_SRC_TANGENT); + CHECK(vertexDecl.routing.data[4].dest == STREAM_DST_TEXCOORD_2); + + REQUIRE(pass.perPrimArgCount == 1); + REQUIRE(pass.perObjArgCount == 2); + REQUIRE(pass.stableArgCount == 6); + + CHECK(pass.args[0].type == MTL_ARG_CODE_VERTEX_CONST); + CHECK(pass.args[0].dest == 4); + CHECK(pass.args[0].u.codeConst.index == CONST_SRC_CODE_TRANSPOSE_WORLD_MATRIX0); + CHECK(pass.args[0].u.codeConst.firstRow == 0); + CHECK(pass.args[0].u.codeConst.rowCount == 4); + + CHECK(pass.args[1].type == MTL_ARG_CODE_VERTEX_CONST); + CHECK(pass.args[1].dest == 0); + CHECK(pass.args[1].u.codeConst.index == CONST_SRC_CODE_TRANSPOSE_VIEW_PROJECTION_MATRIX); + CHECK(pass.args[1].u.codeConst.firstRow == 0); + CHECK(pass.args[1].u.codeConst.rowCount == 4); + + CHECK(pass.args[2].type == MTL_ARG_CODE_PIXEL_SAMPLER); + CHECK(pass.args[2].dest == 5); + CHECK(pass.args[2].u.codeSampler == TEXTURE_SRC_CODE_LIGHT_ATTENUATION); + + CHECK(pass.args[3].type == MTL_ARG_MATERIAL_PIXEL_SAMPLER); + CHECK(pass.args[3].dest == 4); + CHECK(pass.args[3].u.nameHash == 0x59D30D0F); + + CHECK(pass.args[4].type == MTL_ARG_MATERIAL_PIXEL_SAMPLER); + CHECK(pass.args[4].dest == 0); + CHECK(pass.args[4].u.nameHash == 0xA0AB1041); + + CHECK(pass.args[5].type == MTL_ARG_CODE_VERTEX_CONST); + CHECK(pass.args[5].dest == 21); + CHECK(pass.args[5].u.codeConst.index == CONST_SRC_CODE_FOG); + CHECK(pass.args[5].u.codeConst.firstRow == 0); + CHECK(pass.args[5].u.codeConst.rowCount == 1); + + CHECK(pass.args[6].type == MTL_ARG_CODE_PIXEL_CONST); + CHECK(pass.args[6].dest == 17); + CHECK(pass.args[6].u.codeConst.index == CONST_SRC_CODE_LIGHT_POSITION); + CHECK(pass.args[6].u.codeConst.firstRow == 0); + CHECK(pass.args[6].u.codeConst.rowCount == 1); + + CHECK(pass.args[7].type == MTL_ARG_CODE_PIXEL_CONST); + CHECK(pass.args[7].dest == 18); + CHECK(pass.args[7].u.codeConst.index == CONST_SRC_CODE_LIGHT_DIFFUSE); + CHECK(pass.args[7].u.codeConst.firstRow == 0); + CHECK(pass.args[7].u.codeConst.rowCount == 1); + + CHECK(pass.args[8].type == MTL_ARG_LITERAL_PIXEL_CONST); + CHECK(pass.args[8].dest == 0); + CHECK((*pass.args[8].u.literalConst)[0] == 0.0f); + CHECK((*pass.args[8].u.literalConst)[1] == 0.0f); + CHECK((*pass.args[8].u.literalConst)[2] == 0.0f); + CHECK((*pass.args[8].u.literalConst)[3] == 0.0f); + } +} diff --git a/test/ObjCompilingTests/Game/IW4/Techset/TechsetCompilerIW4Test.cpp b/test/ObjCompilingTests/Game/IW4/Techset/TechsetCompilerIW4Test.cpp new file mode 100644 index 00000000..2abbca8b --- /dev/null +++ b/test/ObjCompilingTests/Game/IW4/Techset/TechsetCompilerIW4Test.cpp @@ -0,0 +1,149 @@ +#include "Game/IW4/Techset/TechsetCompilerIW4.h" + +#include "Game/IW4/IW4.h" +#include "SearchPath/MockSearchPath.h" +#include "Techset/TechsetCommon.h" +#include "Utils/TestMemoryManager.h" + +#include +#include +#include +#include + +using namespace IW4; +using namespace std::string_literals; + +namespace +{ + MaterialTechnique* GivenTechnique(const std::string& name, AssetCreationContext& context, MemoryManager& memory) + { + auto* technique = memory.Alloc(); + technique->name = memory.Dup(name.c_str()); + + context.AddSubAsset(name, technique); + + return technique; + } +} // namespace + +TEST_CASE("TechsetCompilerIW4", "[techset][iw4][compiler]") +{ + Zone zone("test", 0, GameId::IW4, GamePlatform::PC); + AssetCreatorCollection creators(zone); + IgnoredAssetLookup ignoredAssets; + AssetCreationContext context(zone, &creators, &ignoredAssets); + MockSearchPath searchPath; + TestMemoryManager memory; + const auto sut = techset::CreateTechsetCompilerIW4(memory, searchPath); + + SECTION("Sets correct worldVertFormat") + { + const auto [techsetName, expectedWorldVertFormat] = GENERATE(Catch::Generators::table({ + {"default", MTL_WORLDVERT_TEX_1_NRM_1}, + {"effect_zeqqz943", MTL_WORLDVERT_TEX_1_NRM_1}, + {"lit_r0c0_t1c1n1", MTL_WORLDVERT_TEX_2_NRM_1}, + {"lit_r0c0n0x0_b1c1n1s1v1", MTL_WORLDVERT_TEX_2_NRM_2}, + {"lit_r0c0n0x0_b1c1n1s1v1_b2c2n2x2", MTL_WORLDVERT_TEX_3_NRM_3}, + {"lit_sm_b0c0_b1c1_b2c2", MTL_WORLDVERT_TEX_3_NRM_1}, + {"lit_sm_b0c0_b1c1n1x1_b2c2n2v2", MTL_WORLDVERT_TEX_3_NRM_2}, + {"lit_sm_r0c0_b1c1_b2c2_b3c3", MTL_WORLDVERT_TEX_4_NRM_1}, + {"lit_sm_r0c0n0_b1c1_b2c2n2v2_m3c3", MTL_WORLDVERT_TEX_4_NRM_2}, + {"lit_sm_r0c0n0_b1c1n1s1", MTL_WORLDVERT_TEX_2_NRM_2}, + {"lit_sm_r0c0n0s0_b1c1n1s1_b2c2n2", MTL_WORLDVERT_TEX_3_NRM_3}, + {"lit_sm_r0c0n0x0_b1c1_b2c2n2s2", MTL_WORLDVERT_TEX_3_NRM_2}, + {"lit_sm_r0c0n0x0_b1c1n1_b2c2n2s2v2", MTL_WORLDVERT_TEX_3_NRM_3}, + {"lit_sm_r0c0n0x0_b1c1s1v1_m2c2_m3c3", MTL_WORLDVERT_TEX_4_NRM_1}, + {"lit_sm_r0c0n0x0_b1c1v1_b2c2n2s2_m3c3", MTL_WORLDVERT_TEX_4_NRM_2}, + {"lit_sm_r0c0s0_b1c1n1s1_m2c2", MTL_WORLDVERT_TEX_3_NRM_1}, + {"lit_sm_r0c0x0_b1c1", MTL_WORLDVERT_TEX_2_NRM_1}, + {"lit_sm_r0c0x0_b1c1n1s1_b2c2n2s2", MTL_WORLDVERT_TEX_3_NRM_2}, + {"lit_sm_r0c0x0_m1c1_m2c2_m3c3", MTL_WORLDVERT_TEX_4_NRM_1}, + {"lit_sm_t0c0n0_b1c1n1v1", MTL_WORLDVERT_TEX_2_NRM_2}, + {"lit_sm_t0c0n0s0_b1c1n1_b2c2n2s2v2", MTL_WORLDVERT_TEX_3_NRM_3}, + {"mc_lit_sm_r0c0d0_2213939z", MTL_WORLDVERT_TEX_1_NRM_1}, + {"wpc_lit_sm_b0c0s0_3f3q946z", MTL_WORLDVERT_TEX_1_NRM_1}, + {"wpc_lit_sm_r0c0n0s0o0_qj92q1f8", MTL_WORLDVERT_TEX_1_NRM_1}, + {"wpc_sw4_3d_burning_embers_nuketown_74j6971w", MTL_WORLDVERT_TEX_1_NRM_1}, + {"wpc_unlitdecalblend_add_j26wq580", MTL_WORLDVERT_TEX_1_NRM_1}, + })); + + CAPTURE(techsetName); + searchPath.AddFileData(techset::GetFileNameForTechsetName(techsetName), ""); + + const auto result = sut->CreateAsset(techsetName, context); + REQUIRE(result.HasBeenSuccessful()); + + const auto* techset = static_cast(result.GetAssetInfo()->m_ptr); + CHECK(techset->worldVertFormat == expectedWorldVertFormat); + } + + SECTION("Can parse simple techset") + { + searchPath.AddFileData(techset::GetFileNameForTechsetName("simple"), R"TECHSET( +"depth prepass": + example_zprepass; + +"lit omni": + example_lit_omni; +)TECHSET"); + + auto* exampleZPrepass = GivenTechnique("example_zprepass", context, memory); + auto* exampleLit = GivenTechnique("example_lit_omni", context, memory); + + const auto result = sut->CreateAsset("simple", context); + REQUIRE(result.HasBeenSuccessful()); + + const auto* techset = static_cast(result.GetAssetInfo()->m_ptr); + CHECK(techset->name == "simple"s); + CHECK(techset->worldVertFormat == MTL_WORLDVERT_TEX_1_NRM_1); + + size_t techniqueCount = 0; + for (auto* technique : techset->techniques) + { + if (technique) + techniqueCount++; + } + + CHECK(techniqueCount == 2); + CHECK(techset->techniques[TECHNIQUE_DEPTH_PREPASS] == exampleZPrepass); + CHECK(techset->techniques[TECHNIQUE_LIT_OMNI] == exampleLit); + } + + SECTION("Can parse techset with same technique used multiple times") + { + searchPath.AddFileData(techset::GetFileNameForTechsetName("simple"), R"TECHSET( +"depth prepass": +"build shadowmap depth": + example_zprepass; + +"lit": +"lit sun": +"lit instanced omni shadow dfog": + example_lit_omni; +)TECHSET"); + + auto* exampleZPrepass = GivenTechnique("example_zprepass", context, memory); + auto* exampleLitOmni = GivenTechnique("example_lit_omni", context, memory); + + const auto result = sut->CreateAsset("simple", context); + REQUIRE(result.HasBeenSuccessful()); + + const auto* techset = static_cast(result.GetAssetInfo()->m_ptr); + CHECK(techset->name == "simple"s); + CHECK(techset->worldVertFormat == MTL_WORLDVERT_TEX_1_NRM_1); + + size_t techniqueCount = 0; + for (auto* technique : techset->techniques) + { + if (technique) + techniqueCount++; + } + + CHECK(techniqueCount == 5); + CHECK(techset->techniques[TECHNIQUE_DEPTH_PREPASS] == exampleZPrepass); + CHECK(techset->techniques[TECHNIQUE_BUILD_SHADOWMAP_DEPTH] == exampleZPrepass); + CHECK(techset->techniques[TECHNIQUE_LIT] == exampleLitOmni); + CHECK(techset->techniques[TECHNIQUE_LIT_SUN] == exampleLitOmni); + CHECK(techset->techniques[TECHNIQUE_LIT_INSTANCED_OMNI_SHADOW_DFOG] == exampleLitOmni); + } +} diff --git a/test/ObjCompilingTests/Game/IW4/Techset/VertexDeclCompilerIW4Test.cpp b/test/ObjCompilingTests/Game/IW4/Techset/VertexDeclCompilerIW4Test.cpp new file mode 100644 index 00000000..5dc24d04 --- /dev/null +++ b/test/ObjCompilingTests/Game/IW4/Techset/VertexDeclCompilerIW4Test.cpp @@ -0,0 +1,63 @@ +#include "Game/IW4/Techset/VertexDeclCompilerIW4.h" + +#include "Game/IW4/IW4.h" +#include "Utils/MemoryManager.h" + +#include + +using namespace IW4; +using namespace Catch; +using namespace std::literals; + +TEST_CASE("VertexDeclCompilerIW4", "[iw4][techset][compiler]") +{ + Zone zone("MockZone", 0, GameId::IW4, GamePlatform::PC); + zone.Register(); + + MemoryManager memory; + AssetCreatorCollection creatorCollection(zone); + IgnoredAssetLookup ignoredAssetLookup; + AssetCreationContext context(zone, &creatorCollection, &ignoredAssetLookup); + + auto loader = techset::CreateVertexDeclCompilerIW4(memory); + + SECTION("Can create simple vertex decl") + { + auto result = loader->CreateAsset("pp", context); + REQUIRE(result.HasBeenSuccessful()); + + const auto* assetInfo = reinterpret_cast*>(result.GetAssetInfo()); + const auto* decl = assetInfo->Asset(); + + CHECK(decl->name == "pp"s); + CHECK(decl->hasOptionalSource == false); + + REQUIRE(decl->streamCount == 1); + CHECK(decl->routing.data[0].source == STREAM_SRC_POSITION); + CHECK(decl->routing.data[0].dest == STREAM_DST_POSITION); + } + + SECTION("Can create advanced vertex decl") + { + auto result = loader->CreateAsset("pdcc1tt7t1t1n1n", context); + REQUIRE(result.HasBeenSuccessful()); + + const auto* assetInfo = reinterpret_cast*>(result.GetAssetInfo()); + const auto* decl = assetInfo->Asset(); + + CHECK(decl->name == "pdcc1tt7t1t1n1n"s); + CHECK(decl->hasOptionalSource == true); + + REQUIRE(decl->streamCount == 5); + CHECK(decl->routing.data[0].source == STREAM_SRC_POSITION); + CHECK(decl->routing.data[0].dest == STREAM_DST_DEPTH); + CHECK(decl->routing.data[1].source == STREAM_SRC_COLOR); + CHECK(decl->routing.data[1].dest == STREAM_DST_COLOR_1); + CHECK(decl->routing.data[2].source == STREAM_SRC_TANGENT); + CHECK(decl->routing.data[2].dest == STREAM_DST_TEXCOORD_7); + CHECK(decl->routing.data[3].source == STREAM_SRC_TEXCOORD_1); + CHECK(decl->routing.data[3].dest == STREAM_DST_TEXCOORD_1); + CHECK(decl->routing.data[4].source == STREAM_SRC_NORMAL_TRANSFORM_1); + CHECK(decl->routing.data[4].dest == STREAM_DST_NORMAL); + } +} diff --git a/test/ObjCompilingTests/Game/IW4/Techset/ps_advanced.hlsl.cso b/test/ObjCompilingTests/Game/IW4/Techset/ps_advanced.hlsl.cso new file mode 100644 index 0000000000000000000000000000000000000000..a05fea8ec4bb724404b2aea3ed70a4168828c458 GIT binary patch literal 860 zcmY*X%WB(D5It9qT7-h@&_$u8aY-QEl(ggnn#84x(2@o_yMPgi>MkJ0JvAG`qYT=9q4BR(XC zLi;JPslc!q0zU=knbAy|g0aAUGd61MhEF+vX$IC=cZfN5tFacbj|w7? z@X}5EDD1kUao|nx%fe&U$fr7IQ4mc&yW@GmAR2ZA`s9b6%X&psRqF~P@P`-42Y)cQ zjJ+!O62-o>Kp0I%Zm^;?j{Cd)w>TPm;eH%@NiT|mxZU&Oq}{!6&v~8YIRe(p1J34lBPVxg7bdNzffN|&>VwxYIh#bkDadHUQ@l{c8@oYEtc`gttaDqrIH+@d9O(0*vl T5si|2(3svIoY%64yextLm_eu< literal 0 HcmV?d00001 diff --git a/test/ObjCompilingTests/Game/IW4/Techset/ps_simple.hlsl.cso b/test/ObjCompilingTests/Game/IW4/Techset/ps_simple.hlsl.cso new file mode 100644 index 0000000000000000000000000000000000000000..e41645559516b9c1d1d17e558730f0e72b3e2e44 GIT binary patch literal 124 zcmZQz{{R2qe_;ma5Jx8&1_lOYAZ7rHfhaIwWKaN!7Zk@E#~U#C7o_Gn78j?MgyiSv u6f1CJ literal 0 HcmV?d00001 diff --git a/test/ObjCompilingTests/Game/IW4/Techset/vs_advanced.hlsl.cso b/test/ObjCompilingTests/Game/IW4/Techset/vs_advanced.hlsl.cso new file mode 100644 index 0000000000000000000000000000000000000000..5edabc04bd1a158fd49c7672fd9d25618e2739b5 GIT binary patch literal 968 zcmaJ1ejkQxEPAi5~R2N*>@ud0fAT$V@GXk1kF=+(!=NimP! zvb+S648zuH_2ksKZNUBf{cCqe^Z|A99m(5Xocr~D7|8*{M(`~rsE89{9Wl?xnO@h< zMocTIUEQelIMkYmmDENowQ0o0B|T2u7O|D%#gLv4ngMp(0dfY6LPqV%rMQY*d2V~I zJx_XWJ>QC)7rRodr-!fSsGd`c+o_syNM&=X`gMU5wYI@IgPa2mz?+X|9NO^}B3n%J-!W(iP`) zg5~@t_?i}Z9whLjr`kj h5An(Iy5-yBeA!BPhQ)X3k8e@-Yy@i<>AUGV;4j1VxJUp1 literal 0 HcmV?d00001 diff --git a/test/ObjCompilingTests/Game/IW4/Techset/vs_simple.hlsl.cso b/test/ObjCompilingTests/Game/IW4/Techset/vs_simple.hlsl.cso new file mode 100644 index 0000000000000000000000000000000000000000..1d11cba27dfb2f778040877a57e2b10d00ff26cd GIT binary patch literal 376 zcmYk2J!-=+9L1k(rBx?GpL}Nw)e3If8dzDnrf@ z_el{Zg1?@APkQ=7eCkf!5HH#E`5wR@3-lt2V+f=>$}7uxlKnT;wSpDVk+|)>O|G{e zrCI5pDmUx5c~#~+v~3$B_A7-?0$WoSi!i;hv+-<%_g}r9TC1zfn8K!+wpIGIoG)}4 zGEP2$fkM&%_2k0F!})qOknAtU2kEYig#P1R;i*x$Yi{^L0YUFrOB@47o(DZk&sRMw e&)0$ZF$cjs_wach@0ng;={f3Id5+0l2mAxGI7{jP literal 0 HcmV?d00001 diff --git a/test/ObjWritingTests/Game/IW4/Techset/TechsetDumperIW4Test.cpp b/test/ObjWritingTests/Game/IW4/Techset/TechsetDumperIW4Test.cpp new file mode 100644 index 00000000..9725ff19 --- /dev/null +++ b/test/ObjWritingTests/Game/IW4/Techset/TechsetDumperIW4Test.cpp @@ -0,0 +1,310 @@ +#include "Game/IW4/Techset/TechsetDumperIW4.h" + +#include "Asset/AssetRegistration.h" +#include "Game/IW4/IW4.h" +#include "OatTestPaths.h" +#include "SearchPath/MockOutputPath.h" +#include "SearchPath/MockSearchPath.h" +#include "Utils/MemoryManager.h" + +#include +#include +#include +#include +#include + +using namespace IW4; +using namespace Catch; +using namespace std::literals; +namespace fs = std::filesystem; + +namespace +{ + std::string Trimmed(const std::string& input) + { + auto start = input.find_first_not_of(" \r\n"); + if (start == std::string::npos) + start = 0; + + auto end = input.find_last_not_of(" \r\n"); + if (end == std::string::npos) + end = input.length(); + else + end = end + 1; + + return input.substr(start, end - start); + } + + MaterialVertexShader* GivenVertexShader(const std::string& name, MemoryManager& memory) + { + const auto filePath = oat::paths::GetTestDirectory() / "ObjWritingTests/Game/IW4/Techset" / std::format("vs_{}.cso", name); + const auto fileSize = static_cast(fs::file_size(filePath)); + + std::ifstream file(filePath, std::ios::binary); + REQUIRE(file.is_open()); + + auto* shader = memory.Alloc(); + shader->name = memory.Dup(name.c_str()); + shader->prog.loadDef.program = reinterpret_cast(memory.Alloc(fileSize)); + shader->prog.loadDef.programSize = static_cast(fileSize / 4u); + file.read(reinterpret_cast(shader->prog.loadDef.program), fileSize); + REQUIRE(file.gcount() == static_cast(fileSize)); + + return shader; + } + + MaterialPixelShader* GivenPixelShader(const std::string& name, MemoryManager& memory) + { + const auto filePath = oat::paths::GetTestDirectory() / "ObjWritingTests/Game/IW4/Techset" / std::format("ps_{}.cso", name); + const auto fileSize = static_cast(fs::file_size(filePath)); + + std::ifstream file(filePath, std::ios::binary); + REQUIRE(file.is_open()); + + auto* shader = memory.Alloc(); + shader->name = memory.Dup(name.c_str()); + shader->prog.loadDef.program = reinterpret_cast(memory.Alloc(fileSize)); + shader->prog.loadDef.programSize = static_cast(fileSize / 4u); + file.read(reinterpret_cast(shader->prog.loadDef.program), fileSize); + REQUIRE(file.gcount() == static_cast(fileSize)); + + return shader; + } + + MaterialTechnique* GivenDepthPrepassTechnique(MemoryManager& memory) + { + auto* technique = memory.Alloc(); + technique->name = "example_zprepass"; + technique->flags = 0x204; + technique->passCount = 1; + + auto& pass = technique->passArray[0]; + pass.perPrimArgCount = 1; + pass.perObjArgCount = 1; + pass.stableArgCount = 0; + pass.customSamplerFlags = 0; + + pass.vertexShader = GivenVertexShader("simple.hlsl", memory); + pass.pixelShader = GivenPixelShader("simple.hlsl", memory); + + pass.vertexDecl = memory.Alloc(); + auto& vertexDecl = *pass.vertexDecl; + vertexDecl.name = "pp"; + vertexDecl.streamCount = 1; + vertexDecl.hasOptionalSource = false; + vertexDecl.routing.data[0].source = STREAM_SRC_POSITION; + vertexDecl.routing.data[0].dest = STREAM_DST_POSITION; + + pass.args = memory.Alloc(2); + pass.args[0].type = MTL_ARG_CODE_VERTEX_CONST; + pass.args[0].dest = 4; + pass.args[0].u.codeConst.index = CONST_SRC_CODE_TRANSPOSE_WORLD_MATRIX0; + pass.args[0].u.codeConst.firstRow = 0; + pass.args[0].u.codeConst.rowCount = 4; + + pass.args[1].type = MTL_ARG_CODE_VERTEX_CONST; + pass.args[1].dest = 0; + pass.args[1].u.codeConst.index = CONST_SRC_CODE_TRANSPOSE_VIEW_PROJECTION_MATRIX; + pass.args[1].u.codeConst.firstRow = 0; + pass.args[1].u.codeConst.rowCount = 4; + + return technique; + } + + MaterialTechnique* GivenLitOmniTechnique(MemoryManager& memory) + { + auto* technique = memory.Alloc(); + technique->name = "example_lit_omni"; + technique->flags = 0; + technique->passCount = 1; + + auto& pass = technique->passArray[0]; + pass.perPrimArgCount = 1; + pass.perObjArgCount = 2; + pass.stableArgCount = 6; + pass.customSamplerFlags = 0; + + pass.vertexShader = GivenVertexShader("advanced.hlsl", memory); + pass.pixelShader = GivenPixelShader("advanced.hlsl", memory); + + pass.vertexDecl = memory.Alloc(); + auto& vertexDecl = *pass.vertexDecl; + vertexDecl.name = "ppcc0t0t0nntt2"; + vertexDecl.streamCount = 5; + vertexDecl.hasOptionalSource = false; + vertexDecl.routing.data[0].source = STREAM_SRC_POSITION; + vertexDecl.routing.data[0].dest = STREAM_DST_POSITION; + vertexDecl.routing.data[1].source = STREAM_SRC_COLOR; + vertexDecl.routing.data[1].dest = STREAM_DST_COLOR_0; + vertexDecl.routing.data[2].source = STREAM_SRC_TEXCOORD_0; + vertexDecl.routing.data[2].dest = STREAM_DST_TEXCOORD_0; + vertexDecl.routing.data[3].source = STREAM_SRC_NORMAL; + vertexDecl.routing.data[3].dest = STREAM_DST_NORMAL; + vertexDecl.routing.data[4].source = STREAM_SRC_TANGENT; + vertexDecl.routing.data[4].dest = STREAM_DST_TEXCOORD_2; + + pass.args = memory.Alloc(9); + pass.args[0].type = MTL_ARG_CODE_VERTEX_CONST; + pass.args[0].dest = 4; + pass.args[0].u.codeConst.index = CONST_SRC_CODE_TRANSPOSE_WORLD_MATRIX0; + pass.args[0].u.codeConst.firstRow = 0; + pass.args[0].u.codeConst.rowCount = 4; + + pass.args[1].type = MTL_ARG_CODE_VERTEX_CONST; + pass.args[1].dest = 0; + pass.args[1].u.codeConst.index = CONST_SRC_CODE_TRANSPOSE_VIEW_PROJECTION_MATRIX; + pass.args[1].u.codeConst.firstRow = 0; + pass.args[1].u.codeConst.rowCount = 4; + + pass.args[2].type = MTL_ARG_CODE_PIXEL_SAMPLER; + pass.args[2].dest = 5; + pass.args[2].u.codeSampler = TEXTURE_SRC_CODE_LIGHT_ATTENUATION; + + pass.args[3].type = MTL_ARG_MATERIAL_PIXEL_SAMPLER; + pass.args[3].dest = 4; + pass.args[3].u.nameHash = 0x59D30D0F; + + pass.args[4].type = MTL_ARG_MATERIAL_PIXEL_SAMPLER; + pass.args[4].dest = 0; + pass.args[4].u.nameHash = 0xA0AB1041; + + pass.args[5].type = MTL_ARG_CODE_VERTEX_CONST; + pass.args[5].dest = 21; + pass.args[5].u.codeConst.index = CONST_SRC_CODE_FOG; + pass.args[5].u.codeConst.firstRow = 0; + pass.args[5].u.codeConst.rowCount = 1; + + pass.args[6].type = MTL_ARG_CODE_PIXEL_CONST; + pass.args[6].dest = 17; + pass.args[6].u.codeConst.index = CONST_SRC_CODE_LIGHT_POSITION; + pass.args[6].u.codeConst.firstRow = 0; + pass.args[6].u.codeConst.rowCount = 1; + + pass.args[7].type = MTL_ARG_CODE_PIXEL_CONST; + pass.args[7].dest = 18; + pass.args[7].u.codeConst.index = CONST_SRC_CODE_LIGHT_DIFFUSE; + pass.args[7].u.codeConst.firstRow = 0; + pass.args[7].u.codeConst.rowCount = 1; + + pass.args[8].type = MTL_ARG_LITERAL_PIXEL_CONST; + pass.args[8].dest = 0; + auto* literalConst = memory.Alloc(); + (*literalConst)[0] = 0.0f; + (*literalConst)[1] = 0.0f; + (*literalConst)[2] = 0.0f; + (*literalConst)[3] = 0.0f; + pass.args[8].u.literalConst = literalConst; + + return technique; + } + + MaterialTechniqueSet* GivenTechset(Zone& zone, MemoryManager& memory) + { + auto* techset = memory.Alloc(); + techset->name = "example_techset"; + techset->worldVertFormat = MTL_WORLDVERT_TEX_4_NRM_2; + + techset->techniques[TECHNIQUE_DEPTH_PREPASS] = GivenDepthPrepassTechnique(memory); + techset->techniques[TECHNIQUE_LIT_OMNI] = GivenLitOmniTechnique(memory); + + zone.m_pools.AddAsset(std::make_unique>(ASSET_TYPE_TECHNIQUE_SET, techset->name, techset)); + return techset; + } +} // namespace + +TEST_CASE("TechsetDumperIW4", "[iw4][techset][dumper]") +{ + Zone zone("MockZone", 0, GameId::IW4, GamePlatform::PC); + zone.Register(); + + MemoryManager memory; + MockSearchPath mockObjPath; + MockOutputPath mockOutput; + AssetDumpingContext context(zone, "", mockOutput, mockObjPath, std::nullopt); + + GivenTechset(zone, memory); + + techset::DumperIW4 dumper(true); + + SECTION("Can dump techset") + { + std::string expected(R"TECHSET( +"depth prepass": + example_zprepass; + +"lit omni": + example_lit_omni; +)TECHSET"); + + dumper.Dump(context); + + const auto* file = mockOutput.GetMockedFile("techsets/example_techset.techset"); + REQUIRE(file); + REQUIRE(Trimmed(file->AsString()) == Trimmed(expected)); + } + + SECTION("Can dump simple technique") + { + std::string expected(R"TECHNIQUE( +// TECHNIQUE FLAGS: 0x4 +// TECHNIQUE FLAGS: 0x200 +{ + stateMap "passthrough"; // TODO + + vertexShader 3.0 "simple.hlsl" + { + // Omitted due to matching accessors: worldMatrix = constant.worldMatrix; + // Omitted due to matching accessors: viewProjectionMatrix = constant.viewProjectionMatrix; + } + + pixelShader 3.0 "simple.hlsl" + { + } + + vertex.position = code.position; +} +)TECHNIQUE"); + dumper.Dump(context); + + const auto* file = mockOutput.GetMockedFile("techniques/example_zprepass.tech"); + REQUIRE(file); + REQUIRE(Trimmed(file->AsString()) == Trimmed(expected)); + } + + SECTION("Can dump advanced technique") + { + std::string expected(R"TECHNIQUE( +{ + stateMap "passthrough"; // TODO + + vertexShader 3.0 "advanced.hlsl" + { + // Omitted due to matching accessors: worldMatrix = constant.worldMatrix; + // Omitted due to matching accessors: viewProjectionMatrix = constant.viewProjectionMatrix; + // Omitted due to matching accessors: fogConsts = constant.fogConsts; + } + + pixelShader 3.0 "advanced.hlsl" + { + // Omitted due to matching accessors: attenuationSampler = sampler.attenuationSampler; + normalMapSampler = material.normalMap; + colorMapSampler = material.colorMap; + // Omitted due to matching accessors: lightPosition = constant.lightPosition; + // Omitted due to matching accessors: lightDiffuse = constant.lightDiffuse; + fogColorLinear = float4(0, 0, 0, 0); + } + + vertex.position = code.position; + vertex.color[0] = code.color; + vertex.texcoord[0] = code.texcoord[0]; + vertex.normal = code.normal; + vertex.texcoord[2] = code.tangent; +} +)TECHNIQUE"); + dumper.Dump(context); + + const auto* file = mockOutput.GetMockedFile("techniques/example_lit_omni.tech"); + REQUIRE(file); + REQUIRE(Trimmed(file->AsString()) == Trimmed(expected)); + } +} diff --git a/test/ObjWritingTests/Game/IW4/Techset/ps_advanced.hlsl.cso b/test/ObjWritingTests/Game/IW4/Techset/ps_advanced.hlsl.cso new file mode 100644 index 0000000000000000000000000000000000000000..a05fea8ec4bb724404b2aea3ed70a4168828c458 GIT binary patch literal 860 zcmY*X%WB(D5It9qT7-h@&_$u8aY-QEl(ggnn#84x(2@o_yMPgi>MkJ0JvAG`qYT=9q4BR(XC zLi;JPslc!q0zU=knbAy|g0aAUGd61MhEF+vX$IC=cZfN5tFacbj|w7? z@X}5EDD1kUao|nx%fe&U$fr7IQ4mc&yW@GmAR2ZA`s9b6%X&psRqF~P@P`-42Y)cQ zjJ+!O62-o>Kp0I%Zm^;?j{Cd)w>TPm;eH%@NiT|mxZU&Oq}{!6&v~8YIRe(p1J34lBPVxg7bdNzffN|&>VwxYIh#bkDadHUQ@l{c8@oYEtc`gttaDqrIH+@d9O(0*vl T5si|2(3svIoY%64yextLm_eu< literal 0 HcmV?d00001 diff --git a/test/ObjWritingTests/Game/IW4/Techset/ps_simple.hlsl.cso b/test/ObjWritingTests/Game/IW4/Techset/ps_simple.hlsl.cso new file mode 100644 index 0000000000000000000000000000000000000000..e41645559516b9c1d1d17e558730f0e72b3e2e44 GIT binary patch literal 124 zcmZQz{{R2qe_;ma5Jx8&1_lOYAZ7rHfhaIwWKaN!7Zk@E#~U#C7o_Gn78j?MgyiSv u6f1CJ literal 0 HcmV?d00001 diff --git a/test/ObjWritingTests/Game/IW4/Techset/vs_advanced.hlsl.cso b/test/ObjWritingTests/Game/IW4/Techset/vs_advanced.hlsl.cso new file mode 100644 index 0000000000000000000000000000000000000000..5edabc04bd1a158fd49c7672fd9d25618e2739b5 GIT binary patch literal 968 zcmaJ1ejkQxEPAi5~R2N*>@ud0fAT$V@GXk1kF=+(!=NimP! zvb+S648zuH_2ksKZNUBf{cCqe^Z|A99m(5Xocr~D7|8*{M(`~rsE89{9Wl?xnO@h< zMocTIUEQelIMkYmmDENowQ0o0B|T2u7O|D%#gLv4ngMp(0dfY6LPqV%rMQY*d2V~I zJx_XWJ>QC)7rRodr-!fSsGd`c+o_syNM&=X`gMU5wYI@IgPa2mz?+X|9NO^}B3n%J-!W(iP`) zg5~@t_?i}Z9whLjr`kj h5An(Iy5-yBeA!BPhQ)X3k8e@-Yy@i<>AUGV;4j1VxJUp1 literal 0 HcmV?d00001 diff --git a/test/ObjWritingTests/Game/IW4/Techset/vs_simple.hlsl.cso b/test/ObjWritingTests/Game/IW4/Techset/vs_simple.hlsl.cso new file mode 100644 index 0000000000000000000000000000000000000000..1d11cba27dfb2f778040877a57e2b10d00ff26cd GIT binary patch literal 376 zcmYk2J!-=+9L1k(rBx?GpL}Nw)e3If8dzDnrf@ z_el{Zg1?@APkQ=7eCkf!5HH#E`5wR@3-lt2V+f=>$}7uxlKnT;wSpDVk+|)>O|G{e zrCI5pDmUx5c~#~+v~3$B_A7-?0$WoSi!i;hv+-<%_g}r9TC1zfn8K!+wpIGIoG)}4 zGEP2$fkM&%_2k0F!})qOknAtU2kEYig#P1R;i*x$Yi{^L0YUFrOB@47o(DZk&sRMw e&)0$ZF$cjs_wach@0ng;={f3Id5+0l2mAxGI7{jP literal 0 HcmV?d00001 From 0c4a2771ef89146c789a0aed90927651096ef7b6 Mon Sep 17 00:00:00 2001 From: Jan Laupetin Date: Fri, 20 Mar 2026 23:51:19 +0100 Subject: [PATCH 4/4] chore: add unit tests for iw3 techsets --- .../IW3/Techset/TechniqueCompilerIW3Test.cpp | 338 ++++++++++++++++++ .../IW3/Techset/TechsetCompilerIW3Test.cpp | 149 ++++++++ .../IW3/Techset/VertexDeclCompilerIW3Test.cpp | 61 ++++ .../Game/IW3/Techset/ps_advanced.hlsl.cso | Bin 0 -> 1240 bytes .../Game/IW3/Techset/ps_simple.hlsl.cso | Bin 0 -> 124 bytes .../Game/IW3/Techset/vs_advanced.hlsl.cso | Bin 0 -> 1196 bytes .../Game/IW3/Techset/vs_simple.hlsl.cso | Bin 0 -> 376 bytes .../Game/IW3/Techset/TechsetDumperIW3Test.cpp | 338 ++++++++++++++++++ .../Game/IW3/Techset/ps_advanced.hlsl.cso | Bin 0 -> 1240 bytes .../Game/IW3/Techset/ps_simple.hlsl.cso | Bin 0 -> 124 bytes .../Game/IW3/Techset/vs_advanced.hlsl.cso | Bin 0 -> 1196 bytes .../Game/IW3/Techset/vs_simple.hlsl.cso | Bin 0 -> 376 bytes 12 files changed, 886 insertions(+) create mode 100644 test/ObjCompilingTests/Game/IW3/Techset/TechniqueCompilerIW3Test.cpp create mode 100644 test/ObjCompilingTests/Game/IW3/Techset/TechsetCompilerIW3Test.cpp create mode 100644 test/ObjCompilingTests/Game/IW3/Techset/VertexDeclCompilerIW3Test.cpp create mode 100644 test/ObjCompilingTests/Game/IW3/Techset/ps_advanced.hlsl.cso create mode 100644 test/ObjCompilingTests/Game/IW3/Techset/ps_simple.hlsl.cso create mode 100644 test/ObjCompilingTests/Game/IW3/Techset/vs_advanced.hlsl.cso create mode 100644 test/ObjCompilingTests/Game/IW3/Techset/vs_simple.hlsl.cso create mode 100644 test/ObjWritingTests/Game/IW3/Techset/TechsetDumperIW3Test.cpp create mode 100644 test/ObjWritingTests/Game/IW3/Techset/ps_advanced.hlsl.cso create mode 100644 test/ObjWritingTests/Game/IW3/Techset/ps_simple.hlsl.cso create mode 100644 test/ObjWritingTests/Game/IW3/Techset/vs_advanced.hlsl.cso create mode 100644 test/ObjWritingTests/Game/IW3/Techset/vs_simple.hlsl.cso diff --git a/test/ObjCompilingTests/Game/IW3/Techset/TechniqueCompilerIW3Test.cpp b/test/ObjCompilingTests/Game/IW3/Techset/TechniqueCompilerIW3Test.cpp new file mode 100644 index 00000000..78daba7e --- /dev/null +++ b/test/ObjCompilingTests/Game/IW3/Techset/TechniqueCompilerIW3Test.cpp @@ -0,0 +1,338 @@ +#include "Game/IW3/Techset/TechniqueCompilerIW3.h" + +#include "Game/IW3/IW3.h" +#include "Game/IW3/Techset/PixelShaderLoaderIW3.h" +#include "Game/IW3/Techset/VertexDeclCompilerIW3.h" +#include "Game/IW3/Techset/VertexShaderLoaderIW3.h" +#include "OatTestPaths.h" +#include "SearchPath/MockSearchPath.h" +#include "Shader/ShaderCommon.h" +#include "Utils/MemoryManager.h" +#include "catch2/generators/catch_generators.hpp" + +#include +#include +#include +#include + +using namespace IW3; +using namespace Catch; +using namespace std::literals; +namespace fs = std::filesystem; + +namespace +{ + void GivenVertexShaderFile(const std::string& name, MockSearchPath& searchPath) + { + const auto filePath = oat::paths::GetTestDirectory() / "ObjCompilingTests/Game/IW3/Techset" / std::format("vs_{}.cso", name); + const auto fileSize = static_cast(fs::file_size(filePath)); + + std::ifstream file(filePath, std::ios::binary); + REQUIRE(file.is_open()); + + std::string data(fileSize, '\0'); + file.read(data.data(), fileSize); + REQUIRE(file.gcount() == static_cast(fileSize)); + + searchPath.AddFileData(shader::GetFileNameForVertexShaderAssetName(name), std::move(data)); + } + + void GivenPixelShaderFile(const std::string& name, MockSearchPath& searchPath) + { + const auto filePath = oat::paths::GetTestDirectory() / "ObjCompilingTests/Game/IW3/Techset" / std::format("ps_{}.cso", name); + const auto fileSize = static_cast(fs::file_size(filePath)); + + std::ifstream file(filePath, std::ios::binary); + REQUIRE(file.is_open()); + + std::string data(fileSize, '\0'); + file.read(data.data(), fileSize); + REQUIRE(file.gcount() == static_cast(fileSize)); + + searchPath.AddFileData(shader::GetFileNameForPixelShaderAssetName(name), std::move(data)); + } +} // namespace + +TEST_CASE("TechniqueCompilerIW3", "[iw3][techset][compiler]") +{ + + Zone zone("MockZone", 0, GameId::IW3, GamePlatform::PC); + zone.Register(); + + MemoryManager memory; + AssetCreatorCollection creatorCollection(zone); + IgnoredAssetLookup ignoredAssetLookup; + AssetCreationContext context(zone, &creatorCollection, &ignoredAssetLookup); + MockSearchPath searchPath; + + creatorCollection.AddSubAssetCreator(techset::CreateVertexDeclCompilerIW3(memory)); + creatorCollection.AddSubAssetCreator(techset::CreateVertexShaderLoaderIW3(memory, searchPath)); + creatorCollection.AddSubAssetCreator(techset::CreatePixelShaderLoaderIW3(memory, searchPath)); + + auto loader = techset::CreateTechniqueCompilerIW3(memory, zone, searchPath); + + SECTION("Can compile simple technique") + { + const auto [inputName, inputData] = GENERATE(Catch::Generators::table({ + {"auto-create args", R"TECHNIQUE( +{ + stateMap "passthrough"; + + vertexShader 3.0 "simple.hlsl" + { + } + + pixelShader 3.0 "simple.hlsl" + { + } + + vertex.position = code.position; +} +)TECHNIQUE"}, + {"manual args", R"TECHNIQUE( +{ + stateMap "passthrough"; + + vertexShader 3.0 "simple.hlsl" + { + worldMatrix = constant.worldMatrix; + viewProjectionMatrix = constant.viewProjectionMatrix; + } + + pixelShader 3.0 "simple.hlsl" + { + } + + vertex.position = code.position; +} +)TECHNIQUE"}, + })); + + CAPTURE(inputName); + searchPath.AddFileData("techniques/zprepass.tech", inputData); + + GivenVertexShaderFile("simple.hlsl", searchPath); + GivenPixelShaderFile("simple.hlsl", searchPath); + + auto result = loader->CreateSubAsset("zprepass", context); + REQUIRE(result.HasBeenSuccessful()); + + const auto* assetInfo = reinterpret_cast*>(result.GetAssetInfo()); + const auto* technique = assetInfo->Asset(); + + CHECK(technique->name == "zprepass"s); + + CHECK(technique->flags == 0x4); + + REQUIRE(technique->passCount == 1); + auto& pass = technique->passArray[0]; + CHECK(pass.customSamplerFlags == 0); + + REQUIRE(pass.vertexShader); + CHECK(pass.vertexShader->name == "simple.hlsl"s); + REQUIRE(pass.pixelShader); + CHECK(pass.pixelShader->name == "simple.hlsl"s); + + REQUIRE(pass.vertexDecl); + auto& vertexDecl = *pass.vertexDecl; + CHECK(vertexDecl.hasOptionalSource == false); + REQUIRE(vertexDecl.streamCount == 1); + CHECK(vertexDecl.routing.data[0].source == STREAM_SRC_POSITION); + CHECK(vertexDecl.routing.data[0].dest == STREAM_DST_POSITION); + + REQUIRE(pass.perPrimArgCount == 1); + REQUIRE(pass.perObjArgCount == 1); + REQUIRE(pass.stableArgCount == 0); + CHECK(pass.args[0].type == MTL_ARG_CODE_VERTEX_CONST); + CHECK(pass.args[0].dest == 4); + CHECK(pass.args[0].u.codeConst.index == CONST_SRC_CODE_TRANSPOSE_WORLD_MATRIX); + CHECK(pass.args[0].u.codeConst.firstRow == 0); + CHECK(pass.args[0].u.codeConst.rowCount == 4); + + CHECK(pass.args[1].type == MTL_ARG_CODE_VERTEX_CONST); + CHECK(pass.args[1].dest == 0); + CHECK(pass.args[1].u.codeConst.index == CONST_SRC_CODE_TRANSPOSE_VIEW_PROJECTION_MATRIX); + CHECK(pass.args[1].u.codeConst.firstRow == 0); + CHECK(pass.args[1].u.codeConst.rowCount == 4); + } + + SECTION("Can compile advanced technique") + { + const auto [inputName, inputData] = GENERATE(Catch::Generators::table({ + {"auto-create args", R"TECHNIQUE( +{ + stateMap "passthrough"; + + vertexShader 3.0 "advanced.hlsl" + { + } + + pixelShader 3.0 "advanced.hlsl" + { + normalMapSampler = material.normalMap; + colorMapSampler = material.colorMap; + } + + vertex.position = code.position; + vertex.color[0] = code.color; + vertex.texcoord[0] = code.texcoord[0]; + vertex.normal = code.normal; + vertex.texcoord[2] = code.tangent; +} +)TECHNIQUE"}, + {"manual args", R"TECHNIQUE( +{ + stateMap "passthrough"; + + vertexShader 3.0 "advanced.hlsl" + { + worldMatrix = constant.worldMatrix; + baseLightingCoords = constant.baseLightingCoords; + viewProjectionMatrix = constant.viewProjectionMatrix; + fogConsts = constant.fogConsts; + } + + pixelShader 3.0 "advanced.hlsl" + { + attenuationSampler = sampler.attenuationSampler; + normalMapSampler = material.normalMap; + colorMapSampler = material.colorMap; + modelLightingSampler = sampler.modelLightingSampler; + fogColor = constant.fogColor; + lightingLookupScale = constant.lightingLookupScale; + lightPosition = constant.lightPosition; + lightDiffuse = constant.lightDiffuse; + lightSpotDir = constant.lightSpotDir; + lightSpotFactors = constant.lightSpotFactors; + } + + vertex.position = code.position; + vertex.color[0] = code.color; + vertex.texcoord[0] = code.texcoord[0]; + vertex.normal = code.normal; + vertex.texcoord[2] = code.tangent; +} +)TECHNIQUE"}, + })); + + CAPTURE(inputName); + searchPath.AddFileData("techniques/example_lit_spot.tech", inputData); + + GivenVertexShaderFile("advanced.hlsl", searchPath); + GivenPixelShaderFile("advanced.hlsl", searchPath); + + auto result = loader->CreateSubAsset("example_lit_spot", context); + REQUIRE(result.HasBeenSuccessful()); + + const auto* assetInfo = reinterpret_cast*>(result.GetAssetInfo()); + const auto* technique = assetInfo->Asset(); + + CHECK(technique->name == "example_lit_spot"s); + + CHECK(technique->flags == 0x10); + + REQUIRE(technique->passCount == 1); + auto& pass = technique->passArray[0]; + CHECK(pass.customSamplerFlags == 0); + + REQUIRE(pass.vertexShader); + CHECK(pass.vertexShader->name == "advanced.hlsl"s); + REQUIRE(pass.pixelShader); + CHECK(pass.pixelShader->name == "advanced.hlsl"s); + + REQUIRE(pass.vertexDecl); + auto& vertexDecl = *pass.vertexDecl; + CHECK(vertexDecl.hasOptionalSource == false); + REQUIRE(vertexDecl.streamCount == 5); + CHECK(vertexDecl.routing.data[0].source == STREAM_SRC_POSITION); + CHECK(vertexDecl.routing.data[0].dest == STREAM_DST_POSITION); + CHECK(vertexDecl.routing.data[1].source == STREAM_SRC_COLOR); + CHECK(vertexDecl.routing.data[1].dest == STREAM_DST_COLOR_0); + CHECK(vertexDecl.routing.data[2].source == STREAM_SRC_TEXCOORD_0); + CHECK(vertexDecl.routing.data[2].dest == STREAM_DST_TEXCOORD_0); + CHECK(vertexDecl.routing.data[3].source == STREAM_SRC_NORMAL); + CHECK(vertexDecl.routing.data[3].dest == STREAM_DST_NORMAL); + CHECK(vertexDecl.routing.data[4].source == STREAM_SRC_TANGENT); + CHECK(vertexDecl.routing.data[4].dest == STREAM_DST_TEXCOORD_2); + + REQUIRE(pass.perPrimArgCount == 2); + REQUIRE(pass.perObjArgCount == 2); + REQUIRE(pass.stableArgCount == 10); + + CHECK(pass.args[0].type == MTL_ARG_CODE_VERTEX_CONST); + CHECK(pass.args[0].dest == 4); + CHECK(pass.args[0].u.codeConst.index == CONST_SRC_CODE_TRANSPOSE_WORLD_MATRIX); + CHECK(pass.args[0].u.codeConst.firstRow == 0); + CHECK(pass.args[0].u.codeConst.rowCount == 4); + + CHECK(pass.args[1].type == MTL_ARG_CODE_VERTEX_CONST); + CHECK(pass.args[1].dest == 8); + CHECK(pass.args[1].u.codeConst.index == CONST_SRC_CODE_BASE_LIGHTING_COORDS); + CHECK(pass.args[1].u.codeConst.firstRow == 0); + CHECK(pass.args[1].u.codeConst.rowCount == 1); + + CHECK(pass.args[2].type == MTL_ARG_CODE_VERTEX_CONST); + CHECK(pass.args[2].dest == 0); + CHECK(pass.args[2].u.codeConst.index == CONST_SRC_CODE_TRANSPOSE_VIEW_PROJECTION_MATRIX); + CHECK(pass.args[2].u.codeConst.firstRow == 0); + CHECK(pass.args[2].u.codeConst.rowCount == 4); + + CHECK(pass.args[3].type == MTL_ARG_CODE_PIXEL_SAMPLER); + CHECK(pass.args[3].dest == 6); + CHECK(pass.args[3].u.codeSampler == TEXTURE_SRC_CODE_LIGHT_ATTENUATION); + + CHECK(pass.args[4].type == MTL_ARG_MATERIAL_PIXEL_SAMPLER); + CHECK(pass.args[4].dest == 5); + CHECK(pass.args[4].u.nameHash == 0x59D30D0F); + + CHECK(pass.args[5].type == MTL_ARG_MATERIAL_PIXEL_SAMPLER); + CHECK(pass.args[5].dest == 0); + CHECK(pass.args[5].u.nameHash == 0xA0AB1041); + + CHECK(pass.args[6].type == MTL_ARG_CODE_VERTEX_CONST); + CHECK(pass.args[6].dest == 21); + CHECK(pass.args[6].u.codeConst.index == CONST_SRC_CODE_FOG); + CHECK(pass.args[6].u.codeConst.firstRow == 0); + CHECK(pass.args[6].u.codeConst.rowCount == 1); + + CHECK(pass.args[7].type == MTL_ARG_CODE_PIXEL_SAMPLER); + CHECK(pass.args[7].dest == 4); + CHECK(pass.args[7].u.codeSampler == TEXTURE_SRC_CODE_MODEL_LIGHTING); + + CHECK(pass.args[8].type == MTL_ARG_CODE_PIXEL_CONST); + CHECK(pass.args[8].dest == 0); + CHECK(pass.args[8].u.codeConst.index == CONST_SRC_CODE_FOG_COLOR); + CHECK(pass.args[8].u.codeConst.firstRow == 0); + CHECK(pass.args[8].u.codeConst.rowCount == 1); + + CHECK(pass.args[9].type == MTL_ARG_CODE_PIXEL_CONST); + CHECK(pass.args[9].dest == 5); + CHECK(pass.args[9].u.codeConst.index == CONST_SRC_CODE_LIGHTING_LOOKUP_SCALE); + CHECK(pass.args[9].u.codeConst.firstRow == 0); + CHECK(pass.args[9].u.codeConst.rowCount == 1); + + CHECK(pass.args[10].type == MTL_ARG_CODE_PIXEL_CONST); + CHECK(pass.args[10].dest == 6); + CHECK(pass.args[10].u.codeConst.index == CONST_SRC_CODE_LIGHT_POSITION); + CHECK(pass.args[10].u.codeConst.firstRow == 0); + CHECK(pass.args[10].u.codeConst.rowCount == 1); + + CHECK(pass.args[11].type == MTL_ARG_CODE_PIXEL_CONST); + CHECK(pass.args[11].dest == 7); + CHECK(pass.args[11].u.codeConst.index == CONST_SRC_CODE_LIGHT_DIFFUSE); + CHECK(pass.args[11].u.codeConst.firstRow == 0); + CHECK(pass.args[11].u.codeConst.rowCount == 1); + + CHECK(pass.args[12].type == MTL_ARG_CODE_PIXEL_CONST); + CHECK(pass.args[12].dest == 8); + CHECK(pass.args[12].u.codeConst.index == CONST_SRC_CODE_LIGHT_SPOTDIR); + CHECK(pass.args[12].u.codeConst.firstRow == 0); + CHECK(pass.args[12].u.codeConst.rowCount == 1); + + CHECK(pass.args[13].type == MTL_ARG_CODE_PIXEL_CONST); + CHECK(pass.args[13].dest == 9); + CHECK(pass.args[13].u.codeConst.index == CONST_SRC_CODE_LIGHT_SPOTFACTORS); + CHECK(pass.args[13].u.codeConst.firstRow == 0); + CHECK(pass.args[13].u.codeConst.rowCount == 1); + } +} diff --git a/test/ObjCompilingTests/Game/IW3/Techset/TechsetCompilerIW3Test.cpp b/test/ObjCompilingTests/Game/IW3/Techset/TechsetCompilerIW3Test.cpp new file mode 100644 index 00000000..642da0e5 --- /dev/null +++ b/test/ObjCompilingTests/Game/IW3/Techset/TechsetCompilerIW3Test.cpp @@ -0,0 +1,149 @@ +#include "Game/IW3/Techset/TechsetCompilerIW3.h" + +#include "Game/IW3/IW3.h" +#include "SearchPath/MockSearchPath.h" +#include "Techset/TechsetCommon.h" +#include "Utils/TestMemoryManager.h" + +#include +#include +#include +#include + +using namespace IW3; +using namespace std::string_literals; + +namespace +{ + MaterialTechnique* GivenTechnique(const std::string& name, AssetCreationContext& context, MemoryManager& memory) + { + auto* technique = memory.Alloc(); + technique->name = memory.Dup(name.c_str()); + + context.AddSubAsset(name, technique); + + return technique; + } +} // namespace + +TEST_CASE("TechsetCompilerIW3", "[techset][iw3][compiler]") +{ + Zone zone("test", 0, GameId::IW3, GamePlatform::PC); + AssetCreatorCollection creators(zone); + IgnoredAssetLookup ignoredAssets; + AssetCreationContext context(zone, &creators, &ignoredAssets); + MockSearchPath searchPath; + TestMemoryManager memory; + const auto sut = techset::CreateTechsetCompilerIW3(memory, searchPath); + + SECTION("Sets correct worldVertFormat") + { + const auto [techsetName, expectedWorldVertFormat] = GENERATE(Catch::Generators::table({ + {"default", MTL_WORLDVERT_TEX_1_NRM_1}, + {"effect_zeqqz943", MTL_WORLDVERT_TEX_1_NRM_1}, + {"lit_r0c0_t1c1n1", MTL_WORLDVERT_TEX_2_NRM_1}, + {"lit_r0c0n0x0_b1c1n1s1v1", MTL_WORLDVERT_TEX_2_NRM_2}, + {"lit_r0c0n0x0_b1c1n1s1v1_b2c2n2x2", MTL_WORLDVERT_TEX_3_NRM_3}, + {"lit_sm_b0c0_b1c1_b2c2", MTL_WORLDVERT_TEX_3_NRM_1}, + {"lit_sm_b0c0_b1c1n1x1_b2c2n2v2", MTL_WORLDVERT_TEX_3_NRM_2}, + {"lit_sm_r0c0_b1c1_b2c2_b3c3", MTL_WORLDVERT_TEX_4_NRM_1}, + {"lit_sm_r0c0n0_b1c1_b2c2n2v2_m3c3", MTL_WORLDVERT_TEX_4_NRM_2}, + {"lit_sm_r0c0n0_b1c1n1s1", MTL_WORLDVERT_TEX_2_NRM_2}, + {"lit_sm_r0c0n0s0_b1c1n1s1_b2c2n2", MTL_WORLDVERT_TEX_3_NRM_3}, + {"lit_sm_r0c0n0x0_b1c1_b2c2n2s2", MTL_WORLDVERT_TEX_3_NRM_2}, + {"lit_sm_r0c0n0x0_b1c1n1_b2c2n2s2v2", MTL_WORLDVERT_TEX_3_NRM_3}, + {"lit_sm_r0c0n0x0_b1c1s1v1_m2c2_m3c3", MTL_WORLDVERT_TEX_4_NRM_1}, + {"lit_sm_r0c0n0x0_b1c1v1_b2c2n2s2_m3c3", MTL_WORLDVERT_TEX_4_NRM_2}, + {"lit_sm_r0c0s0_b1c1n1s1_m2c2", MTL_WORLDVERT_TEX_3_NRM_1}, + {"lit_sm_r0c0x0_b1c1", MTL_WORLDVERT_TEX_2_NRM_1}, + {"lit_sm_r0c0x0_b1c1n1s1_b2c2n2s2", MTL_WORLDVERT_TEX_3_NRM_2}, + {"lit_sm_r0c0x0_m1c1_m2c2_m3c3", MTL_WORLDVERT_TEX_4_NRM_1}, + {"lit_sm_t0c0n0_b1c1n1v1", MTL_WORLDVERT_TEX_2_NRM_2}, + {"lit_sm_t0c0n0s0_b1c1n1_b2c2n2s2v2", MTL_WORLDVERT_TEX_3_NRM_3}, + {"mc_lit_sm_r0c0d0_2213939z", MTL_WORLDVERT_TEX_1_NRM_1}, + {"wpc_lit_sm_b0c0s0_3f3q946z", MTL_WORLDVERT_TEX_1_NRM_1}, + {"wpc_lit_sm_r0c0n0s0o0_qj92q1f8", MTL_WORLDVERT_TEX_1_NRM_1}, + {"wpc_sw4_3d_burning_embers_nuketown_74j6971w", MTL_WORLDVERT_TEX_1_NRM_1}, + {"wpc_unlitdecalblend_add_j26wq580", MTL_WORLDVERT_TEX_1_NRM_1}, + })); + + CAPTURE(techsetName); + searchPath.AddFileData(techset::GetFileNameForTechsetName(techsetName), ""); + + const auto result = sut->CreateAsset(techsetName, context); + REQUIRE(result.HasBeenSuccessful()); + + const auto* techset = static_cast(result.GetAssetInfo()->m_ptr); + CHECK(techset->worldVertFormat == expectedWorldVertFormat); + } + + SECTION("Can parse simple techset") + { + searchPath.AddFileData(techset::GetFileNameForTechsetName("simple"), R"TECHSET( +"depth prepass": + example_zprepass; + +"lit spot": + example_lit_spot; +)TECHSET"); + + auto* exampleZPrepass = GivenTechnique("example_zprepass", context, memory); + auto* exampleLit = GivenTechnique("example_lit_spot", context, memory); + + const auto result = sut->CreateAsset("simple", context); + REQUIRE(result.HasBeenSuccessful()); + + const auto* techset = static_cast(result.GetAssetInfo()->m_ptr); + CHECK(techset->name == "simple"s); + CHECK(techset->worldVertFormat == MTL_WORLDVERT_TEX_1_NRM_1); + + size_t techniqueCount = 0; + for (auto* technique : techset->techniques) + { + if (technique) + techniqueCount++; + } + + CHECK(techniqueCount == 2); + CHECK(techset->techniques[TECHNIQUE_DEPTH_PREPASS] == exampleZPrepass); + CHECK(techset->techniques[TECHNIQUE_LIT_SPOT] == exampleLit); + } + + SECTION("Can parse techset with same technique used multiple times") + { + searchPath.AddFileData(techset::GetFileNameForTechsetName("simple"), R"TECHSET( +"depth prepass": +"build shadowmap depth": + example_zprepass; + +"lit": +"lit sun": +"lit instanced omni shadow": + example_lit_spot; +)TECHSET"); + + auto* exampleZPrepass = GivenTechnique("example_zprepass", context, memory); + auto* exampleLitOmni = GivenTechnique("example_lit_spot", context, memory); + + const auto result = sut->CreateAsset("simple", context); + REQUIRE(result.HasBeenSuccessful()); + + const auto* techset = static_cast(result.GetAssetInfo()->m_ptr); + CHECK(techset->name == "simple"s); + CHECK(techset->worldVertFormat == MTL_WORLDVERT_TEX_1_NRM_1); + + size_t techniqueCount = 0; + for (auto* technique : techset->techniques) + { + if (technique) + techniqueCount++; + } + + CHECK(techniqueCount == 5); + CHECK(techset->techniques[TECHNIQUE_DEPTH_PREPASS] == exampleZPrepass); + CHECK(techset->techniques[TECHNIQUE_BUILD_SHADOWMAP_DEPTH] == exampleZPrepass); + CHECK(techset->techniques[TECHNIQUE_LIT] == exampleLitOmni); + CHECK(techset->techniques[TECHNIQUE_LIT_SUN] == exampleLitOmni); + CHECK(techset->techniques[TECHNIQUE_LIT_INSTANCED_OMNI_SHADOW] == exampleLitOmni); + } +} diff --git a/test/ObjCompilingTests/Game/IW3/Techset/VertexDeclCompilerIW3Test.cpp b/test/ObjCompilingTests/Game/IW3/Techset/VertexDeclCompilerIW3Test.cpp new file mode 100644 index 00000000..6a7d46ab --- /dev/null +++ b/test/ObjCompilingTests/Game/IW3/Techset/VertexDeclCompilerIW3Test.cpp @@ -0,0 +1,61 @@ +#include "Game/IW3/Techset/VertexDeclCompilerIW3.h" + +#include "Game/IW3/IW3.h" +#include "Utils/MemoryManager.h" + +#include + +using namespace IW3; +using namespace Catch; +using namespace std::literals; + +TEST_CASE("VertexDeclCompilerIW3", "[iw3][techset][compiler]") +{ + Zone zone("MockZone", 0, GameId::IW3, GamePlatform::PC); + zone.Register(); + + MemoryManager memory; + AssetCreatorCollection creatorCollection(zone); + IgnoredAssetLookup ignoredAssetLookup; + AssetCreationContext context(zone, &creatorCollection, &ignoredAssetLookup); + + auto loader = techset::CreateVertexDeclCompilerIW3(memory); + + SECTION("Can create simple vertex decl") + { + auto result = loader->CreateSubAsset("pp", context); + REQUIRE(result.HasBeenSuccessful()); + + const auto* assetInfo = reinterpret_cast*>(result.GetAssetInfo()); + const auto* decl = assetInfo->Asset(); + + CHECK(decl->hasOptionalSource == false); + + REQUIRE(decl->streamCount == 1); + CHECK(decl->routing.data[0].source == STREAM_SRC_POSITION); + CHECK(decl->routing.data[0].dest == STREAM_DST_POSITION); + } + + SECTION("Can create advanced vertex decl") + { + auto result = loader->CreateSubAsset("pt6cc1tt7t1t1n1n", context); + REQUIRE(result.HasBeenSuccessful()); + + const auto* assetInfo = reinterpret_cast*>(result.GetAssetInfo()); + const auto* decl = assetInfo->Asset(); + + CHECK(decl->hasOptionalSource == true); + + REQUIRE(decl->streamCount == 5); + CHECK(decl->routing.data[0].source == STREAM_SRC_POSITION); + CHECK(decl->routing.data[0].dest == STREAM_DST_TEXCOORD_6); + CHECK(decl->routing.data[1].source == STREAM_SRC_COLOR); + CHECK(decl->routing.data[1].dest == STREAM_DST_COLOR_1); + CHECK(decl->routing.data[2].source == STREAM_SRC_TANGENT); + CHECK(decl->routing.data[2].dest == STREAM_DST_TEXCOORD_7); + CHECK(decl->routing.data[3].source == STREAM_SRC_TEXCOORD_1); + CHECK(decl->routing.data[3].dest == STREAM_DST_TEXCOORD_1); + CHECK(decl->routing.data[4].source == STREAM_SRC_NORMAL_TRANSFORM_1); + CHECK(decl->routing.data[4].dest == STREAM_DST_NORMAL); + } +} diff --git a/test/ObjCompilingTests/Game/IW3/Techset/ps_advanced.hlsl.cso b/test/ObjCompilingTests/Game/IW3/Techset/ps_advanced.hlsl.cso new file mode 100644 index 0000000000000000000000000000000000000000..fe2d3be16056af8875700eec5fe36c47bfaa9544 GIT binary patch literal 1240 zcmY*YJ#W)c6g|%$i3?OPc0iDdN@b!{u+tC3R8&wJiJ?+uwQd{X#8K={EUy(?2aME# z(QK&s1F-eSSYl(Q3+CMS?6i56>wC{V_v<_dZdLuOK4Eibed8g3E5(1r?(ii(6gt0% zl>(zh7x*hUPe8}zTfv!P3&flnTM{zQOM|sdZiB5G%+_nLZG#0fyj_FM&0qtA`7_ul zF&Cb}zR{NR!(f)z%p7J}G&~8jelqNagHarfG4XJZdGcq^eiA3+x8bNE941Gb0%0DNM4 zY?z!C>jyHU^jS7F9M0Ip6-w@A`#N}{9sDMzEEN9mfkXX>mATC2V4gO#hl||B1>b@# z6YFW09RBL`lF1RQ)OXnvc*JG*xP{NMn&fRn`V z(D}}3#NngLa08lHo`l0ko7|g%`(!vE;K~;f@mX;AWCGkQxVQZ{d^S8hnhwW@dr>qQ z#5nh$=oN6-=Y7PbaXgK2It*v~ljt~nHyuXf=l$tq_yOmNdU4J5vuH9JxW&`>;K9Lt zyf_KRJ8>LNdr>rsJH0TTc3vIz2jRr+A=68sR6{Ebd~a*6^(LUdT`ab{!q>?&enixgzQc-G6}+zlhh6US zuUvy|u5#FuT!$sM;;_=C0+)2Tz-3(_rU$oe={ojP9~%k3JKq*-nk9AoPJv9{V$G~q z3uYtKHx2fX&8=RspG-G^&mMkpZ|foYCViXqv0R?_)a2hVz8*2O#Xj+C)(hoBEkQ5w zUHjcj8u*wtaGcht@ld5cV;`A|#Y^;gEPfgN@;ESY*~PrvOYx`1Du6c2@eJ^5D@ zo_S3^u6RE#-C0)n9WNPQiEhsS$5*H<*{2pP_l14&+@Rzh$r-YzME2sDQ6oC5mHQ;P w!aP|6z&p0awkONCahA5RN^MP+YcQ5?uW)-Qxw1d+ns_FH<&IX_=V}G~0So2iy#N3J literal 0 HcmV?d00001 diff --git a/test/ObjCompilingTests/Game/IW3/Techset/vs_simple.hlsl.cso b/test/ObjCompilingTests/Game/IW3/Techset/vs_simple.hlsl.cso new file mode 100644 index 0000000000000000000000000000000000000000..1d11cba27dfb2f778040877a57e2b10d00ff26cd GIT binary patch literal 376 zcmYk2J!-=+9L1k(rBx?GpL}Nw)e3If8dzDnrf@ z_el{Zg1?@APkQ=7eCkf!5HH#E`5wR@3-lt2V+f=>$}7uxlKnT;wSpDVk+|)>O|G{e zrCI5pDmUx5c~#~+v~3$B_A7-?0$WoSi!i;hv+-<%_g}r9TC1zfn8K!+wpIGIoG)}4 zGEP2$fkM&%_2k0F!})qOknAtU2kEYig#P1R;i*x$Yi{^L0YUFrOB@47o(DZk&sRMw e&)0$ZF$cjs_wach@0ng;={f3Id5+0l2mAxGI7{jP literal 0 HcmV?d00001 diff --git a/test/ObjWritingTests/Game/IW3/Techset/TechsetDumperIW3Test.cpp b/test/ObjWritingTests/Game/IW3/Techset/TechsetDumperIW3Test.cpp new file mode 100644 index 00000000..10df5b18 --- /dev/null +++ b/test/ObjWritingTests/Game/IW3/Techset/TechsetDumperIW3Test.cpp @@ -0,0 +1,338 @@ +#include "Game/IW3/Techset/TechsetDumperIW3.h" + +#include "Asset/AssetRegistration.h" +#include "Game/IW3/IW3.h" +#include "OatTestPaths.h" +#include "SearchPath/MockOutputPath.h" +#include "SearchPath/MockSearchPath.h" +#include "Utils/MemoryManager.h" + +#include +#include +#include +#include +#include + +using namespace IW3; +using namespace Catch; +using namespace std::literals; +namespace fs = std::filesystem; + +namespace +{ + std::string Trimmed(const std::string& input) + { + auto start = input.find_first_not_of(" \r\n"); + if (start == std::string::npos) + start = 0; + + auto end = input.find_last_not_of(" \r\n"); + if (end == std::string::npos) + end = input.length(); + else + end = end + 1; + + return input.substr(start, end - start); + } + + MaterialVertexShader* GivenVertexShader(const std::string& name, MemoryManager& memory) + { + const auto filePath = oat::paths::GetTestDirectory() / "ObjWritingTests/Game/IW3/Techset" / std::format("vs_{}.cso", name); + const auto fileSize = static_cast(fs::file_size(filePath)); + + std::ifstream file(filePath, std::ios::binary); + REQUIRE(file.is_open()); + + auto* shader = memory.Alloc(); + shader->name = memory.Dup(name.c_str()); + shader->prog.loadDef.program = reinterpret_cast(memory.Alloc(fileSize)); + shader->prog.loadDef.programSize = static_cast(fileSize / 4u); + file.read(reinterpret_cast(shader->prog.loadDef.program), fileSize); + REQUIRE(file.gcount() == static_cast(fileSize)); + + return shader; + } + + MaterialPixelShader* GivenPixelShader(const std::string& name, MemoryManager& memory) + { + const auto filePath = oat::paths::GetTestDirectory() / "ObjWritingTests/Game/IW3/Techset" / std::format("ps_{}.cso", name); + const auto fileSize = static_cast(fs::file_size(filePath)); + + std::ifstream file(filePath, std::ios::binary); + REQUIRE(file.is_open()); + + auto* shader = memory.Alloc(); + shader->name = memory.Dup(name.c_str()); + shader->prog.loadDef.program = reinterpret_cast(memory.Alloc(fileSize)); + shader->prog.loadDef.programSize = static_cast(fileSize / 4u); + file.read(reinterpret_cast(shader->prog.loadDef.program), fileSize); + REQUIRE(file.gcount() == static_cast(fileSize)); + + return shader; + } + + MaterialTechnique* GivenDepthPrepassTechnique(MemoryManager& memory) + { + auto* technique = memory.Alloc(); + technique->name = "example_zprepass"; + technique->flags = 0x4; + technique->passCount = 1; + + auto& pass = technique->passArray[0]; + pass.perPrimArgCount = 1; + pass.perObjArgCount = 1; + pass.stableArgCount = 0; + pass.customSamplerFlags = 0; + + pass.vertexShader = GivenVertexShader("simple.hlsl", memory); + pass.pixelShader = GivenPixelShader("simple.hlsl", memory); + + pass.vertexDecl = memory.Alloc(); + auto& vertexDecl = *pass.vertexDecl; + vertexDecl.streamCount = 1; + vertexDecl.hasOptionalSource = false; + vertexDecl.routing.data[0].source = STREAM_SRC_POSITION; + vertexDecl.routing.data[0].dest = STREAM_DST_POSITION; + + pass.args = memory.Alloc(2); + pass.args[0].type = MTL_ARG_CODE_VERTEX_CONST; + pass.args[0].dest = 4; + pass.args[0].u.codeConst.index = CONST_SRC_CODE_TRANSPOSE_WORLD_MATRIX; + pass.args[0].u.codeConst.firstRow = 0; + pass.args[0].u.codeConst.rowCount = 4; + + pass.args[1].type = MTL_ARG_CODE_VERTEX_CONST; + pass.args[1].dest = 0; + pass.args[1].u.codeConst.index = CONST_SRC_CODE_TRANSPOSE_VIEW_PROJECTION_MATRIX; + pass.args[1].u.codeConst.firstRow = 0; + pass.args[1].u.codeConst.rowCount = 4; + + return technique; + } + + MaterialTechnique* GivenLitSpotTechnique(MemoryManager& memory) + { + auto* technique = memory.Alloc(); + technique->name = "example_lit_spot"; + technique->flags = 0x10; + technique->passCount = 1; + + auto& pass = technique->passArray[0]; + pass.perPrimArgCount = 2; + pass.perObjArgCount = 2; + pass.stableArgCount = 10; + pass.customSamplerFlags = 0; + + pass.vertexShader = GivenVertexShader("advanced.hlsl", memory); + pass.pixelShader = GivenPixelShader("advanced.hlsl", memory); + + pass.vertexDecl = memory.Alloc(); + auto& vertexDecl = *pass.vertexDecl; + vertexDecl.streamCount = 5; + vertexDecl.hasOptionalSource = false; + vertexDecl.routing.data[0].source = STREAM_SRC_POSITION; + vertexDecl.routing.data[0].dest = STREAM_DST_POSITION; + vertexDecl.routing.data[1].source = STREAM_SRC_COLOR; + vertexDecl.routing.data[1].dest = STREAM_DST_COLOR_0; + vertexDecl.routing.data[2].source = STREAM_SRC_TEXCOORD_0; + vertexDecl.routing.data[2].dest = STREAM_DST_TEXCOORD_0; + vertexDecl.routing.data[3].source = STREAM_SRC_NORMAL; + vertexDecl.routing.data[3].dest = STREAM_DST_NORMAL; + vertexDecl.routing.data[4].source = STREAM_SRC_TANGENT; + vertexDecl.routing.data[4].dest = STREAM_DST_TEXCOORD_2; + + pass.args = memory.Alloc(14); + pass.args[0].type = MTL_ARG_CODE_VERTEX_CONST; + pass.args[0].dest = 4; + pass.args[0].u.codeConst.index = CONST_SRC_CODE_TRANSPOSE_WORLD_MATRIX; + pass.args[0].u.codeConst.firstRow = 0; + pass.args[0].u.codeConst.rowCount = 4; + + pass.args[1].type = MTL_ARG_CODE_VERTEX_CONST; + pass.args[1].dest = 8; + pass.args[1].u.codeConst.index = CONST_SRC_CODE_BASE_LIGHTING_COORDS; + pass.args[1].u.codeConst.firstRow = 0; + pass.args[1].u.codeConst.rowCount = 1; + + pass.args[2].type = MTL_ARG_CODE_VERTEX_CONST; + pass.args[2].dest = 0; + pass.args[2].u.codeConst.index = CONST_SRC_CODE_TRANSPOSE_VIEW_PROJECTION_MATRIX; + pass.args[2].u.codeConst.firstRow = 0; + pass.args[2].u.codeConst.rowCount = 4; + + pass.args[3].type = MTL_ARG_CODE_PIXEL_SAMPLER; + pass.args[3].dest = 6; + pass.args[3].u.codeSampler = TEXTURE_SRC_CODE_LIGHT_ATTENUATION; + + pass.args[4].type = MTL_ARG_MATERIAL_PIXEL_SAMPLER; + pass.args[4].dest = 5; + pass.args[4].u.nameHash = 0x59D30D0F; + + pass.args[5].type = MTL_ARG_MATERIAL_PIXEL_SAMPLER; + pass.args[5].dest = 0; + pass.args[5].u.nameHash = 0xA0AB1041; + + pass.args[6].type = MTL_ARG_CODE_VERTEX_CONST; + pass.args[6].dest = 21; + pass.args[6].u.codeConst.index = CONST_SRC_CODE_FOG; + pass.args[6].u.codeConst.firstRow = 0; + pass.args[6].u.codeConst.rowCount = 1; + + pass.args[7].type = MTL_ARG_CODE_PIXEL_SAMPLER; + pass.args[7].dest = 4; + pass.args[7].u.codeSampler = TEXTURE_SRC_CODE_MODEL_LIGHTING; + + pass.args[8].type = MTL_ARG_CODE_PIXEL_CONST; + pass.args[8].dest = 0; + pass.args[8].u.codeConst.index = CONST_SRC_CODE_FOG_COLOR; + pass.args[8].u.codeConst.firstRow = 0; + pass.args[8].u.codeConst.rowCount = 1; + + pass.args[9].type = MTL_ARG_CODE_PIXEL_CONST; + pass.args[9].dest = 5; + pass.args[9].u.codeConst.index = CONST_SRC_CODE_LIGHTING_LOOKUP_SCALE; + pass.args[9].u.codeConst.firstRow = 0; + pass.args[9].u.codeConst.rowCount = 1; + + pass.args[10].type = MTL_ARG_CODE_PIXEL_CONST; + pass.args[10].dest = 6; + pass.args[10].u.codeConst.index = CONST_SRC_CODE_LIGHT_POSITION; + pass.args[10].u.codeConst.firstRow = 0; + pass.args[10].u.codeConst.rowCount = 1; + + pass.args[11].type = MTL_ARG_CODE_PIXEL_CONST; + pass.args[11].dest = 7; + pass.args[11].u.codeConst.index = CONST_SRC_CODE_LIGHT_DIFFUSE; + pass.args[11].u.codeConst.firstRow = 0; + pass.args[11].u.codeConst.rowCount = 1; + + pass.args[12].type = MTL_ARG_CODE_PIXEL_CONST; + pass.args[12].dest = 8; + pass.args[12].u.codeConst.index = CONST_SRC_CODE_LIGHT_SPOTDIR; + pass.args[12].u.codeConst.firstRow = 0; + pass.args[12].u.codeConst.rowCount = 1; + + pass.args[13].type = MTL_ARG_CODE_PIXEL_CONST; + pass.args[13].dest = 9; + pass.args[13].u.codeConst.index = CONST_SRC_CODE_LIGHT_SPOTFACTORS; + pass.args[13].u.codeConst.firstRow = 0; + pass.args[13].u.codeConst.rowCount = 1; + + return technique; + } + + MaterialTechniqueSet* GivenTechset(Zone& zone, MemoryManager& memory) + { + auto* techset = memory.Alloc(); + techset->name = "example_techset"; + techset->worldVertFormat = MTL_WORLDVERT_TEX_4_NRM_2; + + techset->techniques[TECHNIQUE_DEPTH_PREPASS] = GivenDepthPrepassTechnique(memory); + techset->techniques[TECHNIQUE_LIT_SPOT] = GivenLitSpotTechnique(memory); + + zone.m_pools.AddAsset(std::make_unique>(ASSET_TYPE_TECHNIQUE_SET, techset->name, techset)); + return techset; + } +} // namespace + +TEST_CASE("TechsetDumperIW3", "[iw3][techset][dumper]") +{ + Zone zone("MockZone", 0, GameId::IW3, GamePlatform::PC); + zone.Register(); + + MemoryManager memory; + MockSearchPath mockObjPath; + MockOutputPath mockOutput; + AssetDumpingContext context(zone, "", mockOutput, mockObjPath, std::nullopt); + + GivenTechset(zone, memory); + + techset::DumperIW3 dumper(true); + + SECTION("Can dump techset") + { + std::string expected(R"TECHSET( +"depth prepass": + example_zprepass; + +"lit spot": + example_lit_spot; +)TECHSET"); + + dumper.Dump(context); + + const auto* file = mockOutput.GetMockedFile("techsets/example_techset.techset"); + REQUIRE(file); + REQUIRE(Trimmed(file->AsString()) == Trimmed(expected)); + } + + SECTION("Can dump simple technique") + { + std::string expected(R"TECHNIQUE( +// TECHNIQUE FLAGS: 0x4 +{ + stateMap "passthrough"; // TODO + + vertexShader 3.0 "simple.hlsl" + { + // Omitted due to matching accessors: worldMatrix = constant.worldMatrix; + // Omitted due to matching accessors: viewProjectionMatrix = constant.viewProjectionMatrix; + } + + pixelShader 3.0 "simple.hlsl" + { + } + + vertex.position = code.position; +} +)TECHNIQUE"); + dumper.Dump(context); + + const auto* file = mockOutput.GetMockedFile("techniques/example_zprepass.tech"); + REQUIRE(file); + REQUIRE(Trimmed(file->AsString()) == Trimmed(expected)); + } + + SECTION("Can dump advanced technique") + { + std::string expected(R"TECHNIQUE( +// TECHNIQUE FLAGS: 0x10 +{ + stateMap "passthrough"; // TODO + + vertexShader 3.0 "advanced.hlsl" + { + // Omitted due to matching accessors: worldMatrix = constant.worldMatrix; + // Omitted due to matching accessors: baseLightingCoords = constant.baseLightingCoords; + // Omitted due to matching accessors: viewProjectionMatrix = constant.viewProjectionMatrix; + // Omitted due to matching accessors: fogConsts = constant.fogConsts; + } + + pixelShader 3.0 "advanced.hlsl" + { + // Omitted due to matching accessors: attenuationSampler = sampler.attenuationSampler; + normalMapSampler = material.normalMap; + colorMapSampler = material.colorMap; + // Omitted due to matching accessors: modelLightingSampler = sampler.modelLightingSampler; + // Omitted due to matching accessors: fogColor = constant.fogColor; + // Omitted due to matching accessors: lightingLookupScale = constant.lightingLookupScale; + // Omitted due to matching accessors: lightPosition = constant.lightPosition; + // Omitted due to matching accessors: lightDiffuse = constant.lightDiffuse; + // Omitted due to matching accessors: lightSpotDir = constant.lightSpotDir; + // Omitted due to matching accessors: lightSpotFactors = constant.lightSpotFactors; + } + + vertex.position = code.position; + vertex.color[0] = code.color; + vertex.texcoord[0] = code.texcoord[0]; + vertex.normal = code.normal; + vertex.texcoord[2] = code.tangent; +} +)TECHNIQUE"); + dumper.Dump(context); + + const auto* file = mockOutput.GetMockedFile("techniques/example_lit_spot.tech"); + REQUIRE(file); + REQUIRE(Trimmed(file->AsString()) == Trimmed(expected)); + } +} diff --git a/test/ObjWritingTests/Game/IW3/Techset/ps_advanced.hlsl.cso b/test/ObjWritingTests/Game/IW3/Techset/ps_advanced.hlsl.cso new file mode 100644 index 0000000000000000000000000000000000000000..fe2d3be16056af8875700eec5fe36c47bfaa9544 GIT binary patch literal 1240 zcmY*YJ#W)c6g|%$i3?OPc0iDdN@b!{u+tC3R8&wJiJ?+uwQd{X#8K={EUy(?2aME# z(QK&s1F-eSSYl(Q3+CMS?6i56>wC{V_v<_dZdLuOK4Eibed8g3E5(1r?(ii(6gt0% zl>(zh7x*hUPe8}zTfv!P3&flnTM{zQOM|sdZiB5G%+_nLZG#0fyj_FM&0qtA`7_ul zF&Cb}zR{NR!(f)z%p7J}G&~8jelqNagHarfG4XJZdGcq^eiA3+x8bNE941Gb0%0DNM4 zY?z!C>jyHU^jS7F9M0Ip6-w@A`#N}{9sDMzEEN9mfkXX>mATC2V4gO#hl||B1>b@# z6YFW09RBL`lF1RQ)OXnvc*JG*xP{NMn&fRn`V z(D}}3#NngLa08lHo`l0ko7|g%`(!vE;K~;f@mX;AWCGkQxVQZ{d^S8hnhwW@dr>qQ z#5nh$=oN6-=Y7PbaXgK2It*v~ljt~nHyuXf=l$tq_yOmNdU4J5vuH9JxW&`>;K9Lt zyf_KRJ8>LNdr>rsJH0TTc3vIz2jRr+A=68sR6{Ebd~a*6^(LUdT`ab{!q>?&enixgzQc-G6}+zlhh6US zuUvy|u5#FuT!$sM;;_=C0+)2Tz-3(_rU$oe={ojP9~%k3JKq*-nk9AoPJv9{V$G~q z3uYtKHx2fX&8=RspG-G^&mMkpZ|foYCViXqv0R?_)a2hVz8*2O#Xj+C)(hoBEkQ5w zUHjcj8u*wtaGcht@ld5cV;`A|#Y^;gEPfgN@;ESY*~PrvOYx`1Du6c2@eJ^5D@ zo_S3^u6RE#-C0)n9WNPQiEhsS$5*H<*{2pP_l14&+@Rzh$r-YzME2sDQ6oC5mHQ;P w!aP|6z&p0awkONCahA5RN^MP+YcQ5?uW)-Qxw1d+ns_FH<&IX_=V}G~0So2iy#N3J literal 0 HcmV?d00001 diff --git a/test/ObjWritingTests/Game/IW3/Techset/vs_simple.hlsl.cso b/test/ObjWritingTests/Game/IW3/Techset/vs_simple.hlsl.cso new file mode 100644 index 0000000000000000000000000000000000000000..1d11cba27dfb2f778040877a57e2b10d00ff26cd GIT binary patch literal 376 zcmYk2J!-=+9L1k(rBx?GpL}Nw)e3If8dzDnrf@ z_el{Zg1?@APkQ=7eCkf!5HH#E`5wR@3-lt2V+f=>$}7uxlKnT;wSpDVk+|)>O|G{e zrCI5pDmUx5c~#~+v~3$B_A7-?0$WoSi!i;hv+-<%_g}r9TC1zfn8K!+wpIGIoG)}4 zGEP2$fkM&%_2k0F!})qOknAtU2kEYig#P1R;i*x$Yi{^L0YUFrOB@47o(DZk&sRMw e&)0$ZF$cjs_wach@0ng;={f3Id5+0l2mAxGI7{jP literal 0 HcmV?d00001