From 7bb9cfcbfa670ed91f12be85c0da89e2b3672173 Mon Sep 17 00:00:00 2001 From: Jan Laupetin Date: Fri, 20 Mar 2026 23:03:02 +0100 Subject: [PATCH] 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