diff --git a/docs/used/SRP_UniversalShadowOwnershipAndBuildHygienePlan_完成归档_2026-04-21.md b/docs/used/SRP_UniversalShadowOwnershipAndBuildHygienePlan_完成归档_2026-04-21.md new file mode 100644 index 00000000..070a60fc --- /dev/null +++ b/docs/used/SRP_UniversalShadowOwnershipAndBuildHygienePlan_完成归档_2026-04-21.md @@ -0,0 +1,64 @@ +# SRP Universal Shadow Ownership And Build Hygiene Plan 2026-04-21 + +## Goal + +Close two issues that are now blocking a clean SRP mainline: + +1. Rendering/managed source files already used by the build are still missing from git tracking. +2. Main directional shadow enablement is still primarily owned by native default policy instead of the managed URP asset layer. + +This stage does not rewrite shadow rendering implementation. It only moves the first ownership decision to the managed URP asset and fixes repository hygiene for rendering-related source files that are already part of the build. + +## Current Problems + +1. `managed/CMakeLists.txt` already references `XCEngine.ScriptCore/Rendering/Graph/*`, but those files are still untracked. +2. Native rendering code includes `Rendering/Internal/RenderSurfacePipelineUtils.h` and `Rendering/Internal/ShaderVariantUtils.h`, but the corresponding public/internal headers are still untracked. +3. `RenderPipelineAsset::ConfigureCameraRenderRequest(...)` still applies default directional shadow planning first. +4. Managed URP can disable shadows only indirectly through a renderer feature, which is weaker than Unity-like asset ownership. + +## Scope + +Included: + +1. Track the missing managed render graph source files used by the current build. +2. Track the missing rendering utility headers used by native rendering code. +3. Add a managed URP asset-level shadow settings object with a first setting for main light shadow enablement. +4. Apply that managed setting during camera request configuration. +5. Rebuild `XCEditor`. +6. Run old editor smoke for at least 10 seconds. + +Not included: + +1. Rewriting shadow GPU passes in C#. +2. Adding new shadow map quality/resolution/cascade controls. +3. Deferred renderer. +4. Editor UI for these new asset settings. + +## Implementation Steps + +### Step 1 + +Track missing rendering/scriptcore source files that are already build dependencies: + +1. `managed/XCEngine.ScriptCore/Rendering/Graph/*` +2. `engine/include/XCEngine/Rendering/RenderSurfacePipelineUtils.h` +3. `engine/include/XCEngine/Rendering/ShaderVariantUtils.h` +4. `engine/include/XCEngine/Rendering/Internal/*` + +### Step 2 + +Add managed request control for shadow ownership: + +1. Expose directional shadow request state on `CameraRenderRequestContext`. +2. Expose the same control on `RendererCameraRequestContext`. +3. Add `UniversalShadowSettings`. +4. Let `UniversalRenderPipelineAsset` clear the default main directional shadow request when the managed asset disables it. + +## Acceptance + +This stage is complete when: + +1. Clean checkout no longer depends on untracked rendering/scriptcore source files. +2. `UniversalRenderPipelineAsset` owns the first managed decision for whether main directional shadows stay enabled. +3. `XCEditor` builds successfully. +4. Old editor smoke reaches scene-ready logs without regression. diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index aa58156c..963917d0 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -537,8 +537,12 @@ add_library(XCEngine STATIC ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/RenderPipeline.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/RenderPipelineAsset.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/RenderSurface.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/RenderSurfacePipelineUtils.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/SceneRenderFeaturePass.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/SceneRenderFeatureHost.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/ShaderVariantUtils.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Internal/RenderSurfacePipelineUtils.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Internal/ShaderVariantUtils.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Execution/RenderPipelineHost.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Caches/RenderResourceCache.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Caches/DirectionalShadowSurfaceCache.h diff --git a/engine/include/XCEngine/Rendering/Internal/RenderSurfacePipelineUtils.h b/engine/include/XCEngine/Rendering/Internal/RenderSurfacePipelineUtils.h new file mode 100644 index 00000000..149c9a2a --- /dev/null +++ b/engine/include/XCEngine/Rendering/Internal/RenderSurfacePipelineUtils.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +namespace XCEngine { +namespace Rendering { +namespace Internal { + +using ::XCEngine::Rendering::ApplySingleColorAttachmentPropertiesToGraphicsPipelineDesc; +using ::XCEngine::Rendering::ApplySurfacePropertiesToGraphicsPipelineDesc; +using ::XCEngine::Rendering::HasKnownDepthAttachmentFormat; +using ::XCEngine::Rendering::HasKnownSingleColorAttachmentFormat; +using ::XCEngine::Rendering::HasSingleColorAttachment; +using ::XCEngine::Rendering::HasValidPipelineSampleDescription; +using ::XCEngine::Rendering::HasZeroOrSingleColorAttachment; +using ::XCEngine::Rendering::IsDepthStyleCompatibleSurface; +using ::XCEngine::Rendering::ResolveSurfaceColorAttachmentCount; +using ::XCEngine::Rendering::ResolveSurfaceColorFormat; +using ::XCEngine::Rendering::ResolveSurfaceColorFormats; +using ::XCEngine::Rendering::ResolveSurfaceDepthFormat; +using ::XCEngine::Rendering::ResolveSurfaceSampleCount; +using ::XCEngine::Rendering::ResolveSurfaceSampleQuality; +using ::XCEngine::Rendering::TryResolveSingleColorAttachmentFormat; +inline constexpr std::uint32_t kMaxPipelineColorAttachmentCount = + ::XCEngine::Rendering::kMaxPipelineColorAttachmentCount; + +} // namespace Internal +} // namespace Rendering +} // namespace XCEngine diff --git a/engine/include/XCEngine/Rendering/Internal/ShaderVariantUtils.h b/engine/include/XCEngine/Rendering/Internal/ShaderVariantUtils.h new file mode 100644 index 00000000..60e710c6 --- /dev/null +++ b/engine/include/XCEngine/Rendering/Internal/ShaderVariantUtils.h @@ -0,0 +1,31 @@ +#pragma once + +#include + +namespace XCEngine { +namespace Rendering { +namespace Internal { + +using ::XCEngine::Rendering::AddShaderCompileMacro; +using ::XCEngine::Rendering::ApplyShaderStageVariant; +using ::XCEngine::Rendering::BuildRuntimeShaderSource; +using ::XCEngine::Rendering::BuildShaderKeywordSignature; +using ::XCEngine::Rendering::EscapeRegexLiteral; +using ::XCEngine::Rendering::InjectShaderBackendMacros; +using ::XCEngine::Rendering::ResolveCompiledBinaryBackend; +using ::XCEngine::Rendering::ResolveRuntimeShaderSourcePath; +using ::XCEngine::Rendering::ShaderPassHasGraphicsVariants; +using ::XCEngine::Rendering::ToRHIShaderBinaryBackend; +using ::XCEngine::Rendering::ToRHIShaderLanguage; +using ::XCEngine::Rendering::ToShaderBackend; +using ::XCEngine::Rendering::ToStdString; +using ::XCEngine::Rendering::ToWideAscii; +using ::XCEngine::Rendering::TryBuildRuntimeShaderBindings; +using ::XCEngine::Rendering::TryCollectShaderPassResourceBindings; +using ::XCEngine::Rendering::TryGetHlslRegisterPrefix; +using ::XCEngine::Rendering::TryRewriteHlslRegisterBinding; +using ::XCEngine::Rendering::TryRewriteHlslRegisterBindingWithName; + +} // namespace Internal +} // namespace Rendering +} // namespace XCEngine diff --git a/engine/include/XCEngine/Rendering/RenderSurfacePipelineUtils.h b/engine/include/XCEngine/Rendering/RenderSurfacePipelineUtils.h new file mode 100644 index 00000000..8a88fbc3 --- /dev/null +++ b/engine/include/XCEngine/Rendering/RenderSurfacePipelineUtils.h @@ -0,0 +1,145 @@ +#pragma once + +#include +#include + +#include +#include + +namespace XCEngine { +namespace Rendering { + +inline constexpr std::uint32_t kMaxPipelineColorAttachmentCount = 8u; + +inline std::uint32_t ResolveSurfaceColorAttachmentCount( + const RenderSurface& surface) { + std::uint32_t count = 0; + for (RHI::RHIResourceView* attachment : surface.GetColorAttachments()) { + if (attachment == nullptr || count >= kMaxPipelineColorAttachmentCount) { + break; + } + + ++count; + } + + return count; +} + +inline bool HasSingleColorAttachment(const RenderSurface& surface) { + return ResolveSurfaceColorAttachmentCount(surface) == 1u; +} + +inline bool HasZeroOrSingleColorAttachment(const RenderSurface& surface) { + return ResolveSurfaceColorAttachmentCount(surface) <= 1u; +} + +inline RHI::Format ResolveSurfaceColorFormat( + const RenderSurface& surface, + std::uint32_t attachmentIndex = 0u) { + const std::vector& colorAttachments = + surface.GetColorAttachments(); + if (attachmentIndex >= colorAttachments.size() || + colorAttachments[attachmentIndex] == nullptr) { + return RHI::Format::Unknown; + } + + return colorAttachments[attachmentIndex]->GetFormat(); +} + +inline bool TryResolveSingleColorAttachmentFormat( + const RenderSurface& surface, + RHI::Format& outFormat) { + if (!HasSingleColorAttachment(surface)) { + outFormat = RHI::Format::Unknown; + return false; + } + + outFormat = ResolveSurfaceColorFormat(surface, 0u); + return outFormat != RHI::Format::Unknown; +} + +inline bool HasKnownSingleColorAttachmentFormat(const RenderSurface& surface) { + return !HasSingleColorAttachment(surface) || + ResolveSurfaceColorFormat(surface, 0u) != RHI::Format::Unknown; +} + +inline std::array +ResolveSurfaceColorFormats(const RenderSurface& surface) { + std::array formats = {}; + const std::uint32_t colorAttachmentCount = + ResolveSurfaceColorAttachmentCount(surface); + for (std::uint32_t attachmentIndex = 0; + attachmentIndex < colorAttachmentCount; + ++attachmentIndex) { + formats[attachmentIndex] = static_cast( + ResolveSurfaceColorFormat(surface, attachmentIndex)); + } + + return formats; +} + +inline RHI::Format ResolveSurfaceDepthFormat(const RenderSurface& surface) { + if (RHI::RHIResourceView* depthAttachment = surface.GetDepthAttachment(); + depthAttachment != nullptr) { + return depthAttachment->GetFormat(); + } + + return RHI::Format::Unknown; +} + +inline bool HasKnownDepthAttachmentFormat(const RenderSurface& surface) { + return ResolveSurfaceDepthFormat(surface) != RHI::Format::Unknown; +} + +inline std::uint32_t ResolveSurfaceSampleCount(const RenderSurface& surface) { + return surface.GetSampleCount(); +} + +inline std::uint32_t ResolveSurfaceSampleQuality(const RenderSurface& surface) { + return surface.GetSampleQuality(); +} + +inline bool HasValidPipelineSampleDescription(const RenderSurface& surface) { + const std::uint32_t sampleCount = ResolveSurfaceSampleCount(surface); + const std::uint32_t sampleQuality = ResolveSurfaceSampleQuality(surface); + return sampleCount > 0u && (sampleCount > 1u || sampleQuality == 0u); +} + +inline bool IsDepthStyleCompatibleSurface(const RenderSurface& surface) { + return HasZeroOrSingleColorAttachment(surface) && + HasKnownSingleColorAttachmentFormat(surface) && + HasKnownDepthAttachmentFormat(surface) && + HasValidPipelineSampleDescription(surface); +} + +inline void ApplySurfacePropertiesToGraphicsPipelineDesc( + const RenderSurface& surface, + RHI::GraphicsPipelineDesc& pipelineDesc) { + const std::array + renderTargetFormats = ResolveSurfaceColorFormats(surface); + pipelineDesc.renderTargetCount = ResolveSurfaceColorAttachmentCount(surface); + for (std::uint32_t attachmentIndex = 0; + attachmentIndex < pipelineDesc.renderTargetCount; + ++attachmentIndex) { + pipelineDesc.renderTargetFormats[attachmentIndex] = + renderTargetFormats[attachmentIndex]; + } + + pipelineDesc.depthStencilFormat = + static_cast(ResolveSurfaceDepthFormat(surface)); + pipelineDesc.sampleCount = ResolveSurfaceSampleCount(surface); + pipelineDesc.sampleQuality = ResolveSurfaceSampleQuality(surface); +} + +inline void ApplySingleColorAttachmentPropertiesToGraphicsPipelineDesc( + const RenderSurface& surface, + RHI::GraphicsPipelineDesc& pipelineDesc) { + pipelineDesc.renderTargetCount = HasSingleColorAttachment(surface) ? 1u : 0u; + pipelineDesc.renderTargetFormats[0] = + static_cast(ResolveSurfaceColorFormat(surface, 0u)); + pipelineDesc.sampleCount = ResolveSurfaceSampleCount(surface); + pipelineDesc.sampleQuality = ResolveSurfaceSampleQuality(surface); +} + +} // namespace Rendering +} // namespace XCEngine diff --git a/engine/include/XCEngine/Rendering/ShaderVariantUtils.h b/engine/include/XCEngine/Rendering/ShaderVariantUtils.h new file mode 100644 index 00000000..a6db9a1e --- /dev/null +++ b/engine/include/XCEngine/Rendering/ShaderVariantUtils.h @@ -0,0 +1,574 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace XCEngine { +namespace Rendering { + +inline 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; + } +} + +inline 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; + } +} + +inline RHI::ShaderBinaryBackend ToRHIShaderBinaryBackend( + Resources::ShaderBackend backend) { + switch (backend) { + case Resources::ShaderBackend::D3D12: + return RHI::ShaderBinaryBackend::D3D12; + case Resources::ShaderBackend::OpenGL: + return RHI::ShaderBinaryBackend::OpenGL; + case Resources::ShaderBackend::Vulkan: + return RHI::ShaderBinaryBackend::Vulkan; + case Resources::ShaderBackend::Generic: + default: + return RHI::ShaderBinaryBackend::Unknown; + } +} + +inline RHI::ShaderBinaryBackend ResolveCompiledBinaryBackend( + Resources::ShaderBackend backend, + const Resources::ShaderStageVariant& variant) { + if (backend == Resources::ShaderBackend::OpenGL && + variant.language == Resources::ShaderLanguage::HLSL) { + return RHI::ShaderBinaryBackend::Vulkan; + } + + return ToRHIShaderBinaryBackend(backend); +} + +inline 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; +} + +inline std::string ToStdString(const Containers::String& value) { + return std::string(value.CStr(), value.Length()); +} + +inline std::string EscapeRegexLiteral(const Containers::String& value) { + std::string escaped; + escaped.reserve(value.Length() * 2u); + for (size_t index = 0; index < value.Length(); ++index) { + const char ch = value[index]; + switch (ch) { + case '\\': + case '^': + case '$': + case '.': + case '|': + case '?': + case '*': + case '+': + case '(': + case ')': + case '[': + case ']': + case '{': + case '}': + escaped.push_back('\\'); + break; + default: + break; + } + + escaped.push_back(ch); + } + + return escaped; +} + +inline bool TryCollectShaderPassResourceBindings( + const Resources::ShaderPass& pass, + Containers::Array& outBindings) { + outBindings.Clear(); + + if (pass.resources.Empty()) { + return false; + } + + outBindings.Reserve(pass.resources.Size()); + for (const Resources::ShaderResourceBindingDesc& binding : pass.resources) { + outBindings.PushBack(binding); + } + + return true; +} + +inline bool TryRewriteHlslRegisterBindingWithName( + std::string& sourceText, + const Containers::String& declarationName, + const char* registerPrefix, + Core::uint32 bindingIndex, + Core::uint32 setIndex, + bool includeRegisterSpace, + Resources::ShaderResourceType resourceType) { + if (declarationName.Empty()) { + return false; + } + + const std::string registerClause = + includeRegisterSpace + ? std::string("register(") + registerPrefix + + std::to_string(bindingIndex) + ", space" + + std::to_string(setIndex) + ")" + : std::string("register(") + registerPrefix + + std::to_string(bindingIndex) + ")"; + const std::string escapedName = EscapeRegexLiteral(declarationName); + + if (resourceType == Resources::ShaderResourceType::ConstantBuffer) { + const std::regex pattern( + "(cbuffer\\s+" + escapedName + + "\\s*)(:\\s*register\\s*\\([^\\)]*\\))?(\\s*\\{)", + std::regex::ECMAScript); + const std::string rewritten = + std::regex_replace(sourceText, pattern, "$1: " + registerClause + "$3"); + if (rewritten != sourceText) { + sourceText = rewritten; + return true; + } + + return false; + } + + if (resourceType == Resources::ShaderResourceType::StructuredBuffer || + resourceType == Resources::ShaderResourceType::RWStructuredBuffer) { + const std::regex pattern( + "((?:globallycoherent\\s+)?(?:StructuredBuffer|RWStructuredBuffer)\\s*<[^;\\r\\n>]+>\\s+" + + escapedName + "\\s*)(:\\s*register\\s*\\([^\\)]*\\))?(\\s*;)", + std::regex::ECMAScript); + const std::string rewritten = + std::regex_replace(sourceText, pattern, "$1: " + registerClause + "$3"); + if (rewritten != sourceText) { + sourceText = rewritten; + return true; + } + + return false; + } + + if (resourceType == Resources::ShaderResourceType::RawBuffer || + resourceType == Resources::ShaderResourceType::RWRawBuffer) { + const std::regex pattern( + "((?:globallycoherent\\s+)?(?:ByteAddressBuffer|RWByteAddressBuffer)\\s+" + + escapedName + "\\s*)(:\\s*register\\s*\\([^\\)]*\\))?(\\s*;)", + std::regex::ECMAScript); + const std::string rewritten = + std::regex_replace(sourceText, pattern, "$1: " + registerClause + "$3"); + if (rewritten != sourceText) { + sourceText = rewritten; + return true; + } + + return false; + } + + const std::regex pattern( + "((?:Texture2D|TextureCube|SamplerState|SamplerComparisonState)\\s+" + + escapedName + "\\s*)(:\\s*register\\s*\\([^\\)]*\\))?(\\s*;)", + std::regex::ECMAScript); + const std::string rewritten = + std::regex_replace(sourceText, pattern, "$1: " + registerClause + "$3"); + if (rewritten != sourceText) { + sourceText = rewritten; + return true; + } + + return false; +} + +inline const char* TryGetHlslRegisterPrefix(Resources::ShaderResourceType type) { + switch (type) { + case Resources::ShaderResourceType::ConstantBuffer: + return "b"; + case Resources::ShaderResourceType::Texture2D: + case Resources::ShaderResourceType::TextureCube: + case Resources::ShaderResourceType::StructuredBuffer: + case Resources::ShaderResourceType::RawBuffer: + return "t"; + case Resources::ShaderResourceType::Sampler: + return "s"; + case Resources::ShaderResourceType::RWStructuredBuffer: + case Resources::ShaderResourceType::RWRawBuffer: + return "u"; + default: + return nullptr; + } +} + +inline bool TryRewriteHlslRegisterBinding( + std::string& sourceText, + const Resources::ShaderResourceBindingDesc& binding, + bool includeRegisterSpace) { + const char* registerPrefix = TryGetHlslRegisterPrefix(binding.type); + if (registerPrefix == nullptr) { + return false; + } + + return TryRewriteHlslRegisterBindingWithName( + sourceText, + binding.name, + registerPrefix, + binding.binding, + binding.set, + includeRegisterSpace, + binding.type); +} + +inline bool TryBuildRuntimeShaderBindings( + const Resources::ShaderPass& pass, + Resources::ShaderBackend backend, + Containers::Array& outBindings, + bool& outIncludeRegisterSpace) { + outBindings.Clear(); + outIncludeRegisterSpace = false; + + if (!TryCollectShaderPassResourceBindings(pass, outBindings)) { + return false; + } + + if (backend == Resources::ShaderBackend::Vulkan) { + outIncludeRegisterSpace = true; + return true; + } + + if (backend != Resources::ShaderBackend::D3D12 && + backend != Resources::ShaderBackend::OpenGL) { + outBindings.Clear(); + return false; + } + + auto sortBindingIndices = [&outBindings](auto&& predicate) { + std::vector indices; + indices.reserve(outBindings.Size()); + for (size_t index = 0; index < outBindings.Size(); ++index) { + if (predicate(outBindings[index])) { + indices.push_back(index); + } + } + + std::sort( + indices.begin(), + indices.end(), + [&outBindings](size_t leftIndex, size_t rightIndex) { + const Resources::ShaderResourceBindingDesc& left = + outBindings[leftIndex]; + const Resources::ShaderResourceBindingDesc& right = + outBindings[rightIndex]; + if (left.set != right.set) { + return left.set < right.set; + } + return left.binding < right.binding; + }); + return indices; + }; + + Core::uint32 nextConstantBufferRegister = 0; + Core::uint32 nextTextureRegister = 0; + Core::uint32 nextSamplerRegister = 0; + Core::uint32 nextUnorderedAccessRegister = 0; + Core::uint32 nextStorageBufferRegister = 0; + + const auto constantBufferIndices = sortBindingIndices( + [](const Resources::ShaderResourceBindingDesc& binding) { + return binding.type == Resources::ShaderResourceType::ConstantBuffer; + }); + for (size_t index : constantBufferIndices) { + outBindings[index].set = 0; + outBindings[index].binding = nextConstantBufferRegister++; + } + + const auto textureIndices = sortBindingIndices( + [](const Resources::ShaderResourceBindingDesc& binding) { + return binding.type == Resources::ShaderResourceType::Texture2D || + binding.type == Resources::ShaderResourceType::TextureCube; + }); + for (size_t index : textureIndices) { + outBindings[index].set = 0; + outBindings[index].binding = nextTextureRegister++; + } + + const auto srvBufferIndices = sortBindingIndices( + [](const Resources::ShaderResourceBindingDesc& binding) { + return binding.type == Resources::ShaderResourceType::StructuredBuffer || + binding.type == Resources::ShaderResourceType::RawBuffer; + }); + for (size_t index : srvBufferIndices) { + outBindings[index].set = 0; + outBindings[index].binding = + backend == Resources::ShaderBackend::OpenGL + ? nextStorageBufferRegister++ + : nextTextureRegister++; + } + + const auto samplerIndices = sortBindingIndices( + [](const Resources::ShaderResourceBindingDesc& binding) { + return binding.type == Resources::ShaderResourceType::Sampler; + }); + for (size_t index : samplerIndices) { + outBindings[index].set = 0; + outBindings[index].binding = nextSamplerRegister++; + } + + const auto uavIndices = sortBindingIndices( + [](const Resources::ShaderResourceBindingDesc& binding) { + return binding.type == Resources::ShaderResourceType::RWStructuredBuffer || + binding.type == Resources::ShaderResourceType::RWRawBuffer; + }); + for (size_t index : uavIndices) { + outBindings[index].set = 0; + outBindings[index].binding = + backend == Resources::ShaderBackend::OpenGL + ? nextStorageBufferRegister++ + : nextUnorderedAccessRegister++; + } + + const auto fallbackUavIndices = sortBindingIndices( + [](const Resources::ShaderResourceBindingDesc& binding) { + return binding.type != Resources::ShaderResourceType::ConstantBuffer && + binding.type != Resources::ShaderResourceType::Texture2D && + binding.type != Resources::ShaderResourceType::TextureCube && + binding.type != Resources::ShaderResourceType::StructuredBuffer && + binding.type != Resources::ShaderResourceType::RawBuffer && + binding.type != Resources::ShaderResourceType::Sampler && + binding.type != Resources::ShaderResourceType::RWStructuredBuffer && + binding.type != Resources::ShaderResourceType::RWRawBuffer; + }); + for (size_t index : fallbackUavIndices) { + outBindings[index].set = 0; + outBindings[index].binding = nextUnorderedAccessRegister++; + } + + return true; +} + +inline std::string BuildRuntimeShaderSource( + const Resources::ShaderPass& pass, + Resources::ShaderBackend backend, + const Resources::ShaderStageVariant& variant) { + std::string sourceText = ToStdString(variant.sourceCode); + + if (variant.language != Resources::ShaderLanguage::HLSL || + backend == Resources::ShaderBackend::Generic) { + return sourceText; + } + + Containers::Array bindings; + bool includeRegisterSpace = false; + if (!TryBuildRuntimeShaderBindings(pass, backend, bindings, includeRegisterSpace)) { + return sourceText; + } + + for (const Resources::ShaderResourceBindingDesc& binding : bindings) { + TryRewriteHlslRegisterBinding(sourceText, binding, includeRegisterSpace); + } + + return sourceText; +} + +inline void AddShaderCompileMacro( + RHI::ShaderCompileDesc& compileDesc, + const wchar_t* name, + const wchar_t* definition = L"1") { + if (name == nullptr || *name == L'\0') { + return; + } + + for (const RHI::ShaderCompileMacro& existingMacro : compileDesc.macros) { + if (existingMacro.name == name) { + return; + } + } + + RHI::ShaderCompileMacro macro = {}; + macro.name = name; + macro.definition = definition != nullptr ? definition : L""; + compileDesc.macros.push_back(std::move(macro)); +} + +inline void InjectShaderBackendMacros( + Resources::ShaderBackend backend, + RHI::ShaderCompileDesc& compileDesc) { + switch (backend) { + case Resources::ShaderBackend::OpenGL: + AddShaderCompileMacro(compileDesc, L"SHADER_API_GLCORE"); + AddShaderCompileMacro(compileDesc, L"UNITY_UV_STARTS_AT_TOP", L"0"); + AddShaderCompileMacro(compileDesc, L"UNITY_NEAR_CLIP_VALUE", L"-1"); + break; + case Resources::ShaderBackend::Vulkan: + AddShaderCompileMacro(compileDesc, L"SHADER_API_VULKAN"); + AddShaderCompileMacro(compileDesc, L"UNITY_UV_STARTS_AT_TOP", L"1"); + AddShaderCompileMacro(compileDesc, L"UNITY_NEAR_CLIP_VALUE", L"0"); + break; + case Resources::ShaderBackend::D3D12: + AddShaderCompileMacro(compileDesc, L"SHADER_API_D3D12"); + AddShaderCompileMacro(compileDesc, L"UNITY_UV_STARTS_AT_TOP", L"1"); + AddShaderCompileMacro(compileDesc, L"UNITY_NEAR_CLIP_VALUE", L"0"); + break; + case Resources::ShaderBackend::Generic: + default: + break; + } +} + +inline void ApplyShaderStageVariant( + const Resources::ShaderStageVariant& variant, + RHI::ShaderCompileDesc& compileDesc) { + compileDesc.fileName.clear(); + compileDesc.includeDirectories.clear(); + 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); + compileDesc.compiledBinaryBackend = + ResolveCompiledBinaryBackend(variant.backend, variant); + compileDesc.compiledBinary.clear(); + if (!variant.compiledBinary.Empty()) { + compileDesc.compiledBinary.assign( + variant.compiledBinary.Data(), + variant.compiledBinary.Data() + variant.compiledBinary.Size()); + } +} + +inline std::wstring ResolveRuntimeShaderSourcePath( + const Containers::String& shaderPath) { + Containers::String resolvedPath = shaderPath; + if (resolvedPath.Empty()) { + return std::wstring(); + } + + if (Resources::IsBuiltinShaderPath(resolvedPath)) { + Containers::String builtinAssetPath; + if (!Resources::TryResolveBuiltinShaderAssetPath( + resolvedPath, + builtinAssetPath)) { + return std::wstring(); + } + resolvedPath = builtinAssetPath; + } + + return ToWideAscii(resolvedPath); +} + +inline void ApplyShaderStageVariant( + const Containers::String& shaderPath, + const Resources::ShaderPass& pass, + Resources::ShaderBackend backend, + const Resources::ShaderStageVariant& variant, + RHI::ShaderCompileDesc& compileDesc) { + const std::string sourceText = + BuildRuntimeShaderSource(pass, backend, variant); + compileDesc.source.assign(sourceText.begin(), sourceText.end()); + compileDesc.fileName = ResolveRuntimeShaderSourcePath(shaderPath); + compileDesc.includeDirectories.clear(); + if (!compileDesc.fileName.empty()) { + const std::filesystem::path shaderFilePath(compileDesc.fileName); + if (shaderFilePath.has_parent_path()) { + compileDesc.includeDirectories.push_back( + shaderFilePath.parent_path().wstring()); + } + } + compileDesc.sourceLanguage = ToRHIShaderLanguage(variant.language); + compileDesc.entryPoint = ToWideAscii(variant.entryPoint); + compileDesc.profile = ToWideAscii(variant.profile); + compileDesc.compiledBinaryBackend = RHI::ShaderBinaryBackend::Unknown; + compileDesc.compiledBinary.clear(); + if (const Containers::Array* binary = + variant.GetCompiledBinaryForBackend(backend)) { + compileDesc.compiledBinaryBackend = + ResolveCompiledBinaryBackend(backend, variant); + compileDesc.compiledBinary.assign( + binary->Data(), + binary->Data() + binary->Size()); + } + InjectShaderBackendMacros(backend, compileDesc); +} + +inline void ApplyShaderStageVariant( + const Resources::ShaderPass& pass, + Resources::ShaderBackend backend, + const Resources::ShaderStageVariant& variant, + RHI::ShaderCompileDesc& compileDesc) { + ApplyShaderStageVariant( + Containers::String(), + pass, + backend, + variant, + compileDesc); +} + +inline Containers::String BuildShaderKeywordSignature( + const Resources::ShaderKeywordSet& keywordSet) { + Resources::ShaderKeywordSet normalizedKeywords = keywordSet; + Resources::NormalizeShaderKeywordSetInPlace(normalizedKeywords); + + Containers::String signature; + for (size_t keywordIndex = 0; + keywordIndex < normalizedKeywords.enabledKeywords.Size(); + ++keywordIndex) { + if (keywordIndex > 0) { + signature += ";"; + } + + signature += normalizedKeywords.enabledKeywords[keywordIndex]; + } + + return signature; +} + +inline bool ShaderPassHasGraphicsVariants( + const Resources::Shader& shader, + const Containers::String& passName, + Resources::ShaderBackend backend, + const Resources::ShaderKeywordSet& enabledKeywords = + Resources::ShaderKeywordSet()) { + return shader.FindVariant( + passName, + Resources::ShaderType::Vertex, + backend, + enabledKeywords) != nullptr && + shader.FindVariant( + passName, + Resources::ShaderType::Fragment, + backend, + enabledKeywords) != nullptr; +} + +} // namespace Rendering +} // namespace XCEngine diff --git a/managed/CMakeLists.txt b/managed/CMakeLists.txt index 3d1e9485..c99e44c9 100644 --- a/managed/CMakeLists.txt +++ b/managed/CMakeLists.txt @@ -243,6 +243,7 @@ set(XCENGINE_RENDER_PIPELINES_UNIVERSAL_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRenderer.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRendererData.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRenderPipelineAsset.cs + ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalShadowSettings.cs ) set(XCENGINE_GAME_SCRIPT_SOURCES diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/RendererCameraRequestContext.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/RendererCameraRequestContext.cs index 58d33d1b..e7ed07f6 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/RendererCameraRequestContext.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/RendererCameraRequestContext.cs @@ -43,6 +43,18 @@ namespace XCEngine.Rendering.Universal } } + public bool hasDirectionalShadow => + m_requestContext != null && + m_requestContext.hasDirectionalShadow; + + public void ClearDirectionalShadow() + { + if (m_requestContext != null) + { + m_requestContext.ClearDirectionalShadow(); + } + } + internal CameraRenderRequestContext requestContext => m_requestContext; } diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRenderPipelineAsset.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRenderPipelineAsset.cs index 11d64448..23a277a4 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRenderPipelineAsset.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRenderPipelineAsset.cs @@ -6,12 +6,36 @@ namespace XCEngine.Rendering.Universal public class UniversalRenderPipelineAsset : RendererBackedRenderPipelineAsset { + public UniversalShadowSettings shadows; + + public UniversalRenderPipelineAsset() + { + shadows = + UniversalShadowSettings.CreateDefault(); + } + protected override ScriptableRenderPipeline CreateRendererBackedPipeline() { return new UniversalRenderPipeline(this); } + protected override void ConfigureRendererCameraRequest( + RendererCameraRequestContext context) + { + if (context == null) + { + return; + } + + if (!GetShadowSettingsInstance() + .supportsMainLightShadows && + context.hasDirectionalShadow) + { + context.ClearDirectionalShadow(); + } + } + protected override ScriptableRendererData CreateDefaultRendererData() { @@ -38,6 +62,17 @@ namespace XCEngine.Rendering.Universal return base.ResolveRendererIndex(context); } + + private UniversalShadowSettings GetShadowSettingsInstance() + { + if (shadows == null) + { + shadows = + UniversalShadowSettings.CreateDefault(); + } + + return shadows; + } } } diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalShadowSettings.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalShadowSettings.cs new file mode 100644 index 00000000..ecc0b688 --- /dev/null +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalShadowSettings.cs @@ -0,0 +1,12 @@ +namespace XCEngine.Rendering.Universal +{ + public sealed class UniversalShadowSettings + { + public bool supportsMainLightShadows = true; + + public static UniversalShadowSettings CreateDefault() + { + return new UniversalShadowSettings(); + } + } +} diff --git a/managed/XCEngine.ScriptCore/Rendering/Core/CameraRenderRequestContext.cs b/managed/XCEngine.ScriptCore/Rendering/Core/CameraRenderRequestContext.cs index e7bad149..7fff03ca 100644 --- a/managed/XCEngine.ScriptCore/Rendering/Core/CameraRenderRequestContext.cs +++ b/managed/XCEngine.ScriptCore/Rendering/Core/CameraRenderRequestContext.cs @@ -53,6 +53,18 @@ namespace XCEngine.Rendering value); } + public bool hasDirectionalShadow => + InternalCalls + .Rendering_CameraRenderRequestContext_GetHasDirectionalShadow( + m_nativeHandle); + + public void ClearDirectionalShadow() + { + InternalCalls + .Rendering_CameraRenderRequestContext_ClearDirectionalShadow( + m_nativeHandle); + } + internal ulong nativeHandle => m_nativeHandle; } diff --git a/managed/XCEngine.ScriptCore/Rendering/Graph/RenderGraphRasterPassBuilder.cs b/managed/XCEngine.ScriptCore/Rendering/Graph/RenderGraphRasterPassBuilder.cs new file mode 100644 index 00000000..473d3014 --- /dev/null +++ b/managed/XCEngine.ScriptCore/Rendering/Graph/RenderGraphRasterPassBuilder.cs @@ -0,0 +1,246 @@ +using System; +using System.Collections.Generic; +using XCEngine; + +namespace XCEngine.Rendering +{ + internal enum RenderGraphRasterPassExecutionKind + { + None = 0, + ColorScaleFullscreen = 1, + ShaderVectorFullscreen = 2 + } + + public sealed class RenderGraphRasterPassBuilder + { + private readonly ScriptableRenderContext m_context; + private readonly string m_passName; + private readonly List m_readTextures = + new List(); + private readonly List m_colorAttachments = + new List(); + private RenderGraphTextureHandle m_sourceColorTexture; + private Vector4 m_vectorPayload; + private string m_shaderPath = string.Empty; + private string m_shaderPassName = string.Empty; + private RenderGraphRasterPassExecutionKind m_executionKind; + private bool m_finalized; + + internal RenderGraphRasterPassBuilder( + ScriptableRenderContext context, + string passName) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (string.IsNullOrEmpty(passName)) + { + throw new ArgumentException( + "Render graph raster pass name cannot be null or empty.", + nameof(passName)); + } + + m_context = context; + m_passName = passName; + } + + public RenderGraphRasterPassBuilder UseColorSource( + RenderGraphTextureHandle texture) + { + m_sourceColorTexture = texture; + return this; + } + + public RenderGraphRasterPassBuilder UseTexture( + RenderGraphTextureHandle texture) + { + if (texture.isValid) + { + m_readTextures.Add(texture); + } + + return this; + } + + public RenderGraphRasterPassBuilder SetColorAttachment( + RenderGraphTextureHandle texture, + int index = 0) + { + if (index < 0) + { + throw new ArgumentOutOfRangeException( + nameof(index), + "Render graph color attachment index cannot be negative."); + } + + while (m_colorAttachments.Count <= index) + { + m_colorAttachments.Add(default); + } + + m_colorAttachments[index] = texture; + return this; + } + + public RenderGraphRasterPassBuilder + SetColorScaleFullscreenExecution( + Vector4 colorScale) + { + m_executionKind = + RenderGraphRasterPassExecutionKind + .ColorScaleFullscreen; + m_vectorPayload = colorScale; + m_shaderPath = string.Empty; + m_shaderPassName = string.Empty; + return this; + } + + public RenderGraphRasterPassBuilder + SetShaderVectorFullscreenExecution( + string shaderPath, + Vector4 vectorPayload, + string shaderPassName = null) + { + if (string.IsNullOrEmpty(shaderPath)) + { + throw new ArgumentException( + "Fullscreen shader path cannot be null or empty.", + nameof(shaderPath)); + } + + m_executionKind = + RenderGraphRasterPassExecutionKind + .ShaderVectorFullscreen; + m_vectorPayload = vectorPayload; + m_shaderPath = shaderPath; + m_shaderPassName = shaderPassName ?? string.Empty; + return this; + } + + public bool Commit() + { + if (m_finalized || + !HasExecutionConfigured() || + !HasAnyColorAttachment()) + { + return false; + } + + m_finalized = true; + + ulong nativePassHandle = + InternalCalls + .Rendering_ScriptableRenderContext_BeginRasterPass( + m_context.nativeHandle, + m_passName); + if (nativePassHandle == 0ul) + { + return false; + } + + if (m_sourceColorTexture.isValid && + !InternalCalls + .Rendering_ScriptableRenderContext_SetRasterPassSourceColorTexture( + m_context.nativeHandle, + nativePassHandle, + m_sourceColorTexture.nativeIndex)) + { + return false; + } + + for (int i = 0; i < m_readTextures.Count; ++i) + { + RenderGraphTextureHandle readTexture = + m_readTextures[i]; + if (!readTexture.isValid) + { + continue; + } + + if (!InternalCalls + .Rendering_ScriptableRenderContext_AddRasterPassReadTexture( + m_context.nativeHandle, + nativePassHandle, + readTexture.nativeIndex)) + { + return false; + } + } + + for (int i = 0; i < m_colorAttachments.Count; ++i) + { + RenderGraphTextureHandle colorAttachment = + m_colorAttachments[i]; + if (!colorAttachment.isValid) + { + continue; + } + + if (!InternalCalls + .Rendering_ScriptableRenderContext_SetRasterPassColorAttachment( + m_context.nativeHandle, + nativePassHandle, + i, + colorAttachment.nativeIndex)) + { + return false; + } + } + + bool configuredExecution; + switch (m_executionKind) + { + case RenderGraphRasterPassExecutionKind + .ColorScaleFullscreen: + configuredExecution = + InternalCalls + .Rendering_ScriptableRenderContext_SetRasterPassColorScaleFullscreenExecution( + m_context.nativeHandle, + nativePassHandle, + ref m_vectorPayload); + break; + case RenderGraphRasterPassExecutionKind + .ShaderVectorFullscreen: + configuredExecution = + InternalCalls + .Rendering_ScriptableRenderContext_SetRasterPassShaderVectorFullscreenExecution( + m_context.nativeHandle, + nativePassHandle, + m_shaderPath, + m_shaderPassName, + ref m_vectorPayload); + break; + default: + configuredExecution = false; + break; + } + + return configuredExecution && + InternalCalls + .Rendering_ScriptableRenderContext_CommitRasterPass( + m_context.nativeHandle, + nativePassHandle); + } + + private bool HasExecutionConfigured() + { + return m_executionKind != + RenderGraphRasterPassExecutionKind.None; + } + + private bool HasAnyColorAttachment() + { + for (int i = 0; i < m_colorAttachments.Count; ++i) + { + if (m_colorAttachments[i].isValid) + { + return true; + } + } + + return false; + } + } +} diff --git a/managed/XCEngine.ScriptCore/Rendering/Graph/RenderGraphTextureDesc.cs b/managed/XCEngine.ScriptCore/Rendering/Graph/RenderGraphTextureDesc.cs new file mode 100644 index 00000000..4cc4c39c --- /dev/null +++ b/managed/XCEngine.ScriptCore/Rendering/Graph/RenderGraphTextureDesc.cs @@ -0,0 +1,20 @@ +namespace XCEngine.Rendering +{ + public struct RenderGraphTextureDesc + { + public uint width; + public uint height; + public uint format; + public uint textureType; + public uint sampleCount; + public uint sampleQuality; + + public bool IsValid() + { + return width > 0u && + height > 0u && + format > 0u && + sampleCount > 0u; + } + } +} diff --git a/managed/XCEngine.ScriptCore/Rendering/Graph/RenderGraphTextureHandle.cs b/managed/XCEngine.ScriptCore/Rendering/Graph/RenderGraphTextureHandle.cs new file mode 100644 index 00000000..1eacdf81 --- /dev/null +++ b/managed/XCEngine.ScriptCore/Rendering/Graph/RenderGraphTextureHandle.cs @@ -0,0 +1,30 @@ +namespace XCEngine.Rendering +{ + public struct RenderGraphTextureHandle + { + private readonly int m_indexPlusOne; + + private RenderGraphTextureHandle( + int indexPlusOne) + { + m_indexPlusOne = indexPlusOne; + } + + public bool isValid => + m_indexPlusOne > 0; + + internal int nativeIndex => + m_indexPlusOne > 0 + ? m_indexPlusOne - 1 + : -1; + + internal static RenderGraphTextureHandle + FromNativeIndex(int nativeIndex) + { + return nativeIndex >= 0 + ? new RenderGraphTextureHandle( + nativeIndex + 1) + : default; + } + } +}