#include #include #include #include #include #include #include #include #include using namespace XCEngine::Resources; using namespace XCEngine::Containers; namespace { void WriteTextFile(const std::filesystem::path& path, const std::string& contents) { std::ofstream output(path, std::ios::binary | std::ios::trunc); ASSERT_TRUE(output.is_open()); output << contents; ASSERT_TRUE(static_cast(output)); } TEST(ShaderLoader, GetResourceType) { ShaderLoader loader; EXPECT_EQ(loader.GetResourceType(), ResourceType::Shader); } TEST(ShaderLoader, GetSupportedExtensions) { ShaderLoader loader; auto extensions = loader.GetSupportedExtensions(); EXPECT_GE(extensions.Size(), 1u); } TEST(ShaderLoader, CanLoad) { ShaderLoader loader; EXPECT_TRUE(loader.CanLoad("test.vert")); EXPECT_TRUE(loader.CanLoad("test.frag")); EXPECT_TRUE(loader.CanLoad("test.glsl")); EXPECT_TRUE(loader.CanLoad("test.hlsl")); EXPECT_TRUE(loader.CanLoad(GetBuiltinForwardLitShaderPath())); EXPECT_TRUE(loader.CanLoad(GetBuiltinObjectIdShaderPath())); EXPECT_TRUE(loader.CanLoad(GetBuiltinObjectIdOutlineShaderPath())); EXPECT_TRUE(loader.CanLoad(GetBuiltinInfiniteGridShaderPath())); EXPECT_FALSE(loader.CanLoad("test.txt")); EXPECT_FALSE(loader.CanLoad("test.png")); } TEST(ShaderLoader, LoadInvalidPath) { ShaderLoader loader; LoadResult result = loader.Load("invalid/path/shader.glsl"); EXPECT_FALSE(result); } TEST(ShaderLoader, LoadLegacySingleStageShaderBuildsDefaultPassVariant) { namespace fs = std::filesystem; const fs::path shaderPath = fs::temp_directory_path() / "xc_shader_loader_stage.vert"; { std::ofstream shaderFile(shaderPath); ASSERT_TRUE(shaderFile.is_open()); shaderFile << "#version 430\nvoid main() {}"; } ShaderLoader loader; LoadResult result = loader.Load(shaderPath.string().c_str()); ASSERT_TRUE(result); ASSERT_NE(result.resource, nullptr); Shader* shader = static_cast(result.resource); ASSERT_NE(shader, nullptr); EXPECT_EQ(shader->GetShaderType(), ShaderType::Vertex); ASSERT_EQ(shader->GetPassCount(), 1u); const ShaderPass* pass = shader->FindPass("Default"); ASSERT_NE(pass, nullptr); ASSERT_EQ(pass->variants.Size(), 1u); EXPECT_EQ(pass->variants[0].stage, ShaderType::Vertex); EXPECT_EQ(pass->variants[0].backend, ShaderBackend::Generic); EXPECT_EQ(pass->variants[0].sourceCode, shader->GetSourceCode()); delete shader; std::remove(shaderPath.string().c_str()); } TEST(ShaderLoader, LoadShaderManifestBuildsMultiPassBackendVariants) { namespace fs = std::filesystem; const fs::path shaderRoot = fs::temp_directory_path() / "xc_shader_manifest_test"; const fs::path stageRoot = shaderRoot / "stages"; const fs::path manifestPath = shaderRoot / "multi_pass.shader"; fs::remove_all(shaderRoot); fs::create_directories(stageRoot); WriteTextFile(stageRoot / "forward_lit.vs.hlsl", "float4 MainVS() : SV_POSITION { return 0; } // FORWARD_LIT_D3D12_VS\n"); WriteTextFile(stageRoot / "forward_lit.ps.hlsl", "float4 MainPS() : SV_TARGET { return 1; } // FORWARD_LIT_D3D12_PS\n"); WriteTextFile(stageRoot / "forward_lit.vert.glsl", "#version 430\n// FORWARD_LIT_GL_VS\nvoid main() {}\n"); WriteTextFile(stageRoot / "forward_lit.frag.glsl", "#version 430\n// FORWARD_LIT_GL_PS\nvoid main() {}\n"); WriteTextFile(stageRoot / "forward_lit.vert.vk.glsl", "#version 450\n// FORWARD_LIT_VK_VS\nvoid main() {}\n"); WriteTextFile(stageRoot / "forward_lit.frag.vk.glsl", "#version 450\n// FORWARD_LIT_VK_PS\nvoid main() {}\n"); WriteTextFile(stageRoot / "depth_only.vs.hlsl", "float4 MainVS() : SV_POSITION { return 0; } // DEPTH_ONLY_D3D12_VS\n"); WriteTextFile(stageRoot / "depth_only.ps.hlsl", "float4 MainPS() : SV_TARGET { return 1; } // DEPTH_ONLY_D3D12_PS\n"); { std::ofstream manifest(manifestPath); ASSERT_TRUE(manifest.is_open()); manifest << "{\n"; manifest << " \"name\": \"TestLitShader\",\n"; manifest << " \"passes\": [\n"; manifest << " {\n"; manifest << " \"name\": \"ForwardLit\",\n"; manifest << " \"tags\": {\n"; manifest << " \"LightMode\": \"ForwardBase\",\n"; manifest << " \"Queue\": \"Geometry\"\n"; manifest << " },\n"; manifest << " \"variants\": [\n"; manifest << " { \"stage\": \"Vertex\", \"backend\": \"D3D12\", \"language\": \"HLSL\", \"source\": \"stages/forward_lit.vs.hlsl\", \"entryPoint\": \"MainVS\", \"profile\": \"vs_5_0\" },\n"; manifest << " { \"stage\": \"Fragment\", \"backend\": \"D3D12\", \"language\": \"HLSL\", \"source\": \"stages/forward_lit.ps.hlsl\", \"entryPoint\": \"MainPS\", \"profile\": \"ps_5_0\" },\n"; manifest << " { \"stage\": \"Vertex\", \"backend\": \"OpenGL\", \"language\": \"GLSL\", \"source\": \"stages/forward_lit.vert.glsl\" },\n"; manifest << " { \"stage\": \"Fragment\", \"backend\": \"OpenGL\", \"language\": \"GLSL\", \"source\": \"stages/forward_lit.frag.glsl\" },\n"; manifest << " { \"stage\": \"Vertex\", \"backend\": \"Vulkan\", \"language\": \"GLSL\", \"source\": \"stages/forward_lit.vert.vk.glsl\" },\n"; manifest << " { \"stage\": \"Fragment\", \"backend\": \"Vulkan\", \"language\": \"GLSL\", \"source\": \"stages/forward_lit.frag.vk.glsl\" }\n"; manifest << " ]\n"; manifest << " },\n"; manifest << " {\n"; manifest << " \"name\": \"DepthOnly\",\n"; manifest << " \"tags\": {\n"; manifest << " \"LightMode\": \"DepthOnly\"\n"; manifest << " },\n"; manifest << " \"variants\": [\n"; manifest << " { \"stage\": \"Vertex\", \"backend\": \"D3D12\", \"language\": \"HLSL\", \"source\": \"stages/depth_only.vs.hlsl\" },\n"; manifest << " { \"stage\": \"Fragment\", \"backend\": \"D3D12\", \"language\": \"HLSL\", \"source\": \"stages/depth_only.ps.hlsl\" }\n"; manifest << " ]\n"; manifest << " }\n"; manifest << " ]\n"; manifest << "}\n"; } ShaderLoader loader; LoadResult result = loader.Load(manifestPath.string().c_str()); ASSERT_TRUE(result); ASSERT_NE(result.resource, nullptr); Shader* shader = static_cast(result.resource); ASSERT_NE(shader, nullptr); ASSERT_TRUE(shader->IsValid()); EXPECT_EQ(shader->GetName(), "TestLitShader"); ASSERT_EQ(shader->GetPassCount(), 2u); const ShaderPass* forwardLitPass = shader->FindPass("ForwardLit"); ASSERT_NE(forwardLitPass, nullptr); ASSERT_EQ(forwardLitPass->tags.Size(), 2u); EXPECT_EQ(forwardLitPass->tags[0].name, "LightMode"); EXPECT_EQ(forwardLitPass->tags[0].value, "ForwardBase"); EXPECT_EQ(forwardLitPass->tags[1].name, "Queue"); EXPECT_EQ(forwardLitPass->tags[1].value, "Geometry"); const ShaderStageVariant* d3d12Vertex = shader->FindVariant("ForwardLit", ShaderType::Vertex, ShaderBackend::D3D12); ASSERT_NE(d3d12Vertex, nullptr); EXPECT_EQ(d3d12Vertex->entryPoint, "MainVS"); EXPECT_EQ(d3d12Vertex->profile, "vs_5_0"); EXPECT_NE(std::string(d3d12Vertex->sourceCode.CStr()).find("FORWARD_LIT_D3D12_VS"), std::string::npos); const ShaderStageVariant* openglFragment = shader->FindVariant("ForwardLit", ShaderType::Fragment, ShaderBackend::OpenGL); ASSERT_NE(openglFragment, nullptr); EXPECT_EQ(openglFragment->entryPoint, "main"); EXPECT_EQ(openglFragment->profile, "fs_4_30"); EXPECT_NE(std::string(openglFragment->sourceCode.CStr()).find("FORWARD_LIT_GL_PS"), std::string::npos); const ShaderStageVariant* vulkanFragment = shader->FindVariant("ForwardLit", ShaderType::Fragment, ShaderBackend::Vulkan); ASSERT_NE(vulkanFragment, nullptr); EXPECT_EQ(vulkanFragment->profile, "fs_4_50"); EXPECT_NE(std::string(vulkanFragment->sourceCode.CStr()).find("FORWARD_LIT_VK_PS"), std::string::npos); const ShaderPass* depthOnlyPass = shader->FindPass("DepthOnly"); ASSERT_NE(depthOnlyPass, nullptr); ASSERT_EQ(depthOnlyPass->tags.Size(), 1u); EXPECT_EQ(depthOnlyPass->tags[0].value, "DepthOnly"); EXPECT_NE(shader->FindVariant("DepthOnly", ShaderType::Vertex, ShaderBackend::D3D12), nullptr); EXPECT_NE(shader->FindVariant("DepthOnly", ShaderType::Fragment, ShaderBackend::D3D12), nullptr); EXPECT_EQ(shader->FindVariant("DepthOnly", ShaderType::Fragment, ShaderBackend::OpenGL), nullptr); delete shader; fs::remove_all(shaderRoot); } TEST(ShaderLoader, ResourceManagerLoadsShaderManifestRelativeToResourceRoot) { namespace fs = std::filesystem; ResourceManager& manager = ResourceManager::Get(); manager.Initialize(); const fs::path previousPath = fs::current_path(); const fs::path projectRoot = fs::temp_directory_path() / "xc_shader_manifest_resource_root"; const fs::path shaderDir = projectRoot / "Assets" / "Shaders"; const fs::path manifestPath = shaderDir / "simple.shader"; fs::remove_all(projectRoot); fs::create_directories(shaderDir); WriteTextFile(shaderDir / "simple.vert.glsl", "#version 430\n// SIMPLE_GL_VS\nvoid main() {}\n"); WriteTextFile(shaderDir / "simple.frag.glsl", "#version 430\n// SIMPLE_GL_PS\nvoid main() {}\n"); { std::ofstream manifest(manifestPath); ASSERT_TRUE(manifest.is_open()); manifest << "{\n"; manifest << " \"name\": \"SimpleShader\",\n"; manifest << " \"passes\": [\n"; manifest << " {\n"; manifest << " \"name\": \"Unlit\",\n"; manifest << " \"tags\": { \"LightMode\": \"Unlit\" },\n"; manifest << " \"variants\": [\n"; manifest << " { \"stage\": \"Vertex\", \"backend\": \"OpenGL\", \"language\": \"GLSL\", \"source\": \"simple.vert.glsl\" },\n"; manifest << " { \"stage\": \"Fragment\", \"backend\": \"OpenGL\", \"language\": \"GLSL\", \"source\": \"simple.frag.glsl\" }\n"; manifest << " ]\n"; manifest << " }\n"; manifest << " ]\n"; manifest << "}\n"; } manager.SetResourceRoot(projectRoot.string().c_str()); fs::current_path(projectRoot.parent_path()); { const ResourceHandle shaderHandle = manager.Load("Assets/Shaders/simple.shader"); ASSERT_TRUE(shaderHandle.IsValid()); EXPECT_EQ(shaderHandle->GetName(), "SimpleShader"); const ShaderStageVariant* vertexVariant = shaderHandle->FindVariant("Unlit", ShaderType::Vertex, ShaderBackend::OpenGL); ASSERT_NE(vertexVariant, nullptr); EXPECT_NE(std::string(vertexVariant->sourceCode.CStr()).find("SIMPLE_GL_VS"), std::string::npos); } fs::current_path(previousPath); manager.SetResourceRoot(""); manager.Shutdown(); fs::remove_all(projectRoot); } TEST(ShaderLoader, LoadBuiltinForwardLitShaderBuildsBackendVariants) { ShaderLoader loader; LoadResult result = loader.Load(GetBuiltinForwardLitShaderPath()); ASSERT_TRUE(result); ASSERT_NE(result.resource, nullptr); Shader* shader = static_cast(result.resource); ASSERT_NE(shader, nullptr); ASSERT_TRUE(shader->IsValid()); const ShaderPass* pass = shader->FindPass("ForwardLit"); ASSERT_NE(pass, nullptr); ASSERT_EQ(pass->variants.Size(), 6u); ASSERT_EQ(pass->tags.Size(), 1u); EXPECT_EQ(pass->tags[0].name, "LightMode"); EXPECT_EQ(pass->tags[0].value, "ForwardBase"); EXPECT_NE(shader->FindVariant("ForwardLit", ShaderType::Vertex, ShaderBackend::D3D12), nullptr); EXPECT_NE(shader->FindVariant("ForwardLit", ShaderType::Fragment, ShaderBackend::D3D12), nullptr); EXPECT_NE(shader->FindVariant("ForwardLit", ShaderType::Vertex, ShaderBackend::OpenGL), nullptr); EXPECT_NE(shader->FindVariant("ForwardLit", ShaderType::Fragment, ShaderBackend::OpenGL), nullptr); EXPECT_NE(shader->FindVariant("ForwardLit", ShaderType::Vertex, ShaderBackend::Vulkan), nullptr); EXPECT_NE(shader->FindVariant("ForwardLit", ShaderType::Fragment, ShaderBackend::Vulkan), nullptr); const ShaderStageVariant* d3d12Vertex = shader->FindVariant( "ForwardLit", ShaderType::Vertex, ShaderBackend::D3D12); ASSERT_NE(d3d12Vertex, nullptr); EXPECT_NE(std::string(d3d12Vertex->sourceCode.CStr()).find("XC_BUILTIN_FORWARD_LIT_D3D12_VS"), std::string::npos); const ShaderStageVariant* openglFragment = shader->FindVariant( "ForwardLit", ShaderType::Fragment, ShaderBackend::OpenGL); ASSERT_NE(openglFragment, nullptr); EXPECT_NE(std::string(openglFragment->sourceCode.CStr()).find("XC_BUILTIN_FORWARD_LIT_OPENGL_PS"), std::string::npos); const ShaderStageVariant* vulkanFragment = shader->FindVariant( "ForwardLit", ShaderType::Fragment, ShaderBackend::Vulkan); ASSERT_NE(vulkanFragment, nullptr); EXPECT_NE(std::string(vulkanFragment->sourceCode.CStr()).find("XC_BUILTIN_FORWARD_LIT_VULKAN_PS"), std::string::npos); delete shader; } TEST(ShaderLoader, LoadBuiltinObjectIdShaderBuildsBackendVariants) { ShaderLoader loader; LoadResult result = loader.Load(GetBuiltinObjectIdShaderPath()); ASSERT_TRUE(result); ASSERT_NE(result.resource, nullptr); Shader* shader = static_cast(result.resource); ASSERT_NE(shader, nullptr); ASSERT_TRUE(shader->IsValid()); const ShaderPass* pass = shader->FindPass("ObjectId"); ASSERT_NE(pass, nullptr); ASSERT_EQ(pass->variants.Size(), 6u); ASSERT_EQ(pass->tags.Size(), 1u); EXPECT_EQ(pass->tags[0].name, "LightMode"); EXPECT_EQ(pass->tags[0].value, "ObjectId"); EXPECT_NE(shader->FindVariant("ObjectId", ShaderType::Vertex, ShaderBackend::D3D12), nullptr); EXPECT_NE(shader->FindVariant("ObjectId", ShaderType::Fragment, ShaderBackend::D3D12), nullptr); EXPECT_NE(shader->FindVariant("ObjectId", ShaderType::Vertex, ShaderBackend::OpenGL), nullptr); EXPECT_NE(shader->FindVariant("ObjectId", ShaderType::Fragment, ShaderBackend::OpenGL), nullptr); EXPECT_NE(shader->FindVariant("ObjectId", ShaderType::Vertex, ShaderBackend::Vulkan), nullptr); EXPECT_NE(shader->FindVariant("ObjectId", ShaderType::Fragment, ShaderBackend::Vulkan), nullptr); const ShaderStageVariant* d3d12Vertex = shader->FindVariant( "ObjectId", ShaderType::Vertex, ShaderBackend::D3D12); ASSERT_NE(d3d12Vertex, nullptr); EXPECT_NE(std::string(d3d12Vertex->sourceCode.CStr()).find("XC_BUILTIN_OBJECT_ID_D3D12_VS"), std::string::npos); const ShaderStageVariant* openglFragment = shader->FindVariant( "ObjectId", ShaderType::Fragment, ShaderBackend::OpenGL); ASSERT_NE(openglFragment, nullptr); EXPECT_NE(std::string(openglFragment->sourceCode.CStr()).find("XC_BUILTIN_OBJECT_ID_OPENGL_PS"), std::string::npos); const ShaderStageVariant* vulkanFragment = shader->FindVariant( "ObjectId", ShaderType::Fragment, ShaderBackend::Vulkan); ASSERT_NE(vulkanFragment, nullptr); EXPECT_NE(std::string(vulkanFragment->sourceCode.CStr()).find("XC_BUILTIN_OBJECT_ID_VULKAN_PS"), std::string::npos); delete shader; } TEST(ShaderLoader, LoadBuiltinInfiniteGridShaderBuildsD3D12Variants) { ShaderLoader loader; LoadResult result = loader.Load(GetBuiltinInfiniteGridShaderPath()); ASSERT_TRUE(result); ASSERT_NE(result.resource, nullptr); Shader* shader = static_cast(result.resource); ASSERT_NE(shader, nullptr); ASSERT_TRUE(shader->IsValid()); const ShaderPass* pass = shader->FindPass("InfiniteGrid"); ASSERT_NE(pass, nullptr); ASSERT_EQ(pass->variants.Size(), 2u); ASSERT_EQ(pass->tags.Size(), 1u); EXPECT_EQ(pass->tags[0].name, "LightMode"); EXPECT_EQ(pass->tags[0].value, "InfiniteGrid"); EXPECT_NE(shader->FindVariant("InfiniteGrid", ShaderType::Vertex, ShaderBackend::D3D12), nullptr); EXPECT_NE(shader->FindVariant("InfiniteGrid", ShaderType::Fragment, ShaderBackend::D3D12), nullptr); EXPECT_EQ(shader->FindVariant("InfiniteGrid", ShaderType::Vertex, ShaderBackend::OpenGL), nullptr); EXPECT_EQ(shader->FindVariant("InfiniteGrid", ShaderType::Fragment, ShaderBackend::OpenGL), nullptr); const ShaderStageVariant* d3d12Fragment = shader->FindVariant( "InfiniteGrid", ShaderType::Fragment, ShaderBackend::D3D12); ASSERT_NE(d3d12Fragment, nullptr); EXPECT_NE(std::string(d3d12Fragment->sourceCode.CStr()).find("XC_BUILTIN_INFINITE_GRID_D3D12_PS"), std::string::npos); delete shader; } TEST(ShaderLoader, LoadBuiltinObjectIdOutlineShaderBuildsD3D12Variants) { ShaderLoader loader; LoadResult result = loader.Load(GetBuiltinObjectIdOutlineShaderPath()); ASSERT_TRUE(result); ASSERT_NE(result.resource, nullptr); Shader* shader = static_cast(result.resource); ASSERT_NE(shader, nullptr); ASSERT_TRUE(shader->IsValid()); const ShaderPass* pass = shader->FindPass("ObjectIdOutline"); ASSERT_NE(pass, nullptr); ASSERT_EQ(pass->variants.Size(), 2u); ASSERT_EQ(pass->tags.Size(), 1u); EXPECT_EQ(pass->tags[0].name, "LightMode"); EXPECT_EQ(pass->tags[0].value, "ObjectIdOutline"); EXPECT_NE(shader->FindVariant("ObjectIdOutline", ShaderType::Vertex, ShaderBackend::D3D12), nullptr); EXPECT_NE(shader->FindVariant("ObjectIdOutline", ShaderType::Fragment, ShaderBackend::D3D12), nullptr); EXPECT_EQ(shader->FindVariant("ObjectIdOutline", ShaderType::Vertex, ShaderBackend::OpenGL), nullptr); EXPECT_EQ(shader->FindVariant("ObjectIdOutline", ShaderType::Fragment, ShaderBackend::OpenGL), nullptr); const ShaderStageVariant* d3d12Fragment = shader->FindVariant( "ObjectIdOutline", ShaderType::Fragment, ShaderBackend::D3D12); ASSERT_NE(d3d12Fragment, nullptr); EXPECT_NE( std::string(d3d12Fragment->sourceCode.CStr()).find("XC_BUILTIN_OBJECT_ID_OUTLINE_D3D12_PS"), std::string::npos); delete shader; } TEST(ShaderLoader, ResourceManagerLazilyLoadsBuiltinForwardLitShader) { ResourceManager& manager = ResourceManager::Get(); manager.Shutdown(); manager.UnregisterLoader(ResourceType::Shader); ResourceHandle shaderHandle = manager.Load(GetBuiltinForwardLitShaderPath()); ASSERT_TRUE(shaderHandle.IsValid()); ASSERT_NE(shaderHandle->FindPass("ForwardLit"), nullptr); ResourceHandle objectIdShaderHandle = manager.Load(GetBuiltinObjectIdShaderPath()); ASSERT_TRUE(objectIdShaderHandle.IsValid()); ASSERT_NE(objectIdShaderHandle->FindPass("ObjectId"), nullptr); ResourceHandle objectIdOutlineShaderHandle = manager.Load(GetBuiltinObjectIdOutlineShaderPath()); ASSERT_TRUE(objectIdOutlineShaderHandle.IsValid()); ASSERT_NE(objectIdOutlineShaderHandle->FindPass("ObjectIdOutline"), nullptr); ResourceHandle infiniteGridShaderHandle = manager.Load(GetBuiltinInfiniteGridShaderPath()); ASSERT_TRUE(infiniteGridShaderHandle.IsValid()); ASSERT_NE(infiniteGridShaderHandle->FindPass("InfiniteGrid"), nullptr); manager.Shutdown(); } } // namespace