From 9f7d8fd68dabc81f1bd118746e140b9b362de69d Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Thu, 2 Apr 2026 19:00:48 +0800 Subject: [PATCH] refactor: route builtin forward pipeline through shader assets --- .../XCEngine/Core/Asset/ResourceManager.h | 4 +- .../Pipelines/BuiltinForwardPipeline.h | 37 +- .../XCEngine/Resources/BuiltinResources.h | 3 + engine/src/Core/Asset/ResourceManager.cpp | 19 +- .../Pipelines/BuiltinForwardPipeline.cpp | 374 ++++++++---------- engine/src/Resources/BuiltinResources.cpp | 317 +++++++++++++++ engine/src/Resources/Shader/ShaderLoader.cpp | 9 + tests/Resources/Shader/test_shader_loader.cpp | 42 ++ 8 files changed, 585 insertions(+), 220 deletions(-) diff --git a/engine/include/XCEngine/Core/Asset/ResourceManager.h b/engine/include/XCEngine/Core/Asset/ResourceManager.h index 094770db..5159a611 100644 --- a/engine/include/XCEngine/Core/Asset/ResourceManager.h +++ b/engine/include/XCEngine/Core/Asset/ResourceManager.h @@ -148,7 +148,8 @@ private: ResourceManager() = default; ~ResourceManager() = default; - + + void EnsureInitialized(); IResource* FindInCache(ResourceGUID guid); void AddToCache(ResourceGUID guid, IResource* resource); IResourceLoader* FindLoader(ResourceType type); @@ -168,6 +169,7 @@ private: ResourceCache m_cache; Core::UniqueRef m_asyncLoader; Threading::Mutex m_mutex; + std::mutex m_initializeMutex; mutable std::recursive_mutex m_ioMutex; std::mutex m_inFlightLoadsMutex; std::unordered_map, InFlightLoadKeyHasher> m_inFlightLoads; diff --git a/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h b/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h index 4ebe55ec..fbefce92 100644 --- a/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h +++ b/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h @@ -14,6 +14,7 @@ #include #include +#include #include namespace XCEngine { @@ -23,6 +24,7 @@ class GameObject; namespace Resources { class Material; +class Shader; class Texture; } // namespace Resources @@ -76,9 +78,41 @@ private: RHI::RHIResourceView* textureView = nullptr; }; + struct ResolvedShaderPass { + const Resources::Shader* shader = nullptr; + const Resources::ShaderPass* pass = nullptr; + Containers::String passName; + }; + + struct PipelineStateKey { + Resources::MaterialRenderState renderState; + const Resources::Shader* shader = nullptr; + Containers::String passName; + + bool operator==(const PipelineStateKey& other) const { + return renderState == other.renderState && + shader == other.shader && + passName == other.passName; + } + }; + + struct PipelineStateKeyHash { + size_t operator()(const PipelineStateKey& key) const noexcept { + size_t hash = MaterialRenderStateHash()(key.renderState); + auto combine = [&hash](size_t value) { + hash ^= value + 0x9e3779b9u + (hash << 6) + (hash >> 2); + }; + + combine(reinterpret_cast(key.shader)); + combine(std::hash{}(key.passName)); + return hash; + } + }; + bool EnsureInitialized(const RenderContext& context); bool CreatePipelineResources(const RenderContext& context); void DestroyPipelineResources(); + ResolvedShaderPass ResolveForwardShaderPass(const Resources::Material* material) const; RHI::RHIPipelineState* GetOrCreatePipelineState( const RenderContext& context, const Resources::Material* material); @@ -99,13 +133,14 @@ private: RHI::RHIDevice* m_device = nullptr; RHI::RHIType m_backendType = RHI::RHIType::D3D12; bool m_initialized = false; + Resources::ResourceHandle m_builtinForwardShader; RenderResourceCache m_resourceCache; RHI::RHIDescriptorPool* m_samplerPool = nullptr; RHI::RHIDescriptorSet* m_samplerSet = nullptr; RHI::RHIPipelineLayout* m_pipelineLayout = nullptr; - std::unordered_map m_pipelineStates; + std::unordered_map m_pipelineStates; std::unordered_map m_perObjectSets; std::unordered_map m_materialBindings; RHI::RHISampler* m_sampler = nullptr; diff --git a/engine/include/XCEngine/Resources/BuiltinResources.h b/engine/include/XCEngine/Resources/BuiltinResources.h index e7bdefe6..f2596136 100644 --- a/engine/include/XCEngine/Resources/BuiltinResources.h +++ b/engine/include/XCEngine/Resources/BuiltinResources.h @@ -17,17 +17,20 @@ enum class BuiltinPrimitiveType { bool IsBuiltinResourcePath(const Containers::String& path); bool IsBuiltinMeshPath(const Containers::String& path); bool IsBuiltinMaterialPath(const Containers::String& path); +bool IsBuiltinShaderPath(const Containers::String& path); bool IsBuiltinTexturePath(const Containers::String& path); const char* GetBuiltinPrimitiveDisplayName(BuiltinPrimitiveType primitiveType); Containers::String GetBuiltinPrimitiveMeshPath(BuiltinPrimitiveType primitiveType); Containers::String GetBuiltinDefaultPrimitiveMaterialPath(); +Containers::String GetBuiltinForwardLitShaderPath(); Containers::String GetBuiltinDefaultPrimitiveTexturePath(); bool TryParseBuiltinPrimitiveType(const Containers::String& path, BuiltinPrimitiveType& outPrimitiveType); LoadResult CreateBuiltinMeshResource(const Containers::String& path); LoadResult CreateBuiltinMaterialResource(const Containers::String& path); +LoadResult CreateBuiltinShaderResource(const Containers::String& path); LoadResult CreateBuiltinTextureResource(const Containers::String& path); } // namespace Resources diff --git a/engine/src/Core/Asset/ResourceManager.cpp b/engine/src/Core/Asset/ResourceManager.cpp index 8876121b..8cc97e6c 100644 --- a/engine/src/Core/Asset/ResourceManager.cpp +++ b/engine/src/Core/Asset/ResourceManager.cpp @@ -65,17 +65,28 @@ ResourceManager::ScopedDeferredSceneLoad::~ScopedDeferredSceneLoad() { } void ResourceManager::Initialize() { + EnsureInitialized(); +} + +void ResourceManager::EnsureInitialized() { if (m_asyncLoader) { return; } - m_asyncLoader = Core::MakeUnique(); - m_asyncLoader->Initialize(2); + std::lock_guard initLock(m_initializeMutex); + if (m_asyncLoader) { + return; + } + + Core::UniqueRef asyncLoader = Core::MakeUnique(); + asyncLoader->Initialize(2); RegisterBuiltinLoader(*this, g_materialLoader); RegisterBuiltinLoader(*this, g_meshLoader); RegisterBuiltinLoader(*this, g_shaderLoader); RegisterBuiltinLoader(*this, g_textureLoader); + + m_asyncLoader = std::move(asyncLoader); } void ResourceManager::Shutdown() { @@ -266,6 +277,8 @@ void ResourceManager::LoadAsync(const Containers::String& path, ResourceType typ void ResourceManager::LoadAsync(const Containers::String& path, ResourceType type, ImportSettings* settings, std::function callback) { + EnsureInitialized(); + if (!m_asyncLoader) { if (callback) { callback(LoadResult("Async loader is not initialized")); @@ -408,6 +421,8 @@ bool ResourceManager::IsDeferredSceneLoadEnabled() const { LoadResult ResourceManager::LoadResource(const Containers::String& path, ResourceType type, ImportSettings* settings) { + EnsureInitialized(); + const ResourceGUID guid = ResourceGUID::Generate(path); if (ShouldTraceResourcePath(path)) { diff --git a/engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp b/engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp index 1b20a2b2..9f78635e 100644 --- a/engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp +++ b/engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp @@ -3,15 +3,17 @@ #include "Components/GameObject.h" #include "Components/MeshFilterComponent.h" #include "Components/MeshRendererComponent.h" -#include "RHI/RHICommandList.h" #include "Debug/Logger.h" +#include "Core/Asset/ResourceManager.h" +#include "RHI/RHICommandList.h" #include "Rendering/RenderMaterialUtility.h" #include "Rendering/RenderSurface.h" +#include "Resources/BuiltinResources.h" #include "Resources/Material/Material.h" +#include "Resources/Shader/Shader.h" #include "Resources/Texture/Texture.h" #include -#include namespace XCEngine { namespace Rendering { @@ -51,189 +53,102 @@ namespace { constexpr uint32_t kDescriptorFirstSet = 1; constexpr uint32_t kDescriptorSetCount = 5; -const char kBuiltinForwardHlsl[] = R"( -Texture2D gBaseColorTexture : register(t1); -SamplerState gLinearSampler : register(s1); - -cbuffer PerObjectConstants : register(b1) { - float4x4 gProjectionMatrix; - float4x4 gViewMatrix; - float4x4 gModelMatrix; - float4x4 gNormalMatrix; - float4 gMainLightDirectionAndIntensity; - float4 gMainLightColorAndFlags; -}; - -cbuffer MaterialConstants : register(b2) { - float4 gBaseColorFactor; -}; - -struct VSInput { - float3 position : POSITION; - float3 normal : NORMAL; - float2 texcoord : TEXCOORD0; -}; - -struct PSInput { - float4 position : SV_POSITION; - float3 normalWS : TEXCOORD0; - float2 texcoord : TEXCOORD1; -}; - -PSInput MainVS(VSInput input) { - PSInput output; - float4 positionWS = mul(gModelMatrix, float4(input.position, 1.0f)); - float4 positionVS = mul(gViewMatrix, positionWS); - output.position = mul(gProjectionMatrix, positionVS); - output.normalWS = mul((float3x3)gNormalMatrix, input.normal); - output.texcoord = input.texcoord; - return output; +Resources::ShaderBackend ToShaderBackend(RHI::RHIType backendType) { + switch (backendType) { + case RHI::RHIType::D3D12: + return Resources::ShaderBackend::D3D12; + case RHI::RHIType::Vulkan: + return Resources::ShaderBackend::Vulkan; + case RHI::RHIType::OpenGL: + default: + return Resources::ShaderBackend::OpenGL; + } } -float4 MainPS(PSInput input) : SV_TARGET { - float4 baseColor = gBaseColorTexture.Sample(gLinearSampler, input.texcoord) * gBaseColorFactor; - if (gMainLightColorAndFlags.a < 0.5f) { - return baseColor; +RHI::ShaderLanguage ToRHIShaderLanguage(Resources::ShaderLanguage language) { + switch (language) { + case Resources::ShaderLanguage::HLSL: + return RHI::ShaderLanguage::HLSL; + case Resources::ShaderLanguage::SPIRV: + return RHI::ShaderLanguage::SPIRV; + case Resources::ShaderLanguage::GLSL: + default: + return RHI::ShaderLanguage::GLSL; + } +} + +std::wstring ToWideAscii(const Containers::String& value) { + std::wstring wide; + wide.reserve(value.Length()); + for (size_t index = 0; index < value.Length(); ++index) { + wide.push_back(static_cast(value[index])); + } + return wide; +} + +bool ShaderPassHasGraphicsVariants( + const Resources::Shader& shader, + const Containers::String& passName, + Resources::ShaderBackend backend) { + return shader.FindVariant(passName, Resources::ShaderType::Vertex, backend) != nullptr && + shader.FindVariant(passName, Resources::ShaderType::Fragment, backend) != nullptr; +} + +const Resources::ShaderPass* FindForwardCompatiblePass( + const Resources::Shader& shader, + const Resources::Material* material, + Resources::ShaderBackend backend) { + if (material != nullptr && !material->GetShaderPass().Empty()) { + const Resources::ShaderPass* explicitPass = shader.FindPass(material->GetShaderPass()); + if (explicitPass != nullptr && + ShaderPassHasGraphicsVariants(shader, explicitPass->name, backend)) { + return explicitPass; + } } - float3 normalWS = normalize(input.normalWS); - float3 directionToLightWS = normalize(gMainLightDirectionAndIntensity.xyz); - float diffuse = saturate(dot(normalWS, directionToLightWS)); - float3 lighting = float3(0.28f, 0.28f, 0.28f) + - gMainLightColorAndFlags.rgb * (diffuse * gMainLightDirectionAndIntensity.w); - return float4(baseColor.rgb * lighting, baseColor.a); -} -)"; - -const char kBuiltinForwardVertexShader[] = R"(#version 430 -layout(location = 0) in vec3 aPosition; -layout(location = 1) in vec3 aNormal; -layout(location = 2) in vec2 aTexCoord; - -layout(std140, binding = 1) uniform PerObjectConstants { - mat4 gProjectionMatrix; - mat4 gViewMatrix; - mat4 gModelMatrix; - mat4 gNormalMatrix; - vec4 gMainLightDirectionAndIntensity; - vec4 gMainLightColorAndFlags; -}; - -out vec3 vNormalWS; -out vec2 vTexCoord; - -void main() { - vec4 positionWS = gModelMatrix * vec4(aPosition, 1.0); - vec4 positionVS = gViewMatrix * positionWS; - gl_Position = gProjectionMatrix * positionVS; - vNormalWS = mat3(gNormalMatrix) * aNormal; - vTexCoord = aTexCoord; -} -)"; - -const char kBuiltinForwardFragmentShader[] = R"(#version 430 -layout(binding = 1) uniform sampler2D uBaseColorTexture; - -layout(std140, binding = 1) uniform PerObjectConstants { - mat4 gProjectionMatrix; - mat4 gViewMatrix; - mat4 gModelMatrix; - mat4 gNormalMatrix; - vec4 gMainLightDirectionAndIntensity; - vec4 gMainLightColorAndFlags; -}; - -layout(std140, binding = 2) uniform MaterialConstants { - vec4 gBaseColorFactor; -}; - -in vec3 vNormalWS; -in vec2 vTexCoord; - -layout(location = 0) out vec4 fragColor; - -void main() { - vec4 baseColor = texture(uBaseColorTexture, vTexCoord) * gBaseColorFactor; - if (gMainLightColorAndFlags.w < 0.5) { - fragColor = baseColor; - return; + for (const Resources::ShaderPass& shaderPass : shader.GetPasses()) { + if (ShaderPassMatchesBuiltinPass(shaderPass, BuiltinMaterialPass::ForwardLit) && + ShaderPassHasGraphicsVariants(shader, shaderPass.name, backend)) { + return &shaderPass; + } } - vec3 normalWS = normalize(vNormalWS); - vec3 directionToLightWS = normalize(gMainLightDirectionAndIntensity.xyz); - float diffuse = max(dot(normalWS, directionToLightWS), 0.0); - vec3 lighting = vec3(0.28) + - gMainLightColorAndFlags.rgb * (diffuse * gMainLightDirectionAndIntensity.w); - fragColor = vec4(baseColor.rgb * lighting, baseColor.a); -} -)"; - -const char kBuiltinForwardVulkanVertexShader[] = R"(#version 450 -layout(location = 0) in vec3 aPosition; -layout(location = 1) in vec3 aNormal; -layout(location = 2) in vec2 aTexCoord; - -layout(set = 1, binding = 0, std140) uniform PerObjectConstants { - mat4 gProjectionMatrix; - mat4 gViewMatrix; - mat4 gModelMatrix; - mat4 gNormalMatrix; - vec4 gMainLightDirectionAndIntensity; - vec4 gMainLightColorAndFlags; -}; - -layout(location = 0) out vec3 vNormalWS; -layout(location = 1) out vec2 vTexCoord; - -void main() { - vec4 positionWS = gModelMatrix * vec4(aPosition, 1.0); - vec4 positionVS = gViewMatrix * positionWS; - gl_Position = gProjectionMatrix * positionVS; - vNormalWS = mat3(gNormalMatrix) * aNormal; - vTexCoord = aTexCoord; -} -)"; - -const char kBuiltinForwardVulkanFragmentShader[] = R"(#version 450 -layout(set = 3, binding = 0) uniform texture2D uBaseColorTexture; -layout(set = 4, binding = 0) uniform sampler uLinearSampler; - -layout(set = 1, binding = 0, std140) uniform PerObjectConstants { - mat4 gProjectionMatrix; - mat4 gViewMatrix; - mat4 gModelMatrix; - mat4 gNormalMatrix; - vec4 gMainLightDirectionAndIntensity; - vec4 gMainLightColorAndFlags; -}; - -layout(set = 2, binding = 0, std140) uniform MaterialConstants { - vec4 gBaseColorFactor; -}; - -layout(location = 0) in vec3 vNormalWS; -layout(location = 1) in vec2 vTexCoord; - -layout(location = 0) out vec4 fragColor; - -void main() { - vec4 baseColor = texture(sampler2D(uBaseColorTexture, uLinearSampler), vTexCoord) * gBaseColorFactor; - if (gMainLightColorAndFlags.w < 0.5) { - fragColor = baseColor; - return; + const Resources::ShaderPass* defaultPass = shader.FindPass("ForwardLit"); + if (defaultPass != nullptr && + ShaderPassHasGraphicsVariants(shader, defaultPass->name, backend)) { + return defaultPass; } - vec3 normalWS = normalize(vNormalWS); - vec3 directionToLightWS = normalize(gMainLightDirectionAndIntensity.xyz); - float diffuse = max(dot(normalWS, directionToLightWS), 0.0); - vec3 lighting = vec3(0.28) + - gMainLightColorAndFlags.rgb * (diffuse * gMainLightDirectionAndIntensity.w); - fragColor = vec4(baseColor.rgb * lighting, baseColor.a); + defaultPass = shader.FindPass("Default"); + if (defaultPass != nullptr && + ShaderPassHasGraphicsVariants(shader, defaultPass->name, backend)) { + return defaultPass; + } + + if (shader.GetPassCount() > 0 && + ShaderPassHasGraphicsVariants(shader, shader.GetPasses()[0].name, backend)) { + return &shader.GetPasses()[0]; + } + + return nullptr; } -)"; + +void ApplyShaderStageVariant( + const Resources::ShaderStageVariant& variant, + RHI::ShaderCompileDesc& compileDesc) { + compileDesc.source.assign( + variant.sourceCode.CStr(), + variant.sourceCode.CStr() + variant.sourceCode.Length()); + compileDesc.sourceLanguage = ToRHIShaderLanguage(variant.language); + compileDesc.entryPoint = ToWideAscii(variant.entryPoint); + compileDesc.profile = ToWideAscii(variant.profile); +} + RHI::GraphicsPipelineDesc CreatePipelineDesc( RHI::RHIType backendType, RHI::RHIPipelineLayout* pipelineLayout, + const Resources::Shader& shader, + const Containers::String& passName, const Resources::Material* material) { RHI::GraphicsPipelineDesc pipelineDesc = {}; pipelineDesc.pipelineLayout = pipelineLayout; @@ -246,44 +161,16 @@ RHI::GraphicsPipelineDesc CreatePipelineDesc( pipelineDesc.inputLayout = BuiltinForwardPipeline::BuildInputLayout(); - if (backendType == RHI::RHIType::D3D12) { - pipelineDesc.vertexShader.source.assign( - kBuiltinForwardHlsl, - kBuiltinForwardHlsl + std::strlen(kBuiltinForwardHlsl)); - pipelineDesc.vertexShader.sourceLanguage = RHI::ShaderLanguage::HLSL; - pipelineDesc.vertexShader.entryPoint = L"MainVS"; - pipelineDesc.vertexShader.profile = L"vs_5_0"; - - pipelineDesc.fragmentShader.source.assign( - kBuiltinForwardHlsl, - kBuiltinForwardHlsl + std::strlen(kBuiltinForwardHlsl)); - pipelineDesc.fragmentShader.sourceLanguage = RHI::ShaderLanguage::HLSL; - pipelineDesc.fragmentShader.entryPoint = L"MainPS"; - pipelineDesc.fragmentShader.profile = L"ps_5_0"; - } else if (backendType == RHI::RHIType::Vulkan) { - pipelineDesc.vertexShader.source.assign( - kBuiltinForwardVulkanVertexShader, - kBuiltinForwardVulkanVertexShader + std::strlen(kBuiltinForwardVulkanVertexShader)); - pipelineDesc.vertexShader.sourceLanguage = RHI::ShaderLanguage::GLSL; - pipelineDesc.vertexShader.profile = L"vs_4_50"; - - pipelineDesc.fragmentShader.source.assign( - kBuiltinForwardVulkanFragmentShader, - kBuiltinForwardVulkanFragmentShader + std::strlen(kBuiltinForwardVulkanFragmentShader)); - pipelineDesc.fragmentShader.sourceLanguage = RHI::ShaderLanguage::GLSL; - pipelineDesc.fragmentShader.profile = L"fs_4_50"; - } else { - pipelineDesc.vertexShader.source.assign( - kBuiltinForwardVertexShader, - kBuiltinForwardVertexShader + std::strlen(kBuiltinForwardVertexShader)); - pipelineDesc.vertexShader.sourceLanguage = RHI::ShaderLanguage::GLSL; - pipelineDesc.vertexShader.profile = L"vs_4_30"; - - pipelineDesc.fragmentShader.source.assign( - kBuiltinForwardFragmentShader, - kBuiltinForwardFragmentShader + std::strlen(kBuiltinForwardFragmentShader)); - pipelineDesc.fragmentShader.sourceLanguage = RHI::ShaderLanguage::GLSL; - pipelineDesc.fragmentShader.profile = L"fs_4_30"; + const Resources::ShaderBackend backend = ToShaderBackend(backendType); + const Resources::ShaderStageVariant* vertexVariant = + shader.FindVariant(passName, Resources::ShaderType::Vertex, backend); + const Resources::ShaderStageVariant* fragmentVariant = + shader.FindVariant(passName, Resources::ShaderType::Fragment, backend); + if (vertexVariant != nullptr) { + ApplyShaderStageVariant(*vertexVariant, pipelineDesc.vertexShader); + } + if (fragmentVariant != nullptr) { + ApplyShaderStageVariant(*fragmentVariant, pipelineDesc.fragmentShader); } return pipelineDesc; @@ -480,6 +367,15 @@ bool BuiltinForwardPipeline::EnsureInitialized(const RenderContext& context) { } bool BuiltinForwardPipeline::CreatePipelineResources(const RenderContext& context) { + m_builtinForwardShader = Resources::ResourceManager::Get().Load( + Resources::GetBuiltinForwardLitShaderPath()); + if (!m_builtinForwardShader.IsValid()) { + Debug::Logger::Get().Error( + Debug::LogCategory::Rendering, + "BuiltinForwardPipeline failed to load builtin forward shader resource"); + return false; + } + RHI::DescriptorSetLayoutBinding constantBinding = {}; constantBinding.binding = 0; constantBinding.type = static_cast(RHI::DescriptorType::CBV); @@ -659,21 +555,67 @@ void BuiltinForwardPipeline::DestroyPipelineResources() { m_device = nullptr; m_initialized = false; + m_builtinForwardShader.Reset(); +} + +BuiltinForwardPipeline::ResolvedShaderPass BuiltinForwardPipeline::ResolveForwardShaderPass( + const Resources::Material* material) const { + ResolvedShaderPass resolved = {}; + const Resources::ShaderBackend backend = ToShaderBackend(m_backendType); + + if (material != nullptr && material->GetShader() != nullptr) { + const Resources::Shader* materialShader = material->GetShader(); + if (const Resources::ShaderPass* shaderPass = + FindForwardCompatiblePass(*materialShader, material, backend)) { + resolved.shader = materialShader; + resolved.pass = shaderPass; + resolved.passName = shaderPass->name; + return resolved; + } + } + + if (m_builtinForwardShader.IsValid()) { + const Resources::Shader* builtinShader = m_builtinForwardShader.Get(); + if (const Resources::ShaderPass* shaderPass = + FindForwardCompatiblePass(*builtinShader, nullptr, backend)) { + resolved.shader = builtinShader; + resolved.pass = shaderPass; + resolved.passName = shaderPass->name; + } + } + + return resolved; } RHI::RHIPipelineState* BuiltinForwardPipeline::GetOrCreatePipelineState( const RenderContext& context, const Resources::Material* material) { - const Resources::MaterialRenderState renderState = - material != nullptr ? material->GetRenderState() : Resources::MaterialRenderState(); + const ResolvedShaderPass resolvedShaderPass = ResolveForwardShaderPass(material); + if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) { + Debug::Logger::Get().Error( + Debug::LogCategory::Rendering, + "BuiltinForwardPipeline could not resolve a valid ForwardLit shader pass"); + return nullptr; + } - const auto existing = m_pipelineStates.find(renderState); + PipelineStateKey pipelineKey = {}; + pipelineKey.renderState = + material != nullptr ? material->GetRenderState() : Resources::MaterialRenderState(); + pipelineKey.shader = resolvedShaderPass.shader; + pipelineKey.passName = resolvedShaderPass.passName; + + const auto existing = m_pipelineStates.find(pipelineKey); if (existing != m_pipelineStates.end()) { return existing->second; } const RHI::GraphicsPipelineDesc pipelineDesc = - CreatePipelineDesc(context.backendType, m_pipelineLayout, material); + CreatePipelineDesc( + context.backendType, + m_pipelineLayout, + *resolvedShaderPass.shader, + resolvedShaderPass.passName, + material); RHI::RHIPipelineState* pipelineState = context.device->CreatePipelineState(pipelineDesc); if (pipelineState == nullptr || !pipelineState->IsValid()) { Debug::Logger::Get().Error( @@ -686,7 +628,7 @@ RHI::RHIPipelineState* BuiltinForwardPipeline::GetOrCreatePipelineState( return nullptr; } - m_pipelineStates.emplace(renderState, pipelineState); + m_pipelineStates.emplace(pipelineKey, pipelineState); return pipelineState; } diff --git a/engine/src/Resources/BuiltinResources.cpp b/engine/src/Resources/BuiltinResources.cpp index 0ef7acd1..8f66d53c 100644 --- a/engine/src/Resources/BuiltinResources.cpp +++ b/engine/src/Resources/BuiltinResources.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -20,8 +21,10 @@ namespace { constexpr const char* kBuiltinPrefix = "builtin://"; constexpr const char* kBuiltinMeshPrefix = "builtin://meshes/"; constexpr const char* kBuiltinMaterialPrefix = "builtin://materials/"; +constexpr const char* kBuiltinShaderPrefix = "builtin://shaders/"; constexpr const char* kBuiltinTexturePrefix = "builtin://textures/"; constexpr const char* kBuiltinDefaultPrimitiveMaterialPath = "builtin://materials/default-primitive"; +constexpr const char* kBuiltinForwardLitShaderPath = "builtin://shaders/forward-lit"; constexpr const char* kBuiltinDefaultPrimitiveTexturePath = "builtin://textures/default-primitive-albedo"; constexpr float kPi = 3.14159265358979323846f; @@ -30,6 +33,187 @@ struct MeshBuffers { std::vector indices; }; +const char kBuiltinForwardHlsl[] = R"( +Texture2D gBaseColorTexture : register(t1); +SamplerState gLinearSampler : register(s1); + +cbuffer PerObjectConstants : register(b1) { + float4x4 gProjectionMatrix; + float4x4 gViewMatrix; + float4x4 gModelMatrix; + float4x4 gNormalMatrix; + float4 gMainLightDirectionAndIntensity; + float4 gMainLightColorAndFlags; +}; + +cbuffer MaterialConstants : register(b2) { + float4 gBaseColorFactor; +}; + +struct VSInput { + float3 position : POSITION; + float3 normal : NORMAL; + float2 texcoord : TEXCOORD0; +}; + +struct PSInput { + float4 position : SV_POSITION; + float3 normalWS : TEXCOORD0; + float2 texcoord : TEXCOORD1; +}; + +PSInput MainVS(VSInput input) { + PSInput output; + float4 positionWS = mul(gModelMatrix, float4(input.position, 1.0f)); + float4 positionVS = mul(gViewMatrix, positionWS); + output.position = mul(gProjectionMatrix, positionVS); + output.normalWS = mul((float3x3)gNormalMatrix, input.normal); + output.texcoord = input.texcoord; + return output; +} + +float4 MainPS(PSInput input) : SV_TARGET { + float4 baseColor = gBaseColorTexture.Sample(gLinearSampler, input.texcoord) * gBaseColorFactor; + if (gMainLightColorAndFlags.a < 0.5f) { + return baseColor; + } + + float3 normalWS = normalize(input.normalWS); + float3 directionToLightWS = normalize(gMainLightDirectionAndIntensity.xyz); + float diffuse = saturate(dot(normalWS, directionToLightWS)); + float3 lighting = float3(0.28f, 0.28f, 0.28f) + + gMainLightColorAndFlags.rgb * (diffuse * gMainLightDirectionAndIntensity.w); + return float4(baseColor.rgb * lighting, baseColor.a); +} +)"; + +const char kBuiltinForwardVertexShader[] = R"(#version 430 +layout(location = 0) in vec3 aPosition; +layout(location = 1) in vec3 aNormal; +layout(location = 2) in vec2 aTexCoord; + +layout(std140, binding = 1) uniform PerObjectConstants { + mat4 gProjectionMatrix; + mat4 gViewMatrix; + mat4 gModelMatrix; + mat4 gNormalMatrix; + vec4 gMainLightDirectionAndIntensity; + vec4 gMainLightColorAndFlags; +}; + +out vec3 vNormalWS; +out vec2 vTexCoord; + +void main() { + vec4 positionWS = gModelMatrix * vec4(aPosition, 1.0); + vec4 positionVS = gViewMatrix * positionWS; + gl_Position = gProjectionMatrix * positionVS; + vNormalWS = mat3(gNormalMatrix) * aNormal; + vTexCoord = aTexCoord; +} +)"; + +const char kBuiltinForwardFragmentShader[] = R"(#version 430 +layout(binding = 1) uniform sampler2D uBaseColorTexture; + +layout(std140, binding = 1) uniform PerObjectConstants { + mat4 gProjectionMatrix; + mat4 gViewMatrix; + mat4 gModelMatrix; + mat4 gNormalMatrix; + vec4 gMainLightDirectionAndIntensity; + vec4 gMainLightColorAndFlags; +}; + +layout(std140, binding = 2) uniform MaterialConstants { + vec4 gBaseColorFactor; +}; + +in vec3 vNormalWS; +in vec2 vTexCoord; + +layout(location = 0) out vec4 fragColor; + +void main() { + vec4 baseColor = texture(uBaseColorTexture, vTexCoord) * gBaseColorFactor; + if (gMainLightColorAndFlags.w < 0.5) { + fragColor = baseColor; + return; + } + + vec3 normalWS = normalize(vNormalWS); + vec3 directionToLightWS = normalize(gMainLightDirectionAndIntensity.xyz); + float diffuse = max(dot(normalWS, directionToLightWS), 0.0); + vec3 lighting = vec3(0.28) + + gMainLightColorAndFlags.rgb * (diffuse * gMainLightDirectionAndIntensity.w); + fragColor = vec4(baseColor.rgb * lighting, baseColor.a); +} +)"; + +const char kBuiltinForwardVulkanVertexShader[] = R"(#version 450 +layout(location = 0) in vec3 aPosition; +layout(location = 1) in vec3 aNormal; +layout(location = 2) in vec2 aTexCoord; + +layout(set = 1, binding = 0, std140) uniform PerObjectConstants { + mat4 gProjectionMatrix; + mat4 gViewMatrix; + mat4 gModelMatrix; + mat4 gNormalMatrix; + vec4 gMainLightDirectionAndIntensity; + vec4 gMainLightColorAndFlags; +}; + +layout(location = 0) out vec3 vNormalWS; +layout(location = 1) out vec2 vTexCoord; + +void main() { + vec4 positionWS = gModelMatrix * vec4(aPosition, 1.0); + vec4 positionVS = gViewMatrix * positionWS; + gl_Position = gProjectionMatrix * positionVS; + vNormalWS = mat3(gNormalMatrix) * aNormal; + vTexCoord = aTexCoord; +} +)"; + +const char kBuiltinForwardVulkanFragmentShader[] = R"(#version 450 +layout(set = 3, binding = 0) uniform texture2D uBaseColorTexture; +layout(set = 4, binding = 0) uniform sampler uLinearSampler; + +layout(set = 1, binding = 0, std140) uniform PerObjectConstants { + mat4 gProjectionMatrix; + mat4 gViewMatrix; + mat4 gModelMatrix; + mat4 gNormalMatrix; + vec4 gMainLightDirectionAndIntensity; + vec4 gMainLightColorAndFlags; +}; + +layout(set = 2, binding = 0, std140) uniform MaterialConstants { + vec4 gBaseColorFactor; +}; + +layout(location = 0) in vec3 vNormalWS; +layout(location = 1) in vec2 vTexCoord; + +layout(location = 0) out vec4 fragColor; + +void main() { + vec4 baseColor = texture(sampler2D(uBaseColorTexture, uLinearSampler), vTexCoord) * gBaseColorFactor; + if (gMainLightColorAndFlags.w < 0.5) { + fragColor = baseColor; + return; + } + + vec3 normalWS = normalize(vNormalWS); + vec3 directionToLightWS = normalize(gMainLightDirectionAndIntensity.xyz); + float diffuse = max(dot(normalWS, directionToLightWS), 0.0); + vec3 lighting = vec3(0.28) + + gMainLightColorAndFlags.rgb * (diffuse * gMainLightDirectionAndIntensity.w); + fragColor = vec4(baseColor.rgb * lighting, baseColor.a); +} +)"; + Math::Bounds ComputeBounds(const std::vector& vertices) { if (vertices.empty()) { return Math::Bounds(); @@ -481,6 +665,117 @@ Mesh* BuildMeshResource( return mesh; } +void AddBuiltinShaderStageVariant( + Shader& shader, + const Containers::String& passName, + ShaderType stage, + ShaderLanguage language, + ShaderBackend backend, + const char* sourceCode, + const char* entryPoint, + const char* profile) { + ShaderStageVariant variant = {}; + variant.stage = stage; + variant.language = language; + variant.backend = backend; + variant.sourceCode = Containers::String(sourceCode); + variant.entryPoint = Containers::String(entryPoint); + variant.profile = Containers::String(profile); + shader.AddPassVariant(passName, variant); +} + +size_t CalculateBuiltinShaderMemorySize(const Shader& shader) { + size_t memorySize = sizeof(Shader) + shader.GetName().Length() + shader.GetPath().Length(); + for (const ShaderPass& pass : shader.GetPasses()) { + memorySize += pass.name.Length(); + for (const ShaderPassTagEntry& tag : pass.tags) { + memorySize += tag.name.Length(); + memorySize += tag.value.Length(); + } + for (const ShaderStageVariant& variant : pass.variants) { + memorySize += variant.entryPoint.Length(); + memorySize += variant.profile.Length(); + memorySize += variant.sourceCode.Length(); + memorySize += variant.compiledBinary.Size(); + } + } + + return memorySize; +} + +Shader* BuildBuiltinForwardLitShader(const Containers::String& path) { + auto* shader = new Shader(); + IResource::ConstructParams params; + params.name = Containers::String("Builtin Forward Lit"); + params.path = path; + params.guid = ResourceGUID::Generate(path); + params.memorySize = 0; + shader->Initialize(params); + + const Containers::String passName("ForwardLit"); + shader->SetPassTag(passName, "LightMode", "ForwardBase"); + + AddBuiltinShaderStageVariant( + *shader, + passName, + ShaderType::Vertex, + ShaderLanguage::HLSL, + ShaderBackend::D3D12, + kBuiltinForwardHlsl, + "MainVS", + "vs_5_0"); + AddBuiltinShaderStageVariant( + *shader, + passName, + ShaderType::Fragment, + ShaderLanguage::HLSL, + ShaderBackend::D3D12, + kBuiltinForwardHlsl, + "MainPS", + "ps_5_0"); + + AddBuiltinShaderStageVariant( + *shader, + passName, + ShaderType::Vertex, + ShaderLanguage::GLSL, + ShaderBackend::OpenGL, + kBuiltinForwardVertexShader, + "main", + "vs_4_30"); + AddBuiltinShaderStageVariant( + *shader, + passName, + ShaderType::Fragment, + ShaderLanguage::GLSL, + ShaderBackend::OpenGL, + kBuiltinForwardFragmentShader, + "main", + "fs_4_30"); + + AddBuiltinShaderStageVariant( + *shader, + passName, + ShaderType::Vertex, + ShaderLanguage::GLSL, + ShaderBackend::Vulkan, + kBuiltinForwardVulkanVertexShader, + "main", + "vs_4_50"); + AddBuiltinShaderStageVariant( + *shader, + passName, + ShaderType::Fragment, + ShaderLanguage::GLSL, + ShaderBackend::Vulkan, + kBuiltinForwardVulkanFragmentShader, + "main", + "fs_4_50"); + + shader->m_memorySize = CalculateBuiltinShaderMemorySize(*shader); + return shader; +} + Material* BuildDefaultPrimitiveMaterial(const Containers::String& path) { auto* material = new Material(); IResource::ConstructParams params; @@ -494,6 +789,7 @@ Material* BuildDefaultPrimitiveMaterial(const Containers::String& path) { renderState.cullMode = MaterialCullMode::Back; material->SetRenderState(renderState); material->SetRenderQueue(MaterialRenderQueue::Geometry); + material->SetShader(ResourceManager::Get().Load(GetBuiltinForwardLitShaderPath())); material->SetTexture( Containers::String("baseColorTexture"), ResourceManager::Get().Load(GetBuiltinDefaultPrimitiveTexturePath())); @@ -542,6 +838,10 @@ bool IsBuiltinMaterialPath(const Containers::String& path) { return path.StartsWith(kBuiltinMaterialPrefix); } +bool IsBuiltinShaderPath(const Containers::String& path) { + return path.StartsWith(kBuiltinShaderPrefix); +} + bool IsBuiltinTexturePath(const Containers::String& path) { return path.StartsWith(kBuiltinTexturePrefix); } @@ -574,6 +874,10 @@ Containers::String GetBuiltinDefaultPrimitiveMaterialPath() { return Containers::String(kBuiltinDefaultPrimitiveMaterialPath); } +Containers::String GetBuiltinForwardLitShaderPath() { + return Containers::String(kBuiltinForwardLitShaderPath); +} + Containers::String GetBuiltinDefaultPrimitiveTexturePath() { return Containers::String(kBuiltinDefaultPrimitiveTexturePath); } @@ -660,6 +964,19 @@ LoadResult CreateBuiltinMaterialResource(const Containers::String& path) { return LoadResult(material); } +LoadResult CreateBuiltinShaderResource(const Containers::String& path) { + if (path != GetBuiltinForwardLitShaderPath()) { + return LoadResult(Containers::String("Unknown builtin shader: ") + path); + } + + Shader* shader = BuildBuiltinForwardLitShader(path); + if (shader == nullptr) { + return LoadResult(Containers::String("Failed to create builtin shader: ") + path); + } + + return LoadResult(shader); +} + LoadResult CreateBuiltinTextureResource(const Containers::String& path) { if (path != GetBuiltinDefaultPrimitiveTexturePath()) { return LoadResult(Containers::String("Unknown builtin texture: ") + path); diff --git a/engine/src/Resources/Shader/ShaderLoader.cpp b/engine/src/Resources/Shader/ShaderLoader.cpp index c2d86004..8d0c3859 100644 --- a/engine/src/Resources/Shader/ShaderLoader.cpp +++ b/engine/src/Resources/Shader/ShaderLoader.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -22,12 +23,20 @@ Containers::Array ShaderLoader::GetSupportedExtensions() con } bool ShaderLoader::CanLoad(const Containers::String& path) const { + if (IsBuiltinShaderPath(path)) { + return true; + } + Containers::String ext = GetExtension(path); return ext == "vert" || ext == "frag" || ext == "geom" || ext == "comp" || ext == "glsl" || ext == "hlsl" || ext == "shader"; } LoadResult ShaderLoader::Load(const Containers::String& path, const ImportSettings* settings) { + if (IsBuiltinShaderPath(path)) { + return CreateBuiltinShaderResource(path); + } + Containers::Array data = ReadFileData(path); if (data.Empty()) { return LoadResult("Failed to read shader file: " + path); diff --git a/tests/Resources/Shader/test_shader_loader.cpp b/tests/Resources/Shader/test_shader_loader.cpp index 02f4b32e..b9fa8b24 100644 --- a/tests/Resources/Shader/test_shader_loader.cpp +++ b/tests/Resources/Shader/test_shader_loader.cpp @@ -1,4 +1,6 @@ #include +#include +#include #include #include #include @@ -29,6 +31,7 @@ TEST(ShaderLoader, CanLoad) { EXPECT_TRUE(loader.CanLoad("test.frag")); EXPECT_TRUE(loader.CanLoad("test.glsl")); EXPECT_TRUE(loader.CanLoad("test.hlsl")); + EXPECT_TRUE(loader.CanLoad(GetBuiltinForwardLitShaderPath())); EXPECT_FALSE(loader.CanLoad("test.txt")); EXPECT_FALSE(loader.CanLoad("test.png")); } @@ -70,4 +73,43 @@ TEST(ShaderLoader, LoadLegacySingleStageShaderBuildsDefaultPassVariant) { std::remove(shaderPath.string().c_str()); } +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); + + 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); + + manager.Shutdown(); +} + } // namespace