From e3323394299f8f6954cc9c24f08c7c63a0499a68 Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Mon, 20 Apr 2026 08:02:10 +0900 Subject: [PATCH 01/60] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9a340937..0a66cca9 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,6 @@ cmake --build out/build/linux-release ``` # TODO -- [ ]스켈레탈 메쉬 -- [ ]애니메이션, 애니메이터 -- [ ]Undo/Redo +- [x] 스켈레탈 메쉬 +- [ ] 애니메이션, 애니메이터 +- [x] Undo/Redo From 117f981ba89474646be6c7067c62106e9b421f4e Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Mon, 20 Apr 2026 21:43:22 +0900 Subject: [PATCH 02/60] feat(ShaderParser): Add SKIN and MATRIX_SKIN keywords into shader --- include/Render/Shader.h | 21 ++++---- include/Render/ShaderAST.h | 1 + include/Render/ShaderLexer.h | 4 +- include/Render/ShaderPass.h | 4 ++ .../Component/Render/SkinnedMeshRenderer.cpp | 2 +- src/Render/Shader.cpp | 52 +++++++------------ src/Render/ShaderLexer.cpp | 2 + src/Render/ShaderParser.cpp | 50 +++++++++++++++--- src/Render/ShaderPass.cpp | 13 +++-- 9 files changed, 90 insertions(+), 59 deletions(-) diff --git a/include/Render/Shader.h b/include/Render/Shader.h index d62e3cd8..e4b4a1a4 100644 --- a/include/Render/Shader.h +++ b/include/Render/Shader.h @@ -39,15 +39,6 @@ namespace sh::render public: SH_RENDER_API Shader(ShaderCreateInfo&& shaderCreateInfo); SH_RENDER_API ~Shader(); - SH_RENDER_API auto GetShaderPasses(const core::Name& lightingPassName) const -> const std::vector>*; - SH_RENDER_API auto GetAllShaderPass() const -> const std::vector&; - - SH_RENDER_API auto GetProperties() const -> const std::unordered_map&; - SH_RENDER_API auto GetProperty(const std::string& name) const -> const PropertyInfo*; - - SH_RENDER_API auto GetShaderAST() const -> const ShaderAST::ShaderNode&; - - SH_RENDER_API auto IsUsingLight() const -> bool; SH_RENDER_API void OnDestroy() override; /// @brief AST와 셰이더 패스를 직렬화 한다. @@ -56,6 +47,17 @@ namespace sh::render /// @brief AST만 역직렬화 시킨다. /// @param json 직렬화 된 json SH_RENDER_API void Deserialize(const core::Json& json) override; + + SH_RENDER_API auto GetShaderPasses(const core::Name& lightingPassName) const -> const std::vector>*; + SH_RENDER_API auto GetAllShaderPass() const -> const std::vector& { return passDatas; } + + SH_RENDER_API auto GetProperties() const -> const std::unordered_map& { return properties; } + SH_RENDER_API auto GetProperty(const std::string& name) const -> const PropertyInfo*; + + SH_RENDER_API auto GetShaderAST() const -> const ShaderAST::ShaderNode& { return shaderNode; } + + SH_RENDER_API auto IsUsingLight() const -> bool { return bUsingLight; } + SH_RENDER_API auto IsUsingSkin() const -> bool { return bUsingSkin; } private: void Clear(); void AddShaderPasses(std::vector&& passes); @@ -73,6 +75,7 @@ namespace sh::render ShaderAST::ShaderNode shaderNode; bool bUsingLight = false; + bool bUsingSkin = false; }; template diff --git a/include/Render/ShaderAST.h b/include/Render/ShaderAST.h index 54ee444e..4fad94c0 100644 --- a/include/Render/ShaderAST.h +++ b/include/Render/ShaderAST.h @@ -100,6 +100,7 @@ namespace sh::render std::vector functions; std::string code; int lightingBinding = -1; + int skinBinding = -1; SH_RENDER_API auto Serialize() const -> core::Json override; SH_RENDER_API void Deserialize(const core::Json& json) override; diff --git a/include/Render/ShaderLexer.h b/include/Render/ShaderLexer.h index 0b38e6cb..2f13bbee 100644 --- a/include/Render/ShaderLexer.h +++ b/include/Render/ShaderLexer.h @@ -16,8 +16,8 @@ namespace sh::render Vertex, Fragment, Layout, Uniform, In, Out, Sampler2D, Constexpr, Const, - VERTEX, UV, NORMAL, TANGENT, MVP, LIGHT, BONE_WEIGHTS, BONE_INDICES, - MATRIX_MODEL, MATRIX_VIEW, MATRIX_PROJ, + VERTEX, UV, NORMAL, TANGENT, MVP, LIGHT, BONE_WEIGHTS, BONE_INDICES, SKIN, + MATRIX_MODEL, MATRIX_VIEW, MATRIX_PROJ, MATRIX_SKIN, LBracket, // ( RBracket, // ) LBrace, // { diff --git a/include/Render/ShaderPass.h b/include/Render/ShaderPass.h index 8f3158ad..d2487526 100644 --- a/include/Render/ShaderPass.h +++ b/include/Render/ShaderPass.h @@ -69,6 +69,9 @@ namespace sh::render /// @brief 라이트 유니폼의 바인딩 번호를 리턴한다. /// @return 라이팅을 안 쓸 시 -1 SH_RENDER_API auto GetLightingBinding() const -> int { return lightingBinding; } + /// @brief 스킨 유니폼의 바인딩 번호를 리턴한다. + /// @return 스킨을 안 쓸 시 -1 + SH_RENDER_API auto GetSkinBinding() const -> int { return skinBinding; } SH_RENDER_API auto GetConstants() const -> const std::unordered_map& { return constantNameMap; } SH_RENDER_API auto GetConstantsInfo(const std::string& name) const -> const ConstantInfo*; SH_RENDER_API auto GetConstantSize() const -> std::size_t; @@ -117,6 +120,7 @@ namespace sh::render uint8_t colorMask = 7; //0b111 std::size_t constantSize = 0; int lightingBinding = -1; + int skinBinding = -1; bool bZWrite = true; bool bZTest = true; bool bHasConstant = false; diff --git a/src/Game/Component/Render/SkinnedMeshRenderer.cpp b/src/Game/Component/Render/SkinnedMeshRenderer.cpp index d0e7bbca..40240a18 100644 --- a/src/Game/Component/Render/SkinnedMeshRenderer.cpp +++ b/src/Game/Component/Render/SkinnedMeshRenderer.cpp @@ -82,7 +82,7 @@ namespace sh::game drawable->GetMaterialData().SetUniformData( pass, render::UniformStructLayout::Type::Object, - 0, + pass.GetSkinBinding(), finalBoneMatrices.data(), dataSize); } diff --git a/src/Render/Shader.cpp b/src/Render/Shader.cpp index 6e22c7c2..42c55725 100644 --- a/src/Render/Shader.cpp +++ b/src/Render/Shader.cpp @@ -41,40 +41,8 @@ namespace sh::render } } } - Shader::~Shader() - { - } - SH_RENDER_API auto Shader::GetShaderPasses(const core::Name& lightingPassName) const -> const std::vector>* - { - const LightingPassData* lightingPassData = GetLightingPass(lightingPassName); - if (lightingPassData == nullptr) - return nullptr; - return &lightingPassData->passes; - } - SH_RENDER_API auto Shader::GetAllShaderPass() const -> const std::vector& - { - return passDatas; - } - SH_RENDER_API auto Shader::GetProperties() const -> const std::unordered_map& - { - return properties; - } - SH_RENDER_API auto Shader::GetProperty(const std::string& name) const -> const PropertyInfo* - { - auto it = properties.find(name); - if (it == properties.end()) - return nullptr; - return &it->second; - } + Shader::~Shader() = default; - SH_RENDER_API auto Shader::GetShaderAST() const -> const ShaderAST::ShaderNode& - { - return shaderNode; - } - SH_RENDER_API auto Shader::IsUsingLight() const -> bool - { - return bUsingLight; - } SH_RENDER_API void Shader::OnDestroy() { for (ShaderPass* shaderPass : passes) @@ -114,6 +82,21 @@ namespace sh::render shaderNode.Deserialize(shaderJson["AST"]); } + SH_RENDER_API auto Shader::GetShaderPasses(const core::Name& lightingPassName) const -> const std::vector>* + { + const LightingPassData* lightingPassData = GetLightingPass(lightingPassName); + if (lightingPassData == nullptr) + return nullptr; + return &lightingPassData->passes; + } + SH_RENDER_API auto Shader::GetProperty(const std::string& name) const -> const PropertyInfo* + { + auto it = properties.find(name); + if (it == properties.end()) + return nullptr; + return &it->second; + } + void Shader::Clear() { properties.clear(); @@ -127,6 +110,9 @@ namespace sh::render { if (pass->GetLightingBinding() != -1) bUsingLight = true; + if (pass->GetSkinBinding() != -1) + bUsingSkin = true; + const core::Name& lightingPassName = this->passes.back()->GetLightingPassName(); LightingPassData* lightingPassData = GetLightingPass(lightingPassName); diff --git a/src/Render/ShaderLexer.cpp b/src/Render/ShaderLexer.cpp index 4f5540fb..93a05399 100644 --- a/src/Render/ShaderLexer.cpp +++ b/src/Render/ShaderLexer.cpp @@ -310,6 +310,8 @@ namespace sh::render {"MATRIX_PROJ", TokenType::MATRIX_PROJ}, {"BONE_WEIGHTS", TokenType::BONE_WEIGHTS}, {"BONE_INDICES", TokenType::BONE_INDICES}, + {"SKIN", TokenType::SKIN}, + {"MATRIX_SKIN", TokenType::MATRIX_SKIN} }; auto it = keywordMap.find(ident); if (it == keywordMap.end()) diff --git a/src/Render/ShaderParser.cpp b/src/Render/ShaderParser.cpp index 977b693c..536e9b8c 100644 --- a/src/Render/ShaderParser.cpp +++ b/src/Render/ShaderParser.cpp @@ -599,7 +599,7 @@ namespace sh::render }; // 버텍스 입력 어트리뷰트를 한 번만 등록하는 헬퍼 - auto ensureInput = [&](bool& bRegistered, const char* name, int binding, ShaderAST::VariableType type) + auto registerAttributeFn = [&](bool& bRegistered, const char* name, int binding, ShaderAST::VariableType type) { if (bRegistered) return; @@ -634,6 +634,8 @@ namespace sh::render bool usingMatrixModel = false; bool usingCamera = false; bool usingLIGHT = false; + bool usingSKIN = false; + bool usingMATRIX_SKIN = false; while (nested != 0 || PeekToken().type != ShaderLexer::TokenType::EndOfFile) { @@ -651,17 +653,17 @@ namespace sh::render code.pop_back(); } else if (CheckToken(ShaderLexer::TokenType::VERTEX)) - ensureInput(usingVertex, "VERTEX", Mesh::VERTEX_ID, ShaderAST::VariableType::Vec3); + registerAttributeFn(usingVertex, "VERTEX", Mesh::VERTEX_ID, ShaderAST::VariableType::Vec3); else if (CheckToken(ShaderLexer::TokenType::UV)) - ensureInput(usingUV, "UV", Mesh::UV_ID, ShaderAST::VariableType::Vec2); + registerAttributeFn(usingUV, "UV", Mesh::UV_ID, ShaderAST::VariableType::Vec2); else if (CheckToken(ShaderLexer::TokenType::NORMAL)) - ensureInput(usingNormal, "NORMAL", Mesh::NORMAL_ID, ShaderAST::VariableType::Vec3); + registerAttributeFn(usingNormal, "NORMAL", Mesh::NORMAL_ID, ShaderAST::VariableType::Vec3); else if (CheckToken(ShaderLexer::TokenType::TANGENT)) - ensureInput(usingTangent, "TANGENT", Mesh::TANGENT_ID, ShaderAST::VariableType::Vec3); + registerAttributeFn(usingTangent, "TANGENT", Mesh::TANGENT_ID, ShaderAST::VariableType::Vec3); else if (CheckToken(ShaderLexer::TokenType::BONE_WEIGHTS)) - ensureInput(usingBoneWeights, "BONE_WEIGHTS", SkinnedMesh::BONE_WEIGHT_ID, ShaderAST::VariableType::Vec4); + registerAttributeFn(usingBoneWeights, "BONE_WEIGHTS", SkinnedMesh::BONE_WEIGHT_ID, ShaderAST::VariableType::Vec4); else if (CheckToken(ShaderLexer::TokenType::BONE_INDICES)) - ensureInput(usingBoneIndices, "BONE_INDICES", SkinnedMesh::BONE_INDEX_ID, ShaderAST::VariableType::IVec4); + registerAttributeFn(usingBoneIndices, "BONE_INDICES", SkinnedMesh::BONE_INDEX_ID, ShaderAST::VariableType::IVec4); else if (CheckToken(ShaderLexer::TokenType::MATRIX_VIEW) || CheckToken(ShaderLexer::TokenType::MATRIX_PROJ)) { if (!usingCamera) @@ -727,6 +729,31 @@ namespace sh::render usingLIGHT = true; } } + else if (CheckToken(ShaderLexer::TokenType::SKIN) || CheckToken(ShaderLexer::TokenType::MATRIX_SKIN)) + { + registerAttributeFn(usingBoneWeights, "BONE_WEIGHTS", SkinnedMesh::BONE_WEIGHT_ID, ShaderAST::VariableType::Vec4); + registerAttributeFn(usingBoneIndices, "BONE_INDICES", SkinnedMesh::BONE_INDEX_ID, ShaderAST::VariableType::IVec4); + if (CheckToken(ShaderLexer::TokenType::MATRIX_SKIN)) + usingMATRIX_SKIN = true; + if (!usingSKIN) + { + auto it = std::find_if(stageNode.uniforms.begin(), stageNode.uniforms.end(), + [](const ShaderAST::UBONode& ubo) { return ubo.name == "SKIN"; }); + if (it == stageNode.uniforms.end()) + { + ShaderAST::UBONode uboNode{}; + uboNode.name = "SKIN"; + uboNode.set = static_cast(UniformStructLayout::Type::Object); + uboNode.binding = lastObjectUniformBinding++; + uboNode.bSampler = false; + uboNode.vars.push_back(ShaderAST::VariableNode{ ShaderAST::VariableType::Mat4, 128, "ibm" }); + stageNode.uniforms.push_back(std::move(uboNode)); + stageNode.skinBinding = uboNode.binding; + uboit = refreshUboIt(); + } + usingSKIN = true; + } + } else if (token.type == ShaderLexer::TokenType::Identifier) { if (uboit != stageNode.uniforms.end()) @@ -740,6 +767,15 @@ namespace sh::render code += SubstitutionFunctionToken(token) + " "; NextToken(); } + if (usingMATRIX_SKIN) + { + code = "mat4 MATRIX_SKIN = " + "BONE_WEIGHTS.x * SKIN.ibm[BONE_INDICES.x] + " + "BONE_WEIGHTS.y * SKIN.ibm[BONE_INDICES.y] + " + "BONE_WEIGHTS.z * SKIN.ibm[BONE_INDICES.z] + " + "BONE_WEIGHTS.w * SKIN.ibm[BONE_INDICES.w]; " + + code; + } return code; } diff --git a/src/Render/ShaderPass.cpp b/src/Render/ShaderPass.cpp index 2afe9cf2..c9c9361c 100644 --- a/src/Render/ShaderPass.cpp +++ b/src/Render/ShaderPass.cpp @@ -14,10 +14,9 @@ namespace sh::render for (auto& stage : passNode.stages) { if (stage.lightingBinding != -1) - { lightingBinding = stage.lightingBinding; - break; - } + if (stage.skinBinding != -1) + skinBinding = stage.skinBinding; } if (!passNode.constants.empty()) { @@ -60,12 +59,11 @@ namespace sh::render cull(other.cull), colorMask(other.colorMask), bZWrite(other.bZWrite), bHasConstant(other.bHasConstant), - lightingBinding(other.lightingBinding) - { - } - ShaderPass::~ShaderPass() + lightingBinding(other.lightingBinding), + skinBinding(other.skinBinding) { } + ShaderPass::~ShaderPass() = default; auto ShaderPass::operator=(ShaderPass&& other) noexcept -> ShaderPass& { @@ -85,6 +83,7 @@ namespace sh::render bZWrite = other.bZWrite; bHasConstant = other.bHasConstant; lightingBinding = other.lightingBinding; + skinBinding = other.skinBinding; return *this; } From 9fb20465f7c73a560e044c19a603d11cb7cffae6 Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Tue, 21 Apr 2026 01:07:20 +0900 Subject: [PATCH 03/60] fix(ShaderAST): Add serialization/deserialization "skinBinding" --- src/Render/Shader.cpp | 1 - src/Render/ShaderAST.cpp | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Render/Shader.cpp b/src/Render/Shader.cpp index 42c55725..68be923a 100644 --- a/src/Render/Shader.cpp +++ b/src/Render/Shader.cpp @@ -53,7 +53,6 @@ namespace sh::render { core::Json mainJson = Super::Serialize(); - mainJson["shader"] = core::Json{}; core::Json& json = mainJson["shader"]; json["AST"] = shaderNode.Serialize(); diff --git a/src/Render/ShaderAST.cpp b/src/Render/ShaderAST.cpp index 2013940c..e798e7bf 100644 --- a/src/Render/ShaderAST.cpp +++ b/src/Render/ShaderAST.cpp @@ -77,6 +77,7 @@ namespace sh::render core::Json json; json["type"] = static_cast(type); json["lightingBinding"] = lightingBinding; + json["skinBinding"] = skinBinding; json["code"] = code; // in @@ -110,6 +111,7 @@ namespace sh::render { type = static_cast(json.at("type").get()); lightingBinding = json.value("lightingBinding", -1); + skinBinding = json.value("skinBinding", -1); code = json.at("code").get(); // in From ab0244246b456244d8bd59ed0d83f521cc59848d Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Tue, 21 Apr 2026 01:27:25 +0900 Subject: [PATCH 04/60] fix(ModelLoader): Fix vertex tangent calculation --- src/Game/Asset/ModelLoader.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Game/Asset/ModelLoader.cpp b/src/Game/Asset/ModelLoader.cpp index fc6ae933..7362b0b0 100644 --- a/src/Game/Asset/ModelLoader.cpp +++ b/src/Game/Asset/ModelLoader.cpp @@ -23,6 +23,7 @@ #include #include #include +#include namespace sh::game { SH_GAME_API auto ModelLoader::LoadObj(const std::filesystem::path& path) const -> render::Model* @@ -417,9 +418,7 @@ namespace sh::game glm::vec2 deltaUV0 = uv1 - uv0; glm::vec2 deltaUV1 = uv2 - uv0; - const float delta = deltaUV0.x * deltaUV1.y - deltaUV1.x * deltaUV0.y; - if (delta < 1e-6) - return glm::vec3{ 0.f }; + const float delta = std::max(deltaUV0.x * deltaUV1.y - deltaUV1.x * deltaUV0.y, std::numeric_limits::epsilon()); float f = 1.0f / delta; From 0441f56b09b1374572f4ec74f84fb96e3307e36a Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Tue, 21 Apr 2026 23:38:32 +0900 Subject: [PATCH 05/60] feat(Render): Add support SSBO, Refactoring shader codes --- include/Game/Asset/ShaderAsset.h | 14 +- .../Component/Render/SkinnedMeshRenderer.h | 2 +- include/Render/BufferFactory.h | 15 +- include/Render/IBuffer.h | 1 + include/Render/IUniformBuffer.h | 2 +- include/Render/MaterialData.h | 17 +- include/Render/ShaderAST.h | 33 ++- include/Render/ShaderParser.h | 2 +- include/Render/SkinnedMesh.h | 1 - include/Render/UniformStructLayout.h | 183 +++++++++------- include/Render/VulkanImpl/VulkanBuffer.h | 15 +- ...nUniformBuffer.h => VulkanDescriptorSet.h} | 29 +-- include/Render/VulkanImpl/VulkanShaderPass.h | 37 ++-- src/Game/Asset/ShaderAsset.cpp | 28 +-- src/Game/Asset/ShaderLoader.cpp | 2 +- src/Game/Component/Render/MeshRenderer.cpp | 12 +- .../Component/Render/SkinnedMeshRenderer.cpp | 10 +- src/Render/BufferFactory.cpp | 21 +- src/Render/CMakeLists.txt | 2 +- src/Render/Drawable.cpp | 23 +- src/Render/Material.cpp | 6 +- src/Render/MaterialData.cpp | 97 +++++---- src/Render/ScriptableRenderer.cpp | 5 +- src/Render/ShaderAST.cpp | 38 ++-- src/Render/ShaderParser.cpp | 143 +++++++------ src/Render/ShaderPass.cpp | 60 +++--- src/Render/UniformStructLayout.cpp | 86 +++----- src/Render/VulkanImpl/VulkanBuffer.cpp | 37 ++-- src/Render/VulkanImpl/VulkanContext.cpp | 1 + .../VulkanImpl/VulkanDescriptorPool.cpp | 3 +- ...formBuffer.cpp => VulkanDescriptorSet.cpp} | 55 ++--- src/Render/VulkanImpl/VulkanRenderImpl.cpp | 20 +- src/Render/VulkanImpl/VulkanShaderPass.cpp | 197 +++++++++--------- .../VulkanImpl/VulkanShaderPassBuilder.cpp | 6 +- 34 files changed, 635 insertions(+), 568 deletions(-) rename include/Render/VulkanImpl/{VulkanUniformBuffer.h => VulkanDescriptorSet.h} (61%) rename src/Render/VulkanImpl/{VulkanUniformBuffer.cpp => VulkanDescriptorSet.cpp} (63%) diff --git a/include/Game/Asset/ShaderAsset.h b/include/Game/Asset/ShaderAsset.h index 06113798..fe44c221 100644 --- a/include/Game/Asset/ShaderAsset.h +++ b/include/Game/Asset/ShaderAsset.h @@ -12,15 +12,8 @@ namespace sh::game class ShaderAsset : public core::Asset { SASSET(ShaderAsset, "shad") - private: - core::SObjWeakPtr shaderPtr; - - core::Json shaderObjJson; public: constexpr static const char* ASSET_NAME = "shad"; - protected: - SH_GAME_API void SetAssetData() const override; - SH_GAME_API auto ParseAssetData() -> bool override; public: SH_GAME_API ShaderAsset(); SH_GAME_API ShaderAsset(const render::Shader& shader); @@ -28,5 +21,12 @@ namespace sh::game SH_GAME_API void SetAsset(const core::SObject& obj) override; SH_GAME_API auto GetShaderObjectJson() const -> const core::Json&; + protected: + SH_GAME_API void SetAssetData() const override; + SH_GAME_API auto ParseAssetData() -> bool override; + private: + core::SObjWeakPtr shaderPtr; + + core::Json shaderObjJson; }; }//namespace \ No newline at end of file diff --git a/include/Game/Component/Render/SkinnedMeshRenderer.h b/include/Game/Component/Render/SkinnedMeshRenderer.h index be63fe14..77b5e3eb 100644 --- a/include/Game/Component/Render/SkinnedMeshRenderer.h +++ b/include/Game/Component/Render/SkinnedMeshRenderer.h @@ -36,6 +36,6 @@ namespace sh::game PROPERTY(bones, core::PropertyOption::invisible) std::vector bones; std::vector inverseBindMatrices; - std::array finalBoneMatrices; + std::vector finalBoneMatrices; }; }//namespace diff --git a/include/Render/BufferFactory.h b/include/Render/BufferFactory.h index 781a4e6b..16dd4a87 100644 --- a/include/Render/BufferFactory.h +++ b/include/Render/BufferFactory.h @@ -17,10 +17,17 @@ namespace sh::render class BufferFactory { - private: - static auto CreateVkUniformBuffer(const vk::VulkanContext& context, std::size_t size, bool bTransferDst) -> std::unique_ptr; public: - SH_RENDER_API static auto Create(const IRenderContext& context, std::size_t size, bool bTransferDst = false) -> std::unique_ptr; - SH_RENDER_API static auto CreateUniformBuffer(const IRenderContext& context, const ShaderPass& shader, UniformStructLayout::Type type) -> std::unique_ptr; + struct CreateInfo + { + std::size_t size = 0; + bool bDynamic = false; // SSBO + bool bTransferDst = false; + }; + public: + SH_RENDER_API static auto Create(const IRenderContext& context, const CreateInfo& info) -> std::unique_ptr; + SH_RENDER_API static auto CreateUniformBuffer(const IRenderContext& context, const ShaderPass& shader, UniformStructLayout::Usage usage) -> std::unique_ptr; + private: + static auto CreateVkUniformBuffer(const vk::VulkanContext& context, const CreateInfo& info) -> std::unique_ptr; }; }//namespace \ No newline at end of file diff --git a/include/Render/IBuffer.h b/include/Render/IBuffer.h index 36ff0847..7e8284bf 100644 --- a/include/Render/IBuffer.h +++ b/include/Render/IBuffer.h @@ -10,5 +10,6 @@ namespace sh::render virtual void SetData(const void* data) = 0; virtual auto GetData() const -> void* = 0; virtual auto GetSize() const -> size_t = 0; + virtual auto Resize(std::size_t size) -> bool = 0; }; }//namespace \ No newline at end of file diff --git a/include/Render/IUniformBuffer.h b/include/Render/IUniformBuffer.h index c34af835..81b5c266 100644 --- a/include/Render/IUniformBuffer.h +++ b/include/Render/IUniformBuffer.h @@ -17,7 +17,7 @@ namespace sh::render public: SH_RENDER_API virtual ~IUniformBuffer() = default; - SH_RENDER_API virtual void Create(const IRenderContext& context, const ShaderPass& shader, UniformStructLayout::Type type) = 0; + SH_RENDER_API virtual void Create(const IRenderContext& context, const ShaderPass& shader, UniformStructLayout::Usage usage) = 0; SH_RENDER_API virtual void Clear() = 0; SH_RENDER_API virtual void Link(uint32_t binding, const IBuffer& buffer, std::size_t bufferSize = 0) = 0; SH_RENDER_API virtual void Link(uint32_t binding, const Texture& texture) = 0; diff --git a/include/Render/MaterialData.h b/include/Render/MaterialData.h index 6082f3c7..71dfa8b0 100644 --- a/include/Render/MaterialData.h +++ b/include/Render/MaterialData.h @@ -29,7 +29,7 @@ namespace sh::render struct PassData { const ShaderPass* pass; - std::map>> uniformData; //set, binding + std::map>> buffers; //set, binding std::map> uniformBuffer; // set }; struct SyncData @@ -61,25 +61,20 @@ namespace sh::render /// @param binding 바인딩 번호 /// @param data 데이터 위치 포인터 /// @param dataSize 데이터 사이즈 - SH_RENDER_API void SetUniformData(const ShaderPass& shaderPass, UniformStructLayout::Type type, uint32_t binding, const void* data, std::size_t dataSize); + SH_RENDER_API void SetUniformData(const ShaderPass& shaderPass, UniformStructLayout::Usage usage, uint32_t binding, const void* data, std::size_t dataSize); /// @brief 유니폼에 데이터를 지정한다. /// @param pass 패스 번호 /// @param binding 바인딩 번호 /// @param data 데이터 - SH_RENDER_API void SetUniformData(const ShaderPass& shaderPass, UniformStructLayout::Type type, uint32_t binding, const std::vector& data); - /// @brief 유니폼에 데이터를 지정한다. - /// @param pass 패스 번호 - /// @param binding 바인딩 번호 - /// @param data 데이터 - SH_RENDER_API void SetUniformData(const ShaderPass& shaderPass, UniformStructLayout::Type type, uint32_t binding, std::vector&& data); + SH_RENDER_API void SetUniformData(const ShaderPass& shaderPass, UniformStructLayout::Usage usage, uint32_t binding, std::vector data); /// @brief 유니폼 텍스쳐 데이터를 지정한다. /// /// @param pass 패스 번호 /// @param binding 텍스쳐 바인딩 번호 /// @param tex 텍스쳐 - SH_RENDER_API void SetTextureData(const ShaderPass& shaderPass, UniformStructLayout::Type type, uint32_t binding, const Texture& tex); + SH_RENDER_API void SetTextureData(const ShaderPass& shaderPass, UniformStructLayout::Usage usage, uint32_t binding, const Texture& tex); - SH_RENDER_API auto GetShaderBuffer(const ShaderPass& shaderPass, UniformStructLayout::Type type, uint32_t binding) const -> IBuffer*; - SH_RENDER_API auto GetUniformBuffer(const ShaderPass& shaderPass, UniformStructLayout::Type type) const -> IUniformBuffer*; + SH_RENDER_API auto GetShaderBuffer(const ShaderPass& shaderPass, UniformStructLayout::Usage usage, uint32_t binding) const -> IBuffer*; + SH_RENDER_API auto GetUniformBuffer(const ShaderPass& shaderPass, UniformStructLayout::Usage usage) const -> IUniformBuffer*; SH_RENDER_API void SyncDirty() override; protected: diff --git a/include/Render/ShaderAST.h b/include/Render/ShaderAST.h index 4fad94c0..622626c4 100644 --- a/include/Render/ShaderAST.h +++ b/include/Render/ShaderAST.h @@ -42,16 +42,25 @@ namespace sh::render struct VariableNode : core::ISerializable { VariableType type = VariableType::Int; - int size = 1; + uint32_t arraySize = 1; // 배열 원소 개수 (단일 변수면 1) std::string name; std::string defaultValue; VariableAttribute attribute = VariableAttribute::None; + bool bDynamicArray = false; // SSBO 끝의 가변 배열 (arraySize 무시) VariableNode() = default; - VariableNode(VariableType type, int size, std::string name) : - type(type), size(size), name(name) + VariableNode(VariableType type, uint32_t arraySize, std::string name) : + type(type), arraySize(arraySize), name(std::move(name)) { } + static auto MakeDynamicArray(VariableType type, std::string name) -> VariableNode + { + VariableNode v; + v.type = type; + v.bDynamicArray = true; + v.name = std::move(name); + return v; + } SH_RENDER_API auto Serialize() const -> core::Json override; SH_RENDER_API void Deserialize(const core::Json& json) override; @@ -78,13 +87,19 @@ namespace sh::render SH_RENDER_API auto Serialize() const -> core::Json override; SH_RENDER_API void Deserialize(const core::Json& json) override; }; - struct UBONode : core::ISerializable + enum class BufferType : uint8_t { - uint32_t set; - uint32_t binding; + Uniform, // UBO (std140) + Storage, // SSBO (std430) + Sampler, + PushConstant // 푸시 상수 + }; + struct BufferNode : core::ISerializable + { + BufferType bufferType = BufferType::Uniform; + uint32_t set = 0; + uint32_t binding = 0; std::string name; - bool bSampler = false; - bool bConstant = false; std::vector vars; SH_RENDER_API auto Serialize() const -> core::Json override; @@ -95,7 +110,7 @@ namespace sh::render StageType type; std::vector in; std::vector out; - std::vector uniforms; + std::vector buffers; std::vector declaration; std::vector functions; std::string code; diff --git a/include/Render/ShaderParser.h b/include/Render/ShaderParser.h index 2d14a1ec..b3657cfa 100644 --- a/include/Render/ShaderParser.h +++ b/include/Render/ShaderParser.h @@ -60,7 +60,7 @@ namespace sh::render auto ParseFunctionBody(ShaderAST::StageNode& stageNode) -> std::string; void ParseLayout(ShaderAST::StageNode& stageNode); void ParseUniform(const ShaderAST::ShaderNode& shaderNode, ShaderAST::StageNode& stageNode); - void ParseUniformBody(ShaderAST::UBONode& uboNode); + void ParseUniformBody(ShaderAST::BufferNode& uboNode); void ParseConstexpr(ShaderAST::PassNode& passNode); void Optimize(ShaderAST::ShaderNode& shaderNode); diff --git a/include/Render/SkinnedMesh.h b/include/Render/SkinnedMesh.h index 1ae11ef9..a1a54ff1 100644 --- a/include/Render/SkinnedMesh.h +++ b/include/Render/SkinnedMesh.h @@ -22,7 +22,6 @@ namespace sh::render static constexpr uint8_t BONE_INDEX_ID = 4; static constexpr uint8_t BONE_WEIGHT_ID = 5; - static constexpr uint32_t MAX_BONES = 128; public: SH_RENDER_API SkinnedMesh(); SH_RENDER_API SkinnedMesh(const SkinnedMesh& other); diff --git a/include/Render/UniformStructLayout.h b/include/Render/UniformStructLayout.h index 32a182e6..72e298cc 100644 --- a/include/Render/UniformStructLayout.h +++ b/include/Render/UniformStructLayout.h @@ -1,6 +1,7 @@ #pragma once #include "Export.h" #include "ShaderEnum.h" +#include "Texture.h" #include "Core/Reflection.hpp" #include "Core/Util.h" @@ -14,76 +15,90 @@ #include namespace sh::render { - class Texture; - - /// @brief 셰이더 유니폼 구조체 레이아웃 - class UniformStructLayout : core::ISerializable + /// @brief 셰이더 유니폼/스토리지 버퍼 구조체 레이아웃 + class UniformStructLayout { public: - enum class Layout + enum class Layout : uint8_t + { + STD140, // UBO 기본 + STD430 // SSBO 기본 + }; + /// @brief 버퍼 타입 + enum class Kind : uint8_t { - STD140, STD430 + Uniform, // UBO — std140 권장 + Storage, // SSBO — std430 권장, 가변 배열 가능 + Sampler, + PushConstant // 푸시 상수 }; - enum class Type + /// @brief 용도 + enum class Usage : uint8_t { - Camera = 0, // 카메라 데이터 - Object = 1, // 객체마다 고유 - Material = 2, // 메테리얼간 공유 + Camera = 0, + Object = 1, + Material = 2, }; struct UniformMember { std::string name; - std::size_t typeHash; - uint32_t offset; - uint32_t layoutSize; - uint32_t count; - bool isArray = false; - bool isSampler = false; + std::size_t typeHash = 0; + uint32_t offset = 0; + uint32_t layoutSize = 0; // 바이트 (가변 배열이면 0) + uint32_t baseAlignment = 0; + uint32_t count = 1; // 배열 원소 개수 (단일이면 1) + bool bDynamicArray = false; // SSBO + + auto IsArray() const -> bool { return count > 1 || bDynamicArray; } }; - struct Std140LayoutInfo + struct LayoutInfo { uint32_t baseAlignment; // 오프셋의 정렬 단위 - uint32_t std140Size; // 실제 차지하는 총 크기(패딩 포함) + uint32_t bytes; // 실제 차지하는 총 크기(패딩 포함) }; - private: - std::vector members; public: - const Layout layout; - const Type type; - const ShaderStage stage; - const uint32_t binding; - const std::string name; - const bool bConstant; - private: + SH_RENDER_API UniformStructLayout(std::string name, uint32_t binding, Usage type, ShaderStage stage, Kind kind = Kind::Uniform); + SH_RENDER_API UniformStructLayout(std::string name, uint32_t binding, Usage type, ShaderStage stage, Kind kind, Layout layout); + + /// @brief 구조체의 맴버 변수를 추가. 구조체의 변수 순서대로 호출해야 한다. template - static auto GetSTD140Layout() -> Std140LayoutInfo; - public: - SH_RENDER_API UniformStructLayout(const std::string name, uint32_t binding, Type type, ShaderStage stage, bool bConstant = false, Layout layout = Layout::STD140); - /// @brief 구조체의 맴버 변수를 추가 하는 함수. 구조체의 변수 순서대로 호출 해야 한다. - /// @tparam T 타입 - /// @param name 이름 - template - void AddMember(const std::string& name); - /// @brief 구조체의 배열 맴버 변수를 추가 하는 함수. 구조체의 변수 순서대로 호출 해야 한다. - /// @tparam T 타입 - /// @param name 이름 - /// @param count 배열 원소 갯수 - template - void AddArrayMember(const std::string& name, std::size_t count); + void AddMember(const std::string& name); + /// @brief 구조체의 고정 배열 맴버를 추가. 구조체의 변수 순서대로 호출해야 한다. + /// @param count 원소 개수 (>= 1) + template + void AddArrayMember(const std::string& name, uint32_t count); + /// @brief SSBO 끝에 들어가는 가변 배열 맴버 추가. 반드시 마지막에 호출. + template + void AddDynamicArrayMember(const std::string& name); SH_RENDER_API auto GetMember(const std::string& name) const -> const UniformMember*; - SH_RENDER_API auto GetMembers() const -> const std::vector&; + SH_RENDER_API auto GetMembers() const -> const std::vector& { return members; } SH_RENDER_API auto HasMember(const std::string& name) const -> bool; - /// @brief 유니폼 구조체의 사이즈를 반환 하는 함수. - /// @return 바이트 + /// @brief 구조체의 사이즈(바이트). 가변 배열 맴버가 있으면 고정 부분까지의 크기만 반환. SH_RENDER_API auto GetSize() const -> std::size_t; + SH_RENDER_API auto GetLayout() const -> Layout { return layout; } + SH_RENDER_API auto GetKind() const -> Kind { return kind; } + SH_RENDER_API auto IsSampler() const -> bool { return kind == Kind::Sampler; } + SH_RENDER_API auto IsPushConstant() const -> bool { return kind == Kind::PushConstant; } + private: + template + static auto GetLayoutInfo() -> LayoutInfo; - SH_RENDER_API auto Serialize() const -> core::Json override; - SH_RENDER_API void Deserialize(const core::Json& json) override; + template + void AddMemberImpl(const std::string& name, uint32_t count, bool bDynamic); + public: + const Kind kind; + const Layout layout; + const Usage usage; + const ShaderStage stage; + const uint32_t binding; + const std::string name; + private: + std::vector members; }; template - inline auto UniformStructLayout::GetSTD140Layout() -> Std140LayoutInfo + inline auto UniformStructLayout::GetLayoutInfo() -> LayoutInfo { if constexpr (std::is_same_v) return { 4, 4 }; @@ -103,50 +118,76 @@ namespace sh::render return { 16, 64 }; #if defined(_MSC_VER) else - static_assert(std::_Always_false, "Unknown type for GetSTD140Layout: " __FUNCSIG__); + static_assert(std::_Always_false, "Unknown type for GetLayoutInfo: " __FUNCSIG__); #elif defined(__GNUC__) || defined(__clang__) else - static_assert(core::alwaysFalse, "Unknown type for GetSTD140Layout "); + static_assert(core::alwaysFalse, "Unknown type for GetLayoutInfo"); # else else - static_assert(always_false, "Unknown type for GetSTD140Layout: Unknown compiler"); + static_assert(always_false, "Unknown type for GetLayoutInfo: Unknown compiler"); #endif } template inline void UniformStructLayout::AddMember(const std::string& name) { - AddArrayMember(name, 1); + AddMemberImpl(name, 1, false); } template - inline void UniformStructLayout::AddArrayMember(const std::string& name, std::size_t count) + inline void UniformStructLayout::AddArrayMember(const std::string& name, uint32_t count) + { + AddMemberImpl(name, count, false); + } + template + inline void UniformStructLayout::AddDynamicArrayMember(const std::string& name) + { + AddMemberImpl(name, 0, true); + } + template + inline void UniformStructLayout::AddMemberImpl(const std::string& name, uint32_t count, bool bDynamic) { if constexpr (std::is_same_v) { - UniformMember member{ name, core::reflection::GetType().hash, 0, 0, count, (count > 1), true }; + UniformMember member{}; + member.name = name; + member.typeHash = core::reflection::GetType().hash; + member.count = count; + member.bDynamicArray = bDynamic; members.push_back(std::move(member)); + return; } else { - if (layout == Layout::STD140) - { - Std140LayoutInfo info = GetSTD140Layout(); - uint32_t arrayBaseAlignment = (count > 1) ? - std::max(info.baseAlignment, 16u) : info.baseAlignment; // 배열이면 최소 16정렬 - uint32_t arrayStride = (count > 1) ? - core::Util::AlignTo(info.std140Size, arrayBaseAlignment) : info.std140Size; - uint32_t totalSize = arrayStride * static_cast(count); + const LayoutInfo info = GetLayoutInfo(); + const bool isArray = (count > 1 || bDynamic); + + // STD140: 배열 원소 stride는 최소 16바이트 정렬 + // STD430: 타입 고유 alignment 사용 + const uint32_t baseAlignment = (layout == Layout::STD140 && isArray) + ? std::max(info.baseAlignment, 16u) + : info.baseAlignment; + + // 배열은 stride를 baseAlignment에 맞춤 + const uint32_t elementStride = isArray + ? core::Util::AlignTo(info.bytes, baseAlignment) + : info.bytes; - UniformMember member{ name, core::reflection::GetType().hash, 0, totalSize, count, (count > 1), false }; - if (members.empty()) - { - members.push_back(std::move(member)); - return; - } - auto& prev = members.back(); - uint32_t prevEnd = prev.offset + prev.layoutSize; - member.offset = core::Util::AlignTo(prevEnd, arrayBaseAlignment); - members.push_back(std::move(member)); + // 가변 배열은 런타임에 크기 결정 + const uint32_t totalSize = bDynamic ? 0 : (elementStride * count); + + UniformMember member{}; + member.name = name; + member.typeHash = core::reflection::GetType().hash; + member.layoutSize = totalSize; + member.baseAlignment = baseAlignment; + member.count = bDynamic ? 0 : count; + member.bDynamicArray = bDynamic; + + if (!members.empty()) + { + const UniformMember& prev = members.back(); + member.offset = core::Util::AlignTo(prev.offset + prev.layoutSize, baseAlignment); } + members.push_back(std::move(member)); } } }//namespace \ No newline at end of file diff --git a/include/Render/VulkanImpl/VulkanBuffer.h b/include/Render/VulkanImpl/VulkanBuffer.h index 0a58274b..067187b2 100644 --- a/include/Render/VulkanImpl/VulkanBuffer.h +++ b/include/Render/VulkanImpl/VulkanBuffer.h @@ -18,14 +18,17 @@ namespace sh::render::vk SH_RENDER_API auto operator=(const VulkanBuffer& other)->VulkanBuffer&; SH_RENDER_API auto operator=(VulkanBuffer&& other) noexcept -> VulkanBuffer&; + SH_RENDER_API auto Resize(std::size_t size) -> bool override; + SH_RENDER_API auto Create(size_t size, VkBufferUsageFlags usageBits, VkSharingMode sharing, VkMemoryPropertyFlags memPropFlagBits, bool persistentMapping = false) -> VkResult; SH_RENDER_API void Clean(); SH_RENDER_API void SetData(const void* data); - SH_RENDER_API auto GetData() const -> void* override; - SH_RENDER_API auto GetBuffer() const->VkBuffer; - SH_RENDER_API auto GetBufferInfo() const -> const VkBufferCreateInfo&; - SH_RENDER_API auto GetBufferMemory() const->VmaAllocation; - SH_RENDER_API auto GetSize() const->size_t; + + SH_RENDER_API auto GetData() const -> void* override { return dataPtr; } + SH_RENDER_API auto GetBuffer() const -> VkBuffer { return buffer; } + SH_RENDER_API auto GetBufferInfo() const -> const VkBufferCreateInfo& { return bufferInfo; } + SH_RENDER_API auto GetBufferMemory() const -> VmaAllocation { return bufferMem; } + SH_RENDER_API auto GetSize() const -> size_t { return size; } private: auto FindMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) -> uint32_t; private: @@ -41,4 +44,4 @@ namespace sh::render::vk void* dataPtr; bool persistentMapping; // 즉시 맵핑 }; -} \ No newline at end of file +}//namespace \ No newline at end of file diff --git a/include/Render/VulkanImpl/VulkanUniformBuffer.h b/include/Render/VulkanImpl/VulkanDescriptorSet.h similarity index 61% rename from include/Render/VulkanImpl/VulkanUniformBuffer.h rename to include/Render/VulkanImpl/VulkanDescriptorSet.h index 99d0af75..916dd3ed 100644 --- a/include/Render/VulkanImpl/VulkanUniformBuffer.h +++ b/include/Render/VulkanImpl/VulkanDescriptorSet.h @@ -3,31 +3,32 @@ #include "VulkanConfig.h" #include "../IUniformBuffer.h" +#include namespace sh::render::vk { class VulkanContext; /// @brief 디스크립터 셋을 추상화한 클래스 - class VulkanUniformBuffer : public IUniformBuffer + class VulkanDescriptorSet : public IUniformBuffer { - private: - const VulkanContext* context; - - VkDescriptorSet descSet; - uint32_t set = 0; - - bool bDynamic = false; public: - SH_RENDER_API VulkanUniformBuffer(); - SH_RENDER_API VulkanUniformBuffer(VulkanUniformBuffer&& other) noexcept; - SH_RENDER_API ~VulkanUniformBuffer(); + SH_RENDER_API VulkanDescriptorSet(); + SH_RENDER_API VulkanDescriptorSet(VulkanDescriptorSet&& other) noexcept; + SH_RENDER_API ~VulkanDescriptorSet(); - SH_RENDER_API void Create(const IRenderContext& context, const ShaderPass& shader, UniformStructLayout::Type type) override; + SH_RENDER_API void Create(const IRenderContext& context, const ShaderPass& shader, UniformStructLayout::Usage usage) override; SH_RENDER_API void Clear() override; SH_RENDER_API void Link(uint32_t binding, const IBuffer& buffer, std::size_t bufferSize) override; SH_RENDER_API void Link(uint32_t binding, const Texture& texture) override; - SH_RENDER_API auto GetSetNumber() const -> uint32_t; - SH_RENDER_API auto GetVkDescriptorSet() const -> VkDescriptorSet; + SH_RENDER_API auto GetSetIndex() const -> uint32_t { return set; } + SH_RENDER_API auto GetVkDescriptorSet() const -> VkDescriptorSet { return descSet; } + private: + const VulkanContext* context; + + VkDescriptorSet descSet; + uint32_t set = 0; + + std::map descriptorTypes; }; }//namespace \ No newline at end of file diff --git a/include/Render/VulkanImpl/VulkanShaderPass.h b/include/Render/VulkanImpl/VulkanShaderPass.h index 989858bf..5f5c7b9f 100644 --- a/include/Render/VulkanImpl/VulkanShaderPass.h +++ b/include/Render/VulkanImpl/VulkanShaderPass.h @@ -1,16 +1,11 @@ #pragma once - #include "../Export.h" #include "../ShaderPass.h" -#include "VulkanConfig.h" -#include "VulkanPipeline.h" - -#include "Core/SContainer.hpp" -#include "Core/NonCopyable.h" #include #include - +#include +#include namespace sh::render::vk { class VulkanContext; @@ -23,10 +18,11 @@ namespace sh::render::vk VkShaderModule vert; VkShaderModule frag; }; - struct DescryptorSetLayoutInfo + struct SetLayout { - VkDescriptorSetLayout layout; - bool bDynamic; + uint32_t set = 0; + VkDescriptorSetLayout descriptorSetLayout = VK_NULL_HANDLE; + std::map descriptorSetLayoutBindings; }; public: SH_RENDER_API VulkanShaderPass(const VulkanContext& context, const ShaderModules& shaderModules, const ShaderAST::PassNode& passNode); @@ -35,18 +31,20 @@ namespace sh::render::vk SH_RENDER_API void Clear() override; - SH_RENDER_API auto GetVertexShader() const -> const VkShaderModule; - SH_RENDER_API auto GetFragmentShader() const -> const VkShaderModule; - SH_RENDER_API void Build(); + SH_RENDER_API auto GetVertexShader() const -> const VkShaderModule { return vertShader; } + SH_RENDER_API auto GetFragmentShader() const -> const VkShaderModule { return fragShader; } + SH_RENDER_API auto GetPipelineLayout() const -> VkPipelineLayout { return pipelineLayout; } /// @brief 디스크립터셋 레이아웃을 반환 하는 함수 /// @param set 세트 번호 /// @return 디스크립터셋 레이아웃 포인터 - SH_RENDER_API auto GetDescriptorSetLayout(uint32_t set) const -> DescryptorSetLayoutInfo; - SH_RENDER_API auto GetSetCount() const -> uint32_t; - SH_RENDER_API auto GetPipelineLayout() const -> VkPipelineLayout; - SH_RENDER_API auto GetSpecializationMapEntry() const -> const std::vector&; + SH_RENDER_API auto GetDescriptorSetLayout(uint32_t set) const -> VkDescriptorSetLayout; + SH_RENDER_API auto GetSetCount() const -> std::size_t { return setlayouts.size(); } + SH_RENDER_API auto GetSetLayout(uint32_t set) const -> std::optional; + SH_RENDER_API auto GetSetLayouts() const -> const std::vector& { return setlayouts; } + SH_RENDER_API auto GetSpecializationMapEntry() const -> const std::vector& { return specializationEntry; } + SH_RENDER_API auto GetDescriptorBinding(uint32_t set, uint32_t binding) const -> std::optional; private: void AddDescriptorBinding(uint32_t set, uint32_t binding, VkDescriptorType type, VkShaderStageFlagBits stage); auto CreateDescriptorLayout() -> VkResult; @@ -59,8 +57,7 @@ namespace sh::render::vk VkShaderModule vertShader; VkShaderModule fragShader; - std::vector> descriptorBindings; // set, binding - std::vector descriptorSetLayout; + std::vector setlayouts; VkPipelineLayout pipelineLayout; @@ -68,4 +65,4 @@ namespace sh::render::vk bool bUseMatrixModel = false; }; -} \ No newline at end of file +}//namespace \ No newline at end of file diff --git a/src/Game/Asset/ShaderAsset.cpp b/src/Game/Asset/ShaderAsset.cpp index 8a06fd2a..69245570 100644 --- a/src/Game/Asset/ShaderAsset.cpp +++ b/src/Game/Asset/ShaderAsset.cpp @@ -2,19 +2,6 @@ namespace sh::game { - SH_GAME_API void ShaderAsset::SetAssetData() const - { - if (!shaderPtr.IsValid()) - return; - - const core::Json shaderObjJson = shaderPtr->Serialize(); - data = core::Json::to_cbor(shaderObjJson); - } - SH_GAME_API auto ShaderAsset::ParseAssetData() -> bool - { - shaderObjJson = core::Json::from_cbor(data); - return true; - } ShaderAsset::ShaderAsset() : Asset(ASSET_NAME) { @@ -37,4 +24,17 @@ namespace sh::game { return shaderObjJson; } -}//namespace \ No newline at end of file + SH_GAME_API void ShaderAsset::SetAssetData() const + { + if (!shaderPtr.IsValid()) + return; + + const core::Json shaderObjJson = shaderPtr->Serialize(); + data = core::Json::to_cbor(shaderObjJson); + } + SH_GAME_API auto ShaderAsset::ParseAssetData() -> bool + { + shaderObjJson = core::Json::from_cbor(data); + return true; + } +}//namespace1 \ No newline at end of file diff --git a/src/Game/Asset/ShaderLoader.cpp b/src/Game/Asset/ShaderLoader.cpp index 25673aa1..b2c424d7 100644 --- a/src/Game/Asset/ShaderLoader.cpp +++ b/src/Game/Asset/ShaderLoader.cpp @@ -118,7 +118,7 @@ namespace sh::game SH_ERROR_FORMAT("Asset({}) is not a shader!", asset.GetAssetUUID().ToString()); return nullptr; } - const auto& shaderAsset = static_cast(asset); + const game::ShaderAsset& shaderAsset = static_cast(asset); const core::Json& shaderObjJson = shaderAsset.GetShaderObjectJson(); if (!shaderObjJson.contains("shader")) diff --git a/src/Game/Component/Render/MeshRenderer.cpp b/src/Game/Component/Render/MeshRenderer.cpp index f6f9f52a..0b01604c 100644 --- a/src/Game/Component/Render/MeshRenderer.cpp +++ b/src/Game/Component/Render/MeshRenderer.cpp @@ -227,7 +227,7 @@ namespace sh::game for (auto& [passPtr, layoutPtr] : locations) { - if (layoutPtr->type == render::UniformStructLayout::Type::Material) + if (layoutPtr->usage == render::UniformStructLayout::Usage::Material) continue; std::vector data(layoutPtr->GetSize()); @@ -278,12 +278,12 @@ namespace sh::game { auto var = block.GetTextureProperty(member.name); if (core::IsValid(var)) - drawable->GetMaterialData().SetTextureData(*passPtr, layoutPtr->type, layoutPtr->binding, *var); + drawable->GetMaterialData().SetTextureData(*passPtr, layoutPtr->usage, layoutPtr->binding, *var); else { render::Texture* const texPtr = static_cast(core::SObject::GetSObjectUsingResolver(blackTexUUID)); if (texPtr != nullptr) - drawable->GetMaterialData().SetTextureData(*passPtr, layoutPtr->type, layoutPtr->binding, *texPtr); + drawable->GetMaterialData().SetTextureData(*passPtr, layoutPtr->usage, layoutPtr->binding, *texPtr); else SH_ERROR("Can't get default texture!"); } @@ -291,7 +291,7 @@ namespace sh::game } } if (!isSampler) - drawable->GetMaterialData().SetUniformData(*passPtr, layoutPtr->type, layoutPtr->binding, std::move(data)); + drawable->GetMaterialData().SetUniformData(*passPtr, layoutPtr->usage, layoutPtr->binding, std::move(data)); } } } @@ -377,7 +377,7 @@ namespace sh::game { for (auto& location : propInfo.locations) { - if (location.layoutPtr->type != render::UniformStructLayout::Type::Object) + if (location.layoutPtr->usage != render::UniformStructLayout::Usage::Object) continue; auto& locs = localUniformLocationsList[i]; auto it = std::find(locs.begin(), locs.end(), std::pair{ location.passPtr.Get(), location.layoutPtr }); @@ -462,7 +462,7 @@ namespace sh::game if (pass.IsPendingKill() || pass.GetLightingBinding() == -1) continue; - drawable.GetMaterialData().SetUniformData(pass, render::UniformStructLayout::Type::Object, pass.GetLightingBinding(), &lightStruct, sizeof(Light)); + drawable.GetMaterialData().SetUniformData(pass, render::UniformStructLayout::Usage::Object, pass.GetLightingBinding(), &lightStruct, sizeof(Light)); } } } diff --git a/src/Game/Component/Render/SkinnedMeshRenderer.cpp b/src/Game/Component/Render/SkinnedMeshRenderer.cpp index 40240a18..3db19b78 100644 --- a/src/Game/Component/Render/SkinnedMeshRenderer.cpp +++ b/src/Game/Component/Render/SkinnedMeshRenderer.cpp @@ -11,7 +11,6 @@ namespace sh::game SkinnedMeshRenderer::SkinnedMeshRenderer(GameObject& owner) : MeshRenderer(owner) { - finalBoneMatrices.fill(glm::mat4{ 1.0f }); } SkinnedMeshRenderer::~SkinnedMeshRenderer() = default; @@ -25,12 +24,12 @@ namespace sh::game SH_GAME_API void SkinnedMeshRenderer::SetSkinnedMesh(render::SkinnedMesh* mesh) { - finalBoneMatrices.fill(glm::mat4{ 1.0f }); SetMesh(mesh); } SH_GAME_API void SkinnedMeshRenderer::SetBones(std::vector boneTransforms) { bones = std::move(boneTransforms); + finalBoneMatrices.resize(bones.size()); const render::SkinnedMesh* const skinnedMesh = static_cast(GetMesh()); if (core::IsValid(skinnedMesh)) @@ -50,6 +49,9 @@ namespace sh::game if (jointCount == 0) return; + if (finalBoneMatrices.size() < bones.size()) + finalBoneMatrices.resize(bones.size()); + for (std::size_t i = 0; i < jointCount; ++i) { if (!core::IsValid(bones[i])) @@ -62,7 +64,7 @@ namespace sh::game if (drawables.empty()) return; - const std::size_t dataSize = render::SkinnedMesh::MAX_BONES * sizeof(glm::mat4); + const std::size_t dataSize = bones.size() * sizeof(glm::mat4); for (std::size_t i = 0; i < drawables.size(); ++i) { @@ -81,7 +83,7 @@ namespace sh::game continue; drawable->GetMaterialData().SetUniformData( pass, - render::UniformStructLayout::Type::Object, + render::UniformStructLayout::Usage::Object, pass.GetSkinBinding(), finalBoneMatrices.data(), dataSize); diff --git a/src/Render/BufferFactory.cpp b/src/Render/BufferFactory.cpp index d8fd1307..1f0417ab 100644 --- a/src/Render/BufferFactory.cpp +++ b/src/Render/BufferFactory.cpp @@ -3,7 +3,7 @@ #include "VulkanContext.h" #include "VulkanImpl/VulkanBuffer.h" -#include "VulkanImpl/VulkanUniformBuffer.h" +#include "VulkanImpl/VulkanDescriptorSet.h" #include "Core/Logger.h" @@ -11,15 +11,16 @@ namespace sh::render { - auto BufferFactory::CreateVkUniformBuffer(const vk::VulkanContext& context, std::size_t size, bool bTransferDst) -> std::unique_ptr + auto BufferFactory::CreateVkUniformBuffer(const vk::VulkanContext& context, const CreateInfo& info) -> std::unique_ptr { std::unique_ptr buffer = std::make_unique(context); - VkBufferUsageFlags usage = VkBufferUsageFlagBits::VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; - if (bTransferDst) + VkBufferUsageFlags usage = + info.bDynamic ? VkBufferUsageFlagBits::VK_BUFFER_USAGE_STORAGE_BUFFER_BIT : VkBufferUsageFlagBits::VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; + if (info.bTransferDst) usage |= VkBufferUsageFlagBits::VK_BUFFER_USAGE_TRANSFER_DST_BIT; - auto result = buffer->Create(size, usage, + VkResult result = buffer->Create(info.size, usage, VkSharingMode::VK_SHARING_MODE_EXCLUSIVE, VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, true); @@ -32,20 +33,20 @@ namespace sh::render return buffer; } - auto BufferFactory::Create(const IRenderContext& context, std::size_t size, bool bTransferDst) -> std::unique_ptr + auto BufferFactory::Create(const IRenderContext& context, const CreateInfo& info) -> std::unique_ptr { assert(context.GetRenderAPIType() == RenderAPI::Vulkan); if (context.GetRenderAPIType() == RenderAPI::Vulkan) - return CreateVkUniformBuffer(static_cast(context), size, bTransferDst); + return CreateVkUniformBuffer(static_cast(context), info); return nullptr; } - auto BufferFactory::CreateUniformBuffer(const IRenderContext& context, const ShaderPass& shader, UniformStructLayout::Type type) -> std::unique_ptr + auto BufferFactory::CreateUniformBuffer(const IRenderContext& context, const ShaderPass& shader, UniformStructLayout::Usage usage) -> std::unique_ptr { if (context.GetRenderAPIType() == RenderAPI::Vulkan) { - auto ptr = std::make_unique(); - ptr->Create(context, shader, type); + auto ptr = std::make_unique(); + ptr->Create(context, shader, usage); return ptr; } else diff --git a/src/Render/CMakeLists.txt b/src/Render/CMakeLists.txt index 332478c7..4f831b71 100644 --- a/src/Render/CMakeLists.txt +++ b/src/Render/CMakeLists.txt @@ -1,6 +1,6 @@ message("Build ShellEngine Renderer") file(GLOB_RECURSE SRC CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) -file(GLOB_RECURSE HEADERS CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/include/Render/*.{h,hpp}) +file(GLOB_RECURSE HEADERS CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/include/Render/*.h ${CMAKE_SOURCE_DIR}/include/Render/*.hpp) add_library(ShellEngineRender SHARED ${SRC} ${HEADERS}) add_library(ShellEngine::Render ALIAS ShellEngineRender) diff --git a/src/Render/Drawable.cpp b/src/Render/Drawable.cpp index e219aed3..98bb5892 100644 --- a/src/Render/Drawable.cpp +++ b/src/Render/Drawable.cpp @@ -3,7 +3,7 @@ #include "Core/ThreadSyncManager.h" #include - +#include namespace sh::render { Drawable::Drawable(const Material& mat, const Mesh& mesh) : @@ -114,24 +114,25 @@ namespace sh::render { for (auto& syncData : syncDatas) { - std::size_t index = syncData.changed.index(); - if (index == 0) + if (std::holds_alternative(syncData.changed)) + { continue; - else if (index == 1) // 메테리얼이 변경됨 + } + else if (std::holds_alternative(syncData.changed)) // 메테리얼이 변경됨 { - this->mat = std::get<1>(syncData.changed); + this->mat = std::get(syncData.changed); } - else if (index == 2) + else if (std::holds_alternative(syncData.changed)) { - this->mesh = std::get<2>(syncData.changed); + this->mesh = std::get(syncData.changed); } - else if (index == 3) + else if (std::holds_alternative(syncData.changed)) { - topology[core::ThreadType::Render] = std::get<3>(syncData.changed); + topology[core::ThreadType::Render] = std::get(syncData.changed); } - else if (index == 4) + else if (std::holds_alternative(syncData.changed)) { - priority[core::ThreadType::Render] = std::get<4>(syncData.changed); + priority[core::ThreadType::Render] = std::get(syncData.changed); } syncData.changed = std::monostate{}; } diff --git a/src/Render/Material.cpp b/src/Render/Material.cpp index 70b37556..a723dfa3 100644 --- a/src/Render/Material.cpp +++ b/src/Render/Material.cpp @@ -154,7 +154,7 @@ namespace sh::render for (auto& [pass, uniformLayout] : dirtyProps) { - if (uniformLayout->type == UniformStructLayout::Type::Object) + if (uniformLayout->usage == UniformStructLayout::Usage::Object) continue; std::vector data(uniformLayout->GetSize(), 0); @@ -229,13 +229,13 @@ namespace sh::render { auto var = propertyBlock.GetTextureProperty(member.name); if (core::IsValid(var)) - materialData->SetTextureData(*pass, uniformLayout->type, uniformLayout->binding, *var); + materialData->SetTextureData(*pass, uniformLayout->usage, uniformLayout->binding, *var); isSampler = true; } } if (!isSampler) - materialData->SetUniformData(*pass, uniformLayout->type, uniformLayout->binding, std::move(data)); + materialData->SetUniformData(*pass, uniformLayout->usage, uniformLayout->binding, std::move(data)); } dirtyProps.clear(); bPropertyDirty = false; diff --git a/src/Render/MaterialData.cpp b/src/Render/MaterialData.cpp index 97c6bc8b..76602700 100644 --- a/src/Render/MaterialData.cpp +++ b/src/Render/MaterialData.cpp @@ -62,7 +62,7 @@ namespace sh::render { if (std::holds_alternative(syncData.data)) // 버퍼 { - auto& bufferData = std::get<0>(syncData.data); + const SyncData::BufferSyncData& bufferData = std::get<0>(syncData.data); SetUniformDataAtSync(bufferData); } else // 텍스쳐 @@ -75,11 +75,11 @@ namespace sh::render bDirty = false; } - SH_RENDER_API void MaterialData::SetUniformData(const ShaderPass& shaderPass, UniformStructLayout::Type type, uint32_t binding, const void* data, std::size_t dataSize) + SH_RENDER_API void MaterialData::SetUniformData(const ShaderPass& shaderPass, UniformStructLayout::Usage usage, uint32_t binding, const void* data, std::size_t dataSize) { SyncData::BufferSyncData syncData{}; syncData.pass = &shaderPass; - syncData.set = static_cast(type); + syncData.set = static_cast(usage); syncData.binding = binding; syncData.data.resize(dataSize); std::memcpy(syncData.data.data(), data, syncData.data.size()); @@ -87,33 +87,22 @@ namespace sh::render syncDatas.push_back(SyncData{ std::move(syncData) }); SyncDirty(); } - SH_RENDER_API void MaterialData::SetUniformData(const ShaderPass& shaderPass, UniformStructLayout::Type type, uint32_t binding, const std::vector& data) + SH_RENDER_API void MaterialData::SetUniformData(const ShaderPass& shaderPass, UniformStructLayout::Usage usage, uint32_t binding, std::vector data) { SyncData::BufferSyncData syncData{}; syncData.pass = &shaderPass; - syncData.set = static_cast(type); - syncData.binding = binding; - syncData.data = data; - - syncDatas.push_back(SyncData{ std::move(syncData) }); - SyncDirty(); - } - SH_RENDER_API void MaterialData::SetUniformData(const ShaderPass& shaderPass, UniformStructLayout::Type type, uint32_t binding, std::vector&& data) - { - SyncData::BufferSyncData syncData{}; - syncData.pass = &shaderPass; - syncData.set = static_cast(type); + syncData.set = static_cast(usage); syncData.binding = binding; syncData.data = std::move(data); syncDatas.push_back(SyncData{ std::move(syncData) }); SyncDirty(); } - SH_RENDER_API void MaterialData::SetTextureData(const ShaderPass& shaderPass, UniformStructLayout::Type type, uint32_t binding, const Texture& tex) + SH_RENDER_API void MaterialData::SetTextureData(const ShaderPass& shaderPass, UniformStructLayout::Usage usage, uint32_t binding, const Texture& tex) { SyncData::UniformBufferSyncData syncData{}; syncData.pass = &shaderPass; - syncData.set = static_cast(type); + syncData.set = static_cast(usage); syncData.binding = binding; syncData.tex = &tex; @@ -121,15 +110,15 @@ namespace sh::render SyncDirty(); } - SH_RENDER_API auto MaterialData::GetShaderBuffer(const ShaderPass& shaderPass, UniformStructLayout::Type type, uint32_t binding) const -> IBuffer* + SH_RENDER_API auto MaterialData::GetShaderBuffer(const ShaderPass& shaderPass, UniformStructLayout::Usage usage, uint32_t binding) const -> IBuffer* { const PassData* passData = GetMaterialPassData(shaderPass); if (passData == nullptr) return nullptr; - uint32_t set = static_cast(type); - auto itSet = passData->uniformData.find(set); - if (itSet == passData->uniformData.end()) + uint32_t set = static_cast(usage); + auto itSet = passData->buffers.find(set); + if (itSet == passData->buffers.end()) return nullptr; const auto& buffers = itSet->second; @@ -138,13 +127,13 @@ namespace sh::render return buffers[binding] ? buffers[binding].get() : nullptr; } - SH_RENDER_API auto MaterialData::GetUniformBuffer(const ShaderPass& shaderPass, UniformStructLayout::Type type) const -> IUniformBuffer* + SH_RENDER_API auto MaterialData::GetUniformBuffer(const ShaderPass& shaderPass, UniformStructLayout::Usage usage) const -> IUniformBuffer* { const PassData* passData = GetMaterialPassData(shaderPass); if (passData == nullptr) return nullptr; - uint32_t set = static_cast(type); + uint32_t set = static_cast(usage); auto it = passData->uniformBuffer.find(set); if (it == passData->uniformBuffer.end()) return nullptr; @@ -162,9 +151,9 @@ namespace sh::render void MaterialData::CreateBuffers(const IRenderContext& context, const Shader& shader, bool bPerObject) { - std::vector usages = { UniformStructLayout::Type::Camera, UniformStructLayout::Type::Material }; + std::initializer_list usages = { UniformStructLayout::Usage::Camera, UniformStructLayout::Usage::Material }; if (bPerObject) - usages = { UniformStructLayout::Type::Object }; + usages = { UniformStructLayout::Usage::Object }; for (auto& [passName, shaderPasses] : shader.GetAllShaderPass()) { @@ -173,50 +162,55 @@ namespace sh::render PassData passData{}; uint32_t maxSet = 0; std::vector sets; - // 유니폼 데이터 버퍼 (텍스쳐외 모든 GPU에 저장할 데이터) - auto layoutsPerPass = { shaderPass.GetVertexUniforms(), shaderPass.GetFragmentUniforms() }; - for (const std::vector& layouts : layoutsPerPass) + // 버퍼 (텍스쳐외 모든 GPU에 저장할 데이터) + for (const std::vector* layouts : { &shaderPass.GetVertexUniforms(), &shaderPass.GetFragmentUniforms() }) { - for (const UniformStructLayout& uniformLayout : layouts) + for (const UniformStructLayout& uniformLayout : *layouts) { - if (uniformLayout.bConstant) + if (uniformLayout.IsPushConstant()) continue; - if (std::find(usages.begin(), usages.end(), uniformLayout.type) == usages.end()) + if (std::find(usages.begin(), usages.end(), uniformLayout.usage) == usages.end()) continue; - uint32_t set = static_cast(uniformLayout.type); + const uint32_t set = static_cast(uniformLayout.usage); maxSet = (maxSet < set) ? set : maxSet; if (std::find(sets.begin(), sets.end(), set) == sets.end()) sets.push_back(set); - auto& bindingBuffers = passData.uniformData[set]; + std::vector>& bindingBuffers = passData.buffers[set]; if (bindingBuffers.size() <= uniformLayout.binding) bindingBuffers.resize(uniformLayout.binding + 1); if (set == 0) // 카메라 데이터는 다른 곳에서 관리한다. continue; - bindingBuffers[uniformLayout.binding] = BufferFactory::Create(context, uniformLayout.GetSize()); + + BufferFactory::CreateInfo info{}; + info.size = uniformLayout.GetSize(); + info.bDynamic = (uniformLayout.GetKind() == UniformStructLayout::Kind::Storage); + if (info.bDynamic && info.size == 0) + info.size = 1; // 실제 데이터 업로드 시 Resize됨 + bindingBuffers[uniformLayout.binding] = BufferFactory::Create(context, info); } } for (const UniformStructLayout& layout : shaderPass.GetSamplerUniforms()) { - uint32_t set = static_cast(layout.type); + uint32_t set = static_cast(layout.usage); maxSet = (maxSet < set) ? set : maxSet; if (std::find(sets.begin(), sets.end(), set) == sets.end()) sets.push_back(set); - auto& bindingBuffers = passData.uniformData[set]; + std::vector>& bindingBuffers = passData.buffers[set]; if (bindingBuffers.size() <= layout.binding) bindingBuffers.resize(layout.binding + 1); } // 유니폼 버퍼 (GPU로 데이터 전송 역할) for (uint32_t set : sets) { - passData.uniformBuffer[set] = BufferFactory::CreateUniformBuffer(context, shaderPass, static_cast(set)); - auto it = passData.uniformData.find(set); - if (it == passData.uniformData.end()) + passData.uniformBuffer[set] = BufferFactory::CreateUniformBuffer(context, shaderPass, static_cast(set)); + auto it = passData.buffers.find(set); + if (it == passData.buffers.end()) continue; - auto& bufferVec = it->second; + const std::vector>& bufferVec = it->second; for (uint32_t binding = 0; binding < bufferVec.size(); ++binding) { if (set != 0) // 카메라 데이터는 다른 곳에서 관리한다. @@ -227,6 +221,7 @@ namespace sh::render } else { + assert(context.GetRenderAPIType() == RenderAPI::Vulkan); // 언젠가 API 추가되면 빼기 // 카메라 데이터 if (context.GetRenderAPIType() == RenderAPI::Vulkan) { @@ -258,17 +253,29 @@ namespace sh::render return; const uint32_t set = bufferSyncData.set; - auto itSet = passData->uniformData.find(set); - if (itSet == passData->uniformData.end()) + auto itSet = passData->buffers.find(set); + if (itSet == passData->buffers.end()) return; const uint32_t binding = bufferSyncData.binding; - auto& bufferVec = itSet->second; + const std::vector>& bufferVec = itSet->second; if (bufferSyncData.binding >= bufferVec.size() || bufferVec[bufferSyncData.binding] == nullptr) return; if (bufferVec[binding]->GetSize() != bufferSyncData.data.size()) { + for (const std::vector* layouts : { &bufferSyncData.pass->GetVertexUniforms(), &bufferSyncData.pass->GetFragmentUniforms() }) + { + for (const UniformStructLayout& layout : *layouts) + { + if (static_cast(layout.usage) != set || layout.binding != binding || layout.GetKind() != UniformStructLayout::Kind::Storage) + continue; + bufferVec[binding]->Resize(bufferSyncData.data.size()); + passData->uniformBuffer.at(set)->Link(binding, *bufferVec[binding].get()); + bufferVec[binding]->SetData(bufferSyncData.data.data()); + return; + } + } SH_ERROR_FORMAT("Buffer size is different! expected: {}, current: {}", bufferVec[binding]->GetSize(), bufferSyncData.data.size()); return; } @@ -291,7 +298,7 @@ namespace sh::render bool bFind = false; for (auto& layout : shaderPass->GetSamplerUniforms()) { - if (static_cast(layout.type) == set && layout.binding == binding) + if (static_cast(layout.usage) == set && layout.binding == binding) { bFind = true; break; diff --git a/src/Render/ScriptableRenderer.cpp b/src/Render/ScriptableRenderer.cpp index 06d70e79..5fd83d3f 100644 --- a/src/Render/ScriptableRenderer.cpp +++ b/src/Render/ScriptableRenderer.cpp @@ -45,7 +45,10 @@ namespace sh::render { for (auto& data : syncDatas) { - std::unique_ptr buffer = BufferFactory::Create(ctx, sizeof(uint8_t) * 4, true); + BufferFactory::CreateInfo ci{}; + ci.size = sizeof(uint8_t) * 4; + ci.bTransferDst = true; + std::unique_ptr buffer = BufferFactory::Create(ctx, ci); IRenderThrMethod::EnqueCopyImagePixelToBuffer(*cpyPass, *data.src, data.x, data.y, *buffer.get()); pendingReadbacks.push_back(PendingReadback{ std::move(buffer), std::move(data.promise) }); diff --git a/src/Render/ShaderAST.cpp b/src/Render/ShaderAST.cpp index e798e7bf..6c9eb97e 100644 --- a/src/Render/ShaderAST.cpp +++ b/src/Render/ShaderAST.cpp @@ -6,17 +6,22 @@ namespace sh::render { core::Json json; json["type"] = static_cast(type); - json["size"] = size; + json["arraySize"] = arraySize; json["name"] = name; json["default"] = defaultValue; json["attribute"] = static_cast(attribute); + json["dynamicArray"] = bDynamicArray; return json; } void ShaderAST::VariableNode::Deserialize(const core::Json& json) { type = static_cast(json.at("type").get()); - size = json.at("size").get(); + if (json.contains("arraySize")) + { + arraySize = json.at("arraySize").get(); + bDynamicArray = json.value("dynamicArray", false); + } name = json.at("name").get(); if (json.contains("default")) defaultValue = json["default"].get(); @@ -37,15 +42,13 @@ namespace sh::render var.Deserialize(json.at("var")); } - auto ShaderAST::UBONode::Serialize() const -> core::Json + auto ShaderAST::BufferNode::Serialize() const -> core::Json { core::Json json; + json["bufferType"] = static_cast(bufferType); json["set"] = set; json["binding"] = binding; json["name"] = name; - json["sampler"] = bSampler; - json["constant"] = bConstant; - core::Json varArray = core::Json::array(); for (const auto& v : vars) { @@ -55,13 +58,14 @@ namespace sh::render return json; } - void ShaderAST::UBONode::Deserialize(const core::Json& json) + void ShaderAST::BufferNode::Deserialize(const core::Json& json) { set = json.at("set").get(); binding = json.at("binding").get(); name = json.at("name").get(); - bSampler = json.at("sampler").get(); - bConstant = json.at("constant").get(); + + if (json.contains("bufferType")) + bufferType = static_cast(json.at("bufferType").get()); vars.clear(); for (const auto& v : json.at("vars")) @@ -93,10 +97,10 @@ namespace sh::render json["out"] = std::move(outArray); // uniforms - core::Json uboArray = core::Json::array(); - for (const auto& u : uniforms) - uboArray.push_back(u.Serialize()); - json["uniforms"] = std::move(uboArray); + core::Json bufferArray = core::Json::array(); + for (const auto& u : buffers) + bufferArray.push_back(u.Serialize()); + json["buffers"] = std::move(bufferArray); // declaration json["declaration"] = declaration; @@ -133,12 +137,12 @@ namespace sh::render } // uniforms - uniforms.clear(); - for (const auto& u : json.at("uniforms")) + buffers.clear(); + for (const auto& u : json.at("buffers")) { - UBONode ubo; + BufferNode ubo; ubo.Deserialize(u); - uniforms.push_back(std::move(ubo)); + buffers.push_back(std::move(ubo)); } // declaration diff --git a/src/Render/ShaderParser.cpp b/src/Render/ShaderParser.cpp index 536e9b8c..9c486afd 100644 --- a/src/Render/ShaderParser.cpp +++ b/src/Render/ShaderParser.cpp @@ -237,7 +237,7 @@ namespace sh::render if (PreviousToken().type == ShaderLexer::TokenType::LSquareBracket) { ConsumeToken(ShaderLexer::TokenType::Number); - varNode.size = std::stoi(PreviousToken().text); + varNode.arraySize = static_cast(std::stoi(PreviousToken().text)); ConsumeToken(ShaderLexer::TokenType::RSquareBracket); ConsumeToken(ShaderLexer::TokenType::Semicolon); } @@ -590,8 +590,8 @@ namespace sh::render // uniforms 벡터 수정 후 UBO 이터레이터를 갱신하는 헬퍼 auto refreshUboIt = [&]() { - return std::find_if(stageNode.uniforms.begin(), stageNode.uniforms.end(), - [](const ShaderAST::UBONode& ubo) + return std::find_if(stageNode.buffers.begin(), stageNode.buffers.end(), + [](const ShaderAST::BufferNode& ubo) { return ubo.name == "UBO"; } @@ -615,7 +615,7 @@ namespace sh::render layoutNode.binding = binding; layoutNode.var.name = name; layoutNode.var.type = type; - layoutNode.var.size = 1; + layoutNode.var.arraySize = 1; stageNode.in.push_back(std::move(layoutNode)); } bRegistered = true; @@ -668,18 +668,18 @@ namespace sh::render { if (!usingCamera) { - auto it = std::find_if(stageNode.uniforms.begin(), stageNode.uniforms.end(), - [](const ShaderAST::UBONode& ubo) { return ubo.name == "CAMERA"; }); - if (it == stageNode.uniforms.end()) + auto it = std::find_if(stageNode.buffers.begin(), stageNode.buffers.end(), + [](const ShaderAST::BufferNode& ubo) { return ubo.name == "CAMERA"; }); + if (it == stageNode.buffers.end()) { - ShaderAST::UBONode uboNode{}; + ShaderAST::BufferNode uboNode{}; + uboNode.bufferType = ShaderAST::BufferType::Uniform; uboNode.name = "CAMERA"; - uboNode.set = static_cast(UniformStructLayout::Type::Camera); + uboNode.set = static_cast(UniformStructLayout::Usage::Camera); uboNode.binding = 0; - uboNode.bSampler = false; uboNode.vars.push_back(ShaderAST::VariableNode{ ShaderAST::VariableType::Mat4, 1, "view" }); uboNode.vars.push_back(ShaderAST::VariableNode{ ShaderAST::VariableType::Mat4, 1, "proj" }); - stageNode.uniforms.push_back(std::move(uboNode)); + stageNode.buffers.push_back(std::move(uboNode)); uboit = refreshUboIt(); } usingCamera = true; @@ -689,18 +689,17 @@ namespace sh::render { if (!usingMatrixModel) { - auto it = std::find_if(stageNode.uniforms.begin(), stageNode.uniforms.end(), - [](const ShaderAST::UBONode& ubo) { return ubo.name == "CONSTANTS"; }); - if (it == stageNode.uniforms.end()) + auto it = std::find_if(stageNode.buffers.begin(), stageNode.buffers.end(), + [](const ShaderAST::BufferNode& ubo) { return ubo.name == "CONSTANTS"; }); + if (it == stageNode.buffers.end()) { - ShaderAST::UBONode uboNode{}; + ShaderAST::BufferNode uboNode{}; + uboNode.bufferType = ShaderAST::BufferType::PushConstant; uboNode.name = "CONSTANTS"; - uboNode.set = static_cast(UniformStructLayout::Type::Object); // 의미 없음 + uboNode.set = static_cast(UniformStructLayout::Usage::Object); // 의미 없음 uboNode.binding = 0; // 의미 없음 - uboNode.bConstant = true; - uboNode.bSampler = false; uboNode.vars.push_back(ShaderAST::VariableNode{ ShaderAST::VariableType::Mat4, 1, "model" }); - stageNode.uniforms.push_back(std::move(uboNode)); + stageNode.buffers.push_back(std::move(uboNode)); uboit = refreshUboIt(); } usingMatrixModel = true; @@ -710,19 +709,19 @@ namespace sh::render { if (!usingLIGHT) { - auto it = std::find_if(stageNode.uniforms.begin(), stageNode.uniforms.end(), - [](const ShaderAST::UBONode& ubo) { return ubo.name == "LIGHT"; }); - if (it == stageNode.uniforms.end()) + auto it = std::find_if(stageNode.buffers.begin(), stageNode.buffers.end(), + [](const ShaderAST::BufferNode& ubo) { return ubo.name == "LIGHT"; }); + if (it == stageNode.buffers.end()) { - ShaderAST::UBONode uboNode{}; + ShaderAST::BufferNode uboNode{}; + uboNode.bufferType = ShaderAST::BufferType::Uniform; uboNode.name = "LIGHT"; - uboNode.set = static_cast(UniformStructLayout::Type::Object); + uboNode.set = static_cast(UniformStructLayout::Usage::Object); uboNode.binding = lastObjectUniformBinding++; - uboNode.bSampler = false; uboNode.vars.push_back(ShaderAST::VariableNode{ ShaderAST::VariableType::Int, 1, "count" }); uboNode.vars.push_back(ShaderAST::VariableNode{ ShaderAST::VariableType::Vec4, 10, "pos" }); uboNode.vars.push_back(ShaderAST::VariableNode{ ShaderAST::VariableType::Vec4, 10, "other" }); - stageNode.uniforms.push_back(std::move(uboNode)); + stageNode.buffers.push_back(std::move(uboNode)); stageNode.lightingBinding = uboNode.binding; uboit = refreshUboIt(); } @@ -737,18 +736,18 @@ namespace sh::render usingMATRIX_SKIN = true; if (!usingSKIN) { - auto it = std::find_if(stageNode.uniforms.begin(), stageNode.uniforms.end(), - [](const ShaderAST::UBONode& ubo) { return ubo.name == "SKIN"; }); - if (it == stageNode.uniforms.end()) + auto it = std::find_if(stageNode.buffers.begin(), stageNode.buffers.end(), + [](const ShaderAST::BufferNode& ssbo) { return ssbo.name == "SKIN"; }); + if (it == stageNode.buffers.end()) { - ShaderAST::UBONode uboNode{}; - uboNode.name = "SKIN"; - uboNode.set = static_cast(UniformStructLayout::Type::Object); - uboNode.binding = lastObjectUniformBinding++; - uboNode.bSampler = false; - uboNode.vars.push_back(ShaderAST::VariableNode{ ShaderAST::VariableType::Mat4, 128, "ibm" }); - stageNode.uniforms.push_back(std::move(uboNode)); - stageNode.skinBinding = uboNode.binding; + ShaderAST::BufferNode ssboNode{}; + ssboNode.bufferType = ShaderAST::BufferType::Storage; + ssboNode.name = "SKIN"; + ssboNode.set = static_cast(UniformStructLayout::Usage::Object); + ssboNode.binding = lastObjectUniformBinding++; + ssboNode.vars.push_back(ShaderAST::VariableNode::MakeDynamicArray(ShaderAST::VariableType::Mat4, "ibm")); + stageNode.buffers.push_back(std::move(ssboNode)); + stageNode.skinBinding = ssboNode.binding; uboit = refreshUboIt(); } usingSKIN = true; @@ -756,7 +755,7 @@ namespace sh::render } else if (token.type == ShaderLexer::TokenType::Identifier) { - if (uboit != stageNode.uniforms.end()) + if (uboit != stageNode.buffers.end()) { auto varit = std::find_if(uboit->vars.begin(), uboit->vars.end(), [&](const ShaderAST::VariableNode& var) { return var.name == token.text; }); @@ -783,7 +782,7 @@ namespace sh::render { ConsumeToken(ShaderLexer::TokenType::Layout); ShaderAST::LayoutNode layoutNode{}; - ShaderAST::UBONode uboNode{}; + ShaderAST::BufferNode uboNode{}; ConsumeToken(ShaderLexer::TokenType::LBracket); // ( ConsumeToken(ShaderLexer::TokenType::Identifier); @@ -848,7 +847,7 @@ namespace sh::render } else if (CheckToken(ShaderLexer::TokenType::Sampler2D)) { - uboNode.bSampler = true; + uboNode.bufferType = ShaderAST::BufferType::Sampler; NextToken(); ConsumeToken(ShaderLexer::TokenType::Identifier); uboNode.name = PreviousToken().text; @@ -857,7 +856,7 @@ namespace sh::render else throw ShaderParserException({ GetTokenErrorString("Not found Identifier or Sampler2D keyword").c_str()}); - stageNode.uniforms.push_back(std::move(uboNode)); + stageNode.buffers.push_back(std::move(uboNode)); } } void ShaderParser::ParseUniform(const ShaderAST::ShaderNode& shaderNode, ShaderAST::StageNode& stageNode) @@ -886,20 +885,20 @@ namespace sh::render if (PreviousToken().type == ShaderLexer::TokenType::LSquareBracket) { ConsumeToken(ShaderLexer::TokenType::Number); - varNode.size = std::stoi(PreviousToken().text); + varNode.arraySize = static_cast(std::stoi(PreviousToken().text)); ConsumeToken(ShaderLexer::TokenType::RSquareBracket); ConsumeToken(ShaderLexer::TokenType::Semicolon); } // 프로퍼티 구문에 있는지 검사 bool hasProperty = false; - uint32_t set = static_cast(UniformStructLayout::Type::Material); + uint32_t set = static_cast(UniformStructLayout::Usage::Material); for (auto& property : shaderNode.properties) { - if (property.type == varNode.type && property.size == varNode.size && property.name == varNode.name) + if (property.type == varNode.type && property.arraySize == varNode.arraySize && property.name == varNode.name) { set = (property.attribute == ShaderAST::VariableAttribute::Local) ? - static_cast(UniformStructLayout::Type::Object) : static_cast(UniformStructLayout::Type::Material); + static_cast(UniformStructLayout::Usage::Object) : static_cast(UniformStructLayout::Usage::Material); hasProperty = true; break; } @@ -909,40 +908,40 @@ namespace sh::render if (varNode.type != ShaderAST::VariableType::Sampler) { - auto it = std::find_if(stageNode.uniforms.begin(), stageNode.uniforms.end(), [](const ShaderAST::UBONode& uboNode) + auto it = std::find_if(stageNode.buffers.begin(), stageNode.buffers.end(), [](const ShaderAST::BufferNode& uboNode) { return uboNode.name == "UBO"; } ); - if (it == stageNode.uniforms.end()) + if (it == stageNode.buffers.end()) { - ShaderAST::UBONode uboNode{}; + ShaderAST::BufferNode uboNode{}; + uboNode.bufferType = ShaderAST::BufferType::Uniform; uboNode.set = set; uboNode.binding = (set == static_cast(ShaderAST::VariableAttribute::Local)) ? lastObjectUniformBinding++ : lastMaterialUniformBinding++; uboNode.name = "UBO"; - uboNode.bSampler = false; uboNode.vars.push_back(std::move(varNode)); - stageNode.uniforms.push_back(std::move(uboNode)); + stageNode.buffers.push_back(std::move(uboNode)); } else { - ShaderAST::UBONode& uboNode = *it; + ShaderAST::BufferNode& uboNode = *it; uboNode.vars.push_back(std::move(varNode)); } } else { - ShaderAST::UBONode uboNode{}; - uboNode.bSampler = true; + ShaderAST::BufferNode uboNode{}; + uboNode.bufferType = ShaderAST::BufferType::Sampler; uboNode.name = varNode.name; uboNode.set = set; - uboNode.binding = (set == static_cast(ShaderAST::VariableAttribute::Local)) ? + uboNode.binding = (set == static_cast(ShaderAST::VariableAttribute::Local)) ? lastObjectUniformBinding++ : lastMaterialUniformBinding++; - stageNode.uniforms.push_back(std::move(uboNode)); + stageNode.buffers.push_back(std::move(uboNode)); } } - void ShaderParser::ParseUniformBody(ShaderAST::UBONode& uboNode) + void ShaderParser::ParseUniformBody(ShaderAST::BufferNode& uboNode) { ConsumeToken(ShaderLexer::TokenType::LBrace); // { @@ -957,7 +956,7 @@ namespace sh::render { NextToken(); ConsumeToken(ShaderLexer::TokenType::Number); - varNode.size = std::stoi(PreviousToken().text); + varNode.arraySize = static_cast(std::stoi(PreviousToken().text)); ConsumeToken(ShaderLexer::TokenType::RSquareBracket); } ConsumeToken(ShaderLexer::TokenType::Semicolon); @@ -1002,14 +1001,14 @@ namespace sh::render for (auto& stageNode : passNode.stages) { // UBO 유니폼 변수 정렬 (레이아웃 재배치) - auto it = std::find_if(stageNode.uniforms.begin(), stageNode.uniforms.end(), [&](const ShaderAST::UBONode& uboNode) + auto it = std::find_if(stageNode.buffers.begin(), stageNode.buffers.end(), [&](const ShaderAST::BufferNode& uboNode) { return uboNode.name == "UBO"; }); - if (it == stageNode.uniforms.end()) + if (it == stageNode.buffers.end()) continue; - ShaderAST::UBONode& uboNode = *it; + ShaderAST::BufferNode& uboNode = *it; std::sort(uboNode.vars.begin(), uboNode.vars.end(), [&](const ShaderAST::VariableNode& varNode1, const ShaderAST::VariableNode& varNode2) { @@ -1037,27 +1036,39 @@ namespace sh::render for (auto& out : stageNode.out) code += fmt::format("layout(location = {}) out {} {};\n", out.binding, VariableTypeToString(out.var.type), out.var.name); - for (auto& uniform : stageNode.uniforms) + for (auto& uniform : stageNode.buffers) { - if (uniform.bSampler) + if (uniform.bufferType == ShaderAST::BufferType::Sampler) { code += fmt::format("layout(set = {}, binding = {}) uniform sampler2D {};\n", uniform.set, uniform.binding, uniform.name); continue; } std::string uniformMembers; + bool bDynamicArrayFlag = false; for (auto& member : uniform.vars) { - if (member.size == 1) + if (member.bDynamicArray) + { + assert(!bDynamicArrayFlag); + if (bDynamicArrayFlag) + throw ShaderParserException{ "SSBO must have at most one dynamic array!" }; + uniformMembers += fmt::format("{} {}[];\n", VariableTypeToString(member.type), member.name); + bDynamicArrayFlag = true; + } + else if (member.arraySize == 1) uniformMembers += fmt::format("{} {};\n", VariableTypeToString(member.type), member.name); else - uniformMembers += fmt::format("{} {}[{}];\n", VariableTypeToString(member.type), member.name, member.size); + uniformMembers += fmt::format("{} {}[{}];\n", VariableTypeToString(member.type), member.name, member.arraySize); } - if (uniform.bConstant) + if (uniform.bufferType == ShaderAST::BufferType::PushConstant) { code += fmt::format("layout(push_constant) uniform UNIFORM_{} {{\n {} }} {};\n", uniform.name, uniformMembers, uniform.name); continue; } - code += fmt::format("layout(set = {}, binding = {}) uniform {} {{\n {} }} {};\n", uniform.set, uniform.binding, "UNIFORM_" + uniform.name, uniformMembers, uniform.name); + if (uniform.bufferType == ShaderAST::BufferType::Storage) + code += fmt::format("layout(std430, set = {}, binding = {}) readonly buffer {} {{\n {} }} {};\n", uniform.set, uniform.binding, "UNIFORM_" + uniform.name, uniformMembers, uniform.name); + else + code += fmt::format("layout(set = {}, binding = {}) uniform {} {{\n {} }} {};\n", uniform.set, uniform.binding, "UNIFORM_" + uniform.name, uniformMembers, uniform.name); } for (int i = 0; i < passNode.constants.size(); ++i) { diff --git a/src/Render/ShaderPass.cpp b/src/Render/ShaderPass.cpp index c9c9361c..21dc5294 100644 --- a/src/Render/ShaderPass.cpp +++ b/src/Render/ShaderPass.cpp @@ -92,7 +92,7 @@ namespace sh::render { if (stage == ShaderStage::Vertex) { - for (auto& uniformStruct : GetVertexUniforms()) + for (const UniformStructLayout& uniformStruct : GetVertexUniforms()) { if (uniformStruct.HasMember(name)) return &uniformStruct; @@ -100,12 +100,12 @@ namespace sh::render } else if (stage == ShaderStage::Fragment) { - for (auto& uniformStruct : GetFragmentUniforms()) + for (const UniformStructLayout& uniformStruct : GetFragmentUniforms()) { if (uniformStruct.HasMember(name)) return &uniformStruct; } - for (auto& uniformStruct : GetSamplerUniforms()) + for (const UniformStructLayout& uniformStruct : GetSamplerUniforms()) { if (uniformStruct.HasMember(name)) return &uniformStruct; @@ -154,8 +154,6 @@ namespace sh::render SH_RENDER_API auto ShaderPass::Serialize() const -> core::Json { core::Json mainJson = Super::Serialize(); - - mainJson["shaderPass"] = core::Json{}; core::Json& shaderPassJson = mainJson["shaderPass"]; shaderPassJson["vertShaderData"] = shaderCode.vert; @@ -225,32 +223,49 @@ namespace sh::render } } } - for (auto& uniform : stage.uniforms) + for (const ShaderAST::BufferNode& bufferNode : stage.buffers) { - UniformStructLayout::Type uniformType = static_cast(uniform.set); + UniformStructLayout::Usage uniformUsage = static_cast(bufferNode.set); + + UniformStructLayout::Kind kind = UniformStructLayout::Kind::Uniform; + switch (bufferNode.bufferType) + { + case render::ShaderAST::BufferType::Storage: kind = UniformStructLayout::Kind::Storage; break; + case render::ShaderAST::BufferType::Sampler: kind = UniformStructLayout::Kind::Sampler; break; + case render::ShaderAST::BufferType::PushConstant: kind = UniformStructLayout::Kind::PushConstant; break; + case render::ShaderAST::BufferType::Uniform: kind = UniformStructLayout::Kind::Uniform; break; + } - render::UniformStructLayout uniformLayout{ uniform.name, uniform.binding, uniformType, stageType, uniform.bConstant }; + render::UniformStructLayout uniformLayout{ bufferNode.name, bufferNode.binding, uniformUsage, stageType, kind }; - bHasConstant |= uniform.bConstant; + bHasConstant |= (kind == UniformStructLayout::Kind::PushConstant); - if (!uniform.bSampler) + if (kind != UniformStructLayout::Kind::Sampler) { - for (auto& var : uniform.vars) + for (auto& var : bufferNode.vars) { + auto addFn = [&](auto typeTag) + { + using T = decltype(typeTag); + if (var.bDynamicArray) + uniformLayout.AddDynamicArrayMember(var.name); + else + uniformLayout.AddArrayMember(var.name, var.arraySize); + }; switch (var.type) { - case render::ShaderAST::VariableType::Vec4: uniformLayout.AddArrayMember(var.name, var.size); break; - case render::ShaderAST::VariableType::Vec3: uniformLayout.AddArrayMember(var.name, var.size); break; - case render::ShaderAST::VariableType::Vec2: uniformLayout.AddArrayMember(var.name, var.size); break; - case render::ShaderAST::VariableType::Mat4: uniformLayout.AddArrayMember(var.name, var.size); break; - case render::ShaderAST::VariableType::Mat3: uniformLayout.AddArrayMember(var.name, var.size); break; - case render::ShaderAST::VariableType::Float: uniformLayout.AddArrayMember< float>(var.name, var.size); break; - case render::ShaderAST::VariableType::Int: uniformLayout.AddArrayMember< int>(var.name, var.size); break; + case render::ShaderAST::VariableType::Vec4: addFn(glm::vec4{}); break; + case render::ShaderAST::VariableType::Vec3: addFn(glm::vec3{}); break; + case render::ShaderAST::VariableType::Vec2: addFn(glm::vec2{}); break; + case render::ShaderAST::VariableType::Mat4: addFn(glm::mat4{}); break; + case render::ShaderAST::VariableType::Mat3: addFn(glm::mat3{}); break; + case render::ShaderAST::VariableType::Float: addFn(float{}); break; + case render::ShaderAST::VariableType::Int: addFn(int{}); break; } } } else - uniformLayout.AddMember(uniform.name); + uniformLayout.AddMember(bufferNode.name); AddUniformLayout(stageType, std::move(uniformLayout)); } @@ -258,11 +273,6 @@ namespace sh::render } auto ShaderPass::IsSamplerLayout(const UniformStructLayout& layout) const -> bool { - for (auto& member : layout.GetMembers()) - { - if (member.isSampler) - return true; - } - return false; + return layout.IsSampler(); } }//namespace \ No newline at end of file diff --git a/src/Render/UniformStructLayout.cpp b/src/Render/UniformStructLayout.cpp index d2e24f07..8d46ba40 100644 --- a/src/Render/UniformStructLayout.cpp +++ b/src/Render/UniformStructLayout.cpp @@ -2,14 +2,26 @@ namespace sh::render { - UniformStructLayout::UniformStructLayout(const std::string name, uint32_t binding, Type type, ShaderStage stage, bool bConstant, Layout layout) : - name(name), binding(binding), type(type), stage(stage), bConstant(bConstant), layout(layout) + static auto DefaultLayoutForKind(UniformStructLayout::Kind kind) -> UniformStructLayout::Layout { + return (kind == UniformStructLayout::Kind::Storage) + ? UniformStructLayout::Layout::STD430 + : UniformStructLayout::Layout::STD140; + } + UniformStructLayout::UniformStructLayout(std::string name, uint32_t binding, Usage usage, ShaderStage stage, Kind kind) : + kind(kind), layout(DefaultLayoutForKind(kind)), + usage(usage), stage(stage), binding(binding), name(std::move(name)) + { + } + UniformStructLayout::UniformStructLayout(std::string name, uint32_t binding, Usage usage, ShaderStage stage, Kind kind, Layout layout) : + kind(kind), layout(layout), + usage(usage), stage(stage), binding(binding), name(std::move(name)) + { } SH_RENDER_API auto UniformStructLayout::GetMember(const std::string& name) const -> const UniformMember* { - for (auto& member : members) + for (const UniformMember& member : members) { if (member.name == name) return &member; @@ -18,71 +30,21 @@ namespace sh::render } SH_RENDER_API auto UniformStructLayout::HasMember(const std::string& name) const -> bool { - for (auto& member : members) - { - if (member.name == name) - return true; - } - return false; + return GetMember(name) != nullptr; } SH_RENDER_API auto UniformStructLayout::GetSize() const -> std::size_t { if (members.empty()) return 0; - if (members.back().offset == 0) - return members.back().layoutSize; - return members.back().offset + members.back().layoutSize; - } - SH_RENDER_API auto UniformStructLayout::GetMembers() const -> const std::vector& - { - return members; - } - SH_RENDER_API auto UniformStructLayout::Serialize() const -> core::Json - { - core::Json json{}; - json["name"] = name; - json["binding"] = binding; - json["layout"] = (layout == Layout::STD140 ? "STD140" : "STD430"); - json["type"] = static_cast(type); - json["stage"] = static_cast(stage); - json["constant"] = bConstant; - json["size"] = GetSize(); - - core::Json membersJson = core::Json::array(); - for (auto const& m : members) - { - core::Json mjson; - mjson["name"] = m.name; - mjson["typeHash"] = m.typeHash; - mjson["offset"] = m.offset; - mjson["layoutSize"] = m.layoutSize; - mjson["count"] = m.count; - mjson["isArray"] = m.isArray; - mjson["isSampler"] = m.isSampler; - membersJson.push_back(std::move(mjson)); - } - json["members"] = std::move(membersJson); - - return json; - } - SH_RENDER_API void UniformStructLayout::Deserialize(const core::Json& json) - { - members.clear(); + const UniformMember& last = members.back(); + const uint32_t rawSize = last.offset + last.layoutSize; - auto const& memArr = json.at("members"); - for (auto const& mjson : memArr) - { - std::string mName = mjson.at("name").get(); - std::size_t typeHash = mjson.at("typeHash").get(); - uint32_t offset = mjson.at("offset").get(); - uint32_t layoutSize = mjson.at("layoutSize").get(); - uint32_t count = mjson.at("count").get(); - bool isArray = mjson.at("isArray").get(); - bool isSampler = mjson.at("isSampler").get(); - - UniformMember member{ mName, typeHash, offset, layoutSize, count, isArray, isSampler }; - members.push_back(std::move(member)); - } + uint32_t maxAlignment = 0; + for (const UniformMember& m : members) + maxAlignment = std::max(maxAlignment, m.baseAlignment); + if (maxAlignment == 0) + return rawSize; + return core::Util::AlignTo(rawSize, maxAlignment); } }//namespace \ No newline at end of file diff --git a/src/Render/VulkanImpl/VulkanBuffer.cpp b/src/Render/VulkanImpl/VulkanBuffer.cpp index 60e84b3b..ac07d025 100644 --- a/src/Render/VulkanImpl/VulkanBuffer.cpp +++ b/src/Render/VulkanImpl/VulkanBuffer.cpp @@ -41,6 +41,9 @@ namespace sh::render::vk } SH_RENDER_API auto VulkanBuffer::operator=(const VulkanBuffer& other) -> VulkanBuffer& { + if (&other == this) + return *this; + Create(other.size, other.bufferInfo.usage, other.bufferInfo.sharingMode, other.memProperty, other.persistentMapping); SetData(other.dataPtr); @@ -48,6 +51,9 @@ namespace sh::render::vk } SH_RENDER_API auto VulkanBuffer::operator=(VulkanBuffer&& other) noexcept -> VulkanBuffer& { + if (&other == this) + return *this; + buffer = other.buffer; bufferMem = other.bufferMem; dataPtr = other.dataPtr; @@ -62,6 +68,14 @@ namespace sh::render::vk return *this; } + SH_RENDER_API auto VulkanBuffer::Resize(std::size_t size) -> bool + { + Clean(); + const VkResult result = Create(size, bufferInfo.usage, bufferInfo.sharingMode, memProperty, this->persistentMapping); + if (result == VkResult::VK_SUCCESS) + return true; + return false; + } SH_RENDER_API void VulkanBuffer::Clean() { if (buffer) @@ -83,6 +97,7 @@ namespace sh::render::vk { this->size = size; this->persistentMapping = persistentMapping; + this->memProperty = memPropFlagBits; Clean(); bufferInfo.sType = VkStructureType::VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; @@ -147,26 +162,6 @@ namespace sh::render::vk else std::memcpy(this->dataPtr, data, static_cast(bufferInfo.size)); } - SH_RENDER_API auto VulkanBuffer::GetData() const -> void* - { - return dataPtr; - } - SH_RENDER_API auto VulkanBuffer::GetBuffer() const -> VkBuffer - { - return buffer; - } - SH_RENDER_API auto VulkanBuffer::GetBufferInfo() const -> const VkBufferCreateInfo& - { - return bufferInfo; - } - SH_RENDER_API auto VulkanBuffer::GetBufferMemory() const -> VmaAllocation - { - return bufferMem; - } - SH_RENDER_API auto VulkanBuffer::GetSize() const -> size_t - { - return size; - } auto VulkanBuffer::FindMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) -> uint32_t { VkPhysicalDeviceMemoryProperties memProperties; @@ -179,4 +174,4 @@ namespace sh::render::vk throw std::runtime_error("Failed to find suitable memory type!"); } -} \ No newline at end of file +}//namespace \ No newline at end of file diff --git a/src/Render/VulkanImpl/VulkanContext.cpp b/src/Render/VulkanImpl/VulkanContext.cpp index ec1ff079..5be6b3c6 100644 --- a/src/Render/VulkanImpl/VulkanContext.cpp +++ b/src/Render/VulkanImpl/VulkanContext.cpp @@ -254,6 +254,7 @@ namespace sh::render::vk VkDescriptorSetLayoutCreateInfo info{}; info.sType = VkStructureType::VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; info.bindingCount = 0; + info.pBindings = nullptr; vkCreateDescriptorSetLayout(device, &info, nullptr, &emptyDescLayout); diff --git a/src/Render/VulkanImpl/VulkanDescriptorPool.cpp b/src/Render/VulkanImpl/VulkanDescriptorPool.cpp index 1ee573d6..37d3d8aa 100644 --- a/src/Render/VulkanImpl/VulkanDescriptorPool.cpp +++ b/src/Render/VulkanImpl/VulkanDescriptorPool.cpp @@ -157,10 +157,11 @@ namespace sh::render::vk } auto VulkanDescriptorPool::CreatePool(uint32_t setCapacity) -> VkDescriptorPool { - std::array poolSizes{}; + std::array poolSizes{}; poolSizes[0] = { VkDescriptorType::VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, setCapacity }; poolSizes[1] = { VkDescriptorType::VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, setCapacity }; poolSizes[2] = { VkDescriptorType::VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, setCapacity }; + poolSizes[3] = { VkDescriptorType::VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, setCapacity }; VkDescriptorPoolCreateInfo info{}; info.sType = VkStructureType::VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; diff --git a/src/Render/VulkanImpl/VulkanUniformBuffer.cpp b/src/Render/VulkanImpl/VulkanDescriptorSet.cpp similarity index 63% rename from src/Render/VulkanImpl/VulkanUniformBuffer.cpp rename to src/Render/VulkanImpl/VulkanDescriptorSet.cpp index 56525d95..41216768 100644 --- a/src/Render/VulkanImpl/VulkanUniformBuffer.cpp +++ b/src/Render/VulkanImpl/VulkanDescriptorSet.cpp @@ -1,4 +1,4 @@ -#include "VulkanUniformBuffer.h" +#include "VulkanDescriptorSet.h" #include "VulkanContext.h" #include "VulkanShaderPass.h" #include "VulkanBuffer.h" @@ -8,52 +8,62 @@ #include namespace sh::render::vk { - VulkanUniformBuffer::VulkanUniformBuffer() : + VulkanDescriptorSet::VulkanDescriptorSet() : context(nullptr), descSet(nullptr) { } - VulkanUniformBuffer::VulkanUniformBuffer(VulkanUniformBuffer&& other) noexcept : + VulkanDescriptorSet::VulkanDescriptorSet(VulkanDescriptorSet&& other) noexcept : context(other.context), descSet(other.descSet), - bDynamic(other.bDynamic) + set(other.set), + descriptorTypes(std::move(other.descriptorTypes)) { other.context = nullptr; other.descSet = nullptr; } - VulkanUniformBuffer::~VulkanUniformBuffer() + VulkanDescriptorSet::~VulkanDescriptorSet() { Clear(); } - void VulkanUniformBuffer::Create(const IRenderContext& context, const ShaderPass& shader, UniformStructLayout::Type type) + void VulkanDescriptorSet::Create(const IRenderContext& context, const ShaderPass& shader, UniformStructLayout::Usage usage) { Clear(); this->context = &static_cast(context); - auto& vkShader = static_cast(shader); + const VulkanShaderPass& vkShader = static_cast(shader); - set = static_cast(type); + set = static_cast(usage); - auto layoutInfo = vkShader.GetDescriptorSetLayout(set); - VkDescriptorSetLayout layout = layoutInfo.layout; - bDynamic = layoutInfo.bDynamic; + if (auto opt = vkShader.GetSetLayout(set); opt.has_value()) + { + const VulkanShaderPass::SetLayout& setLayout = opt.value(); + for (const auto& [binding, layoutBinding] : setLayout.descriptorSetLayoutBindings) + descriptorTypes[binding] = layoutBinding.descriptorType; + } - if (layout != VK_NULL_HANDLE) - descSet = this->context->GetDescriptorPool().AllocateDescriptorSet(layout); + const VkDescriptorSetLayout descLayout = vkShader.GetDescriptorSetLayout(set); + if (descLayout != VK_NULL_HANDLE) + descSet = this->context->GetDescriptorPool().AllocateDescriptorSet(descLayout); } - void VulkanUniformBuffer::Clear() + void VulkanDescriptorSet::Clear() { if (descSet == nullptr || context == nullptr) return; context->GetDescriptorPool().FreeDescriptorSet(descSet); + descSet = VK_NULL_HANDLE; + descriptorTypes.clear(); } - void VulkanUniformBuffer::Link(uint32_t binding, const IBuffer& buffer, std::size_t bufferSize) + void VulkanDescriptorSet::Link(uint32_t binding, const IBuffer& buffer, std::size_t bufferSize) { if (context == nullptr || descSet == nullptr) return; + auto descriptorTypesIt = descriptorTypes.find(binding); + if (descriptorTypesIt == descriptorTypes.end()) + return; - auto& vkBuffer = static_cast(buffer); + const VulkanBuffer& vkBuffer = static_cast(buffer); VkDescriptorBufferInfo bufferInfo{}; bufferInfo.buffer = vkBuffer.GetBuffer(); @@ -65,8 +75,7 @@ namespace sh::render::vk descriptorWrite.dstSet = descSet; descriptorWrite.dstBinding = binding; descriptorWrite.dstArrayElement = 0; - descriptorWrite.descriptorType = bDynamic ? - VkDescriptorType::VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC : VkDescriptorType::VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptorWrite.descriptorType = descriptorTypesIt->second; descriptorWrite.descriptorCount = 1; descriptorWrite.pBufferInfo = &bufferInfo; descriptorWrite.pImageInfo = nullptr; @@ -74,7 +83,7 @@ namespace sh::render::vk vkUpdateDescriptorSets(context->GetDevice(), 1, &descriptorWrite, 0, nullptr); } - void VulkanUniformBuffer::Link(uint32_t binding, const Texture& texture) + void VulkanDescriptorSet::Link(uint32_t binding, const Texture& texture) { if (context == nullptr || descSet == nullptr) return; @@ -97,12 +106,4 @@ namespace sh::render::vk vkUpdateDescriptorSets(context->GetDevice(), 1, &descriptorWrite, 0, nullptr); } - SH_RENDER_API auto VulkanUniformBuffer::GetSetNumber() const -> uint32_t - { - return set; - } - SH_RENDER_API auto VulkanUniformBuffer::GetVkDescriptorSet() const -> VkDescriptorSet - { - return descSet; - } }//namespace \ No newline at end of file diff --git a/src/Render/VulkanImpl/VulkanRenderImpl.cpp b/src/Render/VulkanImpl/VulkanRenderImpl.cpp index 01d3948c..df9043d1 100644 --- a/src/Render/VulkanImpl/VulkanRenderImpl.cpp +++ b/src/Render/VulkanImpl/VulkanRenderImpl.cpp @@ -1,7 +1,7 @@ #include "VulkanRenderImpl.h" #include "VulkanCommandBuffer.h" #include "VulkanSwapChain.h" -#include "VulkanUniformBuffer.h" +#include "VulkanDescriptorSet.h" #include "VulkanVertexBuffer.h" #include "VulkanSkinnedVertexBuffer.h" #include "VulkanPipelineManager.h" @@ -211,39 +211,39 @@ namespace sh::render::vk void VulkanRenderImpl::BindCameraSet(VulkanCommandBuffer& cmd, VkPipelineLayout layout, const ShaderPass& pass, const Material& mat, uint32_t cameraOffset) const { // 카메라 데이터는 다이나믹 디스크립터셋 - auto cameraUBO = static_cast( - mat.GetMaterialData().GetUniformBuffer(pass, UniformStructLayout::Type::Camera)); + VulkanDescriptorSet* const cameraUBO = static_cast( + mat.GetMaterialData().GetUniformBuffer(pass, UniformStructLayout::Usage::Camera)); VkDescriptorSet cameraSet = cameraUBO ? cameraUBO->GetVkDescriptorSet() : ctx.GetEmptyDescriptorSet(); uint32_t dynamicCount = cameraUBO ? 1 : 0; vkCmdBindDescriptorSets(cmd.GetCommandBuffer(), VkPipelineBindPoint::VK_PIPELINE_BIND_POINT_GRAPHICS, layout, - static_cast(UniformStructLayout::Type::Camera), + static_cast(UniformStructLayout::Usage::Camera), 1, &cameraSet, dynamicCount, &cameraOffset); } void VulkanRenderImpl::BindMaterialSet(VulkanCommandBuffer& cmd, VkPipelineLayout layout, const ShaderPass& pass, const Material& mat) const { - auto materialUniformBuffer = static_cast( - mat.GetMaterialData().GetUniformBuffer(pass, UniformStructLayout::Type::Material)); + VulkanDescriptorSet* const materialUniformBuffer = static_cast( + mat.GetMaterialData().GetUniformBuffer(pass, UniformStructLayout::Usage::Material)); VkDescriptorSet materialDescriptorSet = materialUniformBuffer ? materialUniformBuffer->GetVkDescriptorSet() : ctx.GetEmptyDescriptorSet(); vkCmdBindDescriptorSets(cmd.GetCommandBuffer(), VkPipelineBindPoint::VK_PIPELINE_BIND_POINT_GRAPHICS, layout, - static_cast(UniformStructLayout::Type::Material), + static_cast(UniformStructLayout::Usage::Material), 1, &materialDescriptorSet, 0, nullptr); } void VulkanRenderImpl::BindObjectSet(VulkanCommandBuffer& cmd, VkPipelineLayout layout, const ShaderPass& pass, Drawable& drawable) const { - auto objectUniformBuffer = static_cast( - drawable.GetMaterialData().GetUniformBuffer(pass, UniformStructLayout::Type::Object)); + VulkanDescriptorSet* const objectUniformBuffer = static_cast( + drawable.GetMaterialData().GetUniformBuffer(pass, UniformStructLayout::Usage::Object)); VkDescriptorSet objectDescriptorSet = objectUniformBuffer ? objectUniformBuffer->GetVkDescriptorSet() : ctx.GetEmptyDescriptorSet(); vkCmdBindDescriptorSets(cmd.GetCommandBuffer(), VkPipelineBindPoint::VK_PIPELINE_BIND_POINT_GRAPHICS, layout, - static_cast(UniformStructLayout::Type::Object), + static_cast(UniformStructLayout::Usage::Object), 1, &objectDescriptorSet, 0, nullptr); } void VulkanRenderImpl::DrawMesh(VulkanCommandBuffer& cmd, const ShaderPass& pass, const Mesh& mesh, int subMeshIndex) const diff --git a/src/Render/VulkanImpl/VulkanShaderPass.cpp b/src/Render/VulkanImpl/VulkanShaderPass.cpp index 46a0f5d3..604ccef5 100644 --- a/src/Render/VulkanImpl/VulkanShaderPass.cpp +++ b/src/Render/VulkanImpl/VulkanShaderPass.cpp @@ -1,14 +1,17 @@ #include "VulkanShaderPass.h" -#include "VulkanShaderPass.h" #include "VulkanContext.h" +#include "Core/Logger.h" + +#include + namespace sh::render::vk { VulkanShaderPass::VulkanShaderPass(const VulkanContext& context, const ShaderModules& shaderModules, const ShaderAST::PassNode& passNode) : ShaderPass(passNode, ShaderType::SPIR), context(context), vertShader(shaderModules.vert), fragShader(shaderModules.frag), - descriptorSetLayout(1), pipelineLayout(nullptr) + pipelineLayout(nullptr) { // Specialization constant (컴파일 상수) if (!passNode.constants.empty()) @@ -37,9 +40,9 @@ namespace sh::render::vk } for (auto& stage : passNode.stages) { - for (auto& uniform : stage.uniforms) + for (auto& uniform : stage.buffers) { - if (uniform.bConstant) + if (uniform.bufferType == ShaderAST::BufferType::PushConstant) { bUseMatrixModel = true; break; @@ -54,8 +57,7 @@ namespace sh::render::vk ShaderPass(std::move(other)), context(other.context), vertShader(other.vertShader), fragShader(other.fragShader), - descriptorBindings(std::move(other.descriptorBindings)), - descriptorSetLayout(std::move(other.descriptorSetLayout)), + setlayouts(std::move(other.setlayouts)), pipelineLayout(other.pipelineLayout), specializationEntry(std::move(other.specializationEntry)), bUseMatrixModel(other.bUseMatrixModel) @@ -69,6 +71,7 @@ namespace sh::render::vk { Clear(); } + SH_RENDER_API void VulkanShaderPass::Clear() { specializationEntry.clear(); @@ -86,31 +89,78 @@ namespace sh::render::vk fragShader = nullptr; } } - void VulkanShaderPass::CleanDescriptors() + SH_RENDER_API void VulkanShaderPass::Build() { - if (pipelineLayout) + CleanDescriptors(); + // 유니폼 레이아웃 생성 + for (const std::vector* const uniforms : { &vertexUniforms, &fragmentUniforms }) { - vkDestroyPipelineLayout(context.GetDevice(), pipelineLayout, nullptr); - pipelineLayout = nullptr; - } + for (const UniformStructLayout& uniformLayout : *uniforms) + { + if (uniformLayout.IsPushConstant()) + continue; + const uint32_t set = static_cast(uniformLayout.usage); - descriptorBindings.clear(); + // set == 0 카메라 데이터 + VkDescriptorType type = (set == 0) ? + VkDescriptorType::VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC : VkDescriptorType::VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + if (uniformLayout.GetKind() == UniformStructLayout::Kind::Storage) + type = VkDescriptorType::VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; - for (std::size_t i = 0; i < descriptorSetLayout.size(); ++i) + VkShaderStageFlagBits stage = VkShaderStageFlagBits::VK_SHADER_STAGE_VERTEX_BIT; + if (uniforms == &fragmentUniforms) + stage = VkShaderStageFlagBits::VK_SHADER_STAGE_FRAGMENT_BIT; + + AddDescriptorBinding(set, uniformLayout.binding, type, stage); + } + } + for (const UniformStructLayout& uniformLayout : samplerUniforms) { - if (descriptorSetLayout[i] != context.GetEmptyDescriptorSetLayout()) - vkDestroyDescriptorSetLayout(context.GetDevice(), descriptorSetLayout[i], nullptr); + const uint32_t set = static_cast(uniformLayout.usage); + AddDescriptorBinding(set, uniformLayout.binding, + VkDescriptorType::VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + VkShaderStageFlagBits::VK_SHADER_STAGE_FRAGMENT_BIT); + } + VkResult result = CreateDescriptorLayout(); + assert(result == VkResult::VK_SUCCESS); + if (result != VkResult::VK_SUCCESS) + { + SH_ERROR_FORMAT("Failed to CreateDescriptorLayout(): {}", string_VkResult(result)); + throw std::runtime_error{ "Failed to CreateDescriptorLayout()" }; + } + + result = CreatePipelineLayout(); + assert(result == VkResult::VK_SUCCESS); + if (result != VkResult::VK_SUCCESS) + { + SH_ERROR_FORMAT("Failed to CreatePipelineLayout(): {}", string_VkResult(result)); + throw std::runtime_error{ "Failed to CreatePipelineLayout()" }; } - descriptorSetLayout.clear(); } - SH_RENDER_API auto VulkanShaderPass::GetVertexShader() const -> const VkShaderModule + SH_RENDER_API auto VulkanShaderPass::GetDescriptorSetLayout(uint32_t set) const -> VkDescriptorSetLayout { - return vertShader; + if (setlayouts.size() < set) + return VK_NULL_HANDLE; + return setlayouts[set].descriptorSetLayout; } - SH_RENDER_API auto VulkanShaderPass::GetFragmentShader() const -> const VkShaderModule + + SH_RENDER_API auto VulkanShaderPass::GetSetLayout(uint32_t set) const -> std::optional + { + if (setlayouts.size() < set) + return {}; + return setlayouts[set]; + } + + SH_RENDER_API auto VulkanShaderPass::GetDescriptorBinding(uint32_t set, uint32_t binding) const -> std::optional { - return fragShader; + if (setlayouts.size() < set) + return {}; + const SetLayout& setlayout = setlayouts[set]; + auto it = setlayout.descriptorSetLayoutBindings.find(binding); + if (it == setlayout.descriptorSetLayoutBindings.end()) + return{}; + return it->second; } void VulkanShaderPass::AddDescriptorBinding(uint32_t set, uint32_t binding, VkDescriptorType type, VkShaderStageFlagBits stage) @@ -122,35 +172,40 @@ namespace sh::render::vk layoutBinding.pImmutableSamplers = nullptr; layoutBinding.stageFlags = stage; - if (descriptorBindings.size() <= set) - descriptorBindings.resize(set + 1, std::vector()); - - descriptorBindings[set].push_back(layoutBinding); + if (setlayouts.size() <= set) + { + setlayouts.resize(set + 1); + } + setlayouts[set].set = set; + setlayouts[set].descriptorSetLayoutBindings.insert_or_assign(binding, layoutBinding); } auto VulkanShaderPass::CreateDescriptorLayout() -> VkResult { - descriptorSetLayout.resize(descriptorBindings.size()); - VkResult result = VkResult::VK_SUCCESS; - for (int i = 0; i < descriptorBindings.size(); ++i) + for (int i = 0; i < setlayouts.size(); ++i) { - if (descriptorBindings[i].empty()) + if (setlayouts[i].descriptorSetLayoutBindings.empty()) { - descriptorSetLayout[i] = context.GetEmptyDescriptorSetLayout(); + setlayouts[i].set = i; + setlayouts[i].descriptorSetLayout = context.GetEmptyDescriptorSetLayout(); continue; } + std::vector bindings; + bindings.reserve(setlayouts[i].descriptorSetLayoutBindings.size()); + for (const auto& [binding, descriptorSetLayoytBinding] : setlayouts[i].descriptorSetLayoutBindings) + bindings.push_back(descriptorSetLayoytBinding); + VkDescriptorSetLayoutCreateInfo info{}; info.sType = VkStructureType::VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; - info.bindingCount = descriptorBindings[i].size(); - info.pBindings = descriptorBindings[i].data(); + info.bindingCount = bindings.size(); + info.pBindings = bindings.data(); VkDescriptorSetLayout layout{}; result = vkCreateDescriptorSetLayout(context.GetDevice(), &info, nullptr, &layout); - assert(result == VkResult::VK_SUCCESS); if (result != VkResult::VK_SUCCESS) - throw std::runtime_error{ std::string{"Failed to create descriptor layout!: "} + string_VkResult(result) }; + return result; - descriptorSetLayout[i] = layout; + setlayouts[i].descriptorSetLayout = layout; } return result; } @@ -162,76 +217,32 @@ namespace sh::render::vk pushConstantRange.offset = 0; pushConstantRange.size = sizeof(glm::mat4); + std::vector layouts(setlayouts.size(), VK_NULL_HANDLE); + for (int i = 0; i < setlayouts.size(); ++i) + layouts[i] = setlayouts[i].descriptorSetLayout; + VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; pipelineLayoutInfo.sType = VkStructureType::VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; - pipelineLayoutInfo.setLayoutCount = descriptorSetLayout.size(); - pipelineLayoutInfo.pSetLayouts = descriptorSetLayout.data(); + pipelineLayoutInfo.setLayoutCount = layouts.size(); + pipelineLayoutInfo.pSetLayouts = layouts.data(); pipelineLayoutInfo.pushConstantRangeCount = bUseMatrixModel ? 1 : 0; pipelineLayoutInfo.pPushConstantRanges = &pushConstantRange; return vkCreatePipelineLayout(context.GetDevice(), &pipelineLayoutInfo, nullptr, &pipelineLayout); } - - SH_RENDER_API void VulkanShaderPass::Build() + void VulkanShaderPass::CleanDescriptors() { - CleanDescriptors(); - // 유니폼 레이아웃 생성 - for (auto& uniformLayout : vertexUniforms) + if (pipelineLayout != VK_NULL_HANDLE) { - if (uniformLayout.bConstant) - continue; - uint32_t set = static_cast(uniformLayout.type); - - VkDescriptorType type = (set == 0) ? - VkDescriptorType::VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC : VkDescriptorType::VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - - AddDescriptorBinding(set, uniformLayout.binding, type, - VkShaderStageFlagBits::VK_SHADER_STAGE_VERTEX_BIT); + vkDestroyPipelineLayout(context.GetDevice(), pipelineLayout, nullptr); + pipelineLayout = VK_NULL_HANDLE; } - for (auto& uniformLayout : fragmentUniforms) - { - if (uniformLayout.bConstant) - continue; - uint32_t set = static_cast(uniformLayout.type); - VkDescriptorType type = VkDescriptorType::VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - if (set == 0) - type = VkDescriptorType::VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; - - AddDescriptorBinding(set, uniformLayout.binding, type, - VkShaderStageFlagBits::VK_SHADER_STAGE_FRAGMENT_BIT); - } - for (auto& uniformLayout : samplerUniforms) + for (std::size_t i = 0; i < setlayouts.size(); ++i) { - uint32_t set = static_cast(uniformLayout.type); - AddDescriptorBinding(set, uniformLayout.binding, - VkDescriptorType::VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - VkShaderStageFlagBits::VK_SHADER_STAGE_FRAGMENT_BIT); + if (setlayouts[i].descriptorSetLayout != context.GetEmptyDescriptorSetLayout()) + vkDestroyDescriptorSetLayout(context.GetDevice(), setlayouts[i].descriptorSetLayout, nullptr); } - auto result = CreateDescriptorLayout(); - assert(result == VkResult::VK_SUCCESS); - - result = CreatePipelineLayout(); - assert(result == VkResult::VK_SUCCESS); - } - - SH_RENDER_API auto VulkanShaderPass::GetDescriptorSetLayout(uint32_t set) const -> DescryptorSetLayoutInfo - { - DescryptorSetLayoutInfo info{}; - info.layout = descriptorSetLayout[set]; - info.bDynamic = (set == 0); - return info; - } - SH_RENDER_API auto VulkanShaderPass::GetSetCount() const -> uint32_t - { - return static_cast(descriptorSetLayout.size()); - } - SH_RENDER_API auto VulkanShaderPass::GetPipelineLayout() const -> VkPipelineLayout - { - return pipelineLayout; - } - SH_RENDER_API auto sh::render::vk::VulkanShaderPass::GetSpecializationMapEntry() const -> const std::vector& - { - return specializationEntry; + setlayouts.clear(); } -} \ No newline at end of file +}//namespace \ No newline at end of file diff --git a/src/Render/VulkanImpl/VulkanShaderPassBuilder.cpp b/src/Render/VulkanImpl/VulkanShaderPassBuilder.cpp index 859b7767..65f15672 100644 --- a/src/Render/VulkanImpl/VulkanShaderPassBuilder.cpp +++ b/src/Render/VulkanImpl/VulkanShaderPassBuilder.cpp @@ -11,9 +11,7 @@ namespace sh::render::vk { } - VulkanShaderPassBuilder::~VulkanShaderPassBuilder() - { - } + VulkanShaderPassBuilder::~VulkanShaderPassBuilder() = default; SH_RENDER_API auto VulkanShaderPassBuilder::GetContext() const -> const VulkanContext& { @@ -64,7 +62,7 @@ namespace sh::render::vk shaderModules.vert = vertShader; shaderModules.frag = fragShader; - auto retShader = core::SObject::Create(context, shaderModules, passNode); + render::vk::VulkanShaderPass* const retShader = core::SObject::Create(context, shaderModules, passNode); retShader->StoreShaderCode(std::move(shaderCode)); retShader->Build(); From f5b3f942e9259bacb58aae10725a127ada4f1c13 Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Wed, 22 Apr 2026 01:28:40 +0900 Subject: [PATCH 06/60] fix(MaterialData): Resolve release-only bug caused by initializer_list --- src/Render/MaterialData.cpp | 6 ++++-- src/Render/ShaderPass.cpp | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Render/MaterialData.cpp b/src/Render/MaterialData.cpp index 76602700..fc7d916a 100644 --- a/src/Render/MaterialData.cpp +++ b/src/Render/MaterialData.cpp @@ -62,7 +62,7 @@ namespace sh::render { if (std::holds_alternative(syncData.data)) // 버퍼 { - const SyncData::BufferSyncData& bufferData = std::get<0>(syncData.data); + const SyncData::BufferSyncData& bufferData = std::get(syncData.data); SetUniformDataAtSync(bufferData); } else // 텍스쳐 @@ -151,7 +151,7 @@ namespace sh::render void MaterialData::CreateBuffers(const IRenderContext& context, const Shader& shader, bool bPerObject) { - std::initializer_list usages = { UniformStructLayout::Usage::Camera, UniformStructLayout::Usage::Material }; + std::vector usages{ UniformStructLayout::Usage::Camera, UniformStructLayout::Usage::Material }; if (bPerObject) usages = { UniformStructLayout::Usage::Object }; @@ -169,10 +169,12 @@ namespace sh::render { if (uniformLayout.IsPushConstant()) continue; + if (std::find(usages.begin(), usages.end(), uniformLayout.usage) == usages.end()) continue; const uint32_t set = static_cast(uniformLayout.usage); + maxSet = (maxSet < set) ? set : maxSet; if (std::find(sets.begin(), sets.end(), set) == sets.end()) sets.push_back(set); diff --git a/src/Render/ShaderPass.cpp b/src/Render/ShaderPass.cpp index 21dc5294..677fc6e5 100644 --- a/src/Render/ShaderPass.cpp +++ b/src/Render/ShaderPass.cpp @@ -11,7 +11,7 @@ namespace sh::render bZTest = passNode.bZTest; colorMask = passNode.colorMask; FillAttributes(passNode); - for (auto& stage : passNode.stages) + for (const ShaderAST::StageNode& stage : passNode.stages) { if (stage.lightingBinding != -1) lightingBinding = stage.lightingBinding; From e5bb41250f86af99d1652528746ac10e4f854353 Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Mon, 27 Apr 2026 01:50:23 +0900 Subject: [PATCH 07/60] feat(SkinnedMeshRenderer): Use thread pool when calculating finalBoneMatrices --- .../Component/Render/SkinnedMeshRenderer.cpp | 47 +++++++++++++++++-- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/src/Game/Component/Render/SkinnedMeshRenderer.cpp b/src/Game/Component/Render/SkinnedMeshRenderer.cpp index 3db19b78..22685223 100644 --- a/src/Game/Component/Render/SkinnedMeshRenderer.cpp +++ b/src/Game/Component/Render/SkinnedMeshRenderer.cpp @@ -1,6 +1,8 @@ #include "Component/Render/SkinnedMeshRenderer.h" #include "Game/Component/Transform.h" +#include "Core/ThreadPool.h" + #include "Render/UniformStructLayout.h" #include "Render/Shader.h" @@ -52,12 +54,49 @@ namespace sh::game if (finalBoneMatrices.size() < bones.size()) finalBoneMatrices.resize(bones.size()); - for (std::size_t i = 0; i < jointCount; ++i) + //for (std::size_t i = 0; i < jointCount; ++i) + //{ + // if (!core::IsValid(bones[i])) + // continue; + // finalBoneMatrices[i] = bones[i]->localToWorldMatrix * inverseBindMatrices[i]; + //} + + const auto calcMatricesFn = + [this](std::size_t start, std::size_t end) + { + for (std::size_t i = start; i < end; ++i) + { + if (!core::IsValid(bones[i])) + continue; + finalBoneMatrices[i] = bones[i]->localToWorldMatrix * inverseBindMatrices[i]; + } + }; + + if (jointCount > 100) { - if (!core::IsValid(bones[i])) - continue; - finalBoneMatrices[i] = bones[i]->localToWorldMatrix * inverseBindMatrices[i]; + static core::ThreadPool& threadPool = *core::ThreadPool::GetInstance(); + const std::size_t threadCount = 4; + std::vector> futures(threadCount); + const std::size_t perTask = jointCount / threadCount; + const std::size_t rest = jointCount % threadCount; + + std::size_t start = 0; + std::size_t futureIdx = 0; + for (int i = 0; i < rest; ++i) + { + futures[futureIdx++] = threadPool.AddTask(calcMatricesFn, start, start + perTask + 1); + start = start + perTask + 1; + } + for (int i = 0; i < threadCount - rest; ++i) + { + futures[futureIdx++] = threadPool.AddTask(calcMatricesFn, start, start + perTask); + start = start + perTask; + } + for (std::future& future : futures) + future.get(); } + else + calcMatricesFn(0, jointCount); } void SkinnedMeshRenderer::UploadBoneMatrices() { From 8fcb6ae5f884619ad707463ff0bfbb44696534c0 Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Mon, 27 Apr 2026 03:00:57 +0900 Subject: [PATCH 08/60] feat: Implement compute shader lexer and parser --- include/Render/ComputeShaderLexer.h | 51 ++++ include/Render/ComputeShaderParser.h | 56 +++++ include/Render/ShaderAST.h | 27 +- src/Render/ComputeShaderLexer.cpp | 270 ++++++++++++++++++++ src/Render/ComputeShaderParser.cpp | 356 +++++++++++++++++++++++++++ src/Render/ShaderAST.cpp | 46 ++++ 6 files changed, 805 insertions(+), 1 deletion(-) create mode 100644 include/Render/ComputeShaderLexer.h create mode 100644 include/Render/ComputeShaderParser.h create mode 100644 src/Render/ComputeShaderLexer.cpp create mode 100644 src/Render/ComputeShaderParser.cpp diff --git a/include/Render/ComputeShaderLexer.h b/include/Render/ComputeShaderLexer.h new file mode 100644 index 00000000..3ae2d5cd --- /dev/null +++ b/include/Render/ComputeShaderLexer.h @@ -0,0 +1,51 @@ +#pragma once +#include "Export.h" + +#include +#include +#include +namespace sh::render +{ + class ComputeShaderLexer + { + public: + enum class TokenType + { + Preprocessor, + Numthreads, + RBuffer, WBuffer, RWBuffer, + Const, + LBracket, // ( + RBracket, // ) + LBrace, // { + RBrace, // } + LSquareBracket, // [ + RSquareBracket, // ] + Comma, // , + String, Identifier, Literal, Number, Operator, Semicolon, + EndOfFile, + Unknown + }; + struct Token + { + TokenType type; + std::string text; + int line; + int column; + }; + public: + SH_RENDER_API static auto Lex(const std::string& source) -> std::vector; + private: + static auto IsIdentifierChar(char c) -> bool; + }; + + class ComputeShaderLexerException : public std::exception + { + public: + ComputeShaderLexerException(const std::string& err) : err(err) {} + + auto what() const noexcept -> const char* override { return err.c_str(); } + private: + std::string err; + }; +}//namespace diff --git a/include/Render/ComputeShaderParser.h b/include/Render/ComputeShaderParser.h new file mode 100644 index 00000000..0b608a36 --- /dev/null +++ b/include/Render/ComputeShaderParser.h @@ -0,0 +1,56 @@ +#pragma once +#include "Export.h" +#include "ShaderAST.h" +#include "ComputeShaderLexer.h" + +#include +#include +#include +#include + +namespace sh::render +{ + class ComputeShaderParser + { + public: + SH_RENDER_API auto Parse(const std::vector& tokens) -> ShaderAST::ComputeShaderNode; + protected: + /// @brief 현재 토큰을 반환하는 함수. + auto PeekToken() const -> const ComputeShaderLexer::Token&; + /// @brief 현재 토큰 타입 검사 함수. + auto CheckToken(ComputeShaderLexer::TokenType token) const -> bool; + /// @brief 다음 토큰으로 이동하는 함수. + auto NextToken() -> const ComputeShaderLexer::Token&; + /// @brief 직전에 소모된 토큰을 반환하는 함수. + auto PreviousToken() -> const ComputeShaderLexer::Token&; + /// @brief 해당 토큰이 존재하면 다음 토큰으로 이동하며 없다면 예외를 던진다. + void ConsumeToken(ComputeShaderLexer::TokenType token); + /// @brief 해당 토큰들 중 하나라도 존재하면 다음 토큰으로 이동하며 없다면 예외를 던진다. + void ConsumeToken(const std::initializer_list& tokens); + + auto GetCurrentTokenPosString() const -> std::string; + auto GetTokenErrorString(const std::string& msg) const -> std::string; + + auto ParseComputeShader() -> ShaderAST::ComputeShaderNode; + void ParsePreprocessor(ShaderAST::ComputeShaderNode& node); + void ParseNumthreads(ShaderAST::ComputeShaderNode& node); + void ParseBuffer(ShaderAST::ComputeShaderNode& node, ShaderAST::BufferAccess access); + void ParseDeclaration(ShaderAST::ComputeShaderNode& node, const std::string& qualifier = ""); + auto ParseFunctionBody() -> std::string; + + static auto IdentifierToVariableType(const ComputeShaderLexer::Token& token) -> ShaderAST::VariableType; + static auto VariableTypeToString(ShaderAST::VariableType type) -> std::string; + static void GenerateCode(ShaderAST::ComputeShaderNode& node); + private: + const std::vector* tokens = nullptr; + std::size_t pos = 0; + uint32_t lastBinding = 0; + }; + + class ComputeShaderParserException : public std::runtime_error + { + public: + ComputeShaderParserException(const char* err) : std::runtime_error{ err } + {} + }; +}//namespace diff --git a/include/Render/ShaderAST.h b/include/Render/ShaderAST.h index 622626c4..9e742e5e 100644 --- a/include/Render/ShaderAST.h +++ b/include/Render/ShaderAST.h @@ -31,7 +31,8 @@ namespace sh::render Float, Int, Sampler, - Boolean + Boolean, + Struct }; enum class VariableAttribute { @@ -94,13 +95,21 @@ namespace sh::render Sampler, PushConstant // 푸시 상수 }; + enum class BufferAccess : uint8_t + { + Read, // readonly + Write, // writeonly + ReadWrite // (no qualifier) + }; struct BufferNode : core::ISerializable { BufferType bufferType = BufferType::Uniform; + BufferAccess access = BufferAccess::Read; uint32_t set = 0; uint32_t binding = 0; std::string name; std::vector vars; + bool bAnonymousInstance = false; // true이면 블록 인스턴스 이름 없이 멤버를 전역으로 노출 SH_RENDER_API auto Serialize() const -> core::Json override; SH_RENDER_API void Deserialize(const core::Json& json) override; @@ -156,5 +165,21 @@ namespace sh::render SH_RENDER_API auto Serialize() const -> core::Json override; SH_RENDER_API void Deserialize(const core::Json& json) override; }; + + struct ComputeShaderNode : core::ISerializable + { + VersionNode version; + std::string shaderName; + uint32_t numthreadsX = 1; + uint32_t numthreadsY = 1; + uint32_t numthreadsZ = 1; + std::vector buffers; + std::vector declaration; + std::vector functions; + std::string code; + + SH_RENDER_API auto Serialize() const -> core::Json override; + SH_RENDER_API void Deserialize(const core::Json& json) override; + }; }; }//namespace \ No newline at end of file diff --git a/src/Render/ComputeShaderLexer.cpp b/src/Render/ComputeShaderLexer.cpp new file mode 100644 index 00000000..19f9572a --- /dev/null +++ b/src/Render/ComputeShaderLexer.cpp @@ -0,0 +1,270 @@ +#include "ComputeShaderLexer.h" + +#include + +#include +#include + +namespace sh::render +{ + SH_RENDER_API auto ComputeShaderLexer::Lex(const std::string& source) -> std::vector + { + std::vector tokens; + + int line = 1; + int column = 1; + const std::size_t length = source.size(); + std::size_t i = 0; + + auto AddToken = [&](TokenType t, const std::string& txt) + { + tokens.push_back({ t, txt, line, column }); + }; + + while (i < length) + { + char c = source[i]; + if (c == '\n') + { + ++i; + ++line; + column = 0; + continue; + } + if (c == '/') // 주석 + { + if (i + 1 < length) + { + if (source[i + 1] == '/') // // + { + i += 2; + while (true) + { + if (i >= length) + break; + if (source[i] == '\n') + { + ++i; + column = 0; + ++line; + break; + } + ++i; + } + continue; + } + else if (source[i + 1] == '*') // /**/ + { + i += 2; + while (true) + { + if (i >= length || i + 1 >= length) + break; + if (source[i] == '*' && source[i + 1] == '/') + { + i += 2; + break; + } + if (source[i] == '\n') + { + column = 0; + ++line; + } + if (source[i] == '\r' || source[i] == '\t') + ++column; + ++i; + } + continue; + } + } + } + if (c == ' ' || c == '\t' || c == '\r') + { + ++i; + ++column; + continue; + } + + if (c == '{') + { + AddToken(TokenType::LBrace, "{"); + ++i; + ++column; + continue; + } + if (c == '}') + { + AddToken(TokenType::RBrace, "}"); + ++i; + ++column; + continue; + } + if (c == '(') + { + AddToken(TokenType::LBracket, "("); + ++i; + ++column; + continue; + } + if (c == ')') + { + AddToken(TokenType::RBracket, ")"); + ++i; + ++column; + continue; + } + if (c == '[') + { + AddToken(TokenType::LSquareBracket, "["); + ++i; + ++column; + continue; + } + if (c == ']') + { + AddToken(TokenType::RSquareBracket, "]"); + ++i; + ++column; + continue; + } + if (c == ',') + { + AddToken(TokenType::Comma, ","); + ++i; + ++column; + continue; + } + // 두 글자 합성 연산자가 가능한 연산자들 + auto LexCompoundOp = [&](char single, std::initializer_list followers) + { + std::string op{ single }; + ++i; + ++column; + if (i < source.size()) + { + for (char f : followers) + { + if (source[i] == f) + { + op.push_back(source[i]); + ++i; + ++column; + break; + } + } + } + AddToken(TokenType::Operator, op); + }; + if (c == '=') { LexCompoundOp('=', { '=' }); continue; } + if (c == '+') { LexCompoundOp('+', { '+', '=' }); continue; } + if (c == '-') { LexCompoundOp('-', { '-', '=' }); continue; } + if (c == '*') { LexCompoundOp('*', { '=' }); continue; } + if (c == '/') { LexCompoundOp('/', { '=' }); continue; } + if (c == '<') { LexCompoundOp('<', { '=', '<' }); continue; } + if (c == '>') { LexCompoundOp('>', { '=', '>' }); continue; } + if (c == '!') { LexCompoundOp('!', { '=' }); continue; } + if (c == '&') { LexCompoundOp('&', { '&', '=' }); continue; } + if (c == '|') { LexCompoundOp('|', { '|', '=' }); continue; } + if (c == '%') { LexCompoundOp('%', { '=' }); continue; } + if (c == '^') { LexCompoundOp('^', { '=' }); continue; } + + if (c == ';') + { + AddToken(TokenType::Semicolon, ";"); + ++i; + ++column; + continue; + } + + if (c == '\"') + { + ++i; + ++column; + std::string strValue; + bool closed = false; + while (i < length) + { + char c2 = source[i]; + if (c2 == '\"') + { + closed = true; + ++i; + ++column; + break; + } + strValue.push_back(c2); + ++i; + ++column; + } + if (!closed) + throw ComputeShaderLexerException{ fmt::format("\" not closed! (line: {})", line) }; + AddToken(TokenType::String, strValue); + continue; + } + // 숫자 + if (std::isdigit(static_cast(c))) + { + std::string number; + while (i < length && (std::isdigit(static_cast(source[i])) || source[i] == '.')) + { + number.push_back(source[i]); + ++i; + ++column; + } + AddToken(TokenType::Number, number); + if (i < length && source[i] == 'f') + { + ++i; + ++column; + AddToken(TokenType::Literal, "f"); + } + continue; + } + // 식별자 또는 키워드 + if (std::isalpha(static_cast(c)) || c == '_') + { + std::string ident; + while (i < length && IsIdentifierChar(source[i])) + { + ident.push_back(source[i]); + ++i; + ++column; + } + static const std::unordered_map keywordMap = + { + {"Numthreads", TokenType::Numthreads}, + {"RBuffer", TokenType::RBuffer}, + {"WBuffer", TokenType::WBuffer}, + {"RWBuffer", TokenType::RWBuffer}, + {"const", TokenType::Const} + }; + auto it = keywordMap.find(ident); + if (it == keywordMap.end()) + AddToken(TokenType::Identifier, ident); + else + AddToken(it->second, ident); + continue; + } + + if (c == '#') + { + AddToken(TokenType::Preprocessor, "#"); + ++i; + ++column; + continue; + } + + AddToken(TokenType::Unknown, std::string{ c }); + ++i; + ++column; + } + + AddToken(TokenType::EndOfFile, ""); + return tokens; + } + + auto ComputeShaderLexer::IsIdentifierChar(char c) -> bool + { + return std::isalnum(static_cast(c)) || c == '_'; + } +}//namespace diff --git a/src/Render/ComputeShaderParser.cpp b/src/Render/ComputeShaderParser.cpp new file mode 100644 index 00000000..433297d4 --- /dev/null +++ b/src/Render/ComputeShaderParser.cpp @@ -0,0 +1,356 @@ +#include "ComputeShaderParser.h" + +#include + +#include + +namespace sh::render +{ + auto ComputeShaderParser::PeekToken() const -> const ComputeShaderLexer::Token& + { + return (*tokens)[pos]; + } + auto ComputeShaderParser::CheckToken(ComputeShaderLexer::TokenType token) const -> bool + { + return PeekToken().type == token; + } + auto ComputeShaderParser::NextToken() -> const ComputeShaderLexer::Token& + { + if (pos < tokens->size()) + return (*tokens)[pos++]; + return (*tokens)[pos]; + } + auto ComputeShaderParser::PreviousToken() -> const ComputeShaderLexer::Token& + { + return (*tokens)[pos - 1]; + } + void ComputeShaderParser::ConsumeToken(ComputeShaderLexer::TokenType token) + { + if (CheckToken(token)) + NextToken(); + else + throw ComputeShaderParserException{ GetTokenErrorString(fmt::format("Unexpected token '{}'", PeekToken().text)).c_str() }; + } + void ComputeShaderParser::ConsumeToken(const std::initializer_list& tokens) + { + for (ComputeShaderLexer::TokenType token : tokens) + { + if (CheckToken(token)) + { + NextToken(); + return; + } + } + throw ComputeShaderParserException{ GetTokenErrorString(fmt::format("Unexpected token '{}'", PeekToken().text)).c_str() }; + } + + auto ComputeShaderParser::GetCurrentTokenPosString() const -> std::string + { + const ComputeShaderLexer::Token& tk = PeekToken(); + return fmt::format("(line: {}, col: {})", tk.line, tk.column); + } + auto ComputeShaderParser::GetTokenErrorString(const std::string& msg) const -> std::string + { + const ComputeShaderLexer::Token& tk = PeekToken(); + return fmt::format("{} (line: {}, col: {})", msg, tk.line, tk.column); + } + + auto ComputeShaderParser::ParseComputeShader() -> ShaderAST::ComputeShaderNode + { + ShaderAST::ComputeShaderNode node; + + while (CheckToken(ComputeShaderLexer::TokenType::Preprocessor)) + { + NextToken(); + ParsePreprocessor(node); + } + + while (!CheckToken(ComputeShaderLexer::TokenType::EndOfFile)) + { + if (CheckToken(ComputeShaderLexer::TokenType::Numthreads)) + ParseNumthreads(node); + else if (CheckToken(ComputeShaderLexer::TokenType::RBuffer)) + ParseBuffer(node, ShaderAST::BufferAccess::Read); + else if (CheckToken(ComputeShaderLexer::TokenType::WBuffer)) + ParseBuffer(node, ShaderAST::BufferAccess::Write); + else if (CheckToken(ComputeShaderLexer::TokenType::RWBuffer)) + ParseBuffer(node, ShaderAST::BufferAccess::ReadWrite); + else if (CheckToken(ComputeShaderLexer::TokenType::Const)) + { + NextToken(); + ParseDeclaration(node, "const"); + } + else if (CheckToken(ComputeShaderLexer::TokenType::Identifier)) + ParseDeclaration(node); + else + NextToken(); + } + + return node; + } + + void ComputeShaderParser::ParsePreprocessor(ShaderAST::ComputeShaderNode& node) + { + ConsumeToken(ComputeShaderLexer::TokenType::Identifier); + if (PreviousToken().text == "version") + { + ShaderAST::VersionNode versionNode{}; + ConsumeToken(ComputeShaderLexer::TokenType::Number); + versionNode.versionNumber = std::stoi(PreviousToken().text); + ConsumeToken(ComputeShaderLexer::TokenType::Identifier); + versionNode.profile = PreviousToken().text; + node.version = versionNode; + return; + } + throw ComputeShaderParserException{ + fmt::format("Unknown preprocessor identifier {} {}", PreviousToken().text, GetCurrentTokenPosString()).c_str() + }; + } + + void ComputeShaderParser::ParseNumthreads(ShaderAST::ComputeShaderNode& node) + { + // Numthreads(x, y, z); + ConsumeToken(ComputeShaderLexer::TokenType::Numthreads); + ConsumeToken(ComputeShaderLexer::TokenType::LBracket); + + ConsumeToken(ComputeShaderLexer::TokenType::Number); + node.numthreadsX = static_cast(std::stoi(PreviousToken().text)); + ConsumeToken(ComputeShaderLexer::TokenType::Comma); + ConsumeToken(ComputeShaderLexer::TokenType::Number); + node.numthreadsY = static_cast(std::stoi(PreviousToken().text)); + ConsumeToken(ComputeShaderLexer::TokenType::Comma); + ConsumeToken(ComputeShaderLexer::TokenType::Number); + node.numthreadsZ = static_cast(std::stoi(PreviousToken().text)); + + ConsumeToken(ComputeShaderLexer::TokenType::RBracket); + ConsumeToken(ComputeShaderLexer::TokenType::Semicolon); + } + + void ComputeShaderParser::ParseBuffer(ShaderAST::ComputeShaderNode& node, ShaderAST::BufferAccess access) + { + // (R|W|RW)Buffer name; + NextToken(); // RBuffer / WBuffer / RWBuffer + + // '<' + if (!CheckToken(ComputeShaderLexer::TokenType::Operator) || PeekToken().text != "<") + throw ComputeShaderParserException{ GetTokenErrorString("Expected '<' for buffer template").c_str() }; + NextToken(); + + // 원소 타입 + ConsumeToken(ComputeShaderLexer::TokenType::Identifier); + ShaderAST::VariableType elementType = IdentifierToVariableType(PreviousToken()); + + // '>' + if (!CheckToken(ComputeShaderLexer::TokenType::Operator) || PeekToken().text != ">") + throw ComputeShaderParserException{ GetTokenErrorString("Expected '>' for buffer template").c_str() }; + NextToken(); + + // 버퍼 이름 + ConsumeToken(ComputeShaderLexer::TokenType::Identifier); + std::string bufferName = PreviousToken().text; + + ConsumeToken(ComputeShaderLexer::TokenType::Semicolon); + + ShaderAST::BufferNode buffer{}; + buffer.bufferType = ShaderAST::BufferType::Storage; + buffer.access = access; + buffer.bAnonymousInstance = true; + buffer.set = 0; + buffer.binding = lastBinding++; + buffer.name = bufferName; + buffer.vars.push_back(ShaderAST::VariableNode::MakeDynamicArray(elementType, bufferName)); + + node.buffers.push_back(std::move(buffer)); + } + + void ComputeShaderParser::ParseDeclaration(ShaderAST::ComputeShaderNode& node, const std::string& qualifier) + { + // + // ::= ';' | '=' ';' | '(' ')' + ConsumeToken(ComputeShaderLexer::TokenType::Identifier); + const std::string type = PreviousToken().text; + ConsumeToken(ComputeShaderLexer::TokenType::Identifier); + const std::string name = PreviousToken().text; + bool array = false; + std::size_t arraySize = 0; + if (CheckToken(ComputeShaderLexer::TokenType::LSquareBracket)) + { + NextToken(); + ConsumeToken(ComputeShaderLexer::TokenType::Number); + arraySize = std::stoi(PreviousToken().text); + ConsumeToken(ComputeShaderLexer::TokenType::RSquareBracket); + array = true; + } + ConsumeToken({ ComputeShaderLexer::TokenType::Semicolon, + ComputeShaderLexer::TokenType::Operator, + ComputeShaderLexer::TokenType::LBracket }); + auto& prevToken = PreviousToken(); + if (prevToken.type == ComputeShaderLexer::TokenType::Semicolon) // 변수 선언 + { + if (!array) + node.declaration.push_back(fmt::format("{} {} {};", qualifier, type, name)); + else + node.declaration.push_back(fmt::format("{} {} {}[{}];", qualifier, type, name, arraySize)); + } + else if (prevToken.type == ComputeShaderLexer::TokenType::Operator) // 변수 선언+초기화 + { + if (prevToken.text != "=") + throw ComputeShaderParserException{ GetTokenErrorString("Expected '='").c_str() }; + std::string expr; + while (!CheckToken(ComputeShaderLexer::TokenType::Semicolon)) + { + if (PeekToken().type == ComputeShaderLexer::TokenType::Literal) + expr.pop_back(); + expr += PeekToken().text; + expr += " "; + NextToken(); + } + NextToken(); // ; + if (!array) + node.declaration.push_back(fmt::format("{} {} {} = {};", qualifier, type, name, std::move(expr))); + else + node.declaration.push_back(fmt::format("{} {} {}[{}] = {};", qualifier, type, name, arraySize, std::move(expr))); + } + else if (prevToken.type == ComputeShaderLexer::TokenType::LBracket) // 함수 정의 + { + std::string parameters; + while (!CheckToken(ComputeShaderLexer::TokenType::RBracket)) + { + if (PeekToken().type == ComputeShaderLexer::TokenType::Literal) + parameters.pop_back(); + parameters += PeekToken().text; + parameters += " "; + NextToken(); + } + ConsumeToken(ComputeShaderLexer::TokenType::RBracket); + ConsumeToken(ComputeShaderLexer::TokenType::LBrace); + std::string body = ParseFunctionBody(); + ConsumeToken(ComputeShaderLexer::TokenType::RBrace); + node.functions.push_back(fmt::format("{} {} {}({}) {{ {} }}", qualifier, type, name, std::move(parameters), std::move(body))); + } + } + + auto ComputeShaderParser::ParseFunctionBody() -> std::string + { + std::string code; + int nested = 1; + while (PeekToken().type != ComputeShaderLexer::TokenType::EndOfFile) + { + auto& token = PeekToken(); + if (token.type == ComputeShaderLexer::TokenType::RBrace) + { + if (--nested == 0) + break; + } + else if (token.type == ComputeShaderLexer::TokenType::LBrace) + ++nested; + else if (token.type == ComputeShaderLexer::TokenType::Literal) + { + if (!code.empty()) + code.pop_back(); + } + code += token.text + " "; + NextToken(); + } + return code; + } + + auto ComputeShaderParser::IdentifierToVariableType(const ComputeShaderLexer::Token& token) -> ShaderAST::VariableType + { + const std::string& type = token.text; + if (type == "vec4") return ShaderAST::VariableType::Vec4; + if (type == "vec3") return ShaderAST::VariableType::Vec3; + if (type == "vec2") return ShaderAST::VariableType::Vec2; + if (type == "mat4") return ShaderAST::VariableType::Mat4; + if (type == "mat3") return ShaderAST::VariableType::Mat3; + if (type == "mat2") return ShaderAST::VariableType::Mat2; + if (type == "ivec4") return ShaderAST::VariableType::IVec4; + if (type == "int") return ShaderAST::VariableType::Int; + if (type == "float") return ShaderAST::VariableType::Float; + if (type == "bool") return ShaderAST::VariableType::Boolean; + else return ShaderAST::VariableType::Struct; + } + auto ComputeShaderParser::VariableTypeToString(ShaderAST::VariableType type) -> std::string + { + switch (type) + { + case ShaderAST::VariableType::Mat4: return "mat4"; + case ShaderAST::VariableType::Mat3: return "mat3"; + case ShaderAST::VariableType::Mat2: return "mat2"; + case ShaderAST::VariableType::Vec4: return "vec4"; + case ShaderAST::VariableType::Vec3: return "vec3"; + case ShaderAST::VariableType::Vec2: return "vec2"; + case ShaderAST::VariableType::IVec4: return "ivec4"; + case ShaderAST::VariableType::Float: return "float"; + case ShaderAST::VariableType::Int: return "int"; + case ShaderAST::VariableType::Sampler: return "sampler2D"; + case ShaderAST::VariableType::Boolean: return "bool"; + default: return "unknown"; + } + } + + void ComputeShaderParser::GenerateCode(ShaderAST::ComputeShaderNode& node) + { + std::string code = fmt::format("#version {} {}\n", node.version.versionNumber, node.version.profile); + code += fmt::format("layout(local_size_x = {}, local_size_y = {}, local_size_z = {}) in;\n", + node.numthreadsX, node.numthreadsY, node.numthreadsZ); + + for (auto& buf : node.buffers) + { + std::string members; + for (ShaderAST::VariableNode& v : buf.vars) + { + if (v.bDynamicArray) + members += fmt::format(" {} {}[];\n", VariableTypeToString(v.type), v.name); + else if (v.arraySize == 1) + members += fmt::format(" {} {};\n", VariableTypeToString(v.type), v.name); + else + members += fmt::format(" {} {}[{}];\n", VariableTypeToString(v.type), v.name, v.arraySize); + } + + const char* accessQualifier = ""; + switch (buf.access) + { + case ShaderAST::BufferAccess::Read: accessQualifier = "readonly "; break; + case ShaderAST::BufferAccess::Write: accessQualifier = "writeonly "; break; + case ShaderAST::BufferAccess::ReadWrite: accessQualifier = ""; break; + } + + if (buf.bufferType == ShaderAST::BufferType::Storage) + { + if (buf.bAnonymousInstance) + { + code += fmt::format("layout(std430, binding = {}) {}buffer BUFFER_{} {{\n{}}};\n", + buf.binding, accessQualifier, buf.name, members); + } + else + { + code += fmt::format("layout(std430, set = {}, binding = {}) {}buffer UNIFORM_{} {{\n{}}} {};\n", + buf.set, buf.binding, accessQualifier, buf.name, members, buf.name); + } + } + else if (buf.bufferType == ShaderAST::BufferType::Uniform) + { + code += fmt::format("layout(set = {}, binding = {}) uniform UNIFORM_{} {{\n{}}} {};\n", + buf.set, buf.binding, buf.name, members, buf.name); + } + } + for (auto& decl : node.declaration) + code += decl + '\n'; + for (auto& fn : node.functions) + code += fn + '\n'; + + node.code = std::move(code); + } + + SH_RENDER_API auto ComputeShaderParser::Parse(const std::vector& tokens) -> ShaderAST::ComputeShaderNode + { + this->tokens = &tokens; + pos = 0; + lastBinding = 0; + + ShaderAST::ComputeShaderNode node = ParseComputeShader(); + GenerateCode(node); + return node; + } +}//namespace diff --git a/src/Render/ShaderAST.cpp b/src/Render/ShaderAST.cpp index 6c9eb97e..84b01908 100644 --- a/src/Render/ShaderAST.cpp +++ b/src/Render/ShaderAST.cpp @@ -46,6 +46,8 @@ namespace sh::render { core::Json json; json["bufferType"] = static_cast(bufferType); + json["access"] = static_cast(access); + json["anonymous"] = bAnonymousInstance; json["set"] = set; json["binding"] = binding; json["name"] = name; @@ -66,6 +68,10 @@ namespace sh::render if (json.contains("bufferType")) bufferType = static_cast(json.at("bufferType").get()); + if (json.contains("access")) + access = static_cast(json.at("access").get()); + if (json.contains("anonymous")) + bAnonymousInstance = json.at("anonymous").get(); vars.clear(); for (const auto& v : json.at("vars")) @@ -270,4 +276,44 @@ namespace sh::render } } + auto ShaderAST::ComputeShaderNode::Serialize() const -> core::Json + { + core::Json json; + json["version"] = version.Serialize(); + json["shaderName"] = shaderName; + json["numthreadsX"] = numthreadsX; + json["numthreadsY"] = numthreadsY; + json["numthreadsZ"] = numthreadsZ; + json["code"] = code; + + core::Json bufferArray = core::Json::array(); + for (const auto& b : buffers) + bufferArray.push_back(b.Serialize()); + json["buffers"] = std::move(bufferArray); + + json["declaration"] = declaration; + json["functions"] = functions; + return json; + } + + void ShaderAST::ComputeShaderNode::Deserialize(const core::Json& json) + { + version.Deserialize(json.at("version")); + shaderName = json.at("shaderName").get(); + numthreadsX = json.value("numthreadsX", 1u); + numthreadsY = json.value("numthreadsY", 1u); + numthreadsZ = json.value("numthreadsZ", 1u); + code = json.at("code").get(); + + buffers.clear(); + for (const auto& b : json.at("buffers")) + { + BufferNode buf; + buf.Deserialize(b); + buffers.push_back(std::move(buf)); + } + declaration = json.at("declaration").get>(); + functions = json.at("functions").get>(); + } + }//namespace \ No newline at end of file From fab4a64665de6b24c26f93a79619685c4f3222c3 Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Mon, 27 Apr 2026 04:11:40 +0900 Subject: [PATCH 09/60] feat: Implement compute shader class and asset --- include/Editor/AssetExtensions.h | 3 +- include/Game/Asset/ComputeShaderAsset.h | 32 +++++ include/Game/Asset/ComputeShaderLoader.h | 31 +++++ include/Render/ComputeShader.h | 41 ++++++ include/Render/ComputeShaderCreateInfo.h | 12 ++ src/Editor/AssetDatabase.cpp | 12 +- src/Editor/AssetExtensions.cpp | 5 +- src/Game/Asset/ComputeShaderAsset.cpp | 36 +++++ src/Game/Asset/ComputeShaderLoader.cpp | 161 +++++++++++++++++++++++ src/Render/ComputeShader.cpp | 45 +++++++ 10 files changed, 374 insertions(+), 4 deletions(-) create mode 100644 include/Game/Asset/ComputeShaderAsset.h create mode 100644 include/Game/Asset/ComputeShaderLoader.h create mode 100644 include/Render/ComputeShader.h create mode 100644 include/Render/ComputeShaderCreateInfo.h create mode 100644 src/Game/Asset/ComputeShaderAsset.cpp create mode 100644 src/Game/Asset/ComputeShaderLoader.cpp create mode 100644 src/Render/ComputeShader.cpp diff --git a/include/Editor/AssetExtensions.h b/include/Editor/AssetExtensions.h index fbbb8b2b..4c7c695f 100644 --- a/include/Editor/AssetExtensions.h +++ b/include/Editor/AssetExtensions.h @@ -22,7 +22,8 @@ namespace sh::editor Font, Binary, Sound, - ScriptableObject + ScriptableObject, + ComputeShader, }; public: SH_EDITOR_API static void AddExtension(const std::string& ext, Type type); diff --git a/include/Game/Asset/ComputeShaderAsset.h b/include/Game/Asset/ComputeShaderAsset.h new file mode 100644 index 00000000..42507bc0 --- /dev/null +++ b/include/Game/Asset/ComputeShaderAsset.h @@ -0,0 +1,32 @@ +#pragma once +#include "../Export.h" + +#include "Core/Asset.h" +#include "Core/SContainer.hpp" +#include "Core/ISerializable.h" + +#include "Render/ComputeShader.h" + +namespace sh::game +{ + class ComputeShaderAsset : public core::Asset + { + SASSET(ComputeShaderAsset, "comp") + public: + constexpr static const char* ASSET_NAME = "comp"; + public: + SH_GAME_API ComputeShaderAsset(); + SH_GAME_API ComputeShaderAsset(const render::ComputeShader& shader); + + SH_GAME_API void SetAsset(const core::SObject& obj) override; + + SH_GAME_API auto GetShaderObjectJson() const -> const core::Json& { return shaderObjJson; } + protected: + SH_GAME_API void SetAssetData() const override; + SH_GAME_API auto ParseAssetData() -> bool override; + private: + core::SObjWeakPtr shaderPtr; + + core::Json shaderObjJson; + }; +}//namespace diff --git a/include/Game/Asset/ComputeShaderLoader.h b/include/Game/Asset/ComputeShaderLoader.h new file mode 100644 index 00000000..dfc07323 --- /dev/null +++ b/include/Game/Asset/ComputeShaderLoader.h @@ -0,0 +1,31 @@ +#pragma once +#include "../Export.h" + +#include "Core/Asset.h" +#include "Core/IAssetLoader.h" + +#include "Render/ComputeShader.h" + +#include + +namespace sh::game +{ + class ComputeShaderLoader : public core::IAssetLoader + { + public: + SH_GAME_API ComputeShaderLoader(); + SH_GAME_API ~ComputeShaderLoader(); + + SH_GAME_API void SetCachePath(const std::filesystem::path& path) { cachePath = path; } + + SH_GAME_API auto Load(const std::filesystem::path& filePath) const -> core::SObject* override; + SH_GAME_API auto Load(const core::Asset& asset) const -> core::SObject* override; + + SH_GAME_API auto GetCachePath() const -> const std::filesystem::path& { return cachePath; } + SH_GAME_API auto GetAssetName() const -> const char* override { return ASSET_NAME; } + private: + static constexpr const char* ASSET_NAME = "comp"; + + std::filesystem::path cachePath; + }; +}//namespace diff --git a/include/Render/ComputeShader.h b/include/Render/ComputeShader.h new file mode 100644 index 00000000..49993297 --- /dev/null +++ b/include/Render/ComputeShader.h @@ -0,0 +1,41 @@ +#pragma once +#include "Export.h" +#include "ShaderAST.h" + +#include "Core/SObject.h" +#include "Core/Name.h" +#include "Core/Reflection.hpp" + +#include +#include +namespace sh::render +{ + struct ComputeShaderCreateInfo; + + class ComputeShader : public core::SObject + { + SCLASS(ComputeShader) + public: + SH_RENDER_API ComputeShader(ComputeShaderCreateInfo createInfo); + SH_RENDER_API ~ComputeShader(); + + /// @brief AST와 SPIR-V 바이너리를 직렬화 한다. + /// @return 직렬화 된 json + SH_RENDER_API auto Serialize() const -> core::Json override; + /// @brief AST와 SPIR-V 바이너리를 역직렬화 한다. + /// @param json 직렬화 된 json + SH_RENDER_API void Deserialize(const core::Json& json) override; + + SH_RENDER_API auto GetSpirv() const -> const std::vector& { return spirv; } + SH_RENDER_API auto GetShaderAST() const -> const ShaderAST::ComputeShaderNode& { return shaderNode; } + + SH_RENDER_API auto GetNumthreadsX() const -> uint32_t { return shaderNode.numthreadsX; } + SH_RENDER_API auto GetNumthreadsY() const -> uint32_t { return shaderNode.numthreadsY; } + SH_RENDER_API auto GetNumthreadsZ() const -> uint32_t { return shaderNode.numthreadsZ; } + + SH_RENDER_API auto GetBuffers() const -> const std::vector& { return shaderNode.buffers; } + private: + ShaderAST::ComputeShaderNode shaderNode; + std::vector spirv; + }; +}//namespace diff --git a/include/Render/ComputeShaderCreateInfo.h b/include/Render/ComputeShaderCreateInfo.h new file mode 100644 index 00000000..bfeac71f --- /dev/null +++ b/include/Render/ComputeShaderCreateInfo.h @@ -0,0 +1,12 @@ +#pragma once +#include "ShaderAST.h" + +#include +namespace sh::render +{ + struct ComputeShaderCreateInfo + { + ShaderAST::ComputeShaderNode shaderNode; + std::vector spirv; + }; +}//namespace diff --git a/src/Editor/AssetDatabase.cpp b/src/Editor/AssetDatabase.cpp index f51685a5..f481bc7e 100644 --- a/src/Editor/AssetDatabase.cpp +++ b/src/Editor/AssetDatabase.cpp @@ -30,6 +30,7 @@ #include "Game/Asset/TextLoader.h" #include "Game/Asset/FontLoader.h" #include "Game/Asset/ScriptableObjectLoader.h" +#include "Game/Asset/ComputeShaderLoader.h" #include "Game/Asset/TextureAsset.h" #include "Game/Asset/ModelAsset.h" @@ -43,6 +44,7 @@ #include "Game/Asset/TextAsset.h" #include "Game/Asset/FontAsset.h" #include "Game/Asset/ScriptableObjectAsset.h" +#include "Game/Asset/ComputeShaderAsset.h" #include "Sound/SoundClip.h" @@ -79,13 +81,17 @@ namespace sh::editor assert(ctx.GetRenderAPIType() == render::RenderAPI::Vulkan); // TODO static render::vk::VulkanShaderPassBuilder passBuilder{ static_cast(ctx) }; - auto shaderLoader = std::make_unique(&passBuilder); + std::unique_ptr shaderLoader = std::make_unique(&passBuilder); shaderLoader->SetCachePath(projectPath / "temp"); + std::unique_ptr computeShaderLoader = std::make_unique(); + computeShaderLoader->SetCachePath(projectPath / "temp"); + assetLoaders.Clear(); assetLoaders.RegisterLoader(AssetExtensions::Type::Model, std::make_unique(ctx), 2, true); assetLoaders.RegisterLoader(AssetExtensions::Type::Mesh, std::make_unique(ctx), 2, true); assetLoaders.RegisterLoader(AssetExtensions::Type::Texture, std::make_unique(ctx), 2, true); + assetLoaders.RegisterLoader(AssetExtensions::Type::ComputeShader, std::move(computeShaderLoader), 2, false); assetLoaders.RegisterLoader(AssetExtensions::Type::Shader, std::move(shaderLoader), 2, false); assetLoaders.RegisterLoader(AssetExtensions::Type::Binary, std::make_unique(), 2, false); assetLoaders.RegisterLoader(AssetExtensions::Type::Sound, std::make_unique(), 2, false); @@ -439,13 +445,15 @@ namespace sh::editor assetType = game::SoundAsset::ASSET_NAME; else if (obj.GetType().IsChildOf(game::ScriptableObject::GetStaticType())) assetType = game::ScriptableObjectAsset::ASSET_NAME; + else if (obj.GetType() == render::ComputeShader::GetStaticType()) + assetType = game::ComputeShaderAsset::ASSET_NAME; else { SH_ERROR_FORMAT("Failed to export asset (type: {})", obj.GetType().name.ToString()); return false; } - auto asset = core::Factory::GetInstance()->Create(assetType); + std::unique_ptr asset = core::Factory::GetInstance()->Create(assetType); if (asset == nullptr) { SH_ERROR_FORMAT("Asset type({}) is invalid", assetType); diff --git a/src/Editor/AssetExtensions.cpp b/src/Editor/AssetExtensions.cpp index 16bf5d10..3f5e52b5 100644 --- a/src/Editor/AssetExtensions.cpp +++ b/src/Editor/AssetExtensions.cpp @@ -18,7 +18,8 @@ namespace sh::editor {".ttf", Type::Binary}, {".ogg", Type::Sound}, {".wav", Type::Sound}, - {".srpo", Type::ScriptableObject} + {".srpo", Type::ScriptableObject}, + {".compute", Type::ComputeShader} }; SH_EDITOR_API void AssetExtensions::AddExtension(const std::string& ext, Type type) @@ -60,6 +61,8 @@ namespace sh::editor return "Sound"; case Type::ScriptableObject: return "ScriptableObject"; + case Type::ComputeShader: + return "ComputeShader"; } return "None"; } diff --git a/src/Game/Asset/ComputeShaderAsset.cpp b/src/Game/Asset/ComputeShaderAsset.cpp new file mode 100644 index 00000000..66d23ce5 --- /dev/null +++ b/src/Game/Asset/ComputeShaderAsset.cpp @@ -0,0 +1,36 @@ +#include "Asset/ComputeShaderAsset.h" + +namespace sh::game +{ + ComputeShaderAsset::ComputeShaderAsset() : + Asset(ASSET_NAME) + { + } + ComputeShaderAsset::ComputeShaderAsset(const render::ComputeShader& shader) : + Asset(ASSET_NAME), + shaderPtr(static_cast(&shader)) + { + assetUUID = shaderPtr->GetUUID(); + } + SH_GAME_API void ComputeShaderAsset::SetAsset(const core::SObject& obj) + { + if (obj.GetType() != render::ComputeShader::GetStaticType()) + return; + + shaderPtr = static_cast(&obj); + assetUUID = shaderPtr->GetUUID(); + } + SH_GAME_API void ComputeShaderAsset::SetAssetData() const + { + if (!shaderPtr.IsValid()) + return; + + const core::Json shaderObjJson = shaderPtr->Serialize(); + data = core::Json::to_cbor(shaderObjJson); + } + SH_GAME_API auto ComputeShaderAsset::ParseAssetData() -> bool + { + shaderObjJson = core::Json::from_cbor(data); + return true; + } +}//namespace diff --git a/src/Game/Asset/ComputeShaderLoader.cpp b/src/Game/Asset/ComputeShaderLoader.cpp new file mode 100644 index 00000000..62601e83 --- /dev/null +++ b/src/Game/Asset/ComputeShaderLoader.cpp @@ -0,0 +1,161 @@ +#include "Asset/ComputeShaderLoader.h" +#include "Asset/ComputeShaderAsset.h" + +#include "Core/FileSystem.h" +#include "Core/Logger.h" +#include "Core/ExecuteProcess.h" + +#include "Render/ComputeShader.h" +#include "Render/ComputeShaderCreateInfo.h" +#include "Render/ComputeShaderLexer.h" +#include "Render/ComputeShaderParser.h" + +#include + +#include +#include +#include +namespace sh::game +{ + static auto ReplaceSpaceString(const std::string& str) -> std::string + { + std::string result; + result.reserve(str.size()); + for (char c : str) + { + if (std::isalnum(static_cast(c))) + result.push_back(c); + else + result.push_back('_'); + } + return result; + } + + ComputeShaderLoader::ComputeShaderLoader() + { + cachePath = std::filesystem::current_path() / "cache"; + if (!std::filesystem::exists(cachePath)) + std::filesystem::create_directory(cachePath); + } + ComputeShaderLoader::~ComputeShaderLoader() = default; + + SH_GAME_API auto ComputeShaderLoader::Load(const std::filesystem::path& path) const -> core::SObject* + { + std::optional srcOpt = core::FileSystem::LoadText(path); + if (!srcOpt.has_value()) + { + SH_ERROR_FORMAT("Can't load file: {}", path.string()); + return nullptr; + } + if (!std::filesystem::exists(cachePath)) + std::filesystem::create_directory(cachePath); + + render::ComputeShaderLexer lexer{}; + render::ComputeShaderParser parser{}; + + render::ComputeShaderCreateInfo csCI{}; + try + { + csCI.shaderNode = parser.Parse(lexer.Lex(srcOpt.value())); + } + catch (const render::ComputeShaderParserException& e) + { + SH_ERROR_FORMAT("Compute shader parsing error: {}", e.what()); + return nullptr; + } + catch (const render::ComputeShaderLexerException& e) + { + SH_ERROR_FORMAT("Compute shader lexing error: {}", e.what()); + return nullptr; + } + + // 셰이더 이름이 비어있으면 파일명을 사용 + if (csCI.shaderNode.shaderName.empty()) + csCI.shaderNode.shaderName = path.stem().string(); + + // GLSL 파일 작성 + const std::string fileName = ReplaceSpaceString(csCI.shaderNode.shaderName) + ".comp"; + const std::filesystem::path glslPath = cachePath / fileName; + if (!core::FileSystem::SaveText(csCI.shaderNode.code, glslPath)) + { + SH_ERROR_FORMAT("Can't write compute shader source: {}", glslPath.string()); + return nullptr; + } + + // SPIR-V 컴파일 + const std::filesystem::path spirvPath = cachePath / (fileName + ".spv"); + + std::string output; + std::string compiler = "glslc"; +#if _WIN32 + std::vector args = { + "-fshader-stage=compute", + fmt::format("\"{}\"", glslPath.u8string()), + "-o", + fmt::format("\"{}\"", spirvPath.u8string()) + }; + compiler += ".exe"; +#else + std::vector args = { + "-fshader-stage=compute", + fmt::format("{}", glslPath.u8string()), + "-o", + fmt::format("{}", spirvPath.u8string()) + }; +#endif + if (!core::ExecuteProcess::Execute(compiler, args, output)) + { + SH_ERROR_FORMAT("Compute shader compile error: {}", output); + return nullptr; + } + + // SPIR-V 로드 + auto spirvOpt = core::FileSystem::LoadBinary(spirvPath); + if (!spirvOpt.has_value()) + { + SH_ERROR_FORMAT("Compute shader spirv load error: {}", spirvPath.string()); + return nullptr; + } + csCI.spirv = std::move(spirvOpt.value()); + + render::ComputeShader* const shader = core::SObject::Create(std::move(csCI)); + return shader; + } + + SH_GAME_API auto ComputeShaderLoader::Load(const core::Asset& asset) const -> core::SObject* + { + if (std::strcmp(asset.GetType(), ASSET_NAME) != 0) + { + SH_ERROR_FORMAT("Asset({}) is not a compute shader!", asset.GetAssetUUID().ToString()); + return nullptr; + } + const game::ComputeShaderAsset& csAsset = static_cast(asset); + const core::Json& shaderObjJson = csAsset.GetShaderObjectJson(); + + if (!shaderObjJson.contains("computeShader")) + return nullptr; + + const core::Json& csJson = shaderObjJson["computeShader"]; + + render::ComputeShaderCreateInfo csCI{}; + if (!csJson.contains("AST")) + return nullptr; + + csCI.shaderNode.Deserialize(csJson["AST"]); + if (csJson.contains("spirv")) + csCI.spirv = csJson["spirv"].get>(); + + if (core::SObject* const oldObj = core::SObjectManager::GetInstance()->GetSObject(asset.GetAssetUUID()); oldObj != nullptr) + { + render::ComputeShader* const oldShaderPtr = core::reflection::Cast(oldObj); + if (oldShaderPtr == nullptr) + return nullptr; + oldShaderPtr->Deserialize(shaderObjJson); + return oldShaderPtr; + } + render::ComputeShader* const shader = core::SObject::Create(std::move(csCI)); + shader->Deserialize(shaderObjJson); + + return shader; + } +}//namespace diff --git a/src/Render/ComputeShader.cpp b/src/Render/ComputeShader.cpp new file mode 100644 index 00000000..410433eb --- /dev/null +++ b/src/Render/ComputeShader.cpp @@ -0,0 +1,45 @@ +#include "ComputeShader.h" +#include "ComputeShaderCreateInfo.h" + +namespace sh::render +{ + ComputeShader::ComputeShader(ComputeShaderCreateInfo createInfo) + { + SetName(createInfo.shaderNode.shaderName); + + shaderNode = std::move(createInfo.shaderNode); + spirv = std::move(createInfo.spirv); + + // 코드 텍스트는 보존할 필요 없음 + shaderNode.code.clear(); + shaderNode.functions.clear(); + shaderNode.declaration.clear(); + } + ComputeShader::~ComputeShader() = default; + + SH_RENDER_API auto ComputeShader::Serialize() const -> core::Json + { + core::Json mainJson = Super::Serialize(); + + core::Json& json = mainJson["computeShader"]; + json["AST"] = shaderNode.Serialize(); + json["spirv"] = spirv; + + return mainJson; + } + + SH_RENDER_API void ComputeShader::Deserialize(const core::Json& json) + { + Super::Deserialize(json); + + if (!json.contains("computeShader")) + return; + + const core::Json& csJson = json["computeShader"]; + + if (csJson.contains("AST")) + shaderNode.Deserialize(csJson["AST"]); + if (csJson.contains("spirv")) + spirv = csJson["spirv"].get>(); + } +}//namespace From b32abcd0c28187ab4a404ec950c466edd603b65f Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Tue, 28 Apr 2026 00:07:33 +0900 Subject: [PATCH 10/60] feat(VulkanBuffer): Change GetData() declaration and Add support for device-local buffers in SetData() --- include/Game/Component/Render/PickingCamera.h | 2 +- include/Render/BufferFactory.h | 2 +- include/Render/IBuffer.h | 4 +- include/Render/VulkanImpl/VulkanBuffer.h | 7 +- .../Render/VulkanImpl/VulkanVertexBuffer.h | 4 +- src/Game/Component/Render/PickingCamera.cpp | 4 +- src/Render/BufferFactory.cpp | 13 +- src/Render/ScriptableRenderer.cpp | 1 - src/Render/VulkanImpl/VulkanBuffer.cpp | 155 ++++++++++++++---- src/Render/VulkanImpl/VulkanCameraBuffers.cpp | 8 +- src/Render/VulkanImpl/VulkanVertexBuffer.cpp | 55 +------ 11 files changed, 157 insertions(+), 98 deletions(-) diff --git a/include/Game/Component/Render/PickingCamera.h b/include/Game/Component/Render/PickingCamera.h index 0cae30da..5a2cd0e3 100644 --- a/include/Game/Component/Render/PickingCamera.h +++ b/include/Game/Component/Render/PickingCamera.h @@ -39,7 +39,7 @@ namespace sh::game int x = 0, y = 0; - uint8_t* pixels = nullptr; + std::vector pixels; std::future> bufferFuture; diff --git a/include/Render/BufferFactory.h b/include/Render/BufferFactory.h index 16dd4a87..35c89857 100644 --- a/include/Render/BufferFactory.h +++ b/include/Render/BufferFactory.h @@ -22,7 +22,7 @@ namespace sh::render { std::size_t size = 0; bool bDynamic = false; // SSBO - bool bTransferDst = false; + bool bGPUOnly = false; }; public: SH_RENDER_API static auto Create(const IRenderContext& context, const CreateInfo& info) -> std::unique_ptr; diff --git a/include/Render/IBuffer.h b/include/Render/IBuffer.h index 7e8284bf..f4626993 100644 --- a/include/Render/IBuffer.h +++ b/include/Render/IBuffer.h @@ -1,5 +1,6 @@ #pragma once +#include namespace sh::render { class IBuffer @@ -8,7 +9,8 @@ namespace sh::render virtual ~IBuffer() = default; virtual void SetData(const void* data) = 0; - virtual auto GetData() const -> void* = 0; + virtual void SetData(const void* data, std::size_t offset, std::size_t size) = 0; + virtual auto GetData() const -> std::vector = 0; virtual auto GetSize() const -> size_t = 0; virtual auto Resize(std::size_t size) -> bool = 0; }; diff --git a/include/Render/VulkanImpl/VulkanBuffer.h b/include/Render/VulkanImpl/VulkanBuffer.h index 067187b2..7304a773 100644 --- a/include/Render/VulkanImpl/VulkanBuffer.h +++ b/include/Render/VulkanImpl/VulkanBuffer.h @@ -23,14 +23,15 @@ namespace sh::render::vk SH_RENDER_API auto Create(size_t size, VkBufferUsageFlags usageBits, VkSharingMode sharing, VkMemoryPropertyFlags memPropFlagBits, bool persistentMapping = false) -> VkResult; SH_RENDER_API void Clean(); SH_RENDER_API void SetData(const void* data); + SH_RENDER_API void SetData(const void* data, std::size_t offset, std::size_t size) override; - SH_RENDER_API auto GetData() const -> void* override { return dataPtr; } + SH_RENDER_API auto GetData() const -> std::vector override; SH_RENDER_API auto GetBuffer() const -> VkBuffer { return buffer; } SH_RENDER_API auto GetBufferInfo() const -> const VkBufferCreateInfo& { return bufferInfo; } SH_RENDER_API auto GetBufferMemory() const -> VmaAllocation { return bufferMem; } SH_RENDER_API auto GetSize() const -> size_t { return size; } private: - auto FindMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) -> uint32_t; + // auto FindMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) -> uint32_t; // 이제 안 씀 private: const VulkanContext& context; @@ -42,6 +43,6 @@ namespace sh::render::vk size_t size; void* dataPtr; - bool persistentMapping; // 즉시 맵핑 + bool bPersistentMapping; // 즉시 맵핑 }; }//namespace \ No newline at end of file diff --git a/include/Render/VulkanImpl/VulkanVertexBuffer.h b/include/Render/VulkanImpl/VulkanVertexBuffer.h index 89cd39c1..c54b10b4 100644 --- a/include/Render/VulkanImpl/VulkanVertexBuffer.h +++ b/include/Render/VulkanImpl/VulkanVertexBuffer.h @@ -29,8 +29,8 @@ namespace sh::render::vk SH_RENDER_API static auto GetBindingDescription() -> VkVertexInputBindingDescription; SH_RENDER_API static auto GetAttributeDescriptions() -> std::vector; - SH_RENDER_API auto GetVertexBuffer() const -> const VulkanBuffer&; - SH_RENDER_API auto GetIndexBuffer() const -> const VulkanBuffer&; + SH_RENDER_API auto GetVertexBuffer() const -> const VulkanBuffer& { return vertexBuffer; } + SH_RENDER_API auto GetIndexBuffer() const -> const VulkanBuffer& { return indexBuffer; } private: void CreateVertexBuffer(const Mesh& mesh); private: diff --git a/src/Game/Component/Render/PickingCamera.cpp b/src/Game/Component/Render/PickingCamera.cpp index 1cd37850..6478486a 100644 --- a/src/Game/Component/Render/PickingCamera.cpp +++ b/src/Game/Component/Render/PickingCamera.cpp @@ -15,6 +15,8 @@ namespace sh::game PickingCamera::PickingCamera(GameObject& owner) : Camera(owner) { + pixels.resize(4); + render::RenderTargetLayout rt{}; rt.format = render::TextureFormat::RGBA32; rt.depthFormat = render::TextureFormat::D24S8; @@ -55,7 +57,7 @@ namespace sh::game auto status = bufferFuture.wait_for(std::chrono::milliseconds(0)); if (status == std::future_status::ready) { - pixels = reinterpret_cast(bufferFuture.get()->GetData()); + pixels = bufferFuture.get()->GetData(); SH_INFO_FORMAT("{}, {}, {}, {}", pixels[0], pixels[1], pixels[2], pixels[3]); pickingCallback.Notify({ pixels[0], pixels[1], pixels[2], pixels[3] }); diff --git a/src/Render/BufferFactory.cpp b/src/Render/BufferFactory.cpp index 1f0417ab..7ec9ebfd 100644 --- a/src/Render/BufferFactory.cpp +++ b/src/Render/BufferFactory.cpp @@ -17,13 +17,14 @@ namespace sh::render VkBufferUsageFlags usage = info.bDynamic ? VkBufferUsageFlagBits::VK_BUFFER_USAGE_STORAGE_BUFFER_BIT : VkBufferUsageFlagBits::VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; - if (info.bTransferDst) - usage |= VkBufferUsageFlagBits::VK_BUFFER_USAGE_TRANSFER_DST_BIT; - VkResult result = buffer->Create(info.size, usage, - VkSharingMode::VK_SHARING_MODE_EXCLUSIVE, - VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, - true); + usage |= VkBufferUsageFlagBits::VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + usage |= VkBufferUsageFlagBits::VK_BUFFER_USAGE_TRANSFER_DST_BIT; + + const VkMemoryPropertyFlags memFlag = info.bGPUOnly ? VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT : + VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + + const VkResult result = buffer->Create(info.size, usage, VkSharingMode::VK_SHARING_MODE_EXCLUSIVE,memFlag, true); assert(result == VkResult::VK_SUCCESS); if (result != VkResult::VK_SUCCESS) { diff --git a/src/Render/ScriptableRenderer.cpp b/src/Render/ScriptableRenderer.cpp index 5fd83d3f..71f932f1 100644 --- a/src/Render/ScriptableRenderer.cpp +++ b/src/Render/ScriptableRenderer.cpp @@ -47,7 +47,6 @@ namespace sh::render { BufferFactory::CreateInfo ci{}; ci.size = sizeof(uint8_t) * 4; - ci.bTransferDst = true; std::unique_ptr buffer = BufferFactory::Create(ctx, ci); IRenderThrMethod::EnqueCopyImagePixelToBuffer(*cpyPass, *data.src, data.x, data.y, *buffer.get()); diff --git a/src/Render/VulkanImpl/VulkanBuffer.cpp b/src/Render/VulkanImpl/VulkanBuffer.cpp index ac07d025..24baba0f 100644 --- a/src/Render/VulkanImpl/VulkanBuffer.cpp +++ b/src/Render/VulkanImpl/VulkanBuffer.cpp @@ -1,5 +1,10 @@ #include "VulkanBuffer.h" #include "VulkanContext.h" +#include "VulkanCommandBufferPool.h" +#include "VulkanCommandBuffer.h" +#include "VulkanQueueManager.h" + +#include "Core/Logger.h" #include #include @@ -11,7 +16,7 @@ namespace sh::render::vk context(context), buffer(nullptr), bufferMem(nullptr), dataPtr(nullptr), bufferInfo(), memProperty(0), - persistentMapping(false), + bPersistentMapping(false), size(0) { } @@ -19,7 +24,7 @@ namespace sh::render::vk context(other.context), buffer(nullptr), bufferMem(nullptr), dataPtr(nullptr), bufferInfo(other.bufferInfo), memProperty(other.memProperty), - persistentMapping(other.persistentMapping), + bPersistentMapping(other.bPersistentMapping), size(other.size) { operator=(other); @@ -28,7 +33,7 @@ namespace sh::render::vk context(other.context), buffer(other.buffer), bufferMem(other.bufferMem), dataPtr(other.dataPtr), bufferInfo(other.bufferInfo), memProperty(other.memProperty), - persistentMapping(other.persistentMapping), + bPersistentMapping(other.bPersistentMapping), size(other.size) { other.buffer = nullptr; @@ -44,8 +49,9 @@ namespace sh::render::vk if (&other == this) return *this; - Create(other.size, other.bufferInfo.usage, other.bufferInfo.sharingMode, other.memProperty, other.persistentMapping); - SetData(other.dataPtr); + Create(other.size, other.bufferInfo.usage, other.bufferInfo.sharingMode, other.memProperty, other.bPersistentMapping); + std::vector temp = other.GetData(); + SetData(temp.data()); return *this; } @@ -54,12 +60,14 @@ namespace sh::render::vk if (&other == this) return *this; + Clean(); + buffer = other.buffer; bufferMem = other.bufferMem; dataPtr = other.dataPtr; bufferInfo = other.bufferInfo; memProperty = other.memProperty; - persistentMapping = other.persistentMapping; + bPersistentMapping = other.bPersistentMapping; size = other.size; other.buffer = nullptr; @@ -71,7 +79,7 @@ namespace sh::render::vk SH_RENDER_API auto VulkanBuffer::Resize(std::size_t size) -> bool { Clean(); - const VkResult result = Create(size, bufferInfo.usage, bufferInfo.sharingMode, memProperty, this->persistentMapping); + const VkResult result = Create(size, bufferInfo.usage, bufferInfo.sharingMode, memProperty, this->bPersistentMapping); if (result == VkResult::VK_SUCCESS) return true; return false; @@ -93,10 +101,10 @@ namespace sh::render::vk bufferMem = nullptr; }*/ } - SH_RENDER_API auto VulkanBuffer::Create(size_t size, VkBufferUsageFlags usageBits, VkSharingMode sharing, VkMemoryPropertyFlags memPropFlagBits, bool persistentMapping) -> VkResult + SH_RENDER_API auto VulkanBuffer::Create(size_t size, VkBufferUsageFlags usageBits, VkSharingMode sharing, VkMemoryPropertyFlags memPropFlagBits, bool bPersistentMapping) -> VkResult { this->size = size; - this->persistentMapping = persistentMapping; + this->bPersistentMapping = bPersistentMapping; this->memProperty = memPropFlagBits; Clean(); @@ -122,22 +130,29 @@ namespace sh::render::vk result = vkAllocateMemory(device, &allocInfo, nullptr, &bufferMem);*/ - bool bUseMap = (memPropFlagBits & VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT); + const bool bUseMap = (memPropFlagBits & VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT); + const bool bGPUOnly = (memPropFlagBits & VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + if (bGPUOnly) + this->bPersistentMapping = false; VmaAllocationCreateInfo allocCreateInfo{}; allocCreateInfo.usage = VmaMemoryUsage::VMA_MEMORY_USAGE_AUTO; allocCreateInfo.flags = 0; if (bUseMap) allocCreateInfo.flags |= VmaAllocationCreateFlagBits::VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; - if (persistentMapping) + if (this->bPersistentMapping) allocCreateInfo.flags |= VmaAllocationCreateFlagBits::VMA_ALLOCATION_CREATE_MAPPED_BIT; VmaAllocationInfo allocInfo{}; - auto result = vmaCreateBuffer(context.GetAllocator(), &bufferInfo, &allocCreateInfo, &buffer, &bufferMem, &allocInfo); - - assert(result == VkResult::VK_SUCCESS); + VkResult result = vmaCreateBuffer(context.GetAllocator(), &bufferInfo, &allocCreateInfo, &buffer, &bufferMem, &allocInfo); + if (result != VkResult::VK_SUCCESS) + { + assert(result == VkResult::VK_SUCCESS); + SH_ERROR_FORMAT("Failed to create buffer!: {}", string_VkResult(result)); + return result; + } - if (persistentMapping) + if (this->bPersistentMapping) dataPtr = allocInfo.pMappedData; //result = vkBindBufferMemory(device, buffer, bufferMem, 0); //assert(result == VkResult::VK_SUCCESS); @@ -145,33 +160,115 @@ namespace sh::render::vk return result; } SH_RENDER_API void VulkanBuffer::SetData(const void* data) + { + SetData(data, 0, static_cast(bufferInfo.size)); + } + SH_RENDER_API void VulkanBuffer::SetData(const void* data, std::size_t offset, std::size_t size) { assert(buffer); if (buffer == nullptr) return; - if (!persistentMapping) + const bool bGPUOnly = (memProperty & VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + if (!bGPUOnly) { - //vkMapMemory(device, bufferMem, 0, bufferInfo.size, 0, &this->data); - vmaMapMemory(context.GetAllocator(), bufferMem, &this->dataPtr); - std::memcpy(this->dataPtr, data, static_cast(bufferInfo.size)); - //vkUnmapMemory(device, bufferMem); - vmaUnmapMemory(context.GetAllocator(), bufferMem); + if (!bPersistentMapping) + { + vmaMapMemory(context.GetAllocator(), bufferMem, &this->dataPtr); + std::memcpy(reinterpret_cast(dataPtr) + offset, data, size); + vmaUnmapMemory(context.GetAllocator(), bufferMem); + dataPtr = nullptr; + } + else + std::memcpy(reinterpret_cast(dataPtr) + offset, data, size); } else - std::memcpy(this->dataPtr, data, static_cast(bufferInfo.size)); + { + VulkanBuffer stagingBuffer{ context }; + stagingBuffer.Create(size, VkBufferUsageFlagBits::VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VkSharingMode::VK_SHARING_MODE_EXCLUSIVE, + VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + stagingBuffer.SetData(data); + + VulkanCommandBuffer& cmd = *context.GetCommandBufferPool().AllocateCommandBuffer(std::this_thread::get_id(), VkQueueFlagBits::VK_QUEUE_TRANSFER_BIT); + cmd.Build( + [&]() + { + VkBufferCopy cpy{}; + cpy.srcOffset = 0; + cpy.dstOffset = offset; + cpy.size = size; + vkCmdCopyBuffer(cmd.GetCommandBuffer(), stagingBuffer.GetBuffer(), GetBuffer(), 1, &cpy); + } + , true); + VkFence fence = cmd.GetOrCreateFence(); + context.GetQueueManager().Submit(VulkanQueueManager::Role::Transfer, cmd, fence); + vkWaitForFences(context.GetDevice(), 1, &fence, true, std::numeric_limits::max()); + + context.GetCommandBufferPool().DeallocateCommandBuffer(cmd); + } } - auto VulkanBuffer::FindMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) -> uint32_t + SH_RENDER_API auto VulkanBuffer::GetData() const -> std::vector { - VkPhysicalDeviceMemoryProperties memProperties; - vkGetPhysicalDeviceMemoryProperties(context.GetGPU(), &memProperties); + if (buffer == VK_NULL_HANDLE) + return {}; + + const bool bGPUOnly = (memProperty & VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + const std::size_t size = static_cast(bufferInfo.size); + std::vector result; - for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { - if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) - return i; + if (dataPtr != nullptr) + { + result.resize(size); + result.assign(reinterpret_cast(dataPtr), reinterpret_cast(dataPtr) + size); } + else + { + if (bGPUOnly) + { + VulkanBuffer temp{ context }; + temp.Create(size, VkBufferUsageFlagBits::VK_BUFFER_USAGE_TRANSFER_DST_BIT, VkSharingMode::VK_SHARING_MODE_EXCLUSIVE, + VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, true); + + VulkanCommandBuffer& cmd = *context.GetCommandBufferPool().AllocateCommandBuffer(std::this_thread::get_id(), VkQueueFlagBits::VK_QUEUE_TRANSFER_BIT); + cmd.Build( + [&]() + { + VkBufferCopy cpy{}; + cpy.srcOffset = 0; + cpy.dstOffset = 0; + cpy.size = size; + vkCmdCopyBuffer(cmd.GetCommandBuffer(), GetBuffer(), temp.GetBuffer(), 1, &cpy); + } + , true); + VkFence fence = cmd.GetOrCreateFence(); + context.GetQueueManager().Submit(VulkanQueueManager::Role::Transfer, cmd, fence); + vkWaitForFences(context.GetDevice(), 1, &fence, true, std::numeric_limits::max()); + + context.GetCommandBufferPool().DeallocateCommandBuffer(cmd); + return temp.GetData(); + } - throw std::runtime_error("Failed to find suitable memory type!"); + result.resize(size); + void* tempPtr = nullptr; + vmaMapMemory(context.GetAllocator(), bufferMem, &tempPtr); + result.assign(reinterpret_cast(tempPtr), reinterpret_cast(tempPtr) + size); + vmaUnmapMemory(context.GetAllocator(), bufferMem); + } + return result; } + + //auto VulkanBuffer::FindMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) -> uint32_t + //{ + // VkPhysicalDeviceMemoryProperties memProperties; + // vkGetPhysicalDeviceMemoryProperties(context.GetGPU(), &memProperties); + + // for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { + // if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) + // return i; + // } + + // throw std::runtime_error("Failed to find suitable memory type!"); + //} }//namespace \ No newline at end of file diff --git a/src/Render/VulkanImpl/VulkanCameraBuffers.cpp b/src/Render/VulkanImpl/VulkanCameraBuffers.cpp index 031db5ad..6d967bc5 100644 --- a/src/Render/VulkanImpl/VulkanCameraBuffers.cpp +++ b/src/Render/VulkanImpl/VulkanCameraBuffers.cpp @@ -61,9 +61,11 @@ namespace sh::render::vk auto offsetOpt = GetDynamicOffset(camera); if (offsetOpt.has_value()) { - Camera::BufferData* data = reinterpret_cast((std::size_t)cameraData->GetData() + offsetOpt.value()); - data->matView = camera.GetViewMatrix(core::ThreadType::Render); - data->matProj = camera.GetProjMatrix(core::ThreadType::Render); + //Camera::BufferData* data = reinterpret_cast((std::size_t)cameraData->GetData() + offsetOpt.value()); + Camera::BufferData data; + data.matView = camera.GetViewMatrix(core::ThreadType::Render); + data.matProj = camera.GetProjMatrix(core::ThreadType::Render); + cameraData->SetData(&data, offsetOpt.value(), sizeof(Camera::BufferData)); } } auto VulkanCameraBuffers::GetDynamicOffset(const Camera& camera) const -> std::optional diff --git a/src/Render/VulkanImpl/VulkanVertexBuffer.cpp b/src/Render/VulkanImpl/VulkanVertexBuffer.cpp index fee3ecf9..c51b57e1 100644 --- a/src/Render/VulkanImpl/VulkanVertexBuffer.cpp +++ b/src/Render/VulkanImpl/VulkanVertexBuffer.cpp @@ -101,70 +101,25 @@ namespace sh::render::vk } return attribDescriptions; } - SH_RENDER_API auto VulkanVertexBuffer::GetVertexBuffer() const -> const VulkanBuffer& - { - return vertexBuffer; - } - SH_RENDER_API auto VulkanVertexBuffer::GetIndexBuffer() const -> const VulkanBuffer& - { - return indexBuffer; - } void VulkanVertexBuffer::CreateVertexBuffer(const Mesh& mesh) { if (mesh.GetVertexCount() == 0) return; - VulkanCommandBuffer* cmd = context.GetCommandBufferPool().AllocateCommandBuffer(std::this_thread::get_id(), VkQueueFlagBits::VK_QUEUE_TRANSFER_BIT); - assert(cmd != nullptr); // 버텍스 버퍼 - size_t vertexBufferSize = sizeof(Mesh::Vertex) * mesh.GetVertexCount(); - VulkanBuffer stagingBuffer1{ context }; - stagingBuffer1.Create(vertexBufferSize, VkBufferUsageFlagBits::VK_BUFFER_USAGE_TRANSFER_SRC_BIT, - VkSharingMode::VK_SHARING_MODE_EXCLUSIVE, - VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); - stagingBuffer1.SetData(mesh.GetVertex().data()); - + const size_t vertexBufferSize = sizeof(Mesh::Vertex) * mesh.GetVertexCount(); vertexBuffer.Create(vertexBufferSize, VkBufferUsageFlagBits::VK_BUFFER_USAGE_TRANSFER_DST_BIT | VkBufferUsageFlagBits::VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VkSharingMode::VK_SHARING_MODE_EXCLUSIVE, VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + vertexBuffer.SetData(mesh.GetVertex().data()); // 인덱스 버퍼 - size_t indicesSize = sizeof(uint32_t) * mesh.GetIndices().size(); - VulkanBuffer stagingBuffer2{ context }; - stagingBuffer2.Create(indicesSize, VkBufferUsageFlagBits::VK_BUFFER_USAGE_TRANSFER_SRC_BIT, - VkSharingMode::VK_SHARING_MODE_EXCLUSIVE, - VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); - stagingBuffer2.SetData(mesh.GetIndices().data()); - + const size_t indicesSize = sizeof(uint32_t) * mesh.GetIndices().size(); indexBuffer.Create(indicesSize, VkBufferUsageFlagBits::VK_BUFFER_USAGE_TRANSFER_DST_BIT | VkBufferUsageFlagBits::VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VkSharingMode::VK_SHARING_MODE_EXCLUSIVE, VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); - - cmd->Build( - [&]() - { - VkBufferCopy cpy{}; - cpy.srcOffset = 0; - cpy.dstOffset = 0; - cpy.size = vertexBufferSize; - vkCmdCopyBuffer(cmd->GetCommandBuffer(), stagingBuffer1.GetBuffer(), vertexBuffer.GetBuffer(), 1, &cpy); - - VkBufferCopy cpyIndices{}; - cpyIndices.srcOffset = 0; - cpyIndices.dstOffset = 0; - cpyIndices.size = indicesSize; - vkCmdCopyBuffer(cmd->GetCommandBuffer(), stagingBuffer2.GetBuffer(), indexBuffer.GetBuffer(), 1, &cpyIndices); - }, - true - ); - - VkFence fence = cmd->GetOrCreateFence(); - context.GetQueueManager().Submit(VulkanQueueManager::Role::Transfer, *cmd, fence); - vkWaitForFences(context.GetDevice(), 1, &fence, true, std::numeric_limits::max()); - vkResetFences(context.GetDevice(), 1, &fence); - - context.GetCommandBufferPool().DeallocateCommandBuffer(*cmd); + indexBuffer.SetData(mesh.GetIndices().data()); } -} \ No newline at end of file +}//namespace \ No newline at end of file From 76055577806917a8faf1c24e1a38cc781e2b2515 Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Tue, 28 Apr 2026 02:34:26 +0900 Subject: [PATCH 11/60] refact: Change IUniformBuffer name to IShaderBinding --- include/Render/BufferFactory.h | 4 +- .../{IUniformBuffer.h => IShaderBinding.h} | 8 ++-- include/Render/MaterialData.h | 20 ++++----- .../Render/VulkanImpl/VulkanDescriptorSet.h | 4 +- src/Game/Component/Render/MeshRenderer.cpp | 4 +- .../Component/Render/SkinnedMeshRenderer.cpp | 2 +- src/Render/BufferFactory.cpp | 2 +- src/Render/Material.cpp | 2 +- src/Render/MaterialData.cpp | 42 +++++++++---------- src/Render/VulkanImpl/VulkanRenderImpl.cpp | 6 +-- 10 files changed, 47 insertions(+), 47 deletions(-) rename include/Render/{IUniformBuffer.h => IShaderBinding.h} (81%) diff --git a/include/Render/BufferFactory.h b/include/Render/BufferFactory.h index 35c89857..bdfc397b 100644 --- a/include/Render/BufferFactory.h +++ b/include/Render/BufferFactory.h @@ -8,7 +8,7 @@ namespace sh::render { class IBuffer; - class IUniformBuffer; + class IShaderBinding; class IRenderContext; namespace vk { @@ -26,7 +26,7 @@ namespace sh::render }; public: SH_RENDER_API static auto Create(const IRenderContext& context, const CreateInfo& info) -> std::unique_ptr; - SH_RENDER_API static auto CreateUniformBuffer(const IRenderContext& context, const ShaderPass& shader, UniformStructLayout::Usage usage) -> std::unique_ptr; + SH_RENDER_API static auto CreateShaderBinding(const IRenderContext& context, const ShaderPass& shader, UniformStructLayout::Usage usage) -> std::unique_ptr; private: static auto CreateVkUniformBuffer(const vk::VulkanContext& context, const CreateInfo& info) -> std::unique_ptr; }; diff --git a/include/Render/IUniformBuffer.h b/include/Render/IShaderBinding.h similarity index 81% rename from include/Render/IUniformBuffer.h rename to include/Render/IShaderBinding.h index 81b5c266..fdf08d35 100644 --- a/include/Render/IUniformBuffer.h +++ b/include/Render/IShaderBinding.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "Export.h" #include "UniformStructLayout.h" @@ -12,14 +12,14 @@ namespace sh::render class Texture; class IBuffer; - class IUniformBuffer : public core::INonCopyable + class IShaderBinding : public core::INonCopyable { public: - SH_RENDER_API virtual ~IUniformBuffer() = default; + SH_RENDER_API virtual ~IShaderBinding() = default; SH_RENDER_API virtual void Create(const IRenderContext& context, const ShaderPass& shader, UniformStructLayout::Usage usage) = 0; SH_RENDER_API virtual void Clear() = 0; SH_RENDER_API virtual void Link(uint32_t binding, const IBuffer& buffer, std::size_t bufferSize = 0) = 0; SH_RENDER_API virtual void Link(uint32_t binding, const Texture& texture) = 0; }; -}//namespace \ No newline at end of file +}//namespace diff --git a/include/Render/MaterialData.h b/include/Render/MaterialData.h index 71dfa8b0..32e93380 100644 --- a/include/Render/MaterialData.h +++ b/include/Render/MaterialData.h @@ -1,7 +1,7 @@ #pragma once #include "Export.h" #include "IBuffer.h" -#include "IUniformBuffer.h" +#include "IShaderBinding.h" #include "ShaderEnum.h" #include "Core/ISyncable.h" @@ -30,7 +30,7 @@ namespace sh::render { const ShaderPass* pass; std::map>> buffers; //set, binding - std::map> uniformBuffer; // set + std::map> shaderBindings; // set }; struct SyncData { @@ -41,14 +41,14 @@ namespace sh::render uint32_t binding; std::vector data; }; - struct UniformBufferSyncData + struct ShaderBindingSyncData { const ShaderPass* pass; uint32_t set; uint32_t binding; const Texture* tex = nullptr; }; - std::variant data; + std::variant data; }; public: SH_RENDER_API MaterialData() = default; @@ -56,17 +56,17 @@ namespace sh::render SH_RENDER_API void Create(const IRenderContext& context, const Shader& shader, bool bPerObject = false); SH_RENDER_API void Clear(); - /// @brief 유니폼에 데이터를 지정한다. + /// @brief 버퍼에 데이터를 지정한다. /// @param pass 패스 번호 /// @param binding 바인딩 번호 /// @param data 데이터 위치 포인터 /// @param dataSize 데이터 사이즈 - SH_RENDER_API void SetUniformData(const ShaderPass& shaderPass, UniformStructLayout::Usage usage, uint32_t binding, const void* data, std::size_t dataSize); - /// @brief 유니폼에 데이터를 지정한다. + SH_RENDER_API void SetBindingData(const ShaderPass& shaderPass, UniformStructLayout::Usage usage, uint32_t binding, const void* data, std::size_t dataSize); + /// @brief 버퍼에 데이터를 지정한다. /// @param pass 패스 번호 /// @param binding 바인딩 번호 /// @param data 데이터 - SH_RENDER_API void SetUniformData(const ShaderPass& shaderPass, UniformStructLayout::Usage usage, uint32_t binding, std::vector data); + SH_RENDER_API void SetBindingData(const ShaderPass& shaderPass, UniformStructLayout::Usage usage, uint32_t binding, std::vector data); /// @brief 유니폼 텍스쳐 데이터를 지정한다. /// /// @param pass 패스 번호 /// @param binding 텍스쳐 바인딩 번호 @@ -74,7 +74,7 @@ namespace sh::render SH_RENDER_API void SetTextureData(const ShaderPass& shaderPass, UniformStructLayout::Usage usage, uint32_t binding, const Texture& tex); SH_RENDER_API auto GetShaderBuffer(const ShaderPass& shaderPass, UniformStructLayout::Usage usage, uint32_t binding) const -> IBuffer*; - SH_RENDER_API auto GetUniformBuffer(const ShaderPass& shaderPass, UniformStructLayout::Usage usage) const -> IUniformBuffer*; + SH_RENDER_API auto GetShaderBinding(const ShaderPass& shaderPass, UniformStructLayout::Usage usage) const -> IShaderBinding*; SH_RENDER_API void SyncDirty() override; protected: @@ -83,7 +83,7 @@ namespace sh::render void CreateBuffers(const IRenderContext& context, const Shader& shader, bool bPerObject); auto GetMaterialPassData(const ShaderPass& shaderPass) const -> const MaterialData::PassData*; void SetUniformDataAtSync(const SyncData::BufferSyncData& bufferSyncData); - void SetTextureDataAtSync(const SyncData::UniformBufferSyncData& uniformBufferSyncData); + void SetTextureDataAtSync(const SyncData::ShaderBindingSyncData& uniformBufferSyncData); private: const IRenderContext* context = nullptr; const Shader* shader = nullptr; diff --git a/include/Render/VulkanImpl/VulkanDescriptorSet.h b/include/Render/VulkanImpl/VulkanDescriptorSet.h index 916dd3ed..3a3c8db3 100644 --- a/include/Render/VulkanImpl/VulkanDescriptorSet.h +++ b/include/Render/VulkanImpl/VulkanDescriptorSet.h @@ -1,7 +1,7 @@ #pragma once #include "../Export.h" #include "VulkanConfig.h" -#include "../IUniformBuffer.h" +#include "../IShaderBinding.h" #include namespace sh::render::vk @@ -9,7 +9,7 @@ namespace sh::render::vk class VulkanContext; /// @brief 디스크립터 셋을 추상화한 클래스 - class VulkanDescriptorSet : public IUniformBuffer + class VulkanDescriptorSet : public IShaderBinding { public: SH_RENDER_API VulkanDescriptorSet(); diff --git a/src/Game/Component/Render/MeshRenderer.cpp b/src/Game/Component/Render/MeshRenderer.cpp index 0b01604c..972ab39e 100644 --- a/src/Game/Component/Render/MeshRenderer.cpp +++ b/src/Game/Component/Render/MeshRenderer.cpp @@ -291,7 +291,7 @@ namespace sh::game } } if (!isSampler) - drawable->GetMaterialData().SetUniformData(*passPtr, layoutPtr->usage, layoutPtr->binding, std::move(data)); + drawable->GetMaterialData().SetBindingData(*passPtr, layoutPtr->usage, layoutPtr->binding, std::move(data)); } } } @@ -462,7 +462,7 @@ namespace sh::game if (pass.IsPendingKill() || pass.GetLightingBinding() == -1) continue; - drawable.GetMaterialData().SetUniformData(pass, render::UniformStructLayout::Usage::Object, pass.GetLightingBinding(), &lightStruct, sizeof(Light)); + drawable.GetMaterialData().SetBindingData(pass, render::UniformStructLayout::Usage::Object, pass.GetLightingBinding(), &lightStruct, sizeof(Light)); } } } diff --git a/src/Game/Component/Render/SkinnedMeshRenderer.cpp b/src/Game/Component/Render/SkinnedMeshRenderer.cpp index 22685223..e9052068 100644 --- a/src/Game/Component/Render/SkinnedMeshRenderer.cpp +++ b/src/Game/Component/Render/SkinnedMeshRenderer.cpp @@ -120,7 +120,7 @@ namespace sh::game { if (pass.IsPendingKill()) continue; - drawable->GetMaterialData().SetUniformData( + drawable->GetMaterialData().SetBindingData( pass, render::UniformStructLayout::Usage::Object, pass.GetSkinBinding(), diff --git a/src/Render/BufferFactory.cpp b/src/Render/BufferFactory.cpp index 7ec9ebfd..fc925e42 100644 --- a/src/Render/BufferFactory.cpp +++ b/src/Render/BufferFactory.cpp @@ -42,7 +42,7 @@ namespace sh::render return nullptr; } - auto BufferFactory::CreateUniformBuffer(const IRenderContext& context, const ShaderPass& shader, UniformStructLayout::Usage usage) -> std::unique_ptr + auto BufferFactory::CreateShaderBinding(const IRenderContext& context, const ShaderPass& shader, UniformStructLayout::Usage usage) -> std::unique_ptr { if (context.GetRenderAPIType() == RenderAPI::Vulkan) { diff --git a/src/Render/Material.cpp b/src/Render/Material.cpp index a723dfa3..be33f70e 100644 --- a/src/Render/Material.cpp +++ b/src/Render/Material.cpp @@ -235,7 +235,7 @@ namespace sh::render } } if (!isSampler) - materialData->SetUniformData(*pass, uniformLayout->usage, uniformLayout->binding, std::move(data)); + materialData->SetBindingData(*pass, uniformLayout->usage, uniformLayout->binding, std::move(data)); } dirtyProps.clear(); bPropertyDirty = false; diff --git a/src/Render/MaterialData.cpp b/src/Render/MaterialData.cpp index fc7d916a..bb52215a 100644 --- a/src/Render/MaterialData.cpp +++ b/src/Render/MaterialData.cpp @@ -67,15 +67,15 @@ namespace sh::render } else // 텍스쳐 { - auto& uniformBufferData = std::get<1>(syncData.data); - SetTextureDataAtSync(uniformBufferData); + auto& shaderBindingsData = std::get<1>(syncData.data); + SetTextureDataAtSync(shaderBindingsData); } } syncDatas.clear(); bDirty = false; } - SH_RENDER_API void MaterialData::SetUniformData(const ShaderPass& shaderPass, UniformStructLayout::Usage usage, uint32_t binding, const void* data, std::size_t dataSize) + SH_RENDER_API void MaterialData::SetBindingData(const ShaderPass& shaderPass, UniformStructLayout::Usage usage, uint32_t binding, const void* data, std::size_t dataSize) { SyncData::BufferSyncData syncData{}; syncData.pass = &shaderPass; @@ -87,7 +87,7 @@ namespace sh::render syncDatas.push_back(SyncData{ std::move(syncData) }); SyncDirty(); } - SH_RENDER_API void MaterialData::SetUniformData(const ShaderPass& shaderPass, UniformStructLayout::Usage usage, uint32_t binding, std::vector data) + SH_RENDER_API void MaterialData::SetBindingData(const ShaderPass& shaderPass, UniformStructLayout::Usage usage, uint32_t binding, std::vector data) { SyncData::BufferSyncData syncData{}; syncData.pass = &shaderPass; @@ -100,7 +100,7 @@ namespace sh::render } SH_RENDER_API void MaterialData::SetTextureData(const ShaderPass& shaderPass, UniformStructLayout::Usage usage, uint32_t binding, const Texture& tex) { - SyncData::UniformBufferSyncData syncData{}; + SyncData::ShaderBindingSyncData syncData{}; syncData.pass = &shaderPass; syncData.set = static_cast(usage); syncData.binding = binding; @@ -127,15 +127,15 @@ namespace sh::render return buffers[binding] ? buffers[binding].get() : nullptr; } - SH_RENDER_API auto MaterialData::GetUniformBuffer(const ShaderPass& shaderPass, UniformStructLayout::Usage usage) const -> IUniformBuffer* + SH_RENDER_API auto MaterialData::GetShaderBinding(const ShaderPass& shaderPass, UniformStructLayout::Usage usage) const -> IShaderBinding* { const PassData* passData = GetMaterialPassData(shaderPass); if (passData == nullptr) return nullptr; uint32_t set = static_cast(usage); - auto it = passData->uniformBuffer.find(set); - if (it == passData->uniformBuffer.end()) + auto it = passData->shaderBindings.find(set); + if (it == passData->shaderBindings.end()) return nullptr; return it->second.get(); @@ -208,7 +208,7 @@ namespace sh::render // 유니폼 버퍼 (GPU로 데이터 전송 역할) for (uint32_t set : sets) { - passData.uniformBuffer[set] = BufferFactory::CreateUniformBuffer(context, shaderPass, static_cast(set)); + passData.shaderBindings[set] = BufferFactory::CreateShaderBinding(context, shaderPass, static_cast(set)); auto it = passData.buffers.find(set); if (it == passData.buffers.end()) continue; @@ -219,7 +219,7 @@ namespace sh::render { if (bufferVec[binding] == nullptr) continue; - passData.uniformBuffer[set]->Link(binding, *bufferVec[binding].get()); + passData.shaderBindings[set]->Link(binding, *bufferVec[binding].get()); } else { @@ -227,7 +227,7 @@ namespace sh::render // 카메라 데이터 if (context.GetRenderAPIType() == RenderAPI::Vulkan) { - passData.uniformBuffer[set]->Link(binding, vk::VulkanCameraBuffers::GetInstance()->GetCameraBuffer(), 128); + passData.shaderBindings[set]->Link(binding, vk::VulkanCameraBuffers::GetInstance()->GetCameraBuffer(), 128); } } } @@ -273,7 +273,7 @@ namespace sh::render if (static_cast(layout.usage) != set || layout.binding != binding || layout.GetKind() != UniformStructLayout::Kind::Storage) continue; bufferVec[binding]->Resize(bufferSyncData.data.size()); - passData->uniformBuffer.at(set)->Link(binding, *bufferVec[binding].get()); + passData->shaderBindings.at(set)->Link(binding, *bufferVec[binding].get()); bufferVec[binding]->SetData(bufferSyncData.data.data()); return; } @@ -284,18 +284,18 @@ namespace sh::render bufferVec[binding]->SetData(bufferSyncData.data.data()); } - void MaterialData::SetTextureDataAtSync(const SyncData::UniformBufferSyncData& uniformBufferSyncData) + void MaterialData::SetTextureDataAtSync(const SyncData::ShaderBindingSyncData& shaderBindingsSyncData) { - const ShaderPass* shaderPass = uniformBufferSyncData.pass; - if (!core::IsValid(shaderPass) || !core::IsValid(uniformBufferSyncData.tex)) + const ShaderPass* shaderPass = shaderBindingsSyncData.pass; + if (!core::IsValid(shaderPass) || !core::IsValid(shaderBindingsSyncData.tex)) return; const PassData* passData = GetMaterialPassData(*shaderPass); if (passData == nullptr) return; - const uint32_t set = uniformBufferSyncData.set; - const uint32_t binding = uniformBufferSyncData.binding; + const uint32_t set = shaderBindingsSyncData.set; + const uint32_t binding = shaderBindingsSyncData.binding; bool bFind = false; for (auto& layout : shaderPass->GetSamplerUniforms()) @@ -309,11 +309,11 @@ namespace sh::render if (!bFind) return; - auto itUb = passData->uniformBuffer.find(set); - if (itUb == passData->uniformBuffer.end() || itUb->second == nullptr) + auto itUb = passData->shaderBindings.find(set); + if (itUb == passData->shaderBindings.end() || itUb->second == nullptr) return; - IUniformBuffer& uniformBuffer = *itUb->second.get(); - uniformBuffer.Link(binding, *uniformBufferSyncData.tex); + IShaderBinding& shaderBindings = *itUb->second.get(); + shaderBindings.Link(binding, *shaderBindingsSyncData.tex); } }//namespace \ No newline at end of file diff --git a/src/Render/VulkanImpl/VulkanRenderImpl.cpp b/src/Render/VulkanImpl/VulkanRenderImpl.cpp index df9043d1..5758a860 100644 --- a/src/Render/VulkanImpl/VulkanRenderImpl.cpp +++ b/src/Render/VulkanImpl/VulkanRenderImpl.cpp @@ -212,7 +212,7 @@ namespace sh::render::vk { // 카메라 데이터는 다이나믹 디스크립터셋 VulkanDescriptorSet* const cameraUBO = static_cast( - mat.GetMaterialData().GetUniformBuffer(pass, UniformStructLayout::Usage::Camera)); + mat.GetMaterialData().GetShaderBinding(pass, UniformStructLayout::Usage::Camera)); VkDescriptorSet cameraSet = cameraUBO ? cameraUBO->GetVkDescriptorSet() : ctx.GetEmptyDescriptorSet(); uint32_t dynamicCount = cameraUBO ? 1 : 0; @@ -225,7 +225,7 @@ namespace sh::render::vk void VulkanRenderImpl::BindMaterialSet(VulkanCommandBuffer& cmd, VkPipelineLayout layout, const ShaderPass& pass, const Material& mat) const { VulkanDescriptorSet* const materialUniformBuffer = static_cast( - mat.GetMaterialData().GetUniformBuffer(pass, UniformStructLayout::Usage::Material)); + mat.GetMaterialData().GetShaderBinding(pass, UniformStructLayout::Usage::Material)); VkDescriptorSet materialDescriptorSet = materialUniformBuffer ? materialUniformBuffer->GetVkDescriptorSet() : ctx.GetEmptyDescriptorSet(); @@ -237,7 +237,7 @@ namespace sh::render::vk void VulkanRenderImpl::BindObjectSet(VulkanCommandBuffer& cmd, VkPipelineLayout layout, const ShaderPass& pass, Drawable& drawable) const { VulkanDescriptorSet* const objectUniformBuffer = static_cast( - drawable.GetMaterialData().GetUniformBuffer(pass, UniformStructLayout::Usage::Object)); + drawable.GetMaterialData().GetShaderBinding(pass, UniformStructLayout::Usage::Object)); VkDescriptorSet objectDescriptorSet = objectUniformBuffer ? objectUniformBuffer->GetVkDescriptorSet() : ctx.GetEmptyDescriptorSet(); From 842b8470a55d843f2003dcd7b9358dc05a208d5c Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Tue, 28 Apr 2026 06:01:32 +0900 Subject: [PATCH 12/60] feat: Implement compute shader dispatch --- include/Game/Asset/ComputeShaderLoader.h | 8 +- include/Render/BufferFactory.h | 2 + include/Render/CommandBuffer.h | 5 +- include/Render/ComputeShader.h | 21 +- include/Render/IRenderContext.h | 3 +- include/Render/IShaderBinding.h | 4 +- include/Render/ScriptableRenderer.h | 8 +- include/Render/ShaderPass.h | 1 - .../Render/VulkanImpl/VulkanCommandBuffer.h | 1 + .../VulkanImpl/VulkanCommandBufferPool.h | 16 +- .../Render/VulkanImpl/VulkanComputePipeline.h | 67 +++++ .../VulkanImpl/VulkanComputePipelineManager.h | 39 +++ include/Render/VulkanImpl/VulkanContext.h | 6 +- .../Render/VulkanImpl/VulkanDescriptorSet.h | 1 + .../Render/VulkanImpl/VulkanQueueManager.h | 12 +- src/Editor/AssetDatabase.cpp | 2 +- src/EngineInit.cpp | 3 + src/Game/Asset/ComputeShaderLoader.cpp | 7 +- src/Render/BufferFactory.cpp | 14 +- src/Render/ComputeShader.cpp | 68 ++++- src/Render/ScriptableRenderer.cpp | 27 +- src/Render/VulkanImpl/VulkanCommandBuffer.cpp | 23 +- .../VulkanImpl/VulkanCommandBufferPool.cpp | 12 +- .../VulkanImpl/VulkanComputePipeline.cpp | 247 ++++++++++++++++++ .../VulkanComputePipelineManager.cpp | 40 +++ src/Render/VulkanImpl/VulkanContext.cpp | 20 +- src/Render/VulkanImpl/VulkanDescriptorSet.cpp | 22 ++ src/Render/VulkanImpl/VulkanQueueManager.cpp | 171 +++++++----- 28 files changed, 735 insertions(+), 115 deletions(-) create mode 100644 include/Render/VulkanImpl/VulkanComputePipeline.h create mode 100644 include/Render/VulkanImpl/VulkanComputePipelineManager.h create mode 100644 src/Render/VulkanImpl/VulkanComputePipeline.cpp create mode 100644 src/Render/VulkanImpl/VulkanComputePipelineManager.cpp diff --git a/include/Game/Asset/ComputeShaderLoader.h b/include/Game/Asset/ComputeShaderLoader.h index dfc07323..a076a4c7 100644 --- a/include/Game/Asset/ComputeShaderLoader.h +++ b/include/Game/Asset/ComputeShaderLoader.h @@ -8,12 +8,17 @@ #include +namespace sh::render +{ + class IRenderContext; +} namespace sh::game { + class ComputeShaderLoader : public core::IAssetLoader { public: - SH_GAME_API ComputeShaderLoader(); + SH_GAME_API ComputeShaderLoader(const render::IRenderContext& ctx); SH_GAME_API ~ComputeShaderLoader(); SH_GAME_API void SetCachePath(const std::filesystem::path& path) { cachePath = path; } @@ -25,6 +30,7 @@ namespace sh::game SH_GAME_API auto GetAssetName() const -> const char* override { return ASSET_NAME; } private: static constexpr const char* ASSET_NAME = "comp"; + const render::IRenderContext& ctx; std::filesystem::path cachePath; }; diff --git a/include/Render/BufferFactory.h b/include/Render/BufferFactory.h index bdfc397b..5fe21695 100644 --- a/include/Render/BufferFactory.h +++ b/include/Render/BufferFactory.h @@ -10,6 +10,7 @@ namespace sh::render class IBuffer; class IShaderBinding; class IRenderContext; + class ComputeShader; namespace vk { class VulkanContext; @@ -27,6 +28,7 @@ namespace sh::render public: SH_RENDER_API static auto Create(const IRenderContext& context, const CreateInfo& info) -> std::unique_ptr; SH_RENDER_API static auto CreateShaderBinding(const IRenderContext& context, const ShaderPass& shader, UniformStructLayout::Usage usage) -> std::unique_ptr; + SH_RENDER_API static auto CreateShaderBinding(const IRenderContext& context, const ComputeShader& shader) -> std::unique_ptr; private: static auto CreateVkUniformBuffer(const vk::VulkanContext& context, const CreateInfo& info) -> std::unique_ptr; }; diff --git a/include/Render/CommandBuffer.h b/include/Render/CommandBuffer.h index 039e12cc..4fed432b 100644 --- a/include/Render/CommandBuffer.h +++ b/include/Render/CommandBuffer.h @@ -5,6 +5,7 @@ #include namespace sh::render { + class ComputeShader; class RenderTexture; class IBuffer; @@ -14,7 +15,9 @@ namespace sh::render virtual ~CommandBuffer() = default; virtual void Begin(bool bOnetime) = 0; - virtual void Blit(RenderTexture& src, int x, int y, IBuffer& dst) = 0; virtual void End() = 0; + + virtual void Blit(RenderTexture& src, int x, int y, IBuffer& dst) = 0; + virtual void Dispatch(const ComputeShader& shader, uint32_t x, uint32_t y, uint32_t z) = 0; }; }//namespace \ No newline at end of file diff --git a/include/Render/ComputeShader.h b/include/Render/ComputeShader.h index 49993297..727a9cad 100644 --- a/include/Render/ComputeShader.h +++ b/include/Render/ComputeShader.h @@ -1,6 +1,8 @@ #pragma once #include "Export.h" #include "ShaderAST.h" +#include "IBuffer.h" +#include "IShaderBinding.h" #include "Core/SObject.h" #include "Core/Name.h" @@ -8,15 +10,17 @@ #include #include +#include namespace sh::render { struct ComputeShaderCreateInfo; + class IRenderContext; class ComputeShader : public core::SObject { SCLASS(ComputeShader) public: - SH_RENDER_API ComputeShader(ComputeShaderCreateInfo createInfo); + SH_RENDER_API ComputeShader(const IRenderContext& ctx, ComputeShaderCreateInfo createInfo); SH_RENDER_API ~ComputeShader(); /// @brief AST와 SPIR-V 바이너리를 직렬화 한다. @@ -26,16 +30,27 @@ namespace sh::render /// @param json 직렬화 된 json SH_RENDER_API void Deserialize(const core::Json& json) override; + SH_RENDER_API void SetFloats(const std::string& name, float* values, std::size_t count); + SH_RENDER_API auto GetSpirv() const -> const std::vector& { return spirv; } SH_RENDER_API auto GetShaderAST() const -> const ShaderAST::ComputeShaderNode& { return shaderNode; } SH_RENDER_API auto GetNumthreadsX() const -> uint32_t { return shaderNode.numthreadsX; } SH_RENDER_API auto GetNumthreadsY() const -> uint32_t { return shaderNode.numthreadsY; } SH_RENDER_API auto GetNumthreadsZ() const -> uint32_t { return shaderNode.numthreadsZ; } - - SH_RENDER_API auto GetBuffers() const -> const std::vector& { return shaderNode.buffers; } + SH_RENDER_API auto GetNode() const -> const ShaderAST::ComputeShaderNode& { return shaderNode; } + SH_RENDER_API auto GetBuffers() const -> const std::vector>& { return buffers; } + SH_RENDER_API auto GetShaderBinding() const -> IShaderBinding* { return shaderBinding.get(); } + SH_RENDER_API auto GetBuffer(const std::string& name) -> IBuffer*; + private: + auto GetBinding(const std::string& name) -> std::optional; private: + const IRenderContext& ctx; + ShaderAST::ComputeShaderNode shaderNode; std::vector spirv; + + std::vector> buffers; // binding, buffer + std::unique_ptr shaderBinding; }; }//namespace diff --git a/include/Render/IRenderContext.h b/include/Render/IRenderContext.h index ae294e41..0bd57a18 100644 --- a/include/Render/IRenderContext.h +++ b/include/Render/IRenderContext.h @@ -23,8 +23,9 @@ namespace sh::render virtual void Clear() = 0; virtual auto GetRenderAPIType() const -> RenderAPI = 0; - virtual auto AllocateCommandBuffer() -> CommandBuffer* = 0; + virtual auto AllocateCommandBuffer(bool bCompute) -> CommandBuffer* = 0; virtual void DeallocateCommandBuffer(CommandBuffer& cmd) = 0; + virtual void SubmitCommand(CommandBuffer& cmd) = 0; virtual void SetViewport(const glm::vec2& start, const glm::vec2& end) = 0; virtual auto GetViewportStart() const -> const glm::vec2& = 0; diff --git a/include/Render/IShaderBinding.h b/include/Render/IShaderBinding.h index fdf08d35..46f17cd8 100644 --- a/include/Render/IShaderBinding.h +++ b/include/Render/IShaderBinding.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "Export.h" #include "UniformStructLayout.h" @@ -9,6 +9,7 @@ namespace sh::render { class IRenderContext; class ShaderPass; + class ComputeShader; class Texture; class IBuffer; @@ -18,6 +19,7 @@ namespace sh::render SH_RENDER_API virtual ~IShaderBinding() = default; SH_RENDER_API virtual void Create(const IRenderContext& context, const ShaderPass& shader, UniformStructLayout::Usage usage) = 0; + SH_RENDER_API virtual void Create(const IRenderContext& context, const ComputeShader& shader) = 0; SH_RENDER_API virtual void Clear() = 0; SH_RENDER_API virtual void Link(uint32_t binding, const IBuffer& buffer, std::size_t bufferSize = 0) = 0; SH_RENDER_API virtual void Link(uint32_t binding, const Texture& texture) = 0; diff --git a/include/Render/ScriptableRenderer.h b/include/Render/ScriptableRenderer.h index dd1eacc1..30e77371 100644 --- a/include/Render/ScriptableRenderer.h +++ b/include/Render/ScriptableRenderer.h @@ -5,7 +5,6 @@ #include "ScriptableRenderPass.h" #include "IRenderThrMethod.h" #include "IBuffer.h" -#include "RenderPass/CopyPass.h" #include "Core/Util.h" #include "Core/ISyncable.h" @@ -16,6 +15,9 @@ #include namespace sh::render { + class CopyPass; + class ComputeShader; + template<> struct IRenderThrMethod { @@ -40,11 +42,12 @@ namespace sh::render }; public: SH_RENDER_API ScriptableRenderer(IRenderContext& ctx); - SH_RENDER_API virtual ~ScriptableRenderer() = default; + SH_RENDER_API virtual ~ScriptableRenderer(); SH_RENDER_API auto AddRenderPass(const core::Name& passName, RenderQueue renderQueue) -> ScriptableRenderPass&; SH_RENDER_API auto ReadRenderTextureAsync(RenderTexture& rt, int x, int y) -> std::future>; + SH_RENDER_API void Dispatch(const ComputeShader& computeShader, uint32_t x, uint32_t y, uint32_t z); SH_RENDER_API auto GetSubmittedCommands() const -> const std::vector& { return submittedCmds; } @@ -78,7 +81,6 @@ namespace sh::render std::vector submittedCmds; std::unique_ptr cpyPass; - struct SyncData { RenderTexture* src; diff --git a/include/Render/ShaderPass.h b/include/Render/ShaderPass.h index d2487526..f9f0af39 100644 --- a/include/Render/ShaderPass.h +++ b/include/Render/ShaderPass.h @@ -89,7 +89,6 @@ namespace sh::render SH_RENDER_API void Deserialize(const core::Json& json) override; protected: ShaderPass(const ShaderAST::PassNode& passNode, ShaderType type); - ShaderPass(const ShaderPass& other); ShaderPass(ShaderPass&& other) noexcept; private: void AddUniformLayout(ShaderStage stage, const UniformStructLayout& layout); diff --git a/include/Render/VulkanImpl/VulkanCommandBuffer.h b/include/Render/VulkanImpl/VulkanCommandBuffer.h index e4aedd7b..eb3363f1 100644 --- a/include/Render/VulkanImpl/VulkanCommandBuffer.h +++ b/include/Render/VulkanImpl/VulkanCommandBuffer.h @@ -44,6 +44,7 @@ namespace sh::render::vk SH_RENDER_API void End() override; SH_RENDER_API void Blit(RenderTexture& src, int x, int y, IBuffer& dst) override; + SH_RENDER_API void Dispatch(const ComputeShader& shader, uint32_t x, uint32_t y, uint32_t z) override; SH_RENDER_API auto ResetCommand(VkCommandBufferResetFlags flags = 0) -> VkResult; diff --git a/include/Render/VulkanImpl/VulkanCommandBufferPool.h b/include/Render/VulkanImpl/VulkanCommandBufferPool.h index 4e49de68..f101b055 100644 --- a/include/Render/VulkanImpl/VulkanCommandBufferPool.h +++ b/include/Render/VulkanImpl/VulkanCommandBufferPool.h @@ -17,7 +17,7 @@ namespace sh::render::vk class VulkanCommandBufferPool { public: - SH_RENDER_API VulkanCommandBufferPool(const VulkanContext& context, uint32_t graphicsQueueFamily, uint32_t transferQueueFamily); + SH_RENDER_API VulkanCommandBufferPool(const VulkanContext& context, uint32_t graphicsQueueFamily, uint32_t transferQueueFamily, uint32_t computeQueueFamily); SH_RENDER_API ~VulkanCommandBufferPool(); /// @brief 해당 스레드에서 큐 타입에 맞는 커맨드 버퍼를 할당 받는다. @@ -34,22 +34,16 @@ namespace sh::render::vk const VulkanContext& context; const uint32_t graphicsQueueFamily; const uint32_t transferQueueFamily; - + const uint32_t computeQueueFamily; enum QueueType { Graphics = 0, - Transfer = 1 + Transfer = 1, + Compute = 2 }; struct PerThreadData { - PerThreadData() = default; - PerThreadData(PerThreadData&& other) noexcept : - id(other.id) - { - for (int i = 0; i < commands.size(); ++i) - commands[i] = std::move(other.commands[i]); - } struct Command { Command() = default; @@ -68,7 +62,7 @@ namespace sh::render::vk }; std::thread::id id; - std::array commands; + std::array commands; }; struct AllocData diff --git a/include/Render/VulkanImpl/VulkanComputePipeline.h b/include/Render/VulkanImpl/VulkanComputePipeline.h new file mode 100644 index 00000000..c49a3c65 --- /dev/null +++ b/include/Render/VulkanImpl/VulkanComputePipeline.h @@ -0,0 +1,67 @@ +#pragma once +#include "../Export.h" +#include "../ShaderAST.h" +#include "VulkanConfig.h" + +#include "Core/NonCopyable.h" + +#include +#include +#include +#include +namespace sh::render +{ + class ComputeShader; +} +namespace sh::render::vk +{ + class VulkanContext; + + /// @brief 컴퓨트 셰이더 파이프라인. + /// VkShaderModule, 디스크립터셋 레이아웃, 파이프라인 레이아웃, VkPipeline을 모두 보관한다. + class VulkanComputePipeline : public core::INonCopyable + { + public: + struct SetLayout + { + uint32_t set = 0; + VkDescriptorSetLayout descriptorSetLayout = VK_NULL_HANDLE; + std::map descriptorSetLayoutBindings; + }; + public: + SH_RENDER_API VulkanComputePipeline(const VulkanContext& context, const ComputeShader& shader); + SH_RENDER_API VulkanComputePipeline(VulkanComputePipeline&& other) noexcept; + SH_RENDER_API ~VulkanComputePipeline(); + + /// @brief 셰이더 모듈, 디스크립터/파이프라인 레이아웃, VkPipeline까지 한번에 생성한다. + SH_RENDER_API auto Build() -> VkResult; + SH_RENDER_API void Clean(); + + SH_RENDER_API auto GetPipeline() const -> VkPipeline { return pipeline; } + SH_RENDER_API auto GetPipelineLayout() const -> VkPipelineLayout { return pipelineLayout; } + SH_RENDER_API auto GetShaderModule() const -> VkShaderModule { return shaderModule; } + + SH_RENDER_API auto GetSetLayouts() const -> const std::vector& { return setlayouts; } + SH_RENDER_API auto GetDescriptorSetLayout(uint32_t set) const -> VkDescriptorSetLayout; + SH_RENDER_API auto GetDescriptorBinding(uint32_t set, uint32_t binding) const -> std::optional; + + /// @brief Numthreads (local_size) 반환. + SH_RENDER_API auto GetGroupSize() const -> std::array; + private: + auto CreateShaderModule() -> VkResult; + void AddDescriptorBinding(uint32_t set, uint32_t binding, VkDescriptorType type); + auto CreateDescriptorLayout() -> VkResult; + auto CreatePipelineLayout() -> VkResult; + auto CreatePipeline() -> VkResult; + + void CleanDescriptors(); + private: + const VulkanContext& context; + const ComputeShader* shader; + + VkShaderModule shaderModule = VK_NULL_HANDLE; + std::vector setlayouts; + VkPipelineLayout pipelineLayout = VK_NULL_HANDLE; + VkPipeline pipeline = VK_NULL_HANDLE; + }; +}//namespace diff --git a/include/Render/VulkanImpl/VulkanComputePipelineManager.h b/include/Render/VulkanImpl/VulkanComputePipelineManager.h new file mode 100644 index 00000000..d93d257e --- /dev/null +++ b/include/Render/VulkanImpl/VulkanComputePipelineManager.h @@ -0,0 +1,39 @@ +#pragma once +#include "Export.h" + +#include "Core/NonCopyable.h" +#include "Core/Observer.hpp" + +#include +#include +#include +namespace sh::core +{ + class SObject; +} +namespace sh::render +{ + class ComputeShader; +} +namespace sh::render::vk +{ + class VulkanContext; + class VulkanComputePipeline; + class VulkanComputePipelineManager : public core::INonCopyable + { + public: + SH_RENDER_API VulkanComputePipelineManager(const VulkanContext& context); + SH_RENDER_API VulkanComputePipelineManager(VulkanComputePipelineManager&& other) = delete; + + /// @brief 파이프라인을 생성하거나 가져온다. 스레드 안전하다. + /// @param computeShader 컴퓨트 셰이더 + SH_RENDER_API auto GetOrCreatePipeline(const ComputeShader& computeShader) -> VulkanComputePipeline&; + private: + const VulkanContext& ctx; + + std::shared_mutex mu; + + core::Observer::Listener onShaderDestroy; + std::map> pipelines; + }; +}//namespace \ No newline at end of file diff --git a/include/Render/VulkanImpl/VulkanContext.h b/include/Render/VulkanImpl/VulkanContext.h index 01651268..15ce4170 100644 --- a/include/Render/VulkanImpl/VulkanContext.h +++ b/include/Render/VulkanImpl/VulkanContext.h @@ -27,6 +27,7 @@ namespace sh::render::vk class VulkanCommandBuffer; class VulkanDescriptorPool; class VulkanPipelineManager; + class VulkanComputePipelineManager; class VulkanQueueManager; class VulkanRenderImpl; @@ -41,8 +42,9 @@ namespace sh::render::vk SH_RENDER_API auto GetRenderAPIType() const -> RenderAPI override { return RenderAPI::Vulkan; } - SH_RENDER_API auto AllocateCommandBuffer() -> CommandBuffer* override; + SH_RENDER_API auto AllocateCommandBuffer(bool bCompute) -> CommandBuffer* override; SH_RENDER_API void DeallocateCommandBuffer(CommandBuffer& cmd) override; + SH_RENDER_API void SubmitCommand(CommandBuffer& cmd) override; SH_RENDER_API void SetViewport(const glm::vec2& start, const glm::vec2& end) override; SH_RENDER_API auto GetViewportStart() const -> const glm::vec2 & override; @@ -72,6 +74,7 @@ namespace sh::render::vk SH_RENDER_API auto GetDescriptorPool() const -> VulkanDescriptorPool& { return *descPool; } SH_RENDER_API auto GetAllocator() const -> VmaAllocator { return allocator; } SH_RENDER_API auto GetPipelineManager() const -> VulkanPipelineManager& { return *pipelineManager; } + SH_RENDER_API auto GetComputePipelineManager() const -> VulkanComputePipelineManager& { return *computePipelineManager; } SH_RENDER_API auto GetEmptyDescriptorSetLayout() const -> VkDescriptorSetLayout { return emptyDescLayout; } SH_RENDER_API auto GetEmptyDescriptorSet() const -> VkDescriptorSet { return emptyDescSet; } SH_RENDER_API auto GetMaxSampleCount() const -> VkSampleCountFlagBits; @@ -124,6 +127,7 @@ namespace sh::render::vk std::unique_ptr cmdPool; std::unique_ptr descPool; std::unique_ptr pipelineManager; + std::unique_ptr computePipelineManager; std::unique_ptr renderImpl; VkDescriptorSetLayout emptyDescLayout; diff --git a/include/Render/VulkanImpl/VulkanDescriptorSet.h b/include/Render/VulkanImpl/VulkanDescriptorSet.h index 3a3c8db3..465fcf95 100644 --- a/include/Render/VulkanImpl/VulkanDescriptorSet.h +++ b/include/Render/VulkanImpl/VulkanDescriptorSet.h @@ -17,6 +17,7 @@ namespace sh::render::vk SH_RENDER_API ~VulkanDescriptorSet(); SH_RENDER_API void Create(const IRenderContext& context, const ShaderPass& shader, UniformStructLayout::Usage usage) override; + SH_RENDER_API void Create(const IRenderContext& context, const ComputeShader& shader) override; SH_RENDER_API void Clear() override; SH_RENDER_API void Link(uint32_t binding, const IBuffer& buffer, std::size_t bufferSize) override; SH_RENDER_API void Link(uint32_t binding, const Texture& texture) override; diff --git a/include/Render/VulkanImpl/VulkanQueueManager.h b/include/Render/VulkanImpl/VulkanQueueManager.h index c00f14bf..f4120bb5 100644 --- a/include/Render/VulkanImpl/VulkanQueueManager.h +++ b/include/Render/VulkanImpl/VulkanQueueManager.h @@ -15,7 +15,7 @@ namespace sh::render::vk class VulkanQueueManager : public core::INonCopyable { public: - enum class Role { Graphics, Present, Transfer }; + enum class Role { Graphics, Present, Transfer, Compute }; struct Family { @@ -29,11 +29,13 @@ namespace sh::render::vk Family graphics; Family present; Family transfer; + Family compute; // 역할별 queue index(패밀리 내부 인덱스) uint32_t graphicsQIndex = 0; uint32_t presentQIndex = 0; uint32_t transferQIndex = 0; + uint32_t computeQIndex = 0; }; public: @@ -56,12 +58,11 @@ namespace sh::render::vk auto PickGraphicsFamily() const -> std::optional; auto PickPresentFamily(VkSurfaceKHR surface, uint32_t preferFamily) const -> std::optional; auto PickTransferFamily(uint32_t avoidFamily) const -> std::optional; + auto PickComputeFamily() const -> std::optional; void ComputeRequestedCountsAndIndices(); - auto ClampRequested(uint32_t want, uint32_t avail) const ->uint32_t; auto GetMutexForRole(Role role) const -> std::mutex*; - private: const VulkanContext& ctx; @@ -71,20 +72,23 @@ namespace sh::render::vk VkQueue graphicsQueue = VK_NULL_HANDLE; VkQueue presentQueue = VK_NULL_HANDLE; VkQueue transferQueue = VK_NULL_HANDLE; + VkQueue computeQueue = VK_NULL_HANDLE; // 동일 VkQueue를 공유하면 동일 mutex를 공유하도록 포인터로 묶는다 mutable std::mutex graphicsMtx; mutable std::mutex presentMtx; mutable std::mutex transferMtx; + mutable std::mutex computeMtx; mutable std::mutex* graphicsLock = &graphicsMtx; mutable std::mutex* presentLock = &presentMtx; mutable std::mutex* transferLock = &transferMtx; + mutable std::mutex* computeLock = &computeMtx; - // BuildQueueCreateInfos에서 사용하는 우선순위 배열 (가변 길이) std::vector graphicsPriorities; std::vector presentPriorities; std::vector transferPriorities; + std::vector computePriorities; bool bFamiliesReady = false; bool bQueuesReady = false; diff --git a/src/Editor/AssetDatabase.cpp b/src/Editor/AssetDatabase.cpp index f481bc7e..a283d7d5 100644 --- a/src/Editor/AssetDatabase.cpp +++ b/src/Editor/AssetDatabase.cpp @@ -84,7 +84,7 @@ namespace sh::editor std::unique_ptr shaderLoader = std::make_unique(&passBuilder); shaderLoader->SetCachePath(projectPath / "temp"); - std::unique_ptr computeShaderLoader = std::make_unique(); + std::unique_ptr computeShaderLoader = std::make_unique(ctx); computeShaderLoader->SetCachePath(projectPath / "temp"); assetLoaders.Clear(); diff --git a/src/EngineInit.cpp b/src/EngineInit.cpp index 806e3290..7a30c26c 100644 --- a/src/EngineInit.cpp +++ b/src/EngineInit.cpp @@ -45,6 +45,7 @@ #include "Game/Asset/FontLoader.h" #include "Game/Asset/SoundLoader.h" #include "Game/Asset/ScriptableObjectLoader.h" +#include "Game/Asset/ComputeShaderLoader.h" #include "Game/Asset/TextureAsset.h" #include "Game/Asset/MaterialAsset.h" @@ -58,6 +59,7 @@ #include "Game/Asset/FontAsset.h" #include "Game/Asset/SoundAsset.h" #include "Game/Asset/ScriptableObjectAsset.h" +#include "Game/Asset/ComputeShaderAsset.h" #endif namespace sh @@ -236,6 +238,7 @@ namespace sh assetLoaderFactory->RegisterLoader(game::FontAsset::ASSET_NAME, std::make_unique()); assetLoaderFactory->RegisterLoader(game::SoundAsset::ASSET_NAME, std::make_unique()); assetLoaderFactory->RegisterLoader(game::ScriptableObjectAsset::ASSET_NAME, std::make_unique()); + assetLoaderFactory->RegisterLoader(game::ComputeShaderAsset::ASSET_NAME, std::make_unique(*renderer->GetContext())); core::AssetResolverRegistry::SetResolver( [this](const core::UUID& uuid) -> core::SObject* diff --git a/src/Game/Asset/ComputeShaderLoader.cpp b/src/Game/Asset/ComputeShaderLoader.cpp index 62601e83..0113c6b0 100644 --- a/src/Game/Asset/ComputeShaderLoader.cpp +++ b/src/Game/Asset/ComputeShaderLoader.cpp @@ -31,7 +31,8 @@ namespace sh::game return result; } - ComputeShaderLoader::ComputeShaderLoader() + ComputeShaderLoader::ComputeShaderLoader(const render::IRenderContext& ctx) : + ctx(ctx) { cachePath = std::filesystem::current_path() / "cache"; if (!std::filesystem::exists(cachePath)) @@ -118,7 +119,7 @@ namespace sh::game } csCI.spirv = std::move(spirvOpt.value()); - render::ComputeShader* const shader = core::SObject::Create(std::move(csCI)); + render::ComputeShader* const shader = core::SObject::Create(ctx, std::move(csCI)); return shader; } @@ -153,7 +154,7 @@ namespace sh::game oldShaderPtr->Deserialize(shaderObjJson); return oldShaderPtr; } - render::ComputeShader* const shader = core::SObject::Create(std::move(csCI)); + render::ComputeShader* const shader = core::SObject::Create(ctx, std::move(csCI)); shader->Deserialize(shaderObjJson); return shader; diff --git a/src/Render/BufferFactory.cpp b/src/Render/BufferFactory.cpp index fc925e42..c11acf2c 100644 --- a/src/Render/BufferFactory.cpp +++ b/src/Render/BufferFactory.cpp @@ -46,7 +46,7 @@ namespace sh::render { if (context.GetRenderAPIType() == RenderAPI::Vulkan) { - auto ptr = std::make_unique(); + std::unique_ptr ptr = std::make_unique(); ptr->Create(context, shader, usage); return ptr; } @@ -54,4 +54,16 @@ namespace sh::render assert(false); return nullptr; } + SH_RENDER_API auto BufferFactory::CreateShaderBinding(const IRenderContext& context, const ComputeShader& shader) -> std::unique_ptr + { + if (context.GetRenderAPIType() == RenderAPI::Vulkan) + { + std::unique_ptr ptr = std::make_unique(); + ptr->Create(context, shader); + return ptr; + } + else + assert(false); + return nullptr; + } }//namespace \ No newline at end of file diff --git a/src/Render/ComputeShader.cpp b/src/Render/ComputeShader.cpp index 410433eb..c3a5780c 100644 --- a/src/Render/ComputeShader.cpp +++ b/src/Render/ComputeShader.cpp @@ -1,9 +1,11 @@ #include "ComputeShader.h" #include "ComputeShaderCreateInfo.h" +#include "BufferFactory.h" namespace sh::render { - ComputeShader::ComputeShader(ComputeShaderCreateInfo createInfo) + ComputeShader::ComputeShader(const IRenderContext& ctx, ComputeShaderCreateInfo createInfo) : + ctx(ctx) { SetName(createInfo.shaderNode.shaderName); @@ -42,4 +44,68 @@ namespace sh::render if (csJson.contains("spirv")) spirv = csJson["spirv"].get>(); } + + SH_RENDER_API void ComputeShader::SetFloats(const std::string& name, float* values, std::size_t count) + { + uint32_t binding = 0; + bool bFind = false; + for (const ShaderAST::BufferNode& bufferNode : shaderNode.buffers) + { + if (bufferNode.name == name) + { + if (!bufferNode.vars.empty() && bufferNode.vars[0].type == ShaderAST::VariableType::Float) + { + binding = bufferNode.binding; + bFind = true; + break; + } + } + } + if (!bFind) + return; + + if (buffers.size() <= binding) + buffers.resize(binding + 1); + if (buffers[binding] == nullptr) + { + BufferFactory::CreateInfo ci{}; + ci.bDynamic = true; + ci.size = sizeof(float) * count; + buffers[binding] = BufferFactory::Create(ctx, ci); + buffers[binding]->SetData(values); + + if (shaderBinding == nullptr) + shaderBinding = BufferFactory::CreateShaderBinding(ctx, *this); + shaderBinding->Link(binding, *buffers[binding]); + } + else + { + buffers[binding]->Resize(sizeof(float) * count); + buffers[binding]->SetData(values); + + if (shaderBinding == nullptr) + shaderBinding = BufferFactory::CreateShaderBinding(ctx, *this); + shaderBinding->Link(binding, *buffers[binding]); + } + } + + SH_RENDER_API auto ComputeShader::GetBuffer(const std::string& name) -> IBuffer* + { + std::optional binding = GetBinding(name); + if (!binding.has_value()) + return nullptr; + if (*binding >= buffers.size()) + return nullptr; + return buffers[*binding].get(); + } + + auto ComputeShader::GetBinding(const std::string& name) -> std::optional + { + for (const ShaderAST::BufferNode& bufferNode : shaderNode.buffers) + { + if (bufferNode.name == name) + return bufferNode.binding; + } + return {}; + } }//namespace diff --git a/src/Render/ScriptableRenderer.cpp b/src/Render/ScriptableRenderer.cpp index 71f932f1..a9721bcd 100644 --- a/src/Render/ScriptableRenderer.cpp +++ b/src/Render/ScriptableRenderer.cpp @@ -1,9 +1,12 @@ #include "ScriptableRenderer.h" #include "RenderTexture.h" #include "BufferFactory.h" +#include "ComputeShader.h" +#include "RenderPass/CopyPass.h" #include "Core/ThreadPool.h" #include "Core/ThreadSyncManager.h" +#include "Core/Logger.h" #include namespace sh::render @@ -15,6 +18,7 @@ namespace sh::render { cpyPass = std::make_unique(); } + ScriptableRenderer::~ScriptableRenderer() = default; SH_RENDER_API auto ScriptableRenderer::AddRenderPass(const core::Name& passName, RenderQueue renderQueue) -> ScriptableRenderPass& { @@ -33,6 +37,21 @@ namespace sh::render return syncDatas.emplace_back(std::move(data)).promise.get_future(); } + SH_RENDER_API void ScriptableRenderer::Dispatch(const ComputeShader& computeShader, uint32_t x, uint32_t y, uint32_t z) + { + CommandBuffer* const cmdPtr = ctx.AllocateCommandBuffer(true); + if (cmdPtr == nullptr) + { + SH_ERROR("Failed to allocate command buffer!"); + return; + } + cmdPtr->Begin(true); + cmdPtr->Dispatch(computeShader, x, y, z); + cmdPtr->End(); + ctx.SubmitCommand(*cmdPtr); + ctx.DeallocateCommandBuffer(*cmdPtr); + } + SH_RENDER_API void ScriptableRenderer::SyncDirty() { if (bSyncDirty) @@ -58,7 +77,7 @@ namespace sh::render SH_RENDER_API void ScriptableRenderer::Setup(const RenderTarget& data) { - for (auto& pass : allPasses) + for (const std::unique_ptr& pass : allPasses) activePasses.push_back(pass.get()); } SH_RENDER_API void ScriptableRenderer::Execute(const RenderTarget& data) @@ -71,7 +90,7 @@ namespace sh::render } ); - for (auto& pass : activePasses) + for (ScriptableRenderPass* pass : activePasses) { IRenderThrMethod::Configure(*pass, data); renderCallCount += IRenderThrMethod::GetRenderCallCount(*pass); @@ -94,7 +113,7 @@ namespace sh::render futurePasses.push_back(core::ThreadPool::GetInstance()->AddTask( [idx, pass, &ctx = ctx, &data, &barriers]() -> std::pair { - CommandBuffer* cmd = ctx.AllocateCommandBuffer(); + CommandBuffer* cmd = ctx.AllocateCommandBuffer(false); cmd->Begin(true); IRenderThrMethod::EmitBarrier(*pass, *cmd, ctx, barriers[idx]); IRenderThrMethod::Record(*pass, *cmd, ctx, data); @@ -134,7 +153,7 @@ namespace sh::render rt.target = nullptr; rt.drawables = nullptr; - CommandBuffer* cmd = ctx.AllocateCommandBuffer(); + CommandBuffer* cmd = ctx.AllocateCommandBuffer(false); cmd->Begin(true); IRenderThrMethod::EmitBarrier(*cpyPass, *cmd, ctx, preBarriers); IRenderThrMethod::Record(*cpyPass, *cmd, ctx, rt); diff --git a/src/Render/VulkanImpl/VulkanCommandBuffer.cpp b/src/Render/VulkanImpl/VulkanCommandBuffer.cpp index 67c489da..75edb494 100644 --- a/src/Render/VulkanImpl/VulkanCommandBuffer.cpp +++ b/src/Render/VulkanImpl/VulkanCommandBuffer.cpp @@ -1,8 +1,12 @@ #include "VulkanCommandBuffer.h" #include "VulkanContext.h" #include "VulkanImageBuffer.h" -#include "RenderTexture.h" #include "VulkanBuffer.h" +#include "VulkanDescriptorSet.h" +#include "VulkanComputePipeline.h" +#include "VulkanComputePipelineManager.h" +#include "Render/RenderTexture.h" +#include "Render/ComputeShader.h" #include "Core/Logger.h" @@ -52,6 +56,19 @@ namespace sh::render::vk vkCmdCopyImageToBuffer(this->buffer, imgBuffer.GetImage(), VkImageLayout::VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, buffer.GetBuffer(), 1, &cpy); } + SH_RENDER_API void VulkanCommandBuffer::Dispatch(const ComputeShader& shader, uint32_t x, uint32_t y, uint32_t z) + { + const VulkanDescriptorSet* const descSet = static_cast(shader.GetShaderBinding()); + if (descSet == nullptr) + return; + const VulkanComputePipeline& pipeline = context.GetComputePipelineManager().GetOrCreatePipeline(shader); + VkDescriptorSet vkDescriptorSet = descSet->GetVkDescriptorSet(); + + vkCmdBindPipeline(buffer, VkPipelineBindPoint::VK_PIPELINE_BIND_POINT_COMPUTE, pipeline.GetPipeline()); + vkCmdBindDescriptorSets(buffer, VkPipelineBindPoint::VK_PIPELINE_BIND_POINT_COMPUTE, pipeline.GetPipelineLayout(), 0, 1, &vkDescriptorSet, 0, nullptr); + vkCmdDispatch(buffer, x, y, z); + } + SH_RENDER_API auto VulkanCommandBuffer::Create(VkCommandPool pool, VkCommandBufferLevel level) -> VkResult { Clear(); @@ -112,6 +129,10 @@ namespace sh::render::vk { waitSemaphores.clear(); signalSemaphores.clear(); + if (fence != VK_NULL_HANDLE) + { + vkResetFences(context.GetDevice(), 1, &fence); + } } SH_RENDER_API void VulkanCommandBuffer::SetWaitSemaphores(const std::vector& waits) { diff --git a/src/Render/VulkanImpl/VulkanCommandBufferPool.cpp b/src/Render/VulkanImpl/VulkanCommandBufferPool.cpp index ed75a675..c60b3b7f 100644 --- a/src/Render/VulkanImpl/VulkanCommandBufferPool.cpp +++ b/src/Render/VulkanImpl/VulkanCommandBufferPool.cpp @@ -4,8 +4,8 @@ #include "Core/Logger.h" namespace sh::render::vk { - VulkanCommandBufferPool::VulkanCommandBufferPool(const VulkanContext& context, uint32_t graphicsQueueFamily, uint32_t transferQueueFamily) : - context(context), graphicsQueueFamily(graphicsQueueFamily), transferQueueFamily(transferQueueFamily) + VulkanCommandBufferPool::VulkanCommandBufferPool(const VulkanContext& context, uint32_t graphicsQueueFamily, uint32_t transferQueueFamily, uint32_t computeQueueFamily) : + context(context), graphicsQueueFamily(graphicsQueueFamily), transferQueueFamily(transferQueueFamily), computeQueueFamily(computeQueueFamily) { } @@ -40,6 +40,8 @@ namespace sh::render::vk return QueueType::Graphics; if (qt == VK_QUEUE_TRANSFER_BIT) return QueueType::Transfer; + if (qt == VK_QUEUE_COMPUTE_BIT) + return QueueType::Compute; SH_ERROR_FORMAT("Incorrect queueType: {}", string_VkQueueFlagBits(qt)); assert(false); @@ -49,7 +51,7 @@ namespace sh::render::vk // thread 데이터 찾기 (없으면 생성) PerThreadData* td = nullptr; - for (auto& t : threadDatas) + for (PerThreadData& t : threadDatas) { if (t.id == thr) { @@ -140,6 +142,8 @@ namespace sh::render::vk poolInfo.queueFamilyIndex = graphicsQueueFamily; else if (queueType == VkQueueFlagBits::VK_QUEUE_TRANSFER_BIT) poolInfo.queueFamilyIndex = transferQueueFamily; + else if (queueType == VkQueueFlagBits::VK_QUEUE_COMPUTE_BIT) + poolInfo.queueFamilyIndex = computeQueueFamily; else { assert(false); @@ -148,7 +152,7 @@ namespace sh::render::vk } poolInfo.flags = VkCommandPoolCreateFlagBits::VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; //명령 버퍼가 개별적으로 기록되도록 허용 - auto result = vkCreateCommandPool(context.GetDevice(), &poolInfo, nullptr, &pool); + VkResult result = vkCreateCommandPool(context.GetDevice(), &poolInfo, nullptr, &pool); assert(result == VkResult::VK_SUCCESS); if (result != VkResult::VK_SUCCESS) { diff --git a/src/Render/VulkanImpl/VulkanComputePipeline.cpp b/src/Render/VulkanImpl/VulkanComputePipeline.cpp new file mode 100644 index 00000000..72455d31 --- /dev/null +++ b/src/Render/VulkanImpl/VulkanComputePipeline.cpp @@ -0,0 +1,247 @@ +#include "VulkanComputePipeline.h" +#include "VulkanContext.h" + +#include "Render/ComputeShader.h" + +#include "Core/Logger.h" + +#include +#include + +namespace sh::render::vk +{ + SH_RENDER_API VulkanComputePipeline::VulkanComputePipeline(const VulkanContext& context, const ComputeShader& shader) : + context(context), + shader(&shader) + { + } + + SH_RENDER_API VulkanComputePipeline::VulkanComputePipeline(VulkanComputePipeline&& other) noexcept : + context(other.context), + shader(other.shader), + shaderModule(other.shaderModule), + setlayouts(std::move(other.setlayouts)), + pipelineLayout(other.pipelineLayout), + pipeline(other.pipeline) + { + other.shader = nullptr; + other.shaderModule = VK_NULL_HANDLE; + other.pipelineLayout = VK_NULL_HANDLE; + other.pipeline = VK_NULL_HANDLE; + } + + SH_RENDER_API VulkanComputePipeline::~VulkanComputePipeline() + { + Clean(); + } + + SH_RENDER_API void VulkanComputePipeline::Clean() + { + if (pipeline != VK_NULL_HANDLE) + { + vkDestroyPipeline(context.GetDevice(), pipeline, nullptr); + pipeline = VK_NULL_HANDLE; + } + CleanDescriptors(); + if (shaderModule != VK_NULL_HANDLE) + { + vkDestroyShaderModule(context.GetDevice(), shaderModule, nullptr); + shaderModule = VK_NULL_HANDLE; + } + } + + SH_RENDER_API auto VulkanComputePipeline::Build() -> VkResult + { + assert(shader != nullptr); + Clean(); + + VkResult result = CreateShaderModule(); + if (result != VkResult::VK_SUCCESS) + { + SH_ERROR_FORMAT("Failed to CreateShaderModule(): {}", string_VkResult(result)); + return result; + } + + // 디스크립터 바인딩 수집 + for (const ShaderAST::BufferNode& buf : shader->GetNode().buffers) + { + VkDescriptorType type; + switch (buf.bufferType) + { + case ShaderAST::BufferType::Storage: + type = VkDescriptorType::VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + break; + case ShaderAST::BufferType::Uniform: + type = VkDescriptorType::VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + break; + case ShaderAST::BufferType::Sampler: + type = VkDescriptorType::VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + break; + case ShaderAST::BufferType::PushConstant: + continue; // 푸시 상수는 디스크립터 아님 + default: + continue; + } + AddDescriptorBinding(buf.set, buf.binding, type); + } + + result = CreateDescriptorLayout(); + if (result != VkResult::VK_SUCCESS) + { + SH_ERROR_FORMAT("Failed to CreateDescriptorLayout(): {}", string_VkResult(result)); + return result; + } + + result = CreatePipelineLayout(); + if (result != VkResult::VK_SUCCESS) + { + SH_ERROR_FORMAT("Failed to CreatePipelineLayout(): {}", string_VkResult(result)); + return result; + } + + result = CreatePipeline(); + if (result != VkResult::VK_SUCCESS) + { + SH_ERROR_FORMAT("Failed to CreateComputePipeline(): {}", string_VkResult(result)); + return result; + } + + return VkResult::VK_SUCCESS; + } + + SH_RENDER_API auto VulkanComputePipeline::GetDescriptorSetLayout(uint32_t set) const -> VkDescriptorSetLayout + { + if (set >= setlayouts.size()) + return VK_NULL_HANDLE; + return setlayouts[set].descriptorSetLayout; + } + + SH_RENDER_API auto VulkanComputePipeline::GetDescriptorBinding(uint32_t set, uint32_t binding) const -> std::optional + { + if (set >= setlayouts.size()) + return {}; + const SetLayout& setlayout = setlayouts[set]; + auto it = setlayout.descriptorSetLayoutBindings.find(binding); + if (it == setlayout.descriptorSetLayoutBindings.end()) + return {}; + return it->second; + } + + SH_RENDER_API auto VulkanComputePipeline::GetGroupSize() const -> std::array + { + if (shader == nullptr) + return { 1, 1, 1 }; + return { shader->GetNumthreadsX(), shader->GetNumthreadsY(), shader->GetNumthreadsZ() }; + } + + auto VulkanComputePipeline::CreateShaderModule() -> VkResult + { + const std::vector& spirv = shader->GetSpirv(); + + VkShaderModuleCreateInfo info{}; + info.sType = VkStructureType::VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + info.codeSize = spirv.size(); + info.pCode = reinterpret_cast(spirv.data()); + + return vkCreateShaderModule(context.GetDevice(), &info, nullptr, &shaderModule); + } + + void VulkanComputePipeline::AddDescriptorBinding(uint32_t set, uint32_t binding, VkDescriptorType type) + { + VkDescriptorSetLayoutBinding layoutBinding{}; + layoutBinding.binding = binding; + layoutBinding.descriptorCount = 1; + layoutBinding.descriptorType = type; + layoutBinding.pImmutableSamplers = nullptr; + layoutBinding.stageFlags = VkShaderStageFlagBits::VK_SHADER_STAGE_COMPUTE_BIT; + + if (setlayouts.size() <= set) + setlayouts.resize(set + 1); + + setlayouts[set].set = set; + setlayouts[set].descriptorSetLayoutBindings.insert_or_assign(binding, layoutBinding); + } + + auto VulkanComputePipeline::CreateDescriptorLayout() -> VkResult + { + VkResult result = VkResult::VK_SUCCESS; + for (std::size_t i = 0; i < setlayouts.size(); ++i) + { + if (setlayouts[i].descriptorSetLayoutBindings.empty()) + { + setlayouts[i].set = static_cast(i); + setlayouts[i].descriptorSetLayout = context.GetEmptyDescriptorSetLayout(); + continue; + } + std::vector bindings; + bindings.reserve(setlayouts[i].descriptorSetLayoutBindings.size()); + for (const auto& [binding, layoutBinding] : setlayouts[i].descriptorSetLayoutBindings) + bindings.push_back(layoutBinding); + + VkDescriptorSetLayoutCreateInfo info{}; + info.sType = VkStructureType::VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + info.bindingCount = static_cast(bindings.size()); + info.pBindings = bindings.data(); + + VkDescriptorSetLayout layout{}; + result = vkCreateDescriptorSetLayout(context.GetDevice(), &info, nullptr, &layout); + if (result != VkResult::VK_SUCCESS) + return result; + + setlayouts[i].descriptorSetLayout = layout; + } + return result; + } + + auto VulkanComputePipeline::CreatePipelineLayout() -> VkResult + { + std::vector layouts(setlayouts.size(), VK_NULL_HANDLE); + for (std::size_t i = 0; i < setlayouts.size(); ++i) + layouts[i] = setlayouts[i].descriptorSetLayout; + + VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; + pipelineLayoutInfo.sType = VkStructureType::VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutInfo.setLayoutCount = static_cast(layouts.size()); + pipelineLayoutInfo.pSetLayouts = layouts.data(); + pipelineLayoutInfo.pushConstantRangeCount = 0; + pipelineLayoutInfo.pPushConstantRanges = nullptr; + + return vkCreatePipelineLayout(context.GetDevice(), &pipelineLayoutInfo, nullptr, &pipelineLayout); + } + + auto VulkanComputePipeline::CreatePipeline() -> VkResult + { + VkPipelineShaderStageCreateInfo stageInfo{}; + stageInfo.sType = VkStructureType::VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + stageInfo.stage = VkShaderStageFlagBits::VK_SHADER_STAGE_COMPUTE_BIT; + stageInfo.module = shaderModule; + stageInfo.pName = "main"; + + VkComputePipelineCreateInfo info{}; + info.sType = VkStructureType::VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO; + info.stage = stageInfo; + info.layout = pipelineLayout; + info.basePipelineHandle = VK_NULL_HANDLE; + info.basePipelineIndex = -1; + + return vkCreateComputePipelines(context.GetDevice(), VK_NULL_HANDLE, 1, &info, nullptr, &pipeline); + } + + void VulkanComputePipeline::CleanDescriptors() + { + if (pipelineLayout != VK_NULL_HANDLE) + { + vkDestroyPipelineLayout(context.GetDevice(), pipelineLayout, nullptr); + pipelineLayout = VK_NULL_HANDLE; + } + for (std::size_t i = 0; i < setlayouts.size(); ++i) + { + if (setlayouts[i].descriptorSetLayout != VK_NULL_HANDLE && + setlayouts[i].descriptorSetLayout != context.GetEmptyDescriptorSetLayout()) + { + vkDestroyDescriptorSetLayout(context.GetDevice(), setlayouts[i].descriptorSetLayout, nullptr); + } + } + setlayouts.clear(); + } +}//namespace diff --git a/src/Render/VulkanImpl/VulkanComputePipelineManager.cpp b/src/Render/VulkanImpl/VulkanComputePipelineManager.cpp new file mode 100644 index 00000000..8435a044 --- /dev/null +++ b/src/Render/VulkanImpl/VulkanComputePipelineManager.cpp @@ -0,0 +1,40 @@ +#include "Render/VulkanImpl/VulkanComputePipelineManager.h" +#include "Render/VulkanImpl/VulkanComputePipeline.h" +#include "Render/VulkanImpl/VulkanContext.h" +#include "Render/ComputeShader.h" +namespace sh::render::vk +{ + VulkanComputePipelineManager::VulkanComputePipelineManager(const VulkanContext& context) : + ctx(context) + { + onShaderDestroy.SetCallback( + [this](const core::SObject* shader) + { + std::unique_lock writeLock{ mu }; + auto it = pipelines.find(static_cast(shader)); + if (it != pipelines.end()) + pipelines.erase(it); + } + ); + } + SH_RENDER_API auto VulkanComputePipelineManager::GetOrCreatePipeline(const ComputeShader& computeShader) -> VulkanComputePipeline& + { + { + std::shared_lock readLock{ mu }; + auto it = pipelines.find(&computeShader); + if (it != pipelines.end()) + return *it->second; + } + { + std::unique_lock writeLock{ mu }; + auto it = pipelines.find(&computeShader); + if (it != pipelines.end()) + return *it->second; + computeShader.onDestroy.Register(onShaderDestroy); + auto result = pipelines.insert({ &computeShader, std::make_unique(ctx, computeShader) }); + VulkanComputePipeline& pipeline = *result.first->second; + pipeline.Build(); + return pipeline; + } + } +}//namespace \ No newline at end of file diff --git a/src/Render/VulkanImpl/VulkanContext.cpp b/src/Render/VulkanImpl/VulkanContext.cpp index 5be6b3c6..0eaaff02 100644 --- a/src/Render/VulkanImpl/VulkanContext.cpp +++ b/src/Render/VulkanImpl/VulkanContext.cpp @@ -6,6 +6,7 @@ #include "VulkanCommandBuffer.h" #include "VulkanDescriptorPool.h" #include "VulkanPipelineManager.h" +#include "VulkanComputePipelineManager.h" #include "VulkanRenderImpl.h" #include "Core/Util.h" @@ -243,7 +244,8 @@ namespace sh::render::vk cmdPool = std::make_unique( *this, queueManager->GetFamilyIndex(VulkanQueueManager::Role::Graphics), - queueManager->GetFamilyIndex(VulkanQueueManager::Role::Transfer)); + queueManager->GetFamilyIndex(VulkanQueueManager::Role::Transfer), + queueManager->GetFamilyIndex(VulkanQueueManager::Role::Compute)); } void VulkanContext::DestroyCommandPool() { @@ -327,7 +329,7 @@ namespace sh::render::vk CreateEmptyDescriptor(); pipelineManager = std::make_unique(*this); - + computePipelineManager = std::make_unique(*this); renderImpl = std::make_unique(*this); } SH_RENDER_API void VulkanContext::Clear() @@ -354,15 +356,25 @@ namespace sh::render::vk gpus.clear(); DestroyInstance(); } - SH_RENDER_API auto VulkanContext::AllocateCommandBuffer() -> CommandBuffer* + SH_RENDER_API auto VulkanContext::AllocateCommandBuffer(bool bCompute) -> CommandBuffer* { - auto cmd = GetCommandBufferPool().AllocateCommandBuffer(std::this_thread::get_id(), VkQueueFlagBits::VK_QUEUE_GRAPHICS_BIT); + VkQueueFlagBits flag = VkQueueFlagBits::VK_QUEUE_GRAPHICS_BIT; + if (bCompute) + flag = VkQueueFlagBits::VK_QUEUE_COMPUTE_BIT; + VulkanCommandBuffer* const cmd = GetCommandBufferPool().AllocateCommandBuffer(std::this_thread::get_id(), flag); return cmd; } SH_RENDER_API void VulkanContext::DeallocateCommandBuffer(CommandBuffer& cmd) { GetCommandBufferPool().DeallocateCommandBuffer(static_cast(cmd)); } + SH_RENDER_API void VulkanContext::SubmitCommand(CommandBuffer& cmd) + { + VulkanCommandBuffer& vkCmd = static_cast(cmd); + VkFence fence = vkCmd.GetOrCreateFence(); + GetQueueManager().Submit(VulkanQueueManager::Role::Compute, vkCmd, fence); + vkWaitForFences(device, 1, &fence, true, std::numeric_limits::max()); + } SH_RENDER_API void VulkanContext::SetViewport(const glm::vec2& start, const glm::vec2& end) { viewportStart = start; diff --git a/src/Render/VulkanImpl/VulkanDescriptorSet.cpp b/src/Render/VulkanImpl/VulkanDescriptorSet.cpp index 41216768..8f15c785 100644 --- a/src/Render/VulkanImpl/VulkanDescriptorSet.cpp +++ b/src/Render/VulkanImpl/VulkanDescriptorSet.cpp @@ -4,6 +4,9 @@ #include "VulkanBuffer.h" #include "VulkanImageBuffer.h" #include "VulkanDescriptorPool.h" +#include "VulkanComputePipelineManager.h" +#include "VulkanComputePipeline.h" +#include "ComputeShader.h" #include namespace sh::render::vk @@ -46,6 +49,25 @@ namespace sh::render::vk if (descLayout != VK_NULL_HANDLE) descSet = this->context->GetDescriptorPool().AllocateDescriptorSet(descLayout); } + SH_RENDER_API void VulkanDescriptorSet::Create(const IRenderContext& ctx, const ComputeShader& shader) + { + Clear(); + context = &static_cast(ctx); + set = 0; + const VulkanComputePipeline& pipeline = context->GetComputePipelineManager().GetOrCreatePipeline(shader); + + const VkDescriptorSetLayout descLayout = pipeline.GetDescriptorSetLayout(set); + if (descLayout != VK_NULL_HANDLE) + descSet = this->context->GetDescriptorPool().AllocateDescriptorSet(descLayout); + + for (const VulkanComputePipeline::SetLayout& setLayout : pipeline.GetSetLayouts()) + { + if (set != setLayout.set) + continue; + for (const auto& [binding, layoutBinding] : setLayout.descriptorSetLayoutBindings) + descriptorTypes[binding] = layoutBinding.descriptorType; + } + } void VulkanDescriptorSet::Clear() { if (descSet == nullptr || context == nullptr) diff --git a/src/Render/VulkanImpl/VulkanQueueManager.cpp b/src/Render/VulkanImpl/VulkanQueueManager.cpp index 23494f31..f85eadd8 100644 --- a/src/Render/VulkanImpl/VulkanQueueManager.cpp +++ b/src/Render/VulkanImpl/VulkanQueueManager.cpp @@ -3,7 +3,8 @@ #include "VulkanCommandBuffer.h" #include -#include +#include +#include namespace sh::render::vk { @@ -20,22 +21,25 @@ namespace sh::render::vk graphicsQueue(other.graphicsQueue), presentQueue(other.presentQueue), transferQueue(other.transferQueue), + computeQueue(other.computeQueue), graphicsPriorities(std::move(other.graphicsPriorities)), presentPriorities(std::move(other.presentPriorities)), transferPriorities(std::move(other.transferPriorities)), + computePriorities(std::move(other.computePriorities)), bFamiliesReady(other.bFamiliesReady), bQueuesReady(other.bQueuesReady) { other.graphicsQueue = VK_NULL_HANDLE; other.presentQueue = VK_NULL_HANDLE; other.transferQueue = VK_NULL_HANDLE; + other.computeQueue = VK_NULL_HANDLE; other.bFamiliesReady = false; other.bQueuesReady = false; - // mutex 포인터는 move 이후 재결합이 필요 → FetchQueues에서 재설정하게 둔다 graphicsLock = &graphicsMtx; presentLock = &presentMtx; transferLock = &transferMtx; + computeLock = &computeMtx; } SH_RENDER_API void VulkanQueueManager::QueryFamilies(VkSurfaceKHR surface) { @@ -51,9 +55,14 @@ namespace sh::render::vk if (!t.has_value()) throw std::runtime_error("No transfer queue family found"); + auto c = PickComputeFamily(); + if (!c.has_value()) + throw std::runtime_error("No compute queue family found"); + selection.graphics = *g; selection.present = *p; selection.transfer = *t; + selection.compute = *c; ComputeRequestedCountsAndIndices(); @@ -75,7 +84,7 @@ namespace sh::render::vk auto addReqFn = [&](const Family& family) { - for (auto& r : reqs) + for (Req& r : reqs) if (r.family == family.index) { r.count = std::max(r.count, family.requestedCount); @@ -87,6 +96,7 @@ namespace sh::render::vk addReqFn(selection.graphics); addReqFn(selection.present); addReqFn(selection.transfer); + addReqFn(selection.compute); std::vector infos; infos.reserve(reqs.size()); @@ -94,28 +104,23 @@ namespace sh::render::vk graphicsPriorities.clear(); presentPriorities.clear(); transferPriorities.clear(); + computePriorities.clear(); - struct Blob - { - uint32_t family; - std::vector* prios; - }; - std::vector blobs; - blobs.reserve(reqs.size()); - - auto pickStorageFn = + auto pickPriorityFn = [&](uint32_t family) -> std::vector& { if (family == selection.graphics.index) return graphicsPriorities; if (family == selection.present.index) return presentPriorities; - return transferPriorities; + if (family == selection.transfer.index) + return transferPriorities; + return computePriorities; }; - for (auto& req : reqs) + for (const Req& req : reqs) { - auto& prios = pickStorageFn(req.family); + auto& prios = pickPriorityFn(req.family); prios.assign(req.count, 1.0f); VkDeviceQueueCreateInfo q{}; @@ -139,19 +144,27 @@ namespace sh::render::vk vkGetDeviceQueue(device, selection.graphics.index, selection.graphicsQIndex, &graphicsQueue); vkGetDeviceQueue(device, selection.present.index, selection.presentQIndex, &presentQueue); vkGetDeviceQueue(device, selection.transfer.index, selection.transferQIndex, &transferQueue); + vkGetDeviceQueue(device, selection.compute.index, selection.computeQIndex, &computeQueue); // 동일 VkQueue면 락도 공유 graphicsLock = &graphicsMtx; presentLock = &presentMtx; transferLock = &transferMtx; + computeLock = &computeMtx; if (presentQueue == graphicsQueue) presentLock = graphicsLock; + if (transferQueue == graphicsQueue) transferLock = graphicsLock; if (transferQueue == presentQueue) transferLock = presentLock; + if (computeQueue == graphicsQueue) + computeLock = graphicsLock; + if (computeQueue == transferQueue) + computeLock = transferLock; + bQueuesReady = true; } SH_RENDER_API auto VulkanQueueManager::GetQueue(Role role) const -> VkQueue @@ -166,6 +179,8 @@ namespace sh::render::vk return presentQueue; case Role::Transfer: return transferQueue; + case Role::Compute: + return computeQueue; } return VK_NULL_HANDLE; } @@ -179,6 +194,8 @@ namespace sh::render::vk return selection.present.index; case Role::Transfer: return selection.transfer.index; + case Role::Compute: + return selection.compute.index; } return UINT32_MAX; } @@ -204,7 +221,7 @@ namespace sh::render::vk bool bHasTimeline = false; - for (auto& wait : waits) + for (const VulkanCommandBuffer::WaitSemaphore& wait : waits) { waitSems.push_back(wait.semaphore); waitStages.push_back(wait.stageMask); @@ -219,7 +236,7 @@ namespace sh::render::vk signalSems.reserve(signals.size()); signalValues.reserve(signals.size()); - for (auto& signal : signals) + for (const VulkanCommandBuffer::SignalSemaphore& signal : signals) { signalSems.push_back(signal.semaphore); signalValues.push_back(signal.value); @@ -247,7 +264,7 @@ namespace sh::render::vk si.signalSemaphoreCount = (uint32_t)signalSems.size(); si.pSignalSemaphores = signalSems.empty() ? nullptr : signalSems.data(); - std::mutex* muPtr = GetMutexForRole(role); + std::mutex* const muPtr = GetMutexForRole(role); std::lock_guard lock(*muPtr); VkResult result = vkQueueSubmit(q, 1, &si, fence); @@ -315,10 +332,10 @@ namespace sh::render::vk } auto VulkanQueueManager::PickTransferFamily(uint32_t avoidFamily) const -> std::optional { - // 전용 transfer (TRANSFER 있고 GRAPHICS/COMPUTE 없는 패밀리) 우선 + // 전용 transfer 패밀리 우선 for (int i = 0; i < props.size(); ++i) { - const auto& p = props[i]; + const VkQueueFamilyProperties& p = props[i]; if (p.queueCount == 0) continue; const bool hasTransfer = (p.queueFlags & VkQueueFlagBits::VK_QUEUE_TRANSFER_BIT) != 0; @@ -336,7 +353,7 @@ namespace sh::render::vk // 그래픽과 다른 transfer 가능 패밀리 선호 for (int i = 0; i < props.size(); ++i) { - const auto& p = props[i]; + const VkQueueFamilyProperties& p = props[i]; if (p.queueCount == 0) continue; if (!(p.queueFlags & VkQueueFlagBits::VK_QUEUE_TRANSFER_BIT)) @@ -350,7 +367,7 @@ namespace sh::render::vk return fam; } - // 일치하는 게 하나도 없는 경우 - 그래픽 패밀리(대부분 transfer 포함) + // 일치하는 게 하나도 없는 경우 그래픽 패밀리(대부분 transfer 포함) if (avoidFamily != UINT32_MAX) { const auto& p = props[avoidFamily]; @@ -365,63 +382,77 @@ namespace sh::render::vk return {}; } - void VulkanQueueManager::ComputeRequestedCountsAndIndices() + auto VulkanQueueManager::PickComputeFamily() const -> std::optional { - selection.graphics.requestedCount = 1; - selection.present.requestedCount = 1; - selection.transfer.requestedCount = 1; - - // 같은 패밀리 공유 시, 가능한 한 큐 인덱스를 분리해서 받도록 요청 개수 증가 - if (selection.present.index == selection.graphics.index) - selection.graphics.requestedCount += 1; - - if (selection.transfer.index == selection.graphics.index) - selection.graphics.requestedCount += 1; - - if (selection.transfer.index == selection.present.index && selection.transfer.index != selection.graphics.index) - selection.present.requestedCount += 1; - - selection.graphics.requestedCount = ClampRequested(selection.graphics.requestedCount, selection.graphics.availableCount); - selection.present.requestedCount = ClampRequested(selection.present.requestedCount, selection.present.availableCount); - selection.transfer.requestedCount = ClampRequested(selection.transfer.requestedCount, selection.transfer.availableCount); - - // 역할별 queue index 결정 - selection.graphicsQIndex = 0; - - // present - if (selection.present.index == selection.graphics.index) - { - selection.presentQIndex = (selection.graphics.requestedCount >= 2) ? 1 : 0; - } - else + // Compute전용 큐 패밀리 우선 + // 메모) Vulkan 스펙상 GRAPHICS나 COMPUTE 비트가 켜진 패밀리는 TRANSFER 작업을 반드시 지원 + for (int i = 0; i < props.size(); ++i) { - selection.presentQIndex = 0; + const VkQueueFamilyProperties& p = props[i]; + if (p.queueCount == 0) + continue; + const bool hasGraphics = (p.queueFlags & VkQueueFlagBits::VK_QUEUE_GRAPHICS_BIT) != 0; + const bool hasCompute = (p.queueFlags & VkQueueFlagBits::VK_QUEUE_COMPUTE_BIT) != 0; + if (hasCompute && !hasGraphics) + { + Family fam{}; + fam.index = i; + fam.availableCount = p.queueCount; + return fam; + } } - // transfer - if (selection.transfer.index == selection.graphics.index) + for (int i = 0; i < props.size(); ++i) { - // graphics/present가 이미 0/1을 썼을 가능성이 있으니 가능한 인덱스를 고름 - uint32_t want = 1; - if (selection.present.index == selection.graphics.index && selection.presentQIndex == 1) - want = 2; - selection.transferQIndex = (selection.graphics.requestedCount >= (want + 1)) ? want : 0; + const VkQueueFamilyProperties& p = props[i]; + if (p.queueCount == 0) + continue; + if (p.queueFlags & VkQueueFlagBits::VK_QUEUE_COMPUTE_BIT) + { + Family fam{}; + fam.index = i; + fam.availableCount = p.queueCount; + return fam; + } } - else if (selection.transfer.index == selection.present.index) + return {}; + } + void VulkanQueueManager::ComputeRequestedCountsAndIndices() + { + // 역할을 우선순위 순으로 처리하며 같은 패밀리 안에서 다음 큐 인덱스를 발급한다. + // 패밀리 큐가 모자라면 0번으로 fallback (= 같은 VkQueue 공유, lock도 공유됨). + struct RoleSlot { - selection.transferQIndex = (selection.present.requestedCount >= 2) ? 1 : 0; - } - else + Family* family; + uint32_t* qIndex; + }; + const RoleSlot slots[] = + { + { &selection.graphics, &selection.graphicsQIndex }, + { &selection.present,&selection.presentQIndex }, + { &selection.transfer, &selection.transferQIndex }, + { &selection.compute, &selection.computeQIndex }, + }; + + std::map nextIndexInFamily; + + for (const RoleSlot& slot : slots) { - selection.transferQIndex = 0; + Family& fam = *slot.family; + const uint32_t want = nextIndexInFamily[fam.index]; // 0부터 시작 + const uint32_t needCount = want + 1; + + fam.requestedCount = std::max(fam.requestedCount, needCount); + fam.requestedCount = std::clamp(fam.requestedCount, 0u, fam.availableCount); + + // 요청한 만큼 못 받았으면 0번으로 fallback + const bool granted = fam.requestedCount >= needCount; + *slot.qIndex = granted ? want : 0; + + if (granted) + nextIndexInFamily[fam.index] = want + 1; } } - uint32_t VulkanQueueManager::ClampRequested(uint32_t want, uint32_t avail) const - { - if (avail == 0) - return 0; - return (want > avail) ? avail : want; - } auto VulkanQueueManager::GetMutexForRole(Role role) const -> std::mutex* { switch (role) @@ -432,6 +463,8 @@ namespace sh::render::vk return presentLock; case Role::Transfer: return transferLock; + case Role::Compute: + return computeLock; } return graphicsLock; } From 68a1b730d2a3201a0be53e1c5b17d45c8350fa58 Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Tue, 28 Apr 2026 06:26:19 +0900 Subject: [PATCH 13/60] refact: Refactoring rendering codes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ImageUsage -> ResourceUsage 일반화 BuildDrawList O(N^2) -> O(N) --- RenderArchitecture.md | 6 +- include/Render/RenderData.h | 11 ++-- include/Render/RenderTexture.h | 10 ++-- include/Render/ScriptableRenderPass.h | 4 +- include/Render/ScriptableRenderer.h | 2 +- src/Editor/EditorPostOutlinePass.cpp | 1 + src/Game/UIPass.cpp | 2 +- src/Render/RenderPass/CopyPass.cpp | 2 +- src/Render/RenderPass/TransparentPass.cpp | 1 + src/Render/RenderTexture.cpp | 4 +- src/Render/ScriptableRenderPass.cpp | 65 +++++++++++----------- src/Render/ScriptableRenderer.cpp | 10 ++-- src/Render/VulkanImpl/VulkanRenderImpl.cpp | 16 +++--- 13 files changed, 69 insertions(+), 65 deletions(-) diff --git a/RenderArchitecture.md b/RenderArchitecture.md index 901f5ee1..21ad01f1 100644 --- a/RenderArchitecture.md +++ b/RenderArchitecture.md @@ -134,7 +134,7 @@ ShellEngine은 이 문제를 해결하기 위해서, 그리고 유연한 확장 `VulkanRenderImpl::EmitBarrier()`는 렌더러가 계산한 `BarrierInfo`를 Vulkan API 호출로 번역합니다. -- `ImageUsage`를 image layout, pipeline stage, access mask로 매핑 +- `ResourceUsage`를 image layout, pipeline stage, access mask로 매핑 - `VulkanImageBuffer::BarrierCommand()`를 통해 `vkCmdPipelineBarrier` 기록 이 계층은 전이가 필요한지 판단을 내리지 않으며, 상위에서 계산된 전이 정보를 API 호출로 변환하는 역할만 수행합니다. @@ -144,7 +144,7 @@ ShellEngine은 이 문제를 해결하기 위해서, 그리고 유연한 확장 현재 구조는 아래 기능은 포함되어 있지 않습니다. - 의존성 기반 패스 자동 재배치 -- 버퍼 리소스 상태 추적 (현재는 `ImageUsage` 위주로 구현됨) +- 버퍼 리소스 상태 추적 (현재 `ResourceUsage`는 이미지 상태값만 정의됨) ## 구조 요약 @@ -157,5 +157,5 @@ ScriptableRenderer - 커맨드 버퍼 병렬 기록 VulkanRenderImpl -- EmitBarrier(): ImageUsage -> Vulkan barrier 변환 +- EmitBarrier(): ResourceUsage -> Vulkan barrier 변환 - vkCmdPipelineBarrier 기록 diff --git a/include/Render/RenderData.h b/include/Render/RenderData.h index f7ebc89a..68589c73 100644 --- a/include/Render/RenderData.h +++ b/include/Render/RenderData.h @@ -39,23 +39,26 @@ namespace sh::render std::variant, std::vector> renderData; std::vector> drawCall; + uint32_t drawableCount = 0; + bool bClearColor = true; bool bClearDepth = true; }; - enum class ImageUsage + /// @brief 리소스(이미지/버퍼) 사용 의도. 배리어 계산의 입력으로 쓰임 + enum class ResourceUsage { Undefined, ColorAttachment, SampledRead, Present, - Src + TransferSrc }; struct BarrierInfo { std::variant target; // uint32_t = 스왑체인 - ImageUsage lastUsage; - ImageUsage curUsage; + ResourceUsage lastUsage; + ResourceUsage curUsage; }; }//namespace \ No newline at end of file diff --git a/include/Render/RenderTexture.h b/include/Render/RenderTexture.h index f17ccf88..a767da9b 100644 --- a/include/Render/RenderTexture.h +++ b/include/Render/RenderTexture.h @@ -15,7 +15,7 @@ namespace sh::render template<> struct IRenderThrMethod { - static void ChangeUsage(RenderTexture& rt, ImageUsage newUsage); + static void ChangeUsage(RenderTexture& rt, ResourceUsage newUsage); }; class RenderTexture : public Texture @@ -37,9 +37,9 @@ namespace sh::render SH_RENDER_API auto GetLayout() const -> const RenderTargetLayout& { return layout; } SH_RENDER_API auto GetMSAABuffer() const -> ITextureBuffer* { return msaaBuffer.get(); } SH_RENDER_API auto GetDepthBuffer() const -> ITextureBuffer* { return depthBuffer.get(); } - SH_RENDER_API auto GetUsage() const -> ImageUsage { return usage; } + SH_RENDER_API auto GetUsage() const -> ResourceUsage { return usage; } protected: - SH_RENDER_API void ChangeUsage(ImageUsage newUsage); + SH_RENDER_API void ChangeUsage(ResourceUsage newUsage); private: void CreateBuffers(); private: @@ -53,12 +53,12 @@ namespace sh::render std::unique_ptr msaaBuffer; std::unique_ptr depthBuffer; - ImageUsage usage = ImageUsage::Undefined; + ResourceUsage usage = ResourceUsage::Undefined; bool bChangeSize = false; }; - inline void IRenderThrMethod::ChangeUsage(RenderTexture& rt, ImageUsage newUsage) + inline void IRenderThrMethod::ChangeUsage(RenderTexture& rt, ResourceUsage newUsage) { rt.ChangeUsage(newUsage); } diff --git a/include/Render/ScriptableRenderPass.h b/include/Render/ScriptableRenderPass.h index 404b9709..9f18bc5a 100644 --- a/include/Render/ScriptableRenderPass.h +++ b/include/Render/ScriptableRenderPass.h @@ -53,7 +53,7 @@ namespace sh::render SH_RENDER_API ScriptableRenderPass(const core::Name& passName, RenderQueue renderQueue); SH_RENDER_API virtual ~ScriptableRenderPass() = default; - SH_RENDER_API auto GetRenderTextures() const -> const std::unordered_map& { return renderTextures; } + SH_RENDER_API auto GetRenderTextures() const -> const std::unordered_map& { return renderTextures; } SH_RENDER_API auto IsStoreImage() const -> bool { return bStoreImage; } protected: SH_RENDER_API virtual void Configure(const RenderTarget& renderData); @@ -71,7 +71,7 @@ namespace sh::render const RenderQueue renderQueue; protected: DrawList drawList; - std::unordered_map renderTextures; + std::unordered_map renderTextures; private: bool bStoreImage = true; }; diff --git a/include/Render/ScriptableRenderer.h b/include/Render/ScriptableRenderer.h index 30e77371..f52cea56 100644 --- a/include/Render/ScriptableRenderer.h +++ b/include/Render/ScriptableRenderer.h @@ -75,7 +75,7 @@ namespace sh::render std::vector> allPasses; private: - SH_RENDER_API static std::vector swapChainStates; + SH_RENDER_API static std::vector swapChainStates; std::vector activePasses; std::vector submittedCmds; diff --git a/src/Editor/EditorPostOutlinePass.cpp b/src/Editor/EditorPostOutlinePass.cpp index 726bcd18..3880fc7a 100644 --- a/src/Editor/EditorPostOutlinePass.cpp +++ b/src/Editor/EditorPostOutlinePass.cpp @@ -62,6 +62,7 @@ namespace sh::editor renderItem.drawable = drawable; std::get<1>(list.renderData).push_back(renderItem); + list.drawableCount = 1; return list; } diff --git a/src/Game/UIPass.cpp b/src/Game/UIPass.cpp index 112e7779..2b157611 100644 --- a/src/Game/UIPass.cpp +++ b/src/Game/UIPass.cpp @@ -20,7 +20,7 @@ namespace sh::game { ScriptableRenderPass::Configure(renderData); if (viewportTexture != nullptr) - renderTextures[viewportTexture] = render::ImageUsage::SampledRead; + renderTextures[viewportTexture] = render::ResourceUsage::SampledRead; } SH_GAME_API auto UIPass::BuildDrawList(const render::RenderTarget& renderData) -> render::DrawList { diff --git a/src/Render/RenderPass/CopyPass.cpp b/src/Render/RenderPass/CopyPass.cpp index 2fb2f188..bab593b1 100644 --- a/src/Render/RenderPass/CopyPass.cpp +++ b/src/Render/RenderPass/CopyPass.cpp @@ -17,7 +17,7 @@ namespace sh::render renderTextures.clear(); for (auto& cpyData : cpyDatas) - renderTextures[cpyData.src] = ImageUsage::Src; + renderTextures[cpyData.src] = ResourceUsage::TransferSrc; } SH_RENDER_API void CopyPass::Record(CommandBuffer& cmd, const IRenderContext& ctx, const RenderTarget& renderData) { diff --git a/src/Render/RenderPass/TransparentPass.cpp b/src/Render/RenderPass/TransparentPass.cpp index 834990fb..c566303b 100644 --- a/src/Render/RenderPass/TransparentPass.cpp +++ b/src/Render/RenderPass/TransparentPass.cpp @@ -32,6 +32,7 @@ namespace sh::render item.drawable = drawable; std::get>(list.renderData).push_back(item); + ++list.drawableCount; } const auto& camPos = renderData.camera->GetPos(core::ThreadType::Render); diff --git a/src/Render/RenderTexture.cpp b/src/Render/RenderTexture.cpp index d721eee2..b4bb1214 100644 --- a/src/Render/RenderTexture.cpp +++ b/src/Render/RenderTexture.cpp @@ -63,7 +63,7 @@ namespace sh::render { return { width, height }; } - SH_RENDER_API void RenderTexture::ChangeUsage(ImageUsage newUsage) + SH_RENDER_API void RenderTexture::ChangeUsage(ResourceUsage newUsage) { usage = newUsage; } @@ -112,6 +112,6 @@ namespace sh::render depthBuffer->Create(*context, ci); } - usage = ImageUsage::Undefined; + usage = ResourceUsage::Undefined; } }//namespace \ No newline at end of file diff --git a/src/Render/ScriptableRenderPass.cpp b/src/Render/ScriptableRenderPass.cpp index 28b526e2..64b5907a 100644 --- a/src/Render/ScriptableRenderPass.cpp +++ b/src/Render/ScriptableRenderPass.cpp @@ -4,6 +4,7 @@ #include "RenderTexture.h" #include "Core/Logger.h" +#include "Core/Util.h" namespace sh::render { @@ -28,36 +29,49 @@ namespace sh::render if (renderData.drawables == nullptr) return list; - for (auto drawable : *renderData.drawables) + std::vector& groups = std::get>(list.renderData); + + struct GroupKey + { + const Material* mat; + Mesh::Topology topology; + bool operator==(const GroupKey& other) const noexcept { return mat == other.mat && topology == other.topology; } + }; + struct GroupKeyHash + { + std::size_t operator()(const GroupKey& k) const noexcept + { + return core::Util::CombineHash(std::hash{}(k.mat), std::hash{}(static_cast(k.topology))); + } + }; + std::unordered_map groupIndex; + groupIndex.reserve(renderData.drawables->size()); + + for (Drawable* drawable : *renderData.drawables) { if (!core::IsValid(drawable) || !drawable->CheckAssetValid() || drawable->GetMaterial()->GetShader()->GetShaderPasses(passName) == nullptr) continue; - const Material* mat = drawable->GetMaterial(); + const Material* const mat = drawable->GetMaterial(); Mesh::Topology topology = drawable->GetTopology(core::ThreadType::Render); - DrawList::RenderGroup* renderGroup = nullptr; - for (auto& group : std::get<0>(list.renderData)) - { - if (group.material == mat && group.topology == topology) - { - renderGroup = &group; - break; - } - } - if (renderGroup == nullptr) + GroupKey key{ mat, topology }; + auto it = groupIndex.find(key); + if (it == groupIndex.end()) { DrawList::RenderGroup group{}; group.material = mat; group.topology = topology; group.drawables.push_back(drawable); - std::get<0>(list.renderData).push_back(std::move(group)); + groupIndex.emplace(key, groups.size()); + groups.push_back(std::move(group)); } else { - renderGroup->drawables.push_back(drawable); + groups[it->second].drawables.push_back(drawable); } + ++list.drawableCount; } return list; } @@ -67,28 +81,13 @@ namespace sh::render } SH_RENDER_API auto ScriptableRenderPass::GetRenderCallCount() const -> uint32_t { - uint32_t renderCallCount = drawList.drawCall.size(); - if (drawList.renderData.index() == 0) - { - const auto& renderGroups = std::get<0>(drawList.renderData); - for (const auto& renderGroup : renderGroups) - { - renderCallCount += renderGroup.drawables.size(); - } - } - else - { - const auto& renderItems = std::get<1>(drawList.renderData); - renderCallCount += renderItems.size(); - } - - return renderCallCount; + return static_cast(drawList.drawCall.size()) + drawList.drawableCount; } void ScriptableRenderPass::CollectRenderImages(const RenderTarget& renderData, const DrawList& drawList) { renderTextures.clear(); - renderTextures[renderData.target] = ImageUsage::ColorAttachment; + renderTextures[renderData.target] = ResourceUsage::ColorAttachment; const auto collectFn = [this](const Material* mat) @@ -98,14 +97,14 @@ namespace sh::render auto it = renderTextures.find(rt); if (it != renderTextures.end()) { - if (it->second == ImageUsage::ColorAttachment) + if (it->second == ResourceUsage::ColorAttachment) { SH_ERROR_FORMAT("RenderTexture {} is used as ColorAttachment", it->first->GetName().ToString()); continue; } } else - renderTextures[rt] = ImageUsage::SampledRead; + renderTextures[rt] = ResourceUsage::SampledRead; } }; diff --git a/src/Render/ScriptableRenderer.cpp b/src/Render/ScriptableRenderer.cpp index a9721bcd..03979911 100644 --- a/src/Render/ScriptableRenderer.cpp +++ b/src/Render/ScriptableRenderer.cpp @@ -11,7 +11,7 @@ #include namespace sh::render { - std::vector ScriptableRenderer::swapChainStates{}; + std::vector ScriptableRenderer::swapChainStates{}; ScriptableRenderer::ScriptableRenderer(IRenderContext& ctx) : ctx(ctx) @@ -144,8 +144,8 @@ namespace sh::render IRenderThrMethod::Configure(*cpyPass, target); std::vector preBarriers = BuildBarrierInfo(*cpyPass, imgIdx); - std::vector postBarriers = { BarrierInfo{imgIdx, swapChainStates[imgIdx], ImageUsage::Present} }; - swapChainStates[imgIdx] = ImageUsage::Present; + std::vector postBarriers = { BarrierInfo{imgIdx, swapChainStates[imgIdx], ResourceUsage::Present} }; + swapChainStates[imgIdx] = ResourceUsage::Present; RenderTarget rt{}; rt.frameIndex = imgIdx; @@ -183,7 +183,7 @@ namespace sh::render SH_RENDER_API void ScriptableRenderer::ResetSwapChainStates() { for (auto& state : swapChainStates) - state = ImageUsage::Undefined; + state = ResourceUsage::Undefined; } auto ScriptableRenderer::BuildBarrierInfo(ScriptableRenderPass& pass, uint32_t imgIdx) -> std::vector @@ -198,7 +198,7 @@ namespace sh::render { barrierInfo.target = imgIdx; if (swapChainStates.size() <= imgIdx) - swapChainStates.resize(imgIdx + 1, ImageUsage::Undefined); + swapChainStates.resize(imgIdx + 1, ResourceUsage::Undefined); barrierInfo.lastUsage = swapChainStates[imgIdx]; swapChainStates[imgIdx] = usage; diff --git a/src/Render/VulkanImpl/VulkanRenderImpl.cpp b/src/Render/VulkanImpl/VulkanRenderImpl.cpp index 5758a860..a8ae748b 100644 --- a/src/Render/VulkanImpl/VulkanRenderImpl.cpp +++ b/src/Render/VulkanImpl/VulkanRenderImpl.cpp @@ -44,36 +44,36 @@ namespace sh::render::vk VkPipelineStageFlags srcStage, dstStage; VkAccessFlags srcAccess, dstAccess; - auto mapUsageFn = + auto mapUsageFn = [&]( - ImageUsage u, + ResourceUsage u, VkImageLayout& layout, VkPipelineStageFlags& stage, VkAccessFlags& access) { switch (u) { - case ImageUsage::Undefined: + case ResourceUsage::Undefined: layout = VkImageLayout::VK_IMAGE_LAYOUT_UNDEFINED; stage = VkPipelineStageFlagBits::VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; access = 0; return; - case ImageUsage::ColorAttachment: + case ResourceUsage::ColorAttachment: layout = VkImageLayout::VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; stage = VkPipelineStageFlagBits::VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; access = VkAccessFlagBits::VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VkAccessFlagBits::VK_ACCESS_COLOR_ATTACHMENT_READ_BIT; return; - case ImageUsage::SampledRead: + case ResourceUsage::SampledRead: layout = VkImageLayout::VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; stage = VkPipelineStageFlagBits::VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT; access = VkAccessFlagBits::VK_ACCESS_SHADER_READ_BIT; return; - case ImageUsage::Present: + case ResourceUsage::Present: layout = VkImageLayout::VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; stage = VkPipelineStageFlagBits::VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; access = 0; return; - case ImageUsage::Src: + case ResourceUsage::TransferSrc: layout = VkImageLayout::VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; stage = VkPipelineStageFlagBits::VK_PIPELINE_STAGE_TRANSFER_BIT; access = VkAccessFlagBits::VK_ACCESS_TRANSFER_READ_BIT; @@ -88,7 +88,7 @@ namespace sh::render::vk if (msaaBuffer != nullptr) { // MSAA 버퍼를 다른 용도로 쓸 일이 없을듯? - //if (barrier.curUsage != ImageUsage::Present) + //if (barrier.curUsage != ResourceUsage::Present) //VulkanImageBuffer::BarrierCommand(cmd.GetCommandBuffer(), *msaaBuffer, srcLayout, dstLayout, srcStage, dstStage, srcAccess, dstAccess); } } From 497a7aae8f84904e4d73b2817325e30b8d240c66 Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Tue, 28 Apr 2026 07:13:44 +0900 Subject: [PATCH 14/60] fix: Fix flickering draw call overlay in viewport --- src/Render/VulkanImpl/VulkanRenderer.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Render/VulkanImpl/VulkanRenderer.cpp b/src/Render/VulkanImpl/VulkanRenderer.cpp index 1b56817a..99356ae0 100644 --- a/src/Render/VulkanImpl/VulkanRenderer.cpp +++ b/src/Render/VulkanImpl/VulkanRenderer.cpp @@ -180,7 +180,6 @@ namespace sh::render::vk cams.push_back(camera); } - SetDrawCallCount(0); uint32_t renderCallCount = 0; for (const Camera* cam : cams) { From 6d70f0c8550006bc79e87d3373185738a5ee5405 Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Wed, 29 Apr 2026 03:11:23 +0900 Subject: [PATCH 15/60] fix(Shader): Fix serialization issue with multi-pass shaders --- src/Game/Asset/ShaderLoader.cpp | 14 +++++++------- src/Render/Shader.cpp | 8 +++++--- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/Game/Asset/ShaderLoader.cpp b/src/Game/Asset/ShaderLoader.cpp index b2c424d7..b180b40d 100644 --- a/src/Game/Asset/ShaderLoader.cpp +++ b/src/Game/Asset/ShaderLoader.cpp @@ -61,16 +61,16 @@ namespace sh::game SH_ERROR_FORMAT("Shader parsing error: {}", e.what()); return nullptr; } - for (auto& passNode : shaderCI.shaderNode.passes) + for (const render::ShaderAST::PassNode& passNode : shaderCI.shaderNode.passes) { - auto shaderPaths = render::ShaderGenerator::GenerateShaderFile(shaderCI.shaderNode.shaderName, passNode, cachePath); + std::vector shaderPaths = render::ShaderGenerator::GenerateShaderFile(shaderCI.shaderNode.shaderName, passNode, cachePath); - for (auto& shaderPath : shaderPaths) + for (const std::filesystem::path& shaderPath : shaderPaths) { std::string name = shaderPath.filename().string() + ".spv"; std::filesystem::path spirvPath = (shaderPath.parent_path() / name); - auto stageType = render::ShaderPassBuilder::shaderType::Vertex; + render::ShaderPassBuilder::shaderType stageType = render::ShaderPassBuilder::shaderType::Vertex; if (shaderPath.extension() == ".frag") stageType = render::ShaderPassBuilder::shaderType::Fragment; @@ -90,7 +90,7 @@ namespace sh::game return nullptr; } // spir-v 불러오기 - auto spirv = core::FileSystem::LoadBinary(spirvPath); + std::optional> spirv = core::FileSystem::LoadBinary(spirvPath); if (!spirv.has_value()) { SH_ERROR_FORMAT("Temp shader load error: {}", spirvPath.string()); @@ -100,14 +100,14 @@ namespace sh::game } // 패스 생성 - render::ShaderPass* shaderPass = passBuilder->Build(passNode); + render::ShaderPass* const shaderPass = passBuilder->Build(passNode); if (shaderPass == nullptr) return nullptr; shaderCI.passes.push_back(shaderPass); } // 셰이더 생성 - auto shader = core::SObject::Create(std::move(shaderCI)); + render::Shader* const shader = core::SObject::Create(std::move(shaderCI)); return shader; } diff --git a/src/Render/Shader.cpp b/src/Render/Shader.cpp index 68be923a..3c35c51e 100644 --- a/src/Render/Shader.cpp +++ b/src/Render/Shader.cpp @@ -107,18 +107,20 @@ namespace sh::render this->passes = std::move(passes); for (ShaderPass* pass : this->passes) { + if (pass == nullptr) + continue; if (pass->GetLightingBinding() != -1) bUsingLight = true; if (pass->GetSkinBinding() != -1) bUsingSkin = true; - const core::Name& lightingPassName = this->passes.back()->GetLightingPassName(); + const core::Name& lightingPassName = pass->GetLightingPassName(); - LightingPassData* lightingPassData = GetLightingPass(lightingPassName); + LightingPassData* const lightingPassData = GetLightingPass(lightingPassName); if (lightingPassData == nullptr) { std::vector> v{}; - v.push_back(*this->passes.back()); + v.push_back(*pass); LightingPassData passData{ lightingPassName }; passData.passes = std::move(v); From 843ae39ac96c28cb5484674fccd20f78c99e52d5 Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Wed, 29 Apr 2026 08:08:54 +0900 Subject: [PATCH 16/60] fix(Camera): Update projection matrix when pos and lookPos change --- src/Render/Camera.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Render/Camera.cpp b/src/Render/Camera.cpp index a850ae44..176e80f1 100644 --- a/src/Render/Camera.cpp +++ b/src/Render/Camera.cpp @@ -84,6 +84,7 @@ namespace sh::render { this->pos[core::ThreadType::Game] = pos; dirtyMask |= 1; + UpdateProjMatrix(); UpdateViewMatrix(); SyncDirty(); } @@ -91,6 +92,7 @@ namespace sh::render { this->to[core::ThreadType::Game] = pos; dirtyMask |= 2; + UpdateProjMatrix(); UpdateViewMatrix(); SyncDirty(); } From 9b6e31d6b278fad937336b7c032ee730c909ad74 Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Wed, 29 Apr 2026 02:44:53 +0900 Subject: [PATCH 17/60] chore: Destroy legacy class(VulkanTextureBuffer) --- .../Render/VulkanImpl/VulkanTextureBuffer.h | 45 ------ src/Editor/Component/EditorUI.cpp | 1 - src/Render/VulkanImpl/VulkanTextureBuffer.cpp | 153 ------------------ 3 files changed, 199 deletions(-) delete mode 100644 include/Render/VulkanImpl/VulkanTextureBuffer.h delete mode 100644 src/Render/VulkanImpl/VulkanTextureBuffer.cpp diff --git a/include/Render/VulkanImpl/VulkanTextureBuffer.h b/include/Render/VulkanImpl/VulkanTextureBuffer.h deleted file mode 100644 index cc27a82d..00000000 --- a/include/Render/VulkanImpl/VulkanTextureBuffer.h +++ /dev/null @@ -1,45 +0,0 @@ -//#pragma once -//#include "../Export.h" -//#include "../ITextureBuffer.h" -// -//#include "VulkanImageBuffer.h" -// -//#include -// -//namespace sh::render::vk -//{ -// class VulkanContext; -// class VulkanQueueManager; -// -// class VulkanTextureBuffer : public ITextureBuffer -// { -// public: -// SH_RENDER_API VulkanTextureBuffer(); -// SH_RENDER_API VulkanTextureBuffer(VulkanTextureBuffer&& other) noexcept; -// SH_RENDER_API ~VulkanTextureBuffer(); -// -// SH_RENDER_API void Create(const IRenderContext& context, const CreateInfo& info) override; -// SH_RENDER_API void Clean() override; -// -// /// @brief [게임 스레드용] 텍스쳐 버퍼에 데이터를 지정한다. -// /// @param data 데이터 -// /// @param mipLevel 밉 레벨 -// SH_RENDER_API void SetData(const void* data, uint32_t mipLevel = 1) override; -// -// SH_RENDER_API auto GetImageBuffer() const -> VulkanImageBuffer*; -// SH_RENDER_API auto GetSize() const -> std::size_t; -// private: -// void CopyBufferToImageCommand(VkCommandBuffer cmd, VkBuffer buffer, VkImage image, uint32_t width, uint32_t height, uint32_t mipLevel); -// private: -// const VulkanContext* context = nullptr; -// VulkanQueueManager* queueManager = nullptr; -// -// std::unique_ptr imgBuffer; -// -// uint32_t width = 32, height = 32, channel = 4; -// -// VkFormat format = VkFormat::VK_FORMAT_R8G8B8A8_UNORM; -// -// bool isRenderTexture; -// }; -//} \ No newline at end of file diff --git a/src/Editor/Component/EditorUI.cpp b/src/Editor/Component/EditorUI.cpp index 91ece94e..438edb04 100644 --- a/src/Editor/Component/EditorUI.cpp +++ b/src/Editor/Component/EditorUI.cpp @@ -12,7 +12,6 @@ #include "Game/Asset/FontGenerator.h" #include "Game/Input.h" -#include "Render/VulkanImpl/VulkanTextureBuffer.h" #include "Render/RenderTexture.h" #include diff --git a/src/Render/VulkanImpl/VulkanTextureBuffer.cpp b/src/Render/VulkanImpl/VulkanTextureBuffer.cpp deleted file mode 100644 index e4fc7f79..00000000 --- a/src/Render/VulkanImpl/VulkanTextureBuffer.cpp +++ /dev/null @@ -1,153 +0,0 @@ -//#include "VulkanTextureBuffer.h" -//#include "VulkanContext.h" -//#include "VulkanBuffer.h" -//#include "VulkanFramebuffer.h" -//#include "VulkanQueueManager.h" -//#include "VulkanCommandBuffer.h" -//#include "VulkanCommandBufferPool.h" -// -//#include -//#include -//namespace sh::render::vk -//{ -// VulkanTextureBuffer::VulkanTextureBuffer() : -// isRenderTexture(false) -// { -// } -// VulkanTextureBuffer::VulkanTextureBuffer(VulkanTextureBuffer&& other) noexcept : -// context(other.context), queueManager(other.queueManager), -// imgBuffer(std::move(other.imgBuffer)), -// isRenderTexture(other.isRenderTexture), -// width(other.width), height(other.height) -// { -// other.context = nullptr; -// other.queueManager = nullptr; -// } -// VulkanTextureBuffer::~VulkanTextureBuffer() -// { -// } -// SH_RENDER_API void VulkanTextureBuffer::Clean() -// { -// isRenderTexture = false; -// imgBuffer.reset(); -// } -// -// void VulkanTextureBuffer::CopyBufferToImageCommand(VkCommandBuffer cmd, VkBuffer buffer, VkImage image, uint32_t width, uint32_t height, uint32_t mipLevel) -// { -// VkBufferImageCopy region{}; -// region.bufferOffset = 0; -// region.bufferRowLength = 0; -// region.bufferImageHeight = 0; -// -// region.imageSubresource.aspectMask = VkImageAspectFlagBits::VK_IMAGE_ASPECT_COLOR_BIT; -// region.imageSubresource.mipLevel = mipLevel; -// region.imageSubresource.baseArrayLayer = 0; -// region.imageSubresource.layerCount = 1; -// -// region.imageOffset = { 0, 0, 0 }; -// region.imageExtent = -// { -// width, -// height, -// 1 -// }; -// -// vkCmdCopyBufferToImage(cmd, buffer, image, VkImageLayout::VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); -// } -// -// SH_RENDER_API void VulkanTextureBuffer::Create(const IRenderContext& context, const CreateInfo& info) -// { -// this->context = &static_cast(context); -// queueManager = &this->context->GetQueueManager(); -// -// isRenderTexture = false; -// width = info.width; -// height = info.height; -// uint32_t mipLevels = info.bGenerateMipmap ? static_cast(std::floor(std::log2(std::max(width, height)))) + 1 : 1; -// switch (info.format) -// { -// case Texture::TextureFormat::SRGB24: [[fallthrough]]; // 일부 GPU는 3채널을 지원하지 않음 -// case Texture::TextureFormat::SRGBA32: -// format = VkFormat::VK_FORMAT_R8G8B8A8_SRGB; -// channel = 4; -// break; -// case Texture::TextureFormat::RGB24: [[fallthrough]]; // 일부 GPU는 3채널을 지원하지 않음 -// case Texture::TextureFormat::RGBA32: -// format = VkFormat::VK_FORMAT_R8G8B8A8_UNORM; -// channel = 4; -// break; -// } -// if (imgBuffer == nullptr) -// imgBuffer = std::make_unique(*this->context); -// imgBuffer->SetAnisotropy(info.aniso). -// SetFilter(info.filtering == 0 ? VkFilter::VK_FILTER_LINEAR : VkFilter::VK_FILTER_NEAREST); -// imgBuffer->Create(width, height, format, -// VkImageUsageFlagBits::VK_IMAGE_USAGE_TRANSFER_DST_BIT | -// VkImageUsageFlagBits::VK_IMAGE_USAGE_SAMPLED_BIT, -// VkImageAspectFlagBits::VK_IMAGE_ASPECT_COLOR_BIT, -// VkSampleCountFlagBits::VK_SAMPLE_COUNT_1_BIT, -// mipLevels); -// } -// SH_RENDER_API void VulkanTextureBuffer::Create(const Framebuffer& framebuffer) -// { -// isRenderTexture = true; -// this->framebuffer = &framebuffer; -// } -// -// SH_RENDER_API void VulkanTextureBuffer::SetData(const void* data, uint32_t mipLevel) -// { -// VulkanBuffer stagingBuffer{ *context }; -// -// uint32_t mipWidth = width; -// uint32_t mipHeight = height; -// for (int i = 0; i < mipLevel; ++i) -// { -// mipWidth = std::max(1u, mipWidth / 2); -// mipHeight = std::max(1u, mipHeight / 2); -// } -// std::size_t bufferSize = mipWidth * mipHeight * channel; -// -// stagingBuffer.Create(bufferSize, -// VkBufferUsageFlagBits::VK_BUFFER_USAGE_TRANSFER_SRC_BIT, -// VkSharingMode::VK_SHARING_MODE_EXCLUSIVE, -// VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); -// stagingBuffer.SetData(data); -// -// VulkanCommandBuffer* cmd = context->GetCommandBufferPool().AllocateCommandBuffer(std::this_thread::get_id(), VkQueueFlagBits::VK_QUEUE_GRAPHICS_BIT); -// cmd->Build([&] -// { -// imgBuffer->ChangeLayoutCommand(cmd->GetCommandBuffer(), -// VkImageLayout::VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, -// VkPipelineStageFlagBits::VK_PIPELINE_STAGE_TRANSFER_BIT, -// VkAccessFlagBits::VK_ACCESS_TRANSFER_WRITE_BIT); -// -// CopyBufferToImageCommand(cmd->GetCommandBuffer(), stagingBuffer.GetBuffer(), imgBuffer->GetImage(), mipWidth, mipHeight, mipLevel); -// -// imgBuffer->ChangeLayoutCommand(cmd->GetCommandBuffer(), -// VkImageLayout::VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, -// VkPipelineStageFlagBits::VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, -// VkAccessFlagBits::VK_ACCESS_SHADER_READ_BIT); -// }, -// true -// ); -// -// VkFence fence = cmd->GetOrCreateFence(); -// queueManager->Submit(VulkanQueueManager::Role::Graphics, *cmd, fence); -// vkWaitForFences(context->GetDevice(), 1, &fence, true, std::numeric_limits::max()); -// vkResetFences(context->GetDevice(), 1, &fence); -// -// context->GetCommandBufferPool().DeallocateCommandBuffer(*cmd); -// } -// -// SH_RENDER_API auto VulkanTextureBuffer::GetImageBuffer() const -> VulkanImageBuffer* -// { -// if(!isRenderTexture) -// return imgBuffer.get(); -// else -// return static_cast(framebuffer)->GetColorImg(); -// } -// SH_RENDER_API auto VulkanTextureBuffer::GetSize() const -> std::size_t -// { -// return width * height * channel; -// } -//} \ No newline at end of file From 66a5efe5410a3c0cee93afe9ec3317f7155e3066 Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Tue, 28 Apr 2026 23:57:50 +0900 Subject: [PATCH 18/60] refact(EditorRenderer): Derive EditorRenderer from GameRenderer --- include/Editor/EditorRenderer.h | 11 ++++++----- include/Game/GameRenderer.h | 13 +++++++++--- src/Editor/EditorRenderer.cpp | 35 +++++++-------------------------- src/Editor/EditorWorld.cpp | 5 +++-- src/Game/GameRenderer.cpp | 15 ++++++++++---- src/Game/World.cpp | 4 +++- 6 files changed, 40 insertions(+), 43 deletions(-) diff --git a/include/Editor/EditorRenderer.h b/include/Editor/EditorRenderer.h index 320a2140..68570897 100644 --- a/include/Editor/EditorRenderer.h +++ b/include/Editor/EditorRenderer.h @@ -6,19 +6,23 @@ #include "Render/ScriptableRenderer.h" #include "Game/Component/Render/Camera.h" +#include "Game/GameRenderer.h" #include namespace sh::game { class ImGUImpl; class UIPass; + class World; } namespace sh::editor { - class EditorRenderer : public render::ScriptableRenderer + class EditorRenderer : public game::GameRenderer { public: - SH_EDITOR_API EditorRenderer(render::IRenderContext& ctx, game::ImGUImpl& guictx); + SH_EDITOR_API EditorRenderer(render::IRenderContext& ctx, game::ImGUImpl& guictx, game::World& world); + + SH_EDITOR_API void Init() override; SH_EDITOR_API auto GetOutlinePass() const -> EditorOutlinePass* { return outlinePass; } SH_EDITOR_API auto GetPostOutlinePass() const -> EditorPostOutlinePass* { return postOutlinePass; } @@ -37,8 +41,5 @@ namespace sh::editor EditorPostOutlinePass* postOutlinePass = nullptr; game::UIPass* uiPass = nullptr; //render::TransparentPipeline* transParentPass = nullptr; - - std::unordered_map> allowedCamera; - std::unordered_map> ignoreCamera; }; }//namespace \ No newline at end of file diff --git a/include/Game/GameRenderer.h b/include/Game/GameRenderer.h index c550bd1e..76abe169 100644 --- a/include/Game/GameRenderer.h +++ b/include/Game/GameRenderer.h @@ -9,19 +9,26 @@ namespace sh::game { class ImGUImpl; + class World; class GameRenderer : public render::ScriptableRenderer { public: - SH_GAME_API GameRenderer(render::IRenderContext& ctx, game::ImGUImpl& guictx); + SH_GAME_API GameRenderer(render::IRenderContext& ctx, game::ImGUImpl& guictx, World& world); + SH_GAME_API void Init() override; SH_GAME_API void Setup(const render::RenderTarget& data) override; SH_GAME_API void SetUICamera(const render::Camera& camera); - private: - const render::Camera* uiCamera = nullptr; + protected: + protected: + render::IRenderContext& renderCtx; + World& world; + game::ImGUImpl& guiCtx; std::unordered_map> allowedCamera; std::unordered_map> ignoreCamera; + private: + const render::Camera* uiCamera = nullptr; }; }//namespace \ No newline at end of file diff --git a/src/Editor/EditorRenderer.cpp b/src/Editor/EditorRenderer.cpp index c85826c7..959cb008 100644 --- a/src/Editor/EditorRenderer.cpp +++ b/src/Editor/EditorRenderer.cpp @@ -7,8 +7,11 @@ #include namespace sh::editor { - EditorRenderer::EditorRenderer(render::IRenderContext& ctx, game::ImGUImpl& guictx) : - ScriptableRenderer(ctx) + EditorRenderer::EditorRenderer(render::IRenderContext& ctx, game::ImGUImpl& guictx, game::World& world) : + GameRenderer(ctx, guictx, world) + { + } + SH_EDITOR_API void EditorRenderer::Init() { AddRenderPass(core::Name{ "EditorPicking" }, render::RenderQueue::Picking); outlinePass = &AddRenderPass(); @@ -17,7 +20,7 @@ namespace sh::editor AddRenderPass("UI", render::RenderQueue::Transparent); postOutlinePass = &AddRenderPass(ctx); uiPass = &AddRenderPass(); - uiPass->SetImGUIContext(guictx); + uiPass->SetImGUIContext(guiCtx); } SH_EDITOR_API void EditorRenderer::SetImGUICamera(const render::Camera& camera) { @@ -47,30 +50,6 @@ namespace sh::editor SH_EDITOR_API void EditorRenderer::Setup(const render::RenderTarget& data) { - // allowedCamera에 등록된 패스는 등록된 카메라에서만 실행됨 - // ignoreCamera에 등록된 패스는 등록된 카메라에서는 실행 안 함 - for (auto& pass : allPasses) - { - auto allowedIt = allowedCamera.find(pass->passName.ToString()); - if (allowedIt != allowedCamera.end()) - { - const auto& allowedCams = allowedIt->second; - bool bAllowed = std::find(allowedCams.begin(), allowedCams.end(), data.camera) != allowedCams.end(); - if (!bAllowed) - continue; - } - - auto it = ignoreCamera.find(pass->passName.ToString()); - if (it == ignoreCamera.end()) - { - EnqueRenderPass(*pass); - continue; - } - const auto& ignoreCams = it->second; - bool bIgnore = std::find(ignoreCams.begin(), ignoreCams.end(), data.camera) != ignoreCams.end(); - if (bIgnore) - continue; - EnqueRenderPass(*pass); - } + GameRenderer::Setup(data); } }//namespace \ No newline at end of file diff --git a/src/Editor/EditorWorld.cpp b/src/Editor/EditorWorld.cpp index 34148413..f6f0ae5f 100644 --- a/src/Editor/EditorWorld.cpp +++ b/src/Editor/EditorWorld.cpp @@ -80,9 +80,10 @@ namespace sh::editor SH_EDITOR_API void EditorWorld::SetupRenderer() { - customRenderer = std::make_unique(*renderer.GetContext(), GetUiContext()); + customRenderer = std::make_unique(*renderer.GetContext(), GetUiContext(), *this); renderer.SetScriptableRenderer(*customRenderer); - auto& editorRenderer = static_cast(*customRenderer); + EditorRenderer& editorRenderer = static_cast(*customRenderer); + editorRenderer.Init(); auto& uiCamera = GetUICamera(); uiCamera.SetPriority(1000); diff --git a/src/Game/GameRenderer.cpp b/src/Game/GameRenderer.cpp index 0fee30a0..3e04bb98 100644 --- a/src/Game/GameRenderer.cpp +++ b/src/Game/GameRenderer.cpp @@ -1,20 +1,27 @@ #include "GameRenderer.h" #include "UIPass.h" +#include "World.h" #include "Render/RenderPass/TransparentPass.h" namespace sh::game { - GameRenderer::GameRenderer(render::IRenderContext& ctx, game::ImGUImpl& guictx) : - ScriptableRenderer(ctx) + GameRenderer::GameRenderer(render::IRenderContext& ctx, game::ImGUImpl& guictx, World& world) : + ScriptableRenderer(ctx), + renderCtx(ctx), + world(world), + guiCtx(guictx) + { + } + SH_GAME_API void GameRenderer::Init() { AddRenderPass(core::Name{ "Opaque" }, render::RenderQueue::Opaque); AddRenderPass(); AddRenderPass("UI", render::RenderQueue::UI); - AddRenderPass().SetImGUIContext(guictx); + AddRenderPass().SetImGUIContext(guiCtx); } SH_GAME_API void GameRenderer::Setup(const render::RenderTarget& data) { - for (auto& pass : allPasses) + for (const std::unique_ptr& pass : allPasses) { auto allowedIt = allowedCamera.find(pass->passName.ToString()); if (allowedIt != allowedCamera.end()) diff --git a/src/Game/World.cpp b/src/Game/World.cpp index eaa26ed8..358db35b 100644 --- a/src/Game/World.cpp +++ b/src/Game/World.cpp @@ -129,7 +129,9 @@ namespace sh::game if (customRenderer == nullptr) { renderer.AddCamera(uiCamera); - customRenderer = std::make_unique(*renderer.GetContext(), GetUiContext()); + customRenderer = std::make_unique(*renderer.GetContext(), GetUiContext(), *this); + customRenderer->Init(); + renderer.SetScriptableRenderer(*customRenderer); } From 86f792c796adb90cd3e0fd7f18964bb7e5b76174 Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Wed, 29 Apr 2026 08:24:40 +0900 Subject: [PATCH 19/60] feat: Implement shadow pass prototype with shadow map rendering --- include/Editor/EditorRenderer.h | 2 +- .../Game/Component/Render/DirectionalLight.h | 68 ++++- include/Game/Component/Render/PointLight.h | 18 +- include/Game/GameRenderer.h | 10 +- include/Game/ILight.h | 12 +- include/Render/Formats.hpp | 15 +- include/Render/RenderData.h | 4 +- include/Render/RenderPass/ShadowMapPass.h | 18 ++ include/Render/RenderTexture.h | 3 +- include/Render/ScriptableRenderer.h | 8 +- include/Render/VulkanImpl/VulkanImageBuffer.h | 1 - src/Editor/EditorRenderer.cpp | 28 +- .../Component/Render/DirectionalLight.cpp | 147 ++++++++++- src/Game/Component/Render/PointLight.cpp | 29 ++- src/Game/GameRenderer.cpp | 54 +++- src/Render/RenderPass/ShadowMapPass.cpp | 16 ++ src/Render/RenderTexture.cpp | 19 +- src/Render/ScriptableRenderPass.cpp | 20 +- src/Render/ScriptableRenderer.cpp | 20 +- src/Render/VulkanImpl/VulkanImageBuffer.cpp | 30 ++- src/Render/VulkanImpl/VulkanPipeline.cpp | 13 +- src/Render/VulkanImpl/VulkanRenderImpl.cpp | 240 +++++++++++------- src/Render/VulkanImpl/VulkanRenderer.cpp | 17 +- 23 files changed, 606 insertions(+), 186 deletions(-) create mode 100644 include/Render/RenderPass/ShadowMapPass.h create mode 100644 src/Render/RenderPass/ShadowMapPass.cpp diff --git a/include/Editor/EditorRenderer.h b/include/Editor/EditorRenderer.h index 68570897..1e352cdb 100644 --- a/include/Editor/EditorRenderer.h +++ b/include/Editor/EditorRenderer.h @@ -3,7 +3,7 @@ #include "EditorOutlinePass.h" #include "EditorPostOutlinePass.h" -#include "Render/ScriptableRenderer.h" +#include "Render/RenderPass/ShadowMapPass.h" #include "Game/Component/Render/Camera.h" #include "Game/GameRenderer.h" diff --git a/include/Game/Component/Render/DirectionalLight.h b/include/Game/Component/Render/DirectionalLight.h index 5a2ca5ab..812b8605 100644 --- a/include/Game/Component/Render/DirectionalLight.h +++ b/include/Game/Component/Render/DirectionalLight.h @@ -3,31 +3,77 @@ #include "Game/ILight.h" #include "Game/Component/Component.h" +#include "Render/Camera.h" +#include "Render/RenderTexture.h" + +#include + namespace sh::game { class DirectionalLight : public Component, public ILight { COMPONENT(DirectionalLight) - private: - PROPERTY(direction) - Vec3 direction{ -1.f, -1.f, -1.f }; - PROPERTY(intensity) - float intensity = 1.f; public: SH_GAME_API DirectionalLight(GameObject& owner); SH_GAME_API ~DirectionalLight(); + SH_GAME_API void Awake() override; + SH_GAME_API void BeginUpdate() override; SH_GAME_API void OnDestroy() override; + SH_GAME_API void OnPropertyChanged(const core::reflection::Property& prop) override; SH_GAME_API bool Intersect(const render::AABB& aabb) const override; - SH_GAME_API void SetIntensity(float intensity); - SH_GAME_API auto GetIntensity() const -> float; - + SH_GAME_API void SetIntensity(float intensity) override; SH_GAME_API void SetDirection(const Vec3& dir); - SH_GAME_API auto GetDirection() const -> const Vec3&; + SH_GAME_API void SetCastShadow(bool b) override; + SH_GAME_API void SetShadowBias(float bias) override; + SH_GAME_API void SetShadowMapResolution(uint32_t res) override; - SH_GAME_API auto GetPos() const -> const Vec3& override; + SH_GAME_API auto GetPos() const -> const Vec3 & override; SH_GAME_API auto GetLightType() const -> ILight::Type override; + SH_GAME_API auto IsCastShadow() const -> bool override { return bCastShadow; } + SH_GAME_API auto GetShadowBias() const -> float override { return shadowBias; } + SH_GAME_API auto GetShadowMapResolution() const -> uint32_t override { return shadowMapResolution; } + SH_GAME_API auto GetDirection() const -> const Vec3& { return direction; } + SH_GAME_API auto GetIntensity() const -> float { return intensity; } + + /// @brief 디렉셔널 라이트의 광원 공간 변환 행렬 (proj * view)을 계산한다. + /// @details 디렉셔널 라이트는 평행광이므로 직교 투영을 사용한다. 광원 위치는 씬 중심에서 광선 + /// 반대 방향으로 farPlane * 0.5 만큼 이동한 가상 위치를 사용한다. + /// 현재 구현은 단일 셰도우 맵 기준이며, CSM은 후속 작업으로 남겨둔다. + SH_GAME_API auto GetLightSpaceMatrix() const -> glm::mat4; + + SH_GAME_API auto GetShadowCamera() -> render::Camera*; + SH_GAME_API auto GetShadowCamera() const -> const render::Camera*; + SH_GAME_API auto GetShadowMap() const -> render::RenderTexture*; + private: + /// @brief bCastShadow=true 시점에 호출. RT를 생성/빌드하고 카메라를 직교투영으로 설정. + void EnsureShadowResources(); + /// @brief 매 프레임 광원 방향에 맞춰 shadowCamera의 pos/lookAt을 갱신. + void UpdateShadowCamera(); + private: + PROPERTY(direction) + Vec3 direction{ -1.f, -1.f, -1.f }; + PROPERTY(intensity) + float intensity = 1.f; + + PROPERTY(bCastShadow) + bool bCastShadow = false; + PROPERTY(shadowBias) + float shadowBias = 0.005f; + PROPERTY(shadowMapResolution) + uint32_t shadowMapResolution = 1024; + PROPERTY(shadowOrthoSize) + float shadowOrthoSize = 20.f; + PROPERTY(shadowNearPlane) + float shadowNearPlane = 0.1f; + PROPERTY(shadowFarPlane) + float shadowFarPlane = 100.f; + + PROPERTY(shadowMap, core::PropertyOption::invisible) + render::RenderTexture* shadowMap = nullptr; + + render::Camera shadowCamera; }; -}//namespace \ No newline at end of file +}//namespace diff --git a/include/Game/Component/Render/PointLight.h b/include/Game/Component/Render/PointLight.h index c0b3bb32..f486ed2c 100644 --- a/include/Game/Component/Render/PointLight.h +++ b/include/Game/Component/Render/PointLight.h @@ -14,13 +14,20 @@ namespace sh::game PROPERTY(intensity) float intensity = 1.f; + PROPERTY(bCastShadow) + bool bCastShadow = false; + PROPERTY(shadowBias) + float shadowBias = 0.005f; + PROPERTY(shadowMapResolution) + uint32_t shadowMapResolution = 512; + Vec3 lastPos{ 0.f, 0.f, 0.f }; bool bUpdateOctree = false; public: SH_GAME_API PointLight(GameObject& owner); SH_GAME_API ~PointLight(); - SH_GAME_API void Destroy() override; + SH_GAME_API void OnDestroy() override; SH_GAME_API void BeginUpdate() override; @@ -33,7 +40,14 @@ namespace sh::game SH_GAME_API auto GetIntensity() const -> float; SH_GAME_API auto GetPos() const -> const Vec3& override; SH_GAME_API auto GetLightType() const -> ILight::Type override; - + + SH_GAME_API auto IsCastShadow() const -> bool override; + SH_GAME_API void SetCastShadow(bool b) override; + SH_GAME_API auto GetShadowBias() const -> float override; + SH_GAME_API void SetShadowBias(float bias) override; + SH_GAME_API auto GetShadowMapResolution() const -> uint32_t override; + SH_GAME_API void SetShadowMapResolution(uint32_t res) override; + SH_GAME_API void OnPropertyChanged(const core::reflection::Property& prop) override; }; }//namespace \ No newline at end of file diff --git a/include/Game/GameRenderer.h b/include/Game/GameRenderer.h index 76abe169..a16b6fb8 100644 --- a/include/Game/GameRenderer.h +++ b/include/Game/GameRenderer.h @@ -2,10 +2,12 @@ #include "Export.h" #include "Render/ScriptableRenderer.h" +#include "Render/RenderPass/ShadowMapPass.h" #include "Component/Render/Camera.h" #include +#include namespace sh::game { class ImGUImpl; @@ -20,14 +22,18 @@ namespace sh::game SH_GAME_API void Setup(const render::RenderTarget& data) override; SH_GAME_API void SetUICamera(const render::Camera& camera); + SH_GAME_API void AddShadowCasterCamera(const render::Camera& camera); + SH_GAME_API void RemoveShadowCasterCamera(const render::Camera& camera); protected: + SH_GAME_API void AddShadowPass(); protected: render::IRenderContext& renderCtx; World& world; game::ImGUImpl& guiCtx; - std::unordered_map> allowedCamera; - std::unordered_map> ignoreCamera; + render::ShadowMapPass* shadowMapPass = nullptr; + std::unordered_map> allowedCamera; + std::unordered_map> ignoreCamera; private: const render::Camera* uiCamera = nullptr; }; diff --git a/include/Game/ILight.h b/include/Game/ILight.h index f0f102d2..419ef6ef 100644 --- a/include/Game/ILight.h +++ b/include/Game/ILight.h @@ -2,6 +2,8 @@ #include "Game/Export.h" #include "IOctreeElement.h" +#include + namespace sh::render { class Material; @@ -20,6 +22,12 @@ namespace sh::game virtual ~ILight() {}; virtual void SetIntensity(float intensity) = 0; virtual auto GetLightType() const -> Type = 0; - //virtual void SetMaterialData(render::Material& mat) = 0; + + virtual auto IsCastShadow() const -> bool { return false; } + virtual void SetCastShadow(bool b) {} + virtual auto GetShadowBias() const -> float { return 0.005f; } + virtual void SetShadowBias(float bias) {} + virtual auto GetShadowMapResolution() const -> uint32_t { return 1024; } + virtual void SetShadowMapResolution(uint32_t res) {} }; -}//namespace \ No newline at end of file +}//namespace diff --git a/include/Render/Formats.hpp b/include/Render/Formats.hpp index 8be04fb1..04460740 100644 --- a/include/Render/Formats.hpp +++ b/include/Render/Formats.hpp @@ -16,7 +16,9 @@ namespace sh::render R8, D32S8, D24S8, - D16S8 + D16S8, + D32, + D16 }; static auto GetTextureFormatChannel(TextureFormat format) -> int @@ -28,4 +30,15 @@ namespace sh::render } return 4; } + + static auto IsDepthTexture(TextureFormat format) -> bool + { + if (format == TextureFormat::D32S8 || + format == TextureFormat::D24S8 || + format == TextureFormat::D16S8 || + format == TextureFormat::D32 || + format == TextureFormat::D16) + return true; + return false; + } }//namespace \ No newline at end of file diff --git a/include/Render/RenderData.h b/include/Render/RenderData.h index 68589c73..047702f3 100644 --- a/include/Render/RenderData.h +++ b/include/Render/RenderData.h @@ -52,7 +52,9 @@ namespace sh::render ColorAttachment, SampledRead, Present, - TransferSrc + TransferSrc, + DepthStencilAttachment, + DepthStencilSampledRead }; struct BarrierInfo diff --git a/include/Render/RenderPass/ShadowMapPass.h b/include/Render/RenderPass/ShadowMapPass.h new file mode 100644 index 00000000..c6c1a488 --- /dev/null +++ b/include/Render/RenderPass/ShadowMapPass.h @@ -0,0 +1,18 @@ +#pragma once +#include "../Export.h" +#include "../ScriptableRenderPass.h" + +#include + +#include +namespace sh::render +{ + class Camera; + class RenderTexture; + + class ShadowMapPass : public ScriptableRenderPass + { + public: + SH_RENDER_API ShadowMapPass(); + }; +}//namespace diff --git a/include/Render/RenderTexture.h b/include/Render/RenderTexture.h index a767da9b..738e7de0 100644 --- a/include/Render/RenderTexture.h +++ b/include/Render/RenderTexture.h @@ -36,7 +36,8 @@ namespace sh::render SH_RENDER_API auto GetLayout() const -> const RenderTargetLayout& { return layout; } SH_RENDER_API auto GetMSAABuffer() const -> ITextureBuffer* { return msaaBuffer.get(); } - SH_RENDER_API auto GetDepthBuffer() const -> ITextureBuffer* { return depthBuffer.get(); } + SH_RENDER_API auto GetDepthBuffer() const -> ITextureBuffer*; + SH_RENDER_API auto IsDepthOnly() const -> bool { return layout.format == TextureFormat::None; } SH_RENDER_API auto GetUsage() const -> ResourceUsage { return usage; } protected: SH_RENDER_API void ChangeUsage(ResourceUsage newUsage); diff --git a/include/Render/ScriptableRenderer.h b/include/Render/ScriptableRenderer.h index f52cea56..bee9287a 100644 --- a/include/Render/ScriptableRenderer.h +++ b/include/Render/ScriptableRenderer.h @@ -35,7 +35,7 @@ namespace sh::render { friend IRenderThrMethod; public: - struct SubmittedCommand + struct RecordedCommand { ScriptableRenderPass& pass; CommandBuffer& cmd; @@ -44,12 +44,14 @@ namespace sh::render SH_RENDER_API ScriptableRenderer(IRenderContext& ctx); SH_RENDER_API virtual ~ScriptableRenderer(); + SH_RENDER_API virtual void Init() {}; SH_RENDER_API auto AddRenderPass(const core::Name& passName, RenderQueue renderQueue) -> ScriptableRenderPass&; SH_RENDER_API auto ReadRenderTextureAsync(RenderTexture& rt, int x, int y) -> std::future>; SH_RENDER_API void Dispatch(const ComputeShader& computeShader, uint32_t x, uint32_t y, uint32_t z); - SH_RENDER_API auto GetSubmittedCommands() const -> const std::vector& { return submittedCmds; } + SH_RENDER_API auto GetRecordedCommands() const -> const std::vector& { return recordedCmds; } + SH_RENDER_API auto HasPass(const core::Name& passName) const -> bool; template>, typename... Args> auto AddRenderPass(Args&&... args) -> T&; @@ -78,7 +80,7 @@ namespace sh::render SH_RENDER_API static std::vector swapChainStates; std::vector activePasses; - std::vector submittedCmds; + std::vector recordedCmds; std::unique_ptr cpyPass; struct SyncData diff --git a/include/Render/VulkanImpl/VulkanImageBuffer.h b/include/Render/VulkanImpl/VulkanImageBuffer.h index c8355b4d..3757a3b0 100644 --- a/include/Render/VulkanImpl/VulkanImageBuffer.h +++ b/include/Render/VulkanImpl/VulkanImageBuffer.h @@ -56,7 +56,6 @@ namespace sh::render::vk SH_RENDER_API static auto ConvertTextureFormat(TextureFormat format) -> VkFormat; private: static auto GetChannelCount(VkFormat format) -> uint32_t; - static auto IsDepthTexture(TextureFormat format) -> bool; private: const VulkanContext* ctx = nullptr; diff --git a/src/Editor/EditorRenderer.cpp b/src/Editor/EditorRenderer.cpp index 959cb008..c8c4e69e 100644 --- a/src/Editor/EditorRenderer.cpp +++ b/src/Editor/EditorRenderer.cpp @@ -15,6 +15,7 @@ namespace sh::editor { AddRenderPass(core::Name{ "EditorPicking" }, render::RenderQueue::Picking); outlinePass = &AddRenderPass(); + AddShadowPass(); AddRenderPass(core::Name{ "Opaque" }, render::RenderQueue::Opaque); AddRenderPass(); AddRenderPass("UI", render::RenderQueue::Transparent); @@ -25,27 +26,32 @@ namespace sh::editor SH_EDITOR_API void EditorRenderer::SetImGUICamera(const render::Camera& camera) { ImGUICamera = &camera; - allowedCamera["ImGUI"].push_back(&camera); - ignoreCamera["Opaque"].push_back(&camera); - ignoreCamera["Transparent"].push_back(&camera); - ignoreCamera["UI"].push_back(&camera); + allowedCamera[core::Name{ "ImGUI" }].insert(&camera); + for (const std::unique_ptr& pass : allPasses) + { + if (pass->passName == "ImGUI") + continue; + ignoreCamera[pass->passName].insert(&camera); + } } SH_EDITOR_API void EditorRenderer::SetEditorCamera(const render::Camera& camera) { editorCamera = &camera; - allowedCamera[outlinePass->passName].push_back(&camera); - allowedCamera[postOutlinePass->passName].push_back(&camera); + allowedCamera[outlinePass->passName].insert(&camera); + allowedCamera[postOutlinePass->passName].insert(&camera); uiPass->viewportTexture = editorCamera->GetRenderTexture(); } SH_EDITOR_API void EditorRenderer::SetPickingCamera(const render::Camera& camera) { pickingCamera = &camera; - allowedCamera["EditorPicking"].push_back(&camera); - ignoreCamera["Opaque"].push_back(&camera); - ignoreCamera["Transparent"].push_back(&camera); - ignoreCamera["ImGUI"].push_back(&camera); - ignoreCamera["UI"].push_back(&camera); + allowedCamera[core::Name{ "EditorPicking" }].insert(&camera); + for (const std::unique_ptr& pass : allPasses) + { + if (pass->passName == "EditorPicking") + continue; + ignoreCamera[pass->passName].insert(&camera); + } } SH_EDITOR_API void EditorRenderer::Setup(const render::RenderTarget& data) diff --git a/src/Game/Component/Render/DirectionalLight.cpp b/src/Game/Component/Render/DirectionalLight.cpp index b91a7eac..c4a91e45 100644 --- a/src/Game/Component/Render/DirectionalLight.cpp +++ b/src/Game/Component/Render/DirectionalLight.cpp @@ -1,22 +1,74 @@ #include "Component/Render/DirectionalLight.h" #include "World.h" +#include "GameRenderer.h" + +#include "Core/SObject.h" + +#include namespace sh::game { - SH_GAME_API DirectionalLight::DirectionalLight(GameObject& owner) : + DirectionalLight::DirectionalLight(GameObject& owner) : Component(owner) { world.GetLightOctree().Insert(*this); + + GameRenderer* const srPtr = dynamic_cast(world.renderer.GetScriptableRenderer()); + if (srPtr != nullptr) + { + world.renderer.AddCamera(shadowCamera); + srPtr->AddShadowCasterCamera(shadowCamera); + } + else + { + SH_ERROR("ScriptableRenderer is not GameRenderer!"); + } + canPlayInEditor = true; } - SH_GAME_API DirectionalLight::~DirectionalLight() + DirectionalLight::~DirectionalLight() = default; + + SH_GAME_API void DirectionalLight::Awake() { + Super::Awake(); + if (bCastShadow) + EnsureShadowResources(); + } + + SH_GAME_API void DirectionalLight::BeginUpdate() + { + Super::BeginUpdate(); + if (bCastShadow && shadowMap != nullptr) + UpdateShadowCamera(); } SH_GAME_API void DirectionalLight::OnDestroy() { + GameRenderer* const srPtr = dynamic_cast(world.renderer.GetScriptableRenderer()); + if (srPtr != nullptr) + { + srPtr->RemoveShadowCasterCamera(shadowCamera); + world.renderer.RemoveCamera(shadowCamera); + } world.GetLightOctree().Erase(*this); + Super::OnDestroy(); + } + + SH_GAME_API void DirectionalLight::OnPropertyChanged(const core::reflection::Property& prop) + { + if (prop.GetName() == "bCastShadow") + { + if (bCastShadow && shadowMap == nullptr) + EnsureShadowResources(); + // bCastShadow=false로 바뀌어도 RT는 보존(토글 시 재할당 비용 회피). + // 메모리 회수가 필요하면 별도 정리 API를 도입할 것. + } + else if (prop.GetName() == "shadowMapResolution") + { + if (shadowMap != nullptr) + shadowMap->SetSize(shadowMapResolution, shadowMapResolution); + } } SH_GAME_API bool DirectionalLight::Intersect(const render::AABB& aabb) const @@ -27,18 +79,10 @@ namespace sh::game { this->intensity = intensity; } - SH_GAME_API auto DirectionalLight::GetIntensity() const -> float - { - return intensity; - } SH_GAME_API void DirectionalLight::SetDirection(const Vec3& dir) { direction = dir; } - SH_GAME_API auto DirectionalLight::GetDirection() const -> const Vec3& - { - return direction; - } SH_GAME_API auto DirectionalLight::GetPos() const -> const Vec3& { return gameObject.transform->GetWorldPosition(); @@ -48,4 +92,85 @@ namespace sh::game { return ILight::Type::Directional; } -}//namespace \ No newline at end of file + SH_GAME_API void DirectionalLight::SetCastShadow(bool b) + { + bCastShadow = b; + if (bCastShadow && shadowMap == nullptr) + EnsureShadowResources(); + } + SH_GAME_API void DirectionalLight::SetShadowBias(float bias) + { + shadowBias = bias; + } + SH_GAME_API void DirectionalLight::SetShadowMapResolution(uint32_t res) + { + shadowMapResolution = res; + if (shadowMap != nullptr) + shadowMap->SetSize(res, res); + } + + SH_GAME_API auto DirectionalLight::GetLightSpaceMatrix() const -> glm::mat4 + { + const glm::vec3 dir = glm::normalize(glm::vec3{ direction.x, direction.y, direction.z }); + const glm::vec3 sceneCenter = glm::vec3{ 0.f, 0.f, 0.f }; + const float distance = shadowFarPlane * 0.5f; + const glm::vec3 lightPos = sceneCenter - dir * distance; + + const glm::vec3 worldUp = (glm::abs(dir.y) > 0.99f) ? glm::vec3{ 0.f, 0.f, 1.f } : glm::vec3{ 0.f, 1.f, 0.f }; + + const glm::mat4 view = glm::lookAt(lightPos, lightPos + dir, worldUp); + const glm::mat4 proj = glm::ortho(-shadowOrthoSize, shadowOrthoSize, -shadowOrthoSize, shadowOrthoSize, shadowNearPlane, shadowFarPlane); + return proj * view; + } + + SH_GAME_API auto DirectionalLight::GetShadowCamera() -> render::Camera* + { + return bCastShadow ? &shadowCamera : nullptr; + } + SH_GAME_API auto DirectionalLight::GetShadowCamera() const -> const render::Camera* + { + return bCastShadow ? &shadowCamera : nullptr; + } + SH_GAME_API auto DirectionalLight::GetShadowMap() const -> render::RenderTexture* + { + return bCastShadow ? shadowMap : nullptr; + } + + void DirectionalLight::EnsureShadowResources() + { + if (shadowMap == nullptr) + { + render::RenderTargetLayout layout{}; + layout.format = render::TextureFormat::None; + layout.depthFormat = render::TextureFormat::D32; + layout.bUseMSAA = false; + + shadowMap = core::SObject::Create(layout); + shadowMap->SetSize(shadowMapResolution, shadowMapResolution); + shadowMap->Build(*world.renderer.GetContext()); + shadowMap->SetName("DirectionalShadowMap"); + } + + shadowCamera.SetOrthographic(true); + shadowCamera.SetWidth(shadowOrthoSize * 2.f); + shadowCamera.SetHeight(shadowOrthoSize * 2.f); + shadowCamera.SetNearPlane(shadowNearPlane); + shadowCamera.SetFarPlane(shadowFarPlane); + shadowCamera.SetRenderTexture(shadowMap); + + UpdateShadowCamera(); + } + + void DirectionalLight::UpdateShadowCamera() + { + const glm::vec3 dir = glm::normalize(glm::vec3{ direction.x, direction.y, direction.z }); + const glm::vec3 sceneCenter = glm::vec3{ 0.f, 0.f, 0.f }; + const float distance = shadowFarPlane * 0.5f; + const glm::vec3 lightPos = sceneCenter - dir * distance; + const glm::vec3 worldUp = (glm::abs(dir.y) > 0.99f) ? glm::vec3{ 0.f, 0.f, 1.f } : glm::vec3{ 0.f, 1.f, 0.f }; + + shadowCamera.SetPos(lightPos); + shadowCamera.SetLookPos(sceneCenter); + shadowCamera.SetUpVector(worldUp); + } +}//namespace diff --git a/src/Game/Component/Render/PointLight.cpp b/src/Game/Component/Render/PointLight.cpp index e73354c9..ae2b3dc0 100644 --- a/src/Game/Component/Render/PointLight.cpp +++ b/src/Game/Component/Render/PointLight.cpp @@ -14,9 +14,9 @@ namespace sh::game { } - SH_GAME_API void PointLight::Destroy() + SH_GAME_API void PointLight::OnDestroy() { - Super::Destroy(); + Super::OnDestroy(); world.GetLightOctree().Erase(*this); } @@ -105,6 +105,31 @@ namespace sh::game return ILight::Type::Point; } + SH_GAME_API auto PointLight::IsCastShadow() const -> bool + { + return bCastShadow; + } + SH_GAME_API void PointLight::SetCastShadow(bool b) + { + bCastShadow = b; + } + SH_GAME_API auto PointLight::GetShadowBias() const -> float + { + return shadowBias; + } + SH_GAME_API void PointLight::SetShadowBias(float bias) + { + shadowBias = bias; + } + SH_GAME_API auto PointLight::GetShadowMapResolution() const -> uint32_t + { + return shadowMapResolution; + } + SH_GAME_API void PointLight::SetShadowMapResolution(uint32_t res) + { + shadowMapResolution = res; + } + SH_GAME_API void PointLight::OnPropertyChanged(const core::reflection::Property& prop) { if (prop.GetName() == "range") diff --git a/src/Game/GameRenderer.cpp b/src/Game/GameRenderer.cpp index 3e04bb98..8cdc962b 100644 --- a/src/Game/GameRenderer.cpp +++ b/src/Game/GameRenderer.cpp @@ -14,6 +14,7 @@ namespace sh::game } SH_GAME_API void GameRenderer::Init() { + AddShadowPass(); AddRenderPass(core::Name{ "Opaque" }, render::RenderQueue::Opaque); AddRenderPass(); AddRenderPass("UI", render::RenderQueue::UI); @@ -23,23 +24,23 @@ namespace sh::game { for (const std::unique_ptr& pass : allPasses) { - auto allowedIt = allowedCamera.find(pass->passName.ToString()); + auto allowedIt = allowedCamera.find(pass->passName); if (allowedIt != allowedCamera.end()) { - const auto& allowedCams = allowedIt->second; - bool bAllowed = std::find(allowedCams.begin(), allowedCams.end(), data.camera) != allowedCams.end(); + const std::set& allowedCams = allowedIt->second; + bool bAllowed = allowedCams.find(data.camera) != allowedCams.end(); if (!bAllowed) continue; } - auto it = ignoreCamera.find(pass->passName.ToString()); + auto it = ignoreCamera.find(pass->passName); if (it == ignoreCamera.end()) { EnqueRenderPass(*pass); continue; } - const auto& ignoreCams = it->second; - bool bIgnore = std::find(ignoreCams.begin(), ignoreCams.end(), data.camera) != ignoreCams.end(); + const std::set& ignoreCams = it->second; + bool bIgnore = ignoreCams.find(data.camera) != ignoreCams.end(); if (bIgnore) continue; EnqueRenderPass(*pass); @@ -48,9 +49,40 @@ namespace sh::game SH_GAME_API void GameRenderer::SetUICamera(const render::Camera& camera) { uiCamera = &camera; - allowedCamera["UI"].push_back(&camera); - allowedCamera["ImGUI"].push_back(&camera); - ignoreCamera["Opaque"].push_back(&camera); - ignoreCamera["Transparent"].push_back(&camera); + allowedCamera[core::Name{ "UI" }].insert(&camera); + allowedCamera[core::Name{ "ImGUI" }].insert(&camera); + for (const std::unique_ptr& pass : allPasses) + { + if (pass->passName == "UI" || pass->passName == "ImGUI") + continue; + ignoreCamera[pass->passName].insert(&camera); + } + } + + SH_GAME_API void GameRenderer::AddShadowCasterCamera(const render::Camera& camera) + { + allowedCamera[core::Name{ "ShadowMapPass" }].insert(&camera); + for (const std::unique_ptr& pass : allPasses) + { + if (pass->passName == "ShadowMapPass") + continue; + ignoreCamera[pass->passName].insert(&camera); + } + } + + SH_GAME_API void GameRenderer::RemoveShadowCasterCamera(const render::Camera& camera) + { + allowedCamera[core::Name{ "ShadowMapPass" }].erase(&camera); + for (const std::unique_ptr& pass : allPasses) + { + if (pass->passName == "ShadowMapPass") + continue; + ignoreCamera[pass->passName].erase(&camera); + } + } + SH_GAME_API void GameRenderer::AddShadowPass() + { + shadowMapPass = &AddRenderPass(); + allowedCamera.insert({ shadowMapPass->passName, {} }); } -}//namespace \ No newline at end of file +}//namespace diff --git a/src/Render/RenderPass/ShadowMapPass.cpp b/src/Render/RenderPass/ShadowMapPass.cpp new file mode 100644 index 00000000..87dc7e8b --- /dev/null +++ b/src/Render/RenderPass/ShadowMapPass.cpp @@ -0,0 +1,16 @@ +#include "RenderPass/ShadowMapPass.h" +#include "RenderTexture.h" +#include "Camera.h" +#include "Drawable.h" +#include "Material.h" +#include "Shader.h" +#include "IRenderContext.h" +#include "IRenderImpl.h" + +namespace sh::render +{ + ShadowMapPass::ShadowMapPass() : + ScriptableRenderPass(core::Name{ "ShadowMapPass" }, RenderQueue::BeforeRendering) + { + } +}//namespace diff --git a/src/Render/RenderTexture.cpp b/src/Render/RenderTexture.cpp index b4bb1214..cc7cbaee 100644 --- a/src/Render/RenderTexture.cpp +++ b/src/Render/RenderTexture.cpp @@ -71,6 +71,9 @@ namespace sh::render void RenderTexture::CreateBuffers() { assert(context->GetRenderAPIType() == RenderAPI::Vulkan); + + const bool bDepthOnly = (layout.format == TextureFormat::None); + if (textureBuffer == nullptr) { if (context->GetRenderAPIType() == RenderAPI::Vulkan) @@ -79,7 +82,7 @@ namespace sh::render ITextureBuffer::CreateInfo ci{}; ci.width = width; ci.height = height; - ci.format = GetTextureFormat(); + ci.format = bDepthOnly ? layout.depthFormat : GetTextureFormat(); ci.aniso = 0; ci.filtering = 1; ci.mipLevel = 1; @@ -87,6 +90,10 @@ namespace sh::render ci.bRenderTarget = true; textureBuffer->Create(*context, ci); + usage = ResourceUsage::Undefined; + + if (bDepthOnly) + return; if (layout.bUseMSAA) { @@ -111,7 +118,13 @@ namespace sh::render ci.format = layout.depthFormat; depthBuffer->Create(*context, ci); } - - usage = ResourceUsage::Undefined; + } + SH_RENDER_API auto RenderTexture::GetDepthBuffer() const -> ITextureBuffer* + { + if (depthBuffer != nullptr) + return depthBuffer.get(); + if (layout.format == TextureFormat::None) + return textureBuffer.get(); // depth only인 경우 textureBuffer 자체가 DepthBuffer + return nullptr; } }//namespace \ No newline at end of file diff --git a/src/Render/ScriptableRenderPass.cpp b/src/Render/ScriptableRenderPass.cpp index 64b5907a..1f28e404 100644 --- a/src/Render/ScriptableRenderPass.cpp +++ b/src/Render/ScriptableRenderPass.cpp @@ -87,7 +87,10 @@ namespace sh::render void ScriptableRenderPass::CollectRenderImages(const RenderTarget& renderData, const DrawList& drawList) { renderTextures.clear(); - renderTextures[renderData.target] = ResourceUsage::ColorAttachment; + if (renderData.target != nullptr && IsDepthTexture(renderData.target->GetTextureFormat())) + renderTextures[renderData.target] = ResourceUsage::DepthStencilAttachment; + else + renderTextures[renderData.target] = ResourceUsage::ColorAttachment; const auto collectFn = [this](const Material* mat) @@ -97,25 +100,28 @@ namespace sh::render auto it = renderTextures.find(rt); if (it != renderTextures.end()) { - if (it->second == ResourceUsage::ColorAttachment) + if (it->second == ResourceUsage::ColorAttachment || + it->second == ResourceUsage::DepthStencilAttachment) { - SH_ERROR_FORMAT("RenderTexture {} is used as ColorAttachment", it->first->GetName().ToString()); + SH_ERROR_FORMAT("RenderTexture {} is used as Attachment", it->first->GetName().ToString()); continue; } } else - renderTextures[rt] = ResourceUsage::SampledRead; + { + renderTextures[rt] = rt->IsDepthOnly() ? ResourceUsage::DepthStencilSampledRead : ResourceUsage::SampledRead; + } } }; - if (drawList.renderData.index() == 0) + if (std::holds_alternative>(drawList.renderData)) { - for (const auto& group : std::get<0>(drawList.renderData)) + for (const DrawList::RenderGroup& group : std::get>(drawList.renderData)) collectFn(group.material); } else { - for (const auto& item : std::get<1>(drawList.renderData)) + for (const DrawList::RenderItem& item : std::get>(drawList.renderData)) collectFn(item.material); } } diff --git a/src/Render/ScriptableRenderer.cpp b/src/Render/ScriptableRenderer.cpp index 03979911..01f3d295 100644 --- a/src/Render/ScriptableRenderer.cpp +++ b/src/Render/ScriptableRenderer.cpp @@ -52,6 +52,16 @@ namespace sh::render ctx.DeallocateCommandBuffer(*cmdPtr); } + SH_RENDER_API auto ScriptableRenderer::HasPass(const core::Name& passName) const -> bool + { + for (const std::unique_ptr& pass : allPasses) + { + if (pass->passName == passName) + return true; + } + return false; + } + SH_RENDER_API void ScriptableRenderer::SyncDirty() { if (bSyncDirty) @@ -128,7 +138,7 @@ namespace sh::render { auto [pass, cmd] = futurePass.get(); - submittedCmds.push_back({ *pass, *cmd }); + recordedCmds.push_back({ *pass, *cmd }); } activePasses.clear(); @@ -160,7 +170,7 @@ namespace sh::render IRenderThrMethod::EmitBarrier(*cpyPass, *cmd, ctx, postBarriers); cmd->End(); - submittedCmds.push_back({ *cpyPass, *cmd}); + recordedCmds.push_back({ *cpyPass, *cmd}); } SH_RENDER_API void ScriptableRenderer::CallReadbacks() { @@ -175,9 +185,9 @@ namespace sh::render } SH_RENDER_API void ScriptableRenderer::ResetSubmittedCommands(IRenderContext& ctx) { - for (auto& submittedCmd : submittedCmds) - ctx.DeallocateCommandBuffer(submittedCmd.cmd); - submittedCmds.clear(); + for (RecordedCommand& recordedCmd : recordedCmds) + ctx.DeallocateCommandBuffer(recordedCmd.cmd); + recordedCmds.clear(); } SH_RENDER_API void ScriptableRenderer::ResetSwapChainStates() diff --git a/src/Render/VulkanImpl/VulkanImageBuffer.cpp b/src/Render/VulkanImpl/VulkanImageBuffer.cpp index 6f551891..d6ab000b 100644 --- a/src/Render/VulkanImpl/VulkanImageBuffer.cpp +++ b/src/Render/VulkanImpl/VulkanImageBuffer.cpp @@ -83,9 +83,13 @@ namespace sh::render::vk const bool bColorImg = !IsDepthTexture(info.format); format = ConvertTextureFormat(info.format); sample = info.bMSAAImg ? ctx->GetSampleCount() : VkSampleCountFlagBits::VK_SAMPLE_COUNT_1_BIT; - aspect = bColorImg ? - (VkImageAspectFlagBits::VK_IMAGE_ASPECT_COLOR_BIT) : - (VkImageAspectFlagBits::VK_IMAGE_ASPECT_DEPTH_BIT | VkImageAspectFlagBits::VK_IMAGE_ASPECT_STENCIL_BIT); + + const bool bSampleableDepthRT = !bColorImg && info.bRenderTarget && !info.bMSAAImg; + aspect = bColorImg ? + (VkImageAspectFlagBits::VK_IMAGE_ASPECT_COLOR_BIT) : + (bSampleableDepthRT ? + VkImageAspectFlagBits::VK_IMAGE_ASPECT_DEPTH_BIT : + (VkImageAspectFlagBits::VK_IMAGE_ASPECT_DEPTH_BIT | VkImageAspectFlagBits::VK_IMAGE_ASPECT_STENCIL_BIT)); filter = info.filtering == 0 ? VkFilter::VK_FILTER_NEAREST : VkFilter::VK_FILTER_LINEAR; width = info.width == 0 ? 1 : info.width; @@ -119,7 +123,11 @@ namespace sh::render::vk VkImageUsageFlagBits::VK_IMAGE_USAGE_SAMPLED_BIT; } else + { usage = VkImageUsageFlagBits::VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; + if (info.bRenderTarget) + usage |= VkImageUsageFlagBits::VK_IMAGE_USAGE_SAMPLED_BIT; + } } VkImageCreateInfo imageCi{}; imageCi.sType = VkStructureType::VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; @@ -418,6 +426,10 @@ namespace sh::render::vk return VkFormat::VK_FORMAT_D24_UNORM_S8_UINT; case TextureFormat::D16S8: return VkFormat::VK_FORMAT_D16_UNORM_S8_UINT; + case TextureFormat::D32: + return VkFormat::VK_FORMAT_D32_SFLOAT; + case TextureFormat::D16: + return VkFormat::VK_FORMAT_D16_UNORM; default: return VkFormat::VK_FORMAT_UNDEFINED; } @@ -434,18 +446,12 @@ namespace sh::render::vk case VkFormat::VK_FORMAT_D32_SFLOAT_S8_UINT: [[fallthrough]]; case VkFormat::VK_FORMAT_D24_UNORM_S8_UINT: [[fallthrough]]; case VkFormat::VK_FORMAT_D16_UNORM_S8_UINT: [[fallthrough]]; + case VkFormat::VK_FORMAT_D32_SFLOAT: [[fallthrough]]; + case VkFormat::VK_FORMAT_D16_UNORM: [[fallthrough]]; case VkFormat::VK_FORMAT_R8_UNORM: return 1; default: return 0; } } - auto VulkanImageBuffer::IsDepthTexture(TextureFormat format) -> bool - { - if (format == TextureFormat::D32S8 || - format == TextureFormat::D24S8 || - format == TextureFormat::D16S8) - return true; - return false; - } -} \ No newline at end of file +}//namespace \ No newline at end of file diff --git a/src/Render/VulkanImpl/VulkanPipeline.cpp b/src/Render/VulkanImpl/VulkanPipeline.cpp index 9a34084a..4175889d 100644 --- a/src/Render/VulkanImpl/VulkanPipeline.cpp +++ b/src/Render/VulkanImpl/VulkanPipeline.cpp @@ -223,12 +223,14 @@ namespace sh::render::vk colorBlendAttachment.alphaBlendOp = VkBlendOp::VK_BLEND_OP_ADD; colorBlendAttachment.dstAlphaBlendFactor = VkBlendFactor::VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;; + const bool bDepthOnly = (rtLayout.format == TextureFormat::None); + VkPipelineColorBlendStateCreateInfo colorBlending{}; colorBlending.sType = VkStructureType::VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; colorBlending.logicOpEnable = VK_FALSE; colorBlending.logicOp = VkLogicOp::VK_LOGIC_OP_COPY; - colorBlending.attachmentCount = 1; - colorBlending.pAttachments = &colorBlendAttachment; + colorBlending.attachmentCount = bDepthOnly ? 0 : 1; + colorBlending.pAttachments = bDepthOnly ? nullptr : &colorBlendAttachment; colorBlending.blendConstants[0] = 0.0f; colorBlending.blendConstants[1] = 0.0f; colorBlending.blendConstants[2] = 0.0f; @@ -262,10 +264,11 @@ namespace sh::render::vk VkPipelineRenderingCreateInfoKHR pipelineRenderingCI{}; pipelineRenderingCI.sType = VkStructureType::VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR; - pipelineRenderingCI.colorAttachmentCount = 1; - pipelineRenderingCI.pColorAttachmentFormats = &colorFormat; + pipelineRenderingCI.colorAttachmentCount = bDepthOnly ? 0 : 1; + pipelineRenderingCI.pColorAttachmentFormats = bDepthOnly ? nullptr : &colorFormat; pipelineRenderingCI.depthAttachmentFormat = depthFormat; - pipelineRenderingCI.stencilAttachmentFormat = depthFormat; + // depth-only RT는 view aspect=DEPTH-only로 만들었으므로 stencil 포맷도 비활성화. + pipelineRenderingCI.stencilAttachmentFormat = bDepthOnly ? VkFormat::VK_FORMAT_UNDEFINED : depthFormat; VkGraphicsPipelineCreateInfo pipelineInfo{}; pipelineInfo.sType = VkStructureType::VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; diff --git a/src/Render/VulkanImpl/VulkanRenderImpl.cpp b/src/Render/VulkanImpl/VulkanRenderImpl.cpp index a8ae748b..611436a9 100644 --- a/src/Render/VulkanImpl/VulkanRenderImpl.cpp +++ b/src/Render/VulkanImpl/VulkanRenderImpl.cpp @@ -77,6 +77,19 @@ namespace sh::render::vk layout = VkImageLayout::VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; stage = VkPipelineStageFlagBits::VK_PIPELINE_STAGE_TRANSFER_BIT; access = VkAccessFlagBits::VK_ACCESS_TRANSFER_READ_BIT; + return; + case ResourceUsage::DepthStencilAttachment: + layout = VkImageLayout::VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + stage = VkPipelineStageFlagBits::VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | + VkPipelineStageFlagBits::VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; + access = VkAccessFlagBits::VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | + VkAccessFlagBits::VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT; + return; + case ResourceUsage::DepthStencilSampledRead: + layout = VkImageLayout::VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + stage = VkPipelineStageFlagBits::VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + access = VkAccessFlagBits::VK_ACCESS_SHADER_READ_BIT; + return; } }; @@ -93,119 +106,174 @@ namespace sh::render::vk } } } - SH_RENDER_API void VulkanRenderImpl::RecordCommand(CommandBuffer& _cmd, const core::Name& passName, const RenderTarget& renderData, const DrawList& drawList, bool bStoreImage) const + namespace { - // 드로우 목적이 아닌 경우 - if (renderData.camera == nullptr) + struct ResolvedTarget { - for (auto& call : drawList.drawCall) - call(_cmd); - return; + VulkanImageBuffer* color = nullptr; + VulkanImageBuffer* colorMSAA = nullptr; + VulkanImageBuffer* depth = nullptr; + RenderTargetLayout layout{}; + uint32_t width = 0; + uint32_t height = 0; + bool bSwapChain = false; + bool bDepthOnly = false; + }; + + auto ResolveTarget(VulkanContext& ctx, const RenderTarget& renderData) -> ResolvedTarget + { + ResolvedTarget target{}; + if (core::IsValid(renderData.target)) + { + target.bSwapChain = false; + target.bDepthOnly = renderData.target->IsDepthOnly(); + target.color = target.bDepthOnly ? nullptr : static_cast(renderData.target->GetTextureBuffer()); + target.colorMSAA = static_cast(renderData.target->GetMSAABuffer()); + target.depth = static_cast(renderData.target->GetDepthBuffer()); + target.layout = renderData.target->GetLayout(); + + VulkanImageBuffer* const sizeRef = target.color != nullptr ? target.color : target.depth; + target.width = sizeRef->GetWidth(); + target.height = sizeRef->GetHeight(); + } + else + { + target.bSwapChain = true; + const uint32_t idx = renderData.frameIndex; + target.colorMSAA = &ctx.GetSwapChain().GetSwapChainMSAAImages()[idx]; + target.color = &ctx.GetSwapChain().GetSwapChainImages()[idx]; + target.depth = &ctx.GetSwapChain().GetSwapChainDepthImages()[idx]; + target.layout = ctx.GetSwapChain().GetRenderTargetLayout(); + target.width = target.color->GetWidth(); + target.height = target.color->GetHeight(); + } + return target; } - auto& cmd = static_cast(_cmd); - - const float width = ctx.GetViewportEnd().x - ctx.GetViewportStart().x; - const float height = ctx.GetViewportEnd().y - ctx.GetViewportStart().y; - const float surfWidth = static_cast(ctx.GetSwapChain().GetSwapChainSize().width); - const float surfHeight = static_cast(ctx.GetSwapChain().GetSwapChainSize().height); - - VkViewport viewport{}; - viewport.minDepth = 0.0f; - viewport.maxDepth = 1.0f; - viewport.x = ctx.GetViewportStart().x; - viewport.y = ctx.GetViewportEnd().y; - viewport.width = std::min(width, surfWidth); - viewport.height = -std::min(height, surfHeight); - - VkRect2D scissor{}; - scissor.offset = { 0, 0 }; - scissor.extent = ctx.GetSwapChain().GetSwapChainSize(); - - VulkanImageBuffer* imgBufferMSAA = nullptr; - VulkanImageBuffer* imgBuffer = nullptr; - VulkanImageBuffer* depthBuffer = nullptr; + auto BuildViewport(VulkanContext& ctx, const ResolvedTarget& target) -> VkViewport + { + VkViewport viewport{}; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + if (target.bSwapChain) + { + const float w = ctx.GetViewportEnd().x - ctx.GetViewportStart().x; + const float h = ctx.GetViewportEnd().y - ctx.GetViewportStart().y; + const float surfW = static_cast(ctx.GetSwapChain().GetSwapChainSize().width); + const float surfH = static_cast(ctx.GetSwapChain().GetSwapChainSize().height); + viewport.x = ctx.GetViewportStart().x; + viewport.y = ctx.GetViewportEnd().y; + viewport.width = std::min(w, surfW); + viewport.height = -std::min(h, surfH); + } + else + { + viewport.x = 0.f; + viewport.y = static_cast(target.height); + viewport.width = static_cast(target.width); + viewport.height = -static_cast(target.height); + } + return viewport; + } - RenderTargetLayout rtLayout; + auto BuildScissor(VulkanContext& ctx, const ResolvedTarget& target) -> VkRect2D + { + VkRect2D scissor{}; + scissor.offset = { 0, 0 }; + scissor.extent = target.bSwapChain ? ctx.GetSwapChain().GetSwapChainSize() : VkExtent2D{ target.width, target.height }; + return scissor; + } - if (core::IsValid(renderData.target)) + auto BuildColorAttachment(const ResolvedTarget& target, const DrawList& drawList, bool bStoreImage) -> VkRenderingAttachmentInfoKHR { - float w = renderData.target->GetSize().x; - float h = renderData.target->GetSize().y; + VkRenderingAttachmentInfoKHR info{}; + info.sType = VkStructureType::VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; + const bool bMSAA = (target.colorMSAA != nullptr); + info.imageView = bMSAA ? target.colorMSAA->GetImageView() : target.color->GetImageView(); + info.imageLayout = VkImageLayout::VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + info.loadOp = drawList.bClearColor ? VkAttachmentLoadOp::VK_ATTACHMENT_LOAD_OP_CLEAR : VkAttachmentLoadOp::VK_ATTACHMENT_LOAD_OP_LOAD; + info.storeOp = bStoreImage ? VkAttachmentStoreOp::VK_ATTACHMENT_STORE_OP_STORE : VkAttachmentStoreOp::VK_ATTACHMENT_STORE_OP_DONT_CARE; + info.clearValue.color = { { 0.f, 0.f, 0.f, 1.f } }; + if (bMSAA) + { + info.resolveImageView = target.color->GetImageView(); + info.resolveImageLayout = VkImageLayout::VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + info.resolveMode = VkResolveModeFlagBitsKHR::VK_RESOLVE_MODE_AVERAGE_BIT_KHR; + } + return info; + } - viewport.x = 0; - viewport.y = h; - viewport.width = w; - viewport.height = -h; - scissor.extent = { static_cast(w), static_cast(h) }; + auto BuildDepthAttachment(const ResolvedTarget& target, const DrawList& drawList, bool bStoreImage) -> VkRenderingAttachmentInfoKHR + { + VkRenderingAttachmentInfoKHR info{}; + info.sType = VkStructureType::VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; + info.imageView = target.depth->GetImageView(); + info.imageLayout = VkImageLayout::VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + info.loadOp = drawList.bClearDepth ? VkAttachmentLoadOp::VK_ATTACHMENT_LOAD_OP_CLEAR : VkAttachmentLoadOp::VK_ATTACHMENT_LOAD_OP_LOAD; + info.storeOp = bStoreImage ? VkAttachmentStoreOp::VK_ATTACHMENT_STORE_OP_STORE : VkAttachmentStoreOp::VK_ATTACHMENT_STORE_OP_DONT_CARE; + info.clearValue.depthStencil = { 1.0f, 0 }; + return info; + } - imgBuffer = static_cast(renderData.target->GetTextureBuffer()); - imgBufferMSAA = static_cast(renderData.target->GetMSAABuffer()); - depthBuffer = static_cast(renderData.target->GetDepthBuffer()); + auto HasDrawables(const DrawList& drawList) -> bool + { + if (std::holds_alternative>(drawList.renderData)) + return !std::get>(drawList.renderData).empty(); - rtLayout = renderData.target->GetLayout(); + return !std::get>(drawList.renderData).empty(); } - else - { - imgBufferMSAA = &ctx.GetSwapChain().GetSwapChainMSAAImages()[renderData.frameIndex]; - imgBuffer = &ctx.GetSwapChain().GetSwapChainImages()[renderData.frameIndex]; - depthBuffer = &ctx.GetSwapChain().GetSwapChainDepthImages()[renderData.frameIndex]; + }//anonymous namespace - rtLayout = ctx.GetSwapChain().GetRenderTargetLayout(); + SH_RENDER_API void VulkanRenderImpl::RecordCommand(CommandBuffer& _cmd, const core::Name& passName, const RenderTarget& renderData, const DrawList& drawList, bool bStoreImage) const + { + // 드로우 목적이 아닌 경우 + if (renderData.camera == nullptr) + { + for (auto& call : drawList.drawCall) + call(_cmd); + return; } + VulkanCommandBuffer& cmd = static_cast(_cmd); VkCommandBuffer commandBuffer = cmd.GetCommandBuffer(); - const bool bMSAA = imgBufferMSAA != nullptr; + const ResolvedTarget target = ResolveTarget(ctx, renderData); + const VkViewport viewport = BuildViewport(ctx, target); + const VkRect2D scissor = BuildScissor(ctx, target); - VkRenderingAttachmentInfoKHR colorAttachment{}; - colorAttachment.sType = VkStructureType::VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; - colorAttachment.imageView = bMSAA ? imgBufferMSAA->GetImageView() : imgBuffer->GetImageView(); - colorAttachment.imageLayout = VkImageLayout::VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - colorAttachment.loadOp = drawList.bClearColor ? VkAttachmentLoadOp::VK_ATTACHMENT_LOAD_OP_CLEAR : VkAttachmentLoadOp::VK_ATTACHMENT_LOAD_OP_LOAD; - colorAttachment.storeOp = bStoreImage ? VkAttachmentStoreOp::VK_ATTACHMENT_STORE_OP_STORE : VkAttachmentStoreOp::VK_ATTACHMENT_STORE_OP_DONT_CARE; - colorAttachment.clearValue = { 0.f, 0.f, 0.f, 1.f }; - if (bMSAA) - { - colorAttachment.resolveImageView = imgBuffer->GetImageView(); - colorAttachment.resolveImageLayout = VkImageLayout::VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - colorAttachment.resolveMode = VkResolveModeFlagBitsKHR::VK_RESOLVE_MODE_AVERAGE_BIT_KHR; - } - - VkRenderingAttachmentInfoKHR depthAttachment{}; - if (depthBuffer != nullptr) - { - depthAttachment.sType = VkStructureType::VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; - depthAttachment.imageView = depthBuffer->GetImageView(); - depthAttachment.imageLayout = VkImageLayout::VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - depthAttachment.loadOp = drawList.bClearDepth ? VkAttachmentLoadOp::VK_ATTACHMENT_LOAD_OP_CLEAR : VkAttachmentLoadOp::VK_ATTACHMENT_LOAD_OP_LOAD; - depthAttachment.storeOp = bStoreImage ? VkAttachmentStoreOp::VK_ATTACHMENT_STORE_OP_STORE : VkAttachmentStoreOp::VK_ATTACHMENT_STORE_OP_DONT_CARE; - depthAttachment.clearValue = { 1.0f, 0.f }; - } + VkRenderingAttachmentInfoKHR colorAttach{}; + VkRenderingAttachmentInfoKHR depthAttach{}; + if (!target.bDepthOnly) + colorAttach = BuildColorAttachment(target, drawList, bStoreImage); + const bool bHasDepth = (target.depth != nullptr); + if (bHasDepth) + depthAttach = BuildDepthAttachment(target, drawList, bStoreImage); VkRenderingInfoKHR renderingInfo{}; renderingInfo.sType = VkStructureType::VK_STRUCTURE_TYPE_RENDERING_INFO_KHR; - renderingInfo.colorAttachmentCount = 1; - renderingInfo.pColorAttachments = &colorAttachment; - renderingInfo.pDepthAttachment = depthBuffer != nullptr ? &depthAttachment : nullptr; - renderingInfo.pStencilAttachment = depthBuffer != nullptr ? &depthAttachment : nullptr; - renderingInfo.renderArea = { 0, 0, imgBuffer->GetWidth(), imgBuffer->GetHeight() }; + renderingInfo.colorAttachmentCount = target.bDepthOnly ? 0 : 1; + renderingInfo.pColorAttachments = target.bDepthOnly ? nullptr : &colorAttach; + renderingInfo.pDepthAttachment = bHasDepth ? &depthAttach : nullptr; + // depth-only RT는 view aspect=DEPTH-only이므로 stencil 첨부는 비활성화 + renderingInfo.pStencilAttachment = (bHasDepth && !target.bDepthOnly) ? &depthAttach : nullptr; + renderingInfo.renderArea = { { 0, 0 }, { target.width, target.height } }; renderingInfo.layerCount = 1; - static PFN_vkCmdBeginRenderingKHR pfnCmdBeginRenderingKHR = (PFN_vkCmdBeginRenderingKHR)vkGetDeviceProcAddr(ctx.GetDevice(), "vkCmdBeginRenderingKHR"); - pfnCmdBeginRenderingKHR(commandBuffer, &renderingInfo); + static PFN_vkCmdBeginRenderingKHR pfnBegin = (PFN_vkCmdBeginRenderingKHR)vkGetDeviceProcAddr(ctx.GetDevice(), "vkCmdBeginRenderingKHR"); + static PFN_vkCmdEndRenderingKHR pfnEnd = (PFN_vkCmdEndRenderingKHR) vkGetDeviceProcAddr(ctx.GetDevice(), "vkCmdEndRenderingKHR"); + + // 5) 기록 + pfnBegin(commandBuffer, &renderingInfo); vkCmdSetViewport(commandBuffer, 0, 1, &viewport); vkCmdSetScissor(commandBuffer, 0, 1, &scissor); - if (drawList.renderData.index() == 0 && !std::get<0>(drawList.renderData).empty() || - drawList.renderData.index() == 1 && !std::get<1>(drawList.renderData).empty()) - RenderDrawable(cmd, passName, renderData, drawList, rtLayout); + if (HasDrawables(drawList)) + RenderDrawable(cmd, passName, renderData, drawList, target.layout); for (auto& call : drawList.drawCall) call(_cmd); - static PFN_vkCmdEndRenderingKHR pfnCmdEndRenderingKHR = (PFN_vkCmdEndRenderingKHR)vkGetDeviceProcAddr(ctx.GetDevice(), "vkCmdEndRenderingKHR");; - pfnCmdEndRenderingKHR(commandBuffer); + pfnEnd(commandBuffer); } void VulkanRenderImpl::BindCameraSet(VulkanCommandBuffer& cmd, VkPipelineLayout layout, const ShaderPass& pass, const Material& mat, uint32_t cameraOffset) const @@ -215,7 +283,7 @@ namespace sh::render::vk mat.GetMaterialData().GetShaderBinding(pass, UniformStructLayout::Usage::Camera)); VkDescriptorSet cameraSet = cameraUBO ? cameraUBO->GetVkDescriptorSet() : ctx.GetEmptyDescriptorSet(); - uint32_t dynamicCount = cameraUBO ? 1 : 0; + const uint32_t dynamicCount = cameraUBO ? 1 : 0; vkCmdBindDescriptorSets(cmd.GetCommandBuffer(), VkPipelineBindPoint::VK_PIPELINE_BIND_POINT_GRAPHICS, layout, @@ -290,7 +358,7 @@ namespace sh::render::vk } void VulkanRenderImpl::RenderDrawable(VulkanCommandBuffer& cmd, const core::Name& passName, const RenderTarget& renderData, const DrawList& drawList, const RenderTargetLayout& layout) const { - auto cameraOffsetOpt = cameraManager->GetDynamicOffset(*renderData.camera); + std::optional cameraOffsetOpt = cameraManager->GetDynamicOffset(*renderData.camera); if (!cameraOffsetOpt.has_value()) return; diff --git a/src/Render/VulkanImpl/VulkanRenderer.cpp b/src/Render/VulkanImpl/VulkanRenderer.cpp index 99356ae0..15cbf45a 100644 --- a/src/Render/VulkanImpl/VulkanRenderer.cpp +++ b/src/Render/VulkanImpl/VulkanRenderer.cpp @@ -172,7 +172,7 @@ namespace sh::render::vk // 카메라 데이터 GPU에 업로드 std::vector cams{}; cams.reserve(cameras.size()); - for (auto camera : cameras) + for (const Camera* camera : cameras) { if (!camera->GetActive()) continue; @@ -184,6 +184,7 @@ namespace sh::render::vk for (const Camera* cam : cams) { std::vector filteredDrawables; + filteredDrawables.reserve(drawables.size()); RenderTarget rt{}; rt.frameIndex = imgIdx; @@ -203,10 +204,10 @@ namespace sh::render::vk SetDrawCallCount(renderCallCount); IRenderThrMethod::ExecuteTransfer(*renderer, imgIdx); - const auto& submittedCmds = renderer->GetSubmittedCommands(); - if (submittedCmds.size() == 1) + const std::vector& recordedCmds = renderer->GetRecordedCommands(); + if (recordedCmds.size() == 1) { - VulkanCommandBuffer& cmd = static_cast(submittedCmds.front().cmd); + VulkanCommandBuffer& cmd = static_cast(recordedCmds.front().cmd); cmd.AddWaitSemaphore( WaitSemaphore { @@ -219,14 +220,14 @@ namespace sh::render::vk } else { - VulkanCommandBuffer& endCmd = static_cast(submittedCmds.back().cmd); + VulkanCommandBuffer& endCmd = static_cast(recordedCmds.back().cmd); endCmd.AddSignalSemaphore(SignalSemaphore{ renderFinishedSemaphore }); bool bUsedImageAvailableSemaphore = false; - for (const auto& submittedCmd : submittedCmds) + for (const ScriptableRenderer::RecordedCommand& recordedCmd : recordedCmds) { - ScriptableRenderPass& pass = submittedCmd.pass; - VulkanCommandBuffer& cmd = static_cast(submittedCmd.cmd); + ScriptableRenderPass& pass = recordedCmd.pass; + VulkanCommandBuffer& cmd = static_cast(recordedCmd.cmd); // 스왑체인을 쓰는 첫 패스는 imageAvailableSemaphore대기 if (!bUsedImageAvailableSemaphore) From 036d6c953ae41683adc30ff89f0f2f07d01fd545 Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Thu, 30 Apr 2026 09:01:03 +0900 Subject: [PATCH 20/60] feat: Change render pass rendering logic to use command buffers instead of RenderImpl --- include/Editor/EditorPostOutlinePass.h | 2 +- include/Editor/UI/ProjectExplorer.h | 2 + include/Game/UIPass.h | 2 +- include/Render/CommandBuffer.h | 15 +- include/Render/Drawable.h | 8 +- include/Render/IRenderContext.h | 2 - include/Render/IRenderImpl.h | 16 - include/Render/RenderData.h | 41 +- include/Render/RenderPass/CopyPass.h | 18 +- include/Render/RenderPass/TransparentPass.h | 3 +- include/Render/ScriptableRenderPass.h | 37 +- .../Render/VulkanImpl/VulkanCommandBuffer.h | 44 +- include/Render/VulkanImpl/VulkanContext.h | 8 +- include/Render/VulkanImpl/VulkanRenderImpl.h | 34 -- src/Editor/EditorOutlinePass.cpp | 11 +- src/Editor/EditorPostOutlinePass.cpp | 29 +- src/Editor/EditorRenderer.cpp | 2 +- src/Game/UIPass.cpp | 18 +- src/Render/Drawable.cpp | 24 +- src/Render/Material.cpp | 2 +- src/Render/RenderPass/CopyPass.cpp | 26 +- src/Render/RenderPass/ShadowMapPass.cpp | 1 - src/Render/RenderPass/TransparentPass.cpp | 49 +- src/Render/ScriptableRenderPass.cpp | 126 +++-- src/Render/ScriptableRenderer.cpp | 21 +- src/Render/VulkanImpl/VulkanCommandBuffer.cpp | 493 +++++++++++++++-- .../VulkanImpl/VulkanCommandBufferPool.cpp | 2 +- src/Render/VulkanImpl/VulkanContext.cpp | 7 - src/Render/VulkanImpl/VulkanRenderImpl.cpp | 497 ------------------ 29 files changed, 719 insertions(+), 821 deletions(-) delete mode 100644 include/Render/IRenderImpl.h delete mode 100644 include/Render/VulkanImpl/VulkanRenderImpl.h delete mode 100644 src/Render/VulkanImpl/VulkanRenderImpl.cpp diff --git a/include/Editor/EditorPostOutlinePass.h b/include/Editor/EditorPostOutlinePass.h index 31ed0e5a..f0aeaa2a 100644 --- a/include/Editor/EditorPostOutlinePass.h +++ b/include/Editor/EditorPostOutlinePass.h @@ -20,7 +20,7 @@ namespace sh::editor SH_EDITOR_API void SetOutlineMaterial(render::Material& mat); protected: SH_EDITOR_API void Configure(const render::RenderTarget& renderData) override; - SH_EDITOR_API auto BuildDrawList(const render::RenderTarget& renderData) -> render::DrawList override; + SH_EDITOR_API void Record(render::CommandBuffer& cmd, const render::IRenderContext& ctx, const render::RenderTarget& renderData) override; private: const render::IRenderContext& ctx; diff --git a/include/Editor/UI/ProjectExplorer.h b/include/Editor/UI/ProjectExplorer.h index 4538cb81..fc25d666 100644 --- a/include/Editor/UI/ProjectExplorer.h +++ b/include/Editor/UI/ProjectExplorer.h @@ -1,6 +1,8 @@ #pragma once #include "Editor/Export.h" +#include "Core/SObject.h" + #include "Game/ImGUImpl.h" #include diff --git a/include/Game/UIPass.h b/include/Game/UIPass.h index 66b9d709..760bfc17 100644 --- a/include/Game/UIPass.h +++ b/include/Game/UIPass.h @@ -17,7 +17,7 @@ namespace sh::game SH_GAME_API void SetImGUIContext(ImGUImpl& gui); protected: SH_GAME_API void Configure(const render::RenderTarget& renderData) override; - SH_GAME_API auto BuildDrawList(const render::RenderTarget& renderData) -> render::DrawList override; + SH_GAME_API void Record(render::CommandBuffer& cmd, const render::IRenderContext& ctx, const render::RenderTarget& renderData) override; public: render::RenderTexture* viewportTexture = nullptr; private: diff --git a/include/Render/CommandBuffer.h b/include/Render/CommandBuffer.h index 4fed432b..3a8d808e 100644 --- a/include/Render/CommandBuffer.h +++ b/include/Render/CommandBuffer.h @@ -1,6 +1,6 @@ #pragma once - #include "Core/NonCopyable.h" +#include "Core/Name.h" #include namespace sh::render @@ -8,6 +8,11 @@ namespace sh::render class ComputeShader; class RenderTexture; class IBuffer; + class Material; + class Drawable; + class ShaderPass; + struct RenderTarget; + struct BarrierInfo; class CommandBuffer : public core::INonCopyable { @@ -16,8 +21,16 @@ namespace sh::render virtual void Begin(bool bOnetime) = 0; virtual void End() = 0; + virtual void Reset() = 0; virtual void Blit(RenderTexture& src, int x, int y, IBuffer& dst) = 0; virtual void Dispatch(const ComputeShader& shader, uint32_t x, uint32_t y, uint32_t z) = 0; + virtual void SetRenderTarget(const RenderTarget& renderTarget, bool bClearColor = true, bool bClearDepth = true, bool bStoreColor = false, bool bStoreDepth = false) = 0; + virtual void SetViewport(int x, int y, int width, int height) = 0; + virtual void SetScissor(uint32_t x, uint32_t y, uint32_t width, uint32_t height) = 0; + virtual void DrawMeshBatch(const std::vector& drawables, core::Name passName) = 0; + virtual void DrawMesh(const Drawable& drawable, core::Name passName) = 0; + virtual void EmitBarrier(const std::vector& barriers) = 0; + }; }//namespace \ No newline at end of file diff --git a/include/Render/Drawable.h b/include/Render/Drawable.h index 85b6de97..72d5374d 100644 --- a/include/Render/Drawable.h +++ b/include/Render/Drawable.h @@ -34,7 +34,7 @@ namespace sh::render SH_RENDER_API void SetRenderTagId(uint32_t tagId); SH_RENDER_API void SetTopology(Mesh::Topology topology); SH_RENDER_API void SetPriority(int priority); - SH_RENDER_API void SetSubMeshIndex(int idx); + SH_RENDER_API void SetSubMeshIndex(uint32_t idx); SH_RENDER_API auto CheckAssetValid() const -> bool; @@ -46,7 +46,8 @@ namespace sh::render SH_RENDER_API auto GetRenderTagId() const -> uint32_t { return renderTag; } SH_RENDER_API auto GetTopology(core::ThreadType thr = core::ThreadType::Game) const -> Mesh::Topology { return topology[thr]; } SH_RENDER_API auto GetPriority(core::ThreadType thr = core::ThreadType::Game) const -> int { return priority[thr]; } - SH_RENDER_API auto GetSubMeshIndex() const -> int { return subMeshIndex; } + SH_RENDER_API auto GetSubMeshIndex() const -> uint32_t { return subMeshIndex; } + SH_RENDER_API auto IsSkinnedMesh() const -> bool { return bSkinned; } protected: SH_RENDER_API void SyncDirty() override; SH_RENDER_API void Sync() override; @@ -71,8 +72,9 @@ namespace sh::render }; std::array syncDatas; - int subMeshIndex = -1; + uint32_t subMeshIndex = 0; + bool bSkinned = false; bool bDirty = false; bool bMatrixDirty = false; }; diff --git a/include/Render/IRenderContext.h b/include/Render/IRenderContext.h index 0bd57a18..72c23d44 100644 --- a/include/Render/IRenderContext.h +++ b/include/Render/IRenderContext.h @@ -1,7 +1,6 @@ #pragma once #include "Export.h" #include "CommandBuffer.h" -#include "IRenderImpl.h" #include "Core/NonCopyable.h" @@ -30,6 +29,5 @@ namespace sh::render virtual void SetViewport(const glm::vec2& start, const glm::vec2& end) = 0; virtual auto GetViewportStart() const -> const glm::vec2& = 0; virtual auto GetViewportEnd() const -> const glm::vec2& = 0; - virtual auto GetRenderImpl() const -> IRenderImpl& = 0; }; }//namespace \ No newline at end of file diff --git a/include/Render/IRenderImpl.h b/include/Render/IRenderImpl.h deleted file mode 100644 index 5d06543c..00000000 --- a/include/Render/IRenderImpl.h +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once -#include "RenderData.h" -#include "CommandBuffer.h" -#include "Core/Name.h" - -namespace sh::render -{ - class IRenderImpl - { - public: - virtual ~IRenderImpl() = default; - - virtual void EmitBarrier(CommandBuffer& cmd, const std::vector& barriers) const = 0; - virtual void RecordCommand(CommandBuffer& cmd, const core::Name& passName, const RenderTarget& renderData, const DrawList& drawList, bool bStoreImage) const = 0; - }; -}//namespace \ No newline at end of file diff --git a/include/Render/RenderData.h b/include/Render/RenderData.h index 047702f3..a00a072d 100644 --- a/include/Render/RenderData.h +++ b/include/Render/RenderData.h @@ -1,6 +1,5 @@ #pragma once #include "Mesh.h" -#include "CommandBuffer.h" #include "Core/SContainer.hpp" @@ -13,6 +12,7 @@ namespace sh::render class RenderTexture; class Drawable; class Material; + class CommandBuffer; struct RenderTarget { @@ -22,28 +22,23 @@ namespace sh::render const std::vector* drawables; }; - struct DrawList - { - struct RenderGroup - { - const Material* material; - Mesh::Topology topology; - std::vector drawables; - }; - struct RenderItem - { - const Material* material; - Mesh::Topology topology; - Drawable* drawable; - }; - std::variant, std::vector> renderData; - std::vector> drawCall; - - uint32_t drawableCount = 0; - - bool bClearColor = true; - bool bClearDepth = true; - }; + //struct DrawList + //{ + // struct RenderGroup + // { + // const Material* material; + // Mesh::Topology topology; + // std::vector drawables; + // }; + // struct RenderItem + // { + // const Material* material; + // Mesh::Topology topology; + // Drawable* drawable; + // }; + // std::variant, std::vector> renderData; + // uint32_t drawableCount = 0; + //}; /// @brief 리소스(이미지/버퍼) 사용 의도. 배리어 계산의 입력으로 쓰임 enum class ResourceUsage diff --git a/include/Render/RenderPass/CopyPass.h b/include/Render/RenderPass/CopyPass.h index 46366794..eeca7a99 100644 --- a/include/Render/RenderPass/CopyPass.h +++ b/include/Render/RenderPass/CopyPass.h @@ -8,23 +8,17 @@ namespace sh::render class RenderTexture; class IBuffer; - template<> - struct IRenderThrMethod - { - static void EnqueCopyImagePixelToBuffer(CopyPass& pass, RenderTexture& rt, int x, int y, IBuffer& buffer); - }; - class CopyPass : public ScriptableRenderPass { - friend IRenderThrMethod; + friend class IRenderThrMethod; public: SH_RENDER_API CopyPass(); protected: SH_RENDER_API void Configure(const RenderTarget& renderData) override; SH_RENDER_API void Record(CommandBuffer& cmd, const IRenderContext& ctx, const RenderTarget& renderData) override; - SH_RENDER_API auto BuildDrawList(const RenderTarget& renderData) -> DrawList override; SH_RENDER_API void EnqueCopyImagePixelToBuffer(RenderTexture& rt, int x, int y, IBuffer& buffer); + SH_RENDER_API auto IsEmpty() const -> bool { return cpyDatas.empty(); } private: struct CopyData { @@ -36,8 +30,10 @@ namespace sh::render std::vector cpyDatas; }; - inline void IRenderThrMethod::EnqueCopyImagePixelToBuffer(CopyPass& pass, RenderTexture& rt, int x, int y, IBuffer& buffer) + template<> + struct IRenderThrMethod { - pass.EnqueCopyImagePixelToBuffer(rt, x, y, buffer); - } + static void EnqueCopyImagePixelToBuffer(CopyPass& pass, RenderTexture& rt, int x, int y, IBuffer& buffer) { pass.EnqueCopyImagePixelToBuffer(rt, x, y, buffer); } + static auto IsEmpty(CopyPass& pass) -> bool { return pass.IsEmpty(); } + }; }//namespace \ No newline at end of file diff --git a/include/Render/RenderPass/TransparentPass.h b/include/Render/RenderPass/TransparentPass.h index a3ea1a56..23d0a0bd 100644 --- a/include/Render/RenderPass/TransparentPass.h +++ b/include/Render/RenderPass/TransparentPass.h @@ -10,6 +10,7 @@ namespace sh::render public: SH_RENDER_API TransparentPass(std::string_view name = "Transparent", RenderQueue renderQueue = RenderQueue::Transparent); - SH_RENDER_API auto BuildDrawList(const RenderTarget& renderData) -> DrawList override; + SH_RENDER_API void Configure(const RenderTarget& renderData) override; + SH_RENDER_API void Record(CommandBuffer& cmd, const IRenderContext& ctx, const RenderTarget& renderData) override; }; }//namespace \ No newline at end of file diff --git a/include/Render/ScriptableRenderPass.h b/include/Render/ScriptableRenderPass.h index 9f18bc5a..f4abf4cc 100644 --- a/include/Render/ScriptableRenderPass.h +++ b/include/Render/ScriptableRenderPass.h @@ -2,15 +2,19 @@ #include "Export.h" #include "RenderData.h" #include "IRenderThrMethod.h" +#include "Mesh.h" #include "Core/SContainer.hpp" #include +#include namespace sh::render { class IRenderContext; class CommandBuffer; class RenderTexture; + class Material; + class Drawable; enum class RenderQueue { @@ -37,9 +41,7 @@ namespace sh::render struct IRenderThrMethod { static void Configure(ScriptableRenderPass& pass, const RenderTarget& renderData); - static void Record(ScriptableRenderPass& pass, CommandBuffer& cmd, const IRenderContext& ctx, const RenderTarget& renderData); - static void EmitBarrier(ScriptableRenderPass& pass, CommandBuffer& cmd, const IRenderContext& ctx, const std::vector& barriers); - static void SetStoreImg(ScriptableRenderPass& pass, bool bStore); + static void Record(ScriptableRenderPass& pass, CommandBuffer& cmd, const IRenderContext& ctx, const RenderTarget& renderTarget); /// @brief Configure 이후에 호출해야 정확한 렌더콜 갯수를 알 수 있음 /// @param pass 패스 /// @return 렌더콜 횟수 @@ -54,26 +56,31 @@ namespace sh::render SH_RENDER_API virtual ~ScriptableRenderPass() = default; SH_RENDER_API auto GetRenderTextures() const -> const std::unordered_map& { return renderTextures; } - SH_RENDER_API auto IsStoreImage() const -> bool { return bStoreImage; } protected: + struct RenderBatch + { + const Material* material; + Mesh::Topology topology; + bool bSkinned = false; + std::vector drawables; + }; SH_RENDER_API virtual void Configure(const RenderTarget& renderData); SH_RENDER_API virtual void Record(CommandBuffer& cmd, const IRenderContext& ctx, const RenderTarget& renderData); - SH_RENDER_API virtual auto BuildDrawList(const RenderTarget& renderData) -> DrawList; - SH_RENDER_API void EmitBarrier(CommandBuffer& cmd, const IRenderContext& ctx, const std::vector& barriers) const; + SH_RENDER_API auto CreateRenderBatch(const std::vector& drawables) const -> std::vector; + SH_RENDER_API void SetImageUsages(const RenderTarget& renderData); + SH_RENDER_API void SetViewportScissor(CommandBuffer& cmd, const IRenderContext& ctx, const RenderTarget& renderTarget); /// @brief Configure 이후에 호출해야 정확한 렌더콜 갯수를 알 수 있음 - SH_RENDER_API auto GetRenderCallCount() const -> uint32_t; - private: - void CollectRenderImages(const RenderTarget& renderData, const DrawList& drawList); + SH_RENDER_API auto GetRenderCallCount() const -> uint32_t { return renderCallCount; } public: const core::Name passName; const RenderQueue renderQueue; protected: - DrawList drawList; + std::vector renderBatches; std::unordered_map renderTextures; private: - bool bStoreImage = true; + uint32_t renderCallCount = 0; }; inline void IRenderThrMethod::Configure(ScriptableRenderPass& pass, const RenderTarget& renderData) @@ -84,14 +91,6 @@ namespace sh::render { pass.Record(cmd, ctx, renderData); } - inline void IRenderThrMethod::EmitBarrier(ScriptableRenderPass& pass, CommandBuffer& cmd, const IRenderContext& ctx, const std::vector& barriers) - { - pass.EmitBarrier(cmd, ctx, barriers); - } - inline void IRenderThrMethod::SetStoreImg(ScriptableRenderPass& pass, bool bStore) - { - pass.bStoreImage = bStore; - } inline auto IRenderThrMethod::GetRenderCallCount(ScriptableRenderPass& pass) -> uint32_t { return pass.GetRenderCallCount(); diff --git a/include/Render/VulkanImpl/VulkanCommandBuffer.h b/include/Render/VulkanImpl/VulkanCommandBuffer.h index eb3363f1..7fdc56f2 100644 --- a/include/Render/VulkanImpl/VulkanCommandBuffer.h +++ b/include/Render/VulkanImpl/VulkanCommandBuffer.h @@ -1,12 +1,18 @@ #pragma once #include "../Export.h" #include "../CommandBuffer.h" +#include "../RenderTarget.h" #include "VulkanConfig.h" #include "Core/NonCopyable.h" #include #include +namespace sh::render +{ + class Camera; + class Mesh; +} namespace sh::render::vk { class VulkanContext; @@ -42,11 +48,16 @@ namespace sh::render::vk SH_RENDER_API auto Build(const std::function& recordFn, bool bOneTimeSubmit = true) -> VkResult; SH_RENDER_API void Begin(bool bOnetime) override; SH_RENDER_API void End() override; + SH_RENDER_API void Reset() override; SH_RENDER_API void Blit(RenderTexture& src, int x, int y, IBuffer& dst) override; SH_RENDER_API void Dispatch(const ComputeShader& shader, uint32_t x, uint32_t y, uint32_t z) override; - - SH_RENDER_API auto ResetCommand(VkCommandBufferResetFlags flags = 0) -> VkResult; + SH_RENDER_API void SetRenderTarget(const RenderTarget& renderTarget, bool bClearColor = true, bool bClearDepth = true, bool bStoreColor = false, bool bStoreDepth = false) override; + SH_RENDER_API void SetViewport(int x, int y, int width, int height) override; + SH_RENDER_API void SetScissor(uint32_t x, uint32_t y, uint32_t width, uint32_t height) override; + SH_RENDER_API void DrawMesh(const Drawable& drawable, core::Name passName) override; + SH_RENDER_API void DrawMeshBatch(const std::vector& drawables, core::Name passName) override; + SH_RENDER_API void EmitBarrier(const std::vector& barriers) override; SH_RENDER_API auto GetOrCreateFence() -> VkFence; SH_RENDER_API void DestroyFence(); @@ -59,21 +70,38 @@ namespace sh::render::vk SH_RENDER_API void AddWaitSemaphore(const WaitSemaphore& w); SH_RENDER_API void AddSignalSemaphore(const SignalSemaphore& s); - SH_RENDER_API auto GetWaitSemaphores() const -> const std::vector&; - SH_RENDER_API auto GetSignalSemaphores() const -> const std::vector&; - - SH_RENDER_API auto GetCommandBuffer() const -> VkCommandBuffer; - SH_RENDER_API auto GetCommandPool() const -> VkCommandPool; + SH_RENDER_API auto GetWaitSemaphores() const -> const std::vector& { return waitSemaphores; } + SH_RENDER_API auto GetSignalSemaphores() const -> const std::vector& { return signalSemaphores; } + SH_RENDER_API auto GetCommandBuffer() const -> VkCommandBuffer { return buffer; } + SH_RENDER_API auto GetCommandPool() const -> VkCommandPool { return cmdPool; } SH_RENDER_API void Clear(); private: + void BindCameraSet(const Material& mat, const ShaderPass& pass, VkPipelineLayout pipelineLayout, uint32_t cameraOffset); + void BindMaterialSet(const Material& mat, const ShaderPass& pass, VkPipelineLayout pipelineLayout); + void BindObjectSet(const Drawable& drawable, const ShaderPass& pass, VkPipelineLayout pipelineLayout); + void BindMesh(const Mesh& mesh, uint32_t subMeshIdx, bool bSkinned); + private: + struct RenderState + { + RenderTargetLayout layout{}; + const Camera* camera = nullptr; + uint32_t targetWidth = 0; + uint32_t targetHeight = 0; + + uint32_t lastPipelineIdx = 0xffffffff; + uint32_t lastPipelineGen = 0xffffffff; + bool bDepthOnly = false; + } renderState; + const VulkanContext& context; VkCommandBuffer buffer = VK_NULL_HANDLE; VkCommandPool cmdPool = VK_NULL_HANDLE; - std::vector waitSemaphores; + std::vector waitSemaphores; std::vector signalSemaphores; VkFence fence = VK_NULL_HANDLE; + bool bBeginRender = false; }; }//namespace \ No newline at end of file diff --git a/include/Render/VulkanImpl/VulkanContext.h b/include/Render/VulkanImpl/VulkanContext.h index 15ce4170..dfc64c18 100644 --- a/include/Render/VulkanImpl/VulkanContext.h +++ b/include/Render/VulkanImpl/VulkanContext.h @@ -1,6 +1,6 @@ #pragma once -#include "../Export.h" -#include "../IRenderContext.h" +#include "Render/Export.h" +#include "Render/IRenderContext.h" #include "VulkanConfig.h" #include "Core/ISyncable.h" @@ -29,7 +29,6 @@ namespace sh::render::vk class VulkanPipelineManager; class VulkanComputePipelineManager; class VulkanQueueManager; - class VulkanRenderImpl; class VulkanContext : public IRenderContext { @@ -50,8 +49,6 @@ namespace sh::render::vk SH_RENDER_API auto GetViewportStart() const -> const glm::vec2 & override; SH_RENDER_API auto GetViewportEnd() const -> const glm::vec2 & override; - SH_RENDER_API auto GetRenderImpl() const -> IRenderImpl& override; - SH_RENDER_API auto ReSizing() -> bool; SH_RENDER_API void PrintLayers(); @@ -128,7 +125,6 @@ namespace sh::render::vk std::unique_ptr descPool; std::unique_ptr pipelineManager; std::unique_ptr computePipelineManager; - std::unique_ptr renderImpl; VkDescriptorSetLayout emptyDescLayout; VkDescriptorSet emptyDescSet; diff --git a/include/Render/VulkanImpl/VulkanRenderImpl.h b/include/Render/VulkanImpl/VulkanRenderImpl.h deleted file mode 100644 index de1503c6..00000000 --- a/include/Render/VulkanImpl/VulkanRenderImpl.h +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once -#include "../Export.h" -#include "../IRenderImpl.h" -#include "../RenderTarget.h" -#include "VulkanContext.h" -#include "VulkanCameraBuffers.h" - -namespace sh::render -{ - class ShaderPass; -} - -namespace sh::render::vk -{ - class VulkanCommandBuffer; - class VulkanCameraBuffers; - - class VulkanRenderImpl : public IRenderImpl - { - public: - SH_RENDER_API VulkanRenderImpl(VulkanContext& context); - SH_RENDER_API void EmitBarrier(CommandBuffer& cmd, const std::vector& barriers) const override; - SH_RENDER_API void RecordCommand(CommandBuffer& cmd, const core::Name& passName, const RenderTarget& renderData, const DrawList& drawList, bool bStoreImage) const override; - private: - void BindCameraSet(VulkanCommandBuffer& cmd, VkPipelineLayout layout, const ShaderPass& pass, const Material& mat, uint32_t cameraOffset) const; - void BindMaterialSet(VulkanCommandBuffer& cmd, VkPipelineLayout layout, const ShaderPass& pass, const Material& mat) const; - void BindObjectSet(VulkanCommandBuffer& cmd, VkPipelineLayout layout, const ShaderPass& pass, Drawable& drawable) const; - void DrawMesh(VulkanCommandBuffer& cmd, const ShaderPass& pass, const Mesh& mesh, int subMeshIndex = -1) const; - void RenderDrawable(VulkanCommandBuffer& cmd, const core::Name& passName, const RenderTarget& renderData, const DrawList& drawList, const RenderTargetLayout& layout) const; - private: - VulkanContext& ctx; - VulkanCameraBuffers* cameraManager = nullptr; - }; -}//namespace \ No newline at end of file diff --git a/src/Editor/EditorOutlinePass.cpp b/src/Editor/EditorOutlinePass.cpp index a0fd13f4..45a53117 100644 --- a/src/Editor/EditorOutlinePass.cpp +++ b/src/Editor/EditorOutlinePass.cpp @@ -35,6 +35,15 @@ namespace sh::editor rd.frameIndex = renderData.frameIndex; rd.target = output.Get(); - ScriptableRenderPass::Record(cmd, ctx, rd); + SetViewportScissor(cmd, ctx, rd); + cmd.SetRenderTarget(rd, true, true, true, true); + + if (rd.drawables == nullptr) + return; + + for (const RenderBatch& batch : renderBatches) + { + cmd.DrawMeshBatch(batch.drawables, passName); + } } }//namespace \ No newline at end of file diff --git a/src/Editor/EditorPostOutlinePass.cpp b/src/Editor/EditorPostOutlinePass.cpp index 3880fc7a..438b65d7 100644 --- a/src/Editor/EditorPostOutlinePass.cpp +++ b/src/Editor/EditorPostOutlinePass.cpp @@ -45,25 +45,18 @@ namespace sh::editor core::GarbageCollection::GetInstance()->SetRootSet(drawable); } - ScriptableRenderPass::Configure(renderData); + if (mat != nullptr) + { + for (auto& [name, rt] : mat->GetCachedRenderTextures()) + renderTextures[rt] = render::ResourceUsage::SampledRead; + } } - SH_EDITOR_API auto EditorPostOutlinePass::BuildDrawList(const render::RenderTarget& renderData) -> render::DrawList + SH_EDITOR_API void EditorPostOutlinePass::Record(render::CommandBuffer& cmd, const render::IRenderContext& ctx, const render::RenderTarget& renderData) { - render::DrawList list{}; - list.renderData = std::vector{}; - list.bClearColor = false; - - if (!core::IsValid(drawable) || !drawable->CheckAssetValid() || drawable->GetMaterial()->GetShader()->GetShaderPasses(passName) == nullptr) - return list; - - render::DrawList::RenderItem renderItem; - renderItem.material = mat; - renderItem.topology = drawable->GetTopology(core::ThreadType::Render); - renderItem.drawable = drawable; - - std::get<1>(list.renderData).push_back(renderItem); - list.drawableCount = 1; - - return list; + if (!core::IsValid(drawable->GetMesh()) || !core::IsValid(drawable->GetMaterial())) + return; + cmd.SetRenderTarget(renderData, false, false, false, false); + SetViewportScissor(cmd, ctx, renderData); + cmd.DrawMesh(*drawable, passName); } }//namespace \ No newline at end of file diff --git a/src/Editor/EditorRenderer.cpp b/src/Editor/EditorRenderer.cpp index c8c4e69e..da66f31a 100644 --- a/src/Editor/EditorRenderer.cpp +++ b/src/Editor/EditorRenderer.cpp @@ -15,7 +15,7 @@ namespace sh::editor { AddRenderPass(core::Name{ "EditorPicking" }, render::RenderQueue::Picking); outlinePass = &AddRenderPass(); - AddShadowPass(); + //AddShadowPass(); AddRenderPass(core::Name{ "Opaque" }, render::RenderQueue::Opaque); AddRenderPass(); AddRenderPass("UI", render::RenderQueue::Transparent); diff --git a/src/Game/UIPass.cpp b/src/Game/UIPass.cpp index 2b157611..a2f6b15f 100644 --- a/src/Game/UIPass.cpp +++ b/src/Game/UIPass.cpp @@ -18,22 +18,14 @@ namespace sh::game } SH_GAME_API void UIPass::Configure(const render::RenderTarget& renderData) { - ScriptableRenderPass::Configure(renderData); + renderTextures[renderData.target] = render::ResourceUsage::ColorAttachment; if (viewportTexture != nullptr) renderTextures[viewportTexture] = render::ResourceUsage::SampledRead; } - SH_GAME_API auto UIPass::BuildDrawList(const render::RenderTarget& renderData) -> render::DrawList - { - render::DrawList list{}; - - list.drawCall.push_back( - [&](render::CommandBuffer& cmd) - { - ImGui_ImplVulkan_RenderDrawData(&gui->GetDrawData(), static_cast(cmd).GetCommandBuffer()); - } - ); - list.bClearColor = false; - return list; + SH_GAME_API void UIPass::Record(render::CommandBuffer& cmd, const render::IRenderContext& ctx, const render::RenderTarget& renderData) + { + ScriptableRenderPass::Record(cmd, ctx, renderData); + ImGui_ImplVulkan_RenderDrawData(&gui->GetDrawData(), static_cast(cmd).GetCommandBuffer()); } }//namespace \ No newline at end of file diff --git a/src/Render/Drawable.cpp b/src/Render/Drawable.cpp index 98bb5892..85a1a3d4 100644 --- a/src/Render/Drawable.cpp +++ b/src/Render/Drawable.cpp @@ -1,4 +1,5 @@ #include "Drawable.h" +#include "SkinnedMesh.h" #include "Core/ThreadSyncManager.h" @@ -17,16 +18,23 @@ namespace sh::render priority[core::ThreadType::Game] = 0; priority[core::ThreadType::Render] = 0; + + if (mesh.GetType().IsChildOf(SkinnedMesh::GetStaticType())) + bSkinned = true; } Drawable::Drawable(Drawable&& other) noexcept : mat(other.mat), mesh(other.mesh), modelMatrix(other.modelMatrix), materialData(std::move(other.materialData)), renderTag(other.renderTag), - priority(other.priority) + priority(other.priority), + topology(other.topology), + subMeshIndex(other.subMeshIndex), + syncDatas(other.syncDatas), + bSkinned(other.bSkinned), + bDirty(other.bDirty), + bMatrixDirty(other.bMatrixDirty) { other.bDirty = false; - topology[core::ThreadType::Game] = other.topology[core::ThreadType::Game]; - topology[core::ThreadType::Render] = other.topology[core::ThreadType::Render]; } Drawable::~Drawable() { @@ -91,14 +99,14 @@ namespace sh::render SyncDirty(); } } - SH_RENDER_API void Drawable::SetSubMeshIndex(int idx) + SH_RENDER_API void Drawable::SetSubMeshIndex(uint32_t idx) { subMeshIndex = idx; } SH_RENDER_API auto Drawable::CheckAssetValid() const -> bool { - return core::IsValid(mat) && core::IsValid(mesh); + return core::IsValid(mesh) && core::IsValid(mat) && core::IsValid(mesh); } SH_RENDER_API void Drawable::SyncDirty() @@ -120,11 +128,13 @@ namespace sh::render } else if (std::holds_alternative(syncData.changed)) // 메테리얼이 변경됨 { - this->mat = std::get(syncData.changed); + mat = std::get(syncData.changed); } else if (std::holds_alternative(syncData.changed)) { - this->mesh = std::get(syncData.changed); + mesh = std::get(syncData.changed); + if (mesh->GetType().IsChildOf(SkinnedMesh::GetStaticType())) + bSkinned = true; } else if (std::holds_alternative(syncData.changed)) { diff --git a/src/Render/Material.cpp b/src/Render/Material.cpp index be33f70e..d6c45440 100644 --- a/src/Render/Material.cpp +++ b/src/Render/Material.cpp @@ -270,7 +270,7 @@ namespace sh::render bPropertyDirty = true; - if (data->GetType() == RenderTexture::GetStaticType()) + if (data->GetType().IsChildOf(RenderTexture::GetStaticType())) cachedRts[name] = static_cast(data); } SH_RENDER_API void Material::SetProperty(const std::string& name, const glm::vec4& data) diff --git a/src/Render/RenderPass/CopyPass.cpp b/src/Render/RenderPass/CopyPass.cpp index bab593b1..ba380c78 100644 --- a/src/Render/RenderPass/CopyPass.cpp +++ b/src/Render/RenderPass/CopyPass.cpp @@ -1,5 +1,6 @@ #include "RenderPass/CopyPass.h" #include "RenderTexture.h" +#include "CommandBuffer.h" namespace sh::render { @@ -13,10 +14,8 @@ namespace sh::render if (cpyDatas.empty()) return; - drawList = BuildDrawList(renderData); - renderTextures.clear(); - for (auto& cpyData : cpyDatas) + for (const CopyData& cpyData : cpyDatas) renderTextures[cpyData.src] = ResourceUsage::TransferSrc; } SH_RENDER_API void CopyPass::Record(CommandBuffer& cmd, const IRenderContext& ctx, const RenderTarget& renderData) @@ -24,27 +23,10 @@ namespace sh::render if (cpyDatas.empty()) return; - RenderTarget data{}; - data.frameIndex = 0; - data.camera = nullptr; - data.target = nullptr; - data.drawables = nullptr; - - ScriptableRenderPass::Record(cmd, ctx, data); + for (const CopyData& cpyData : cpyDatas) + cmd.Blit(*cpyData.src, cpyData.x, cpyData.y, *cpyData.dst); cpyDatas.clear(); } - SH_RENDER_API auto CopyPass::BuildDrawList(const RenderTarget& renderData) -> DrawList - { - DrawList list{}; - list.drawCall.push_back( - [cpyDatas = cpyDatas](CommandBuffer& cmd) - { - for (auto& cpyData : cpyDatas) - cmd.Blit(*cpyData.src, cpyData.x, cpyData.y, *cpyData.dst); - } - ); - return list; - } SH_RENDER_API void CopyPass::EnqueCopyImagePixelToBuffer(RenderTexture& rt, int x, int y, IBuffer& buffer) { diff --git a/src/Render/RenderPass/ShadowMapPass.cpp b/src/Render/RenderPass/ShadowMapPass.cpp index 87dc7e8b..fa7856b5 100644 --- a/src/Render/RenderPass/ShadowMapPass.cpp +++ b/src/Render/RenderPass/ShadowMapPass.cpp @@ -5,7 +5,6 @@ #include "Material.h" #include "Shader.h" #include "IRenderContext.h" -#include "IRenderImpl.h" namespace sh::render { diff --git a/src/Render/RenderPass/TransparentPass.cpp b/src/Render/RenderPass/TransparentPass.cpp index c566303b..09c591e3 100644 --- a/src/Render/RenderPass/TransparentPass.cpp +++ b/src/Render/RenderPass/TransparentPass.cpp @@ -12,34 +12,41 @@ namespace sh::render ScriptableRenderPass(core::Name(name), renderQueue) { } - SH_RENDER_API auto TransparentPass::BuildDrawList(const RenderTarget& renderData) -> DrawList + + SH_RENDER_API void TransparentPass::Configure(const RenderTarget& renderData) + { + SetImageUsages(renderData); + } + SH_RENDER_API void TransparentPass::Record(CommandBuffer& cmd, const IRenderContext& ctx, const RenderTarget& renderTarget) { - DrawList list{}; - list.renderData = std::vector{}; - list.bClearColor = false; - list.bClearDepth = false; - if (renderData.drawables == nullptr) - return list; - - for (auto drawable : *renderData.drawables) + cmd.SetRenderTarget(renderTarget, false, false, true, false); + SetViewportScissor(cmd, ctx, renderTarget); + + if (renderTarget.drawables == nullptr) + return; + + struct RenderItem { - if (!core::IsValid(drawable) || !drawable->CheckAssetValid() || drawable->GetMaterial()->GetShader()->GetShaderPasses(passName) == nullptr) - continue; + const Material* material; + Mesh::Topology topology; + const Drawable* drawable; + }; - DrawList::RenderItem item{}; + std::vector items; + for (const Drawable* drawable : *renderTarget.drawables) + { + RenderItem item{}; item.material = drawable->GetMaterial(); item.topology = drawable->GetTopology(core::ThreadType::Render); item.drawable = drawable; - - std::get>(list.renderData).push_back(item); - ++list.drawableCount; + items.push_back(item); } - const auto& camPos = renderData.camera->GetPos(core::ThreadType::Render); - const auto& camTo = renderData.camera->GetLookPos(core::ThreadType::Render); + const glm::vec3& camPos = renderTarget.camera->GetPos(core::ThreadType::Render); + const glm::vec3& camTo = renderTarget.camera->GetLookPos(core::ThreadType::Render); const glm::vec3 to = glm::normalize(camTo - camPos); - std::stable_sort(std::get<1>(list.renderData).begin(), std::get<1>(list.renderData).end(), - [&camPos, &to](const DrawList::RenderItem& left, const DrawList::RenderItem& right) -> bool + std::stable_sort(items.begin(), items.end(), + [&camPos, &to](const RenderItem& left, const RenderItem& right) -> bool { const glm::vec3 posLeft = left.drawable->GetModelMatrix(core::ThreadType::Render)[3]; const glm::vec3 posRight = right.drawable->GetModelMatrix(core::ThreadType::Render)[3]; @@ -48,6 +55,8 @@ namespace sh::render return leftLen > rightLen; } ); - return list; + + for (const RenderItem& item : items) + cmd.DrawMesh(*item.drawable, passName); } }//namespace \ No newline at end of file diff --git a/src/Render/ScriptableRenderPass.cpp b/src/Render/ScriptableRenderPass.cpp index 1f28e404..3300e3d8 100644 --- a/src/Render/ScriptableRenderPass.cpp +++ b/src/Render/ScriptableRenderPass.cpp @@ -15,53 +15,67 @@ namespace sh::render } SH_RENDER_API void ScriptableRenderPass::Configure(const RenderTarget& renderData) { - drawList = BuildDrawList(renderData); - CollectRenderImages(renderData, drawList); - } - SH_RENDER_API void ScriptableRenderPass::Record(CommandBuffer& cmd, const IRenderContext& ctx, const RenderTarget& renderData) - { - ctx.GetRenderImpl().RecordCommand(cmd, passName, renderData, drawList, bStoreImage); + if (renderData.drawables != nullptr) + { + renderBatches = CreateRenderBatch(*renderData.drawables); + } + SetImageUsages(renderData); } - SH_RENDER_API auto ScriptableRenderPass::BuildDrawList(const RenderTarget& renderData) -> DrawList + SH_RENDER_API void ScriptableRenderPass::Record(CommandBuffer& cmd, const IRenderContext& ctx, const RenderTarget& renderTarget) { - DrawList list{}; - list.renderData = std::vector{}; - if (renderData.drawables == nullptr) - return list; + cmd.SetRenderTarget(renderTarget, true, true, true, true); + SetViewportScissor(cmd, ctx, renderTarget); + + if (renderTarget.drawables == nullptr) + return; - std::vector& groups = std::get>(list.renderData); + for (const RenderBatch& batch : renderBatches) + { + cmd.DrawMeshBatch(batch.drawables, passName); + } + //ctx.GetRenderImpl().RecordCommand(cmd, passName, renderTarget, drawList, bStoreImage); + } + SH_RENDER_API auto ScriptableRenderPass::CreateRenderBatch(const std::vector& drawables) const -> std::vector + { + std::vector groups; struct GroupKey { const Material* mat; Mesh::Topology topology; - bool operator==(const GroupKey& other) const noexcept { return mat == other.mat && topology == other.topology; } + bool bSkinned; + + bool operator==(const GroupKey& other) const noexcept { return mat == other.mat && topology == other.topology && bSkinned == other.bSkinned; } }; struct GroupKeyHash { std::size_t operator()(const GroupKey& k) const noexcept { - return core::Util::CombineHash(std::hash{}(k.mat), std::hash{}(static_cast(k.topology))); + const std::size_t hash0 = core::Util::CombineHash(std::hash{}(k.mat), std::hash{}(static_cast(k.topology))); + const std::size_t hash1 = core::Util::CombineHash(hash0, std::hash{}(k.bSkinned)); + return hash1; } }; std::unordered_map groupIndex; - groupIndex.reserve(renderData.drawables->size()); + groupIndex.reserve(drawables.size()); - for (Drawable* drawable : *renderData.drawables) + for (const Drawable* drawable : drawables) { - if (!core::IsValid(drawable) || !drawable->CheckAssetValid() || drawable->GetMaterial()->GetShader()->GetShaderPasses(passName) == nullptr) + if (!core::IsValid(drawable) || !drawable->CheckAssetValid()) continue; const Material* const mat = drawable->GetMaterial(); Mesh::Topology topology = drawable->GetTopology(core::ThreadType::Render); + const bool bSkinned = drawable->IsSkinnedMesh(); - GroupKey key{ mat, topology }; + GroupKey key{ mat, topology, bSkinned }; auto it = groupIndex.find(key); if (it == groupIndex.end()) { - DrawList::RenderGroup group{}; + RenderBatch group{}; group.material = mat; group.topology = topology; + group.bSkinned = bSkinned; group.drawables.push_back(drawable); groupIndex.emplace(key, groups.size()); @@ -71,20 +85,10 @@ namespace sh::render { groups[it->second].drawables.push_back(drawable); } - ++list.drawableCount; } - return list; - } - SH_RENDER_API void ScriptableRenderPass::EmitBarrier(CommandBuffer& cmd, const IRenderContext& ctx, const std::vector& barriers) const - { - ctx.GetRenderImpl().EmitBarrier(cmd, barriers); - } - SH_RENDER_API auto ScriptableRenderPass::GetRenderCallCount() const -> uint32_t - { - return static_cast(drawList.drawCall.size()) + drawList.drawableCount; + return groups; } - - void ScriptableRenderPass::CollectRenderImages(const RenderTarget& renderData, const DrawList& drawList) + SH_RENDER_API void ScriptableRenderPass::SetImageUsages(const RenderTarget& renderData) { renderTextures.clear(); if (renderData.target != nullptr && IsDepthTexture(renderData.target->GetTextureFormat())) @@ -92,37 +96,53 @@ namespace sh::render else renderTextures[renderData.target] = ResourceUsage::ColorAttachment; - const auto collectFn = - [this](const Material* mat) + if (renderData.drawables == nullptr) + return; + + for (const Drawable* drawable : *renderData.drawables) + { + if (drawable == nullptr || !drawable->CheckAssetValid()) + continue; + + const Material* const mat = drawable->GetMaterial(); + for (auto& [name, rt] : mat->GetCachedRenderTextures()) { - for (auto& [name, rt] : mat->GetCachedRenderTextures()) + auto it = renderTextures.find(rt); + if (it != renderTextures.end()) { - auto it = renderTextures.find(rt); - if (it != renderTextures.end()) - { - if (it->second == ResourceUsage::ColorAttachment || - it->second == ResourceUsage::DepthStencilAttachment) - { - SH_ERROR_FORMAT("RenderTexture {} is used as Attachment", it->first->GetName().ToString()); - continue; - } - } - else + if (it->second == ResourceUsage::ColorAttachment || + it->second == ResourceUsage::DepthStencilAttachment) { - renderTextures[rt] = rt->IsDepthOnly() ? ResourceUsage::DepthStencilSampledRead : ResourceUsage::SampledRead; + SH_ERROR_FORMAT("RenderTexture {} is used as Attachment", it->first->GetName().ToString()); + continue; } } - }; - - if (std::holds_alternative>(drawList.renderData)) + else + { + renderTextures[rt] = rt->IsDepthOnly() ? ResourceUsage::DepthStencilSampledRead : ResourceUsage::SampledRead; + } + } + } + } + SH_RENDER_API void ScriptableRenderPass::SetViewportScissor(CommandBuffer& cmd, const IRenderContext& ctx, const RenderTarget& renderTarget) + { + if (renderTarget.target == nullptr) { - for (const DrawList::RenderGroup& group : std::get>(drawList.renderData)) - collectFn(group.material); + const int x = static_cast(ctx.GetViewportStart().x); + const int y = static_cast(ctx.GetViewportEnd().y); + const int w = static_cast(ctx.GetViewportEnd().x - ctx.GetViewportStart().x); + const int h = static_cast(ctx.GetViewportEnd().y - ctx.GetViewportStart().y); + cmd.SetViewport(x, y, w, -h); + cmd.SetScissor(0, 0, static_cast(w), static_cast(h)); } else { - for (const DrawList::RenderItem& item : std::get>(drawList.renderData)) - collectFn(item.material); + const int x = 0; + const int y = static_cast(renderTarget.target->GetSize().y); + const int w = static_cast(renderTarget.target->GetSize().x); + const int h = y; + cmd.SetViewport(x, y, w, -h); + cmd.SetScissor(0, 0, static_cast(w), static_cast(h)); } } }//namespace \ No newline at end of file diff --git a/src/Render/ScriptableRenderer.cpp b/src/Render/ScriptableRenderer.cpp index 01f3d295..5182ec6a 100644 --- a/src/Render/ScriptableRenderer.cpp +++ b/src/Render/ScriptableRenderer.cpp @@ -125,7 +125,7 @@ namespace sh::render { CommandBuffer* cmd = ctx.AllocateCommandBuffer(false); cmd->Begin(true); - IRenderThrMethod::EmitBarrier(*pass, *cmd, ctx, barriers[idx]); + cmd->EmitBarrier(barriers[idx]); IRenderThrMethod::Record(*pass, *cmd, ctx, data); cmd->End(); return { pass, cmd }; @@ -154,8 +154,19 @@ namespace sh::render IRenderThrMethod::Configure(*cpyPass, target); std::vector preBarriers = BuildBarrierInfo(*cpyPass, imgIdx); - std::vector postBarriers = { BarrierInfo{imgIdx, swapChainStates[imgIdx], ResourceUsage::Present} }; - swapChainStates[imgIdx] = ResourceUsage::Present; + std::vector postBarriers; + if (swapChainStates.size() > imgIdx) + { + postBarriers = { BarrierInfo{imgIdx, swapChainStates[imgIdx], ResourceUsage::Present} }; + swapChainStates[imgIdx] = ResourceUsage::Present; + } + else + { + swapChainStates.resize(imgIdx + 1, ResourceUsage::Undefined); + postBarriers = { BarrierInfo{imgIdx, ResourceUsage::Undefined, ResourceUsage::Present} }; + swapChainStates[imgIdx] = ResourceUsage::Present; + } + RenderTarget rt{}; rt.frameIndex = imgIdx; @@ -165,9 +176,9 @@ namespace sh::render CommandBuffer* cmd = ctx.AllocateCommandBuffer(false); cmd->Begin(true); - IRenderThrMethod::EmitBarrier(*cpyPass, *cmd, ctx, preBarriers); + cmd->EmitBarrier(preBarriers); IRenderThrMethod::Record(*cpyPass, *cmd, ctx, rt); - IRenderThrMethod::EmitBarrier(*cpyPass, *cmd, ctx, postBarriers); + cmd->EmitBarrier(postBarriers); cmd->End(); recordedCmds.push_back({ *cpyPass, *cmd}); diff --git a/src/Render/VulkanImpl/VulkanCommandBuffer.cpp b/src/Render/VulkanImpl/VulkanCommandBuffer.cpp index 75edb494..309225d9 100644 --- a/src/Render/VulkanImpl/VulkanCommandBuffer.cpp +++ b/src/Render/VulkanImpl/VulkanCommandBuffer.cpp @@ -3,11 +3,26 @@ #include "VulkanImageBuffer.h" #include "VulkanBuffer.h" #include "VulkanDescriptorSet.h" +#include "VulkanPipelineManager.h" #include "VulkanComputePipeline.h" #include "VulkanComputePipelineManager.h" +#include "VulkanSwapChain.h" +#include "VulkanShaderPass.h" +#include "VulkanCameraBuffers.h" +#include "VulkanVertexBuffer.h" +#include "VulkanSkinnedVertexBuffer.h" #include "Render/RenderTexture.h" #include "Render/ComputeShader.h" +#include "Render/Shader.h" +#include "Render/Material.h" +#include "Render/Mesh.h" +#include "Render/SkinnedMesh.h" +#include "Render/Drawable.h" +#include "Render/ShaderPass.h" +#include "Render/Camera.h" +#include "Render/RenderData.h" +#include "Core/Reflection.hpp" #include "Core/Logger.h" #include @@ -35,6 +50,49 @@ namespace sh::render::vk Clear(); } + SH_RENDER_API void VulkanCommandBuffer::Begin(bool bOnetime) + { + if (buffer == VK_NULL_HANDLE) + return; + + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VkStructureType::VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = bOnetime ? VkCommandBufferUsageFlagBits::VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT : 0; + beginInfo.pInheritanceInfo = nullptr; + + VkResult result = vkBeginCommandBuffer(buffer, &beginInfo); + assert(result == VkResult::VK_SUCCESS); + if (result != VkResult::VK_SUCCESS) + { + const std::string err = fmt::format("Failed vkBeginCommandBuffer: {}", string_VkResult(result)); + SH_ERROR(err); + throw std::runtime_error{ err }; + } + } + SH_RENDER_API void VulkanCommandBuffer::End() + { + if (buffer == VK_NULL_HANDLE) + return; + + if (bBeginRender) + { + static PFN_vkCmdEndRenderingKHR pfnEnd = (PFN_vkCmdEndRenderingKHR)vkGetDeviceProcAddr(context.GetDevice(), "vkCmdEndRenderingKHR"); + pfnEnd(buffer); + bBeginRender = false; + } + renderState = RenderState{}; + + VkResult result = vkEndCommandBuffer(buffer); + assert(result == VkResult::VK_SUCCESS); + assert(result == VkResult::VK_SUCCESS); + if (result != VkResult::VK_SUCCESS) + { + const std::string err = fmt::format("Failed vkEndCommandBuffer: {}", string_VkResult(result)); + SH_ERROR(err); + throw std::runtime_error{ err }; + } + } + SH_RENDER_API void VulkanCommandBuffer::Blit(RenderTexture& src, int x, int y, IBuffer& dst) { // 배리어는 렌더 그래프에서 지정해줌 @@ -69,6 +127,323 @@ namespace sh::render::vk vkCmdDispatch(buffer, x, y, z); } + SH_RENDER_API void VulkanCommandBuffer::SetRenderTarget(const RenderTarget& renderTarget, bool bClearColor, bool bClearDepth, bool bStoreColor, bool bStoreDepth) + { + if (bBeginRender) + { + static PFN_vkCmdEndRenderingKHR pfnEndPrev = (PFN_vkCmdEndRenderingKHR)vkGetDeviceProcAddr(context.GetDevice(), "vkCmdEndRenderingKHR"); + pfnEndPrev(buffer); + bBeginRender = false; + } + + const bool bDepthOnly = renderTarget.target == nullptr ? false : renderTarget.target->IsDepthOnly(); + const VulkanImageBuffer* colorImg = nullptr; + const VulkanImageBuffer* colorMSAAImg = nullptr; + const VulkanImageBuffer* depthImg = nullptr; + RenderTargetLayout rtLayout{}; + if (renderTarget.target != nullptr) + { + colorImg = bDepthOnly ? nullptr : static_cast(renderTarget.target->GetTextureBuffer()); + colorMSAAImg = static_cast(renderTarget.target->GetMSAABuffer()); + depthImg = static_cast(renderTarget.target->GetDepthBuffer()); + rtLayout = renderTarget.target->GetLayout(); + } + else + { + colorImg = &context.GetSwapChain().GetSwapChainImages()[renderTarget.frameIndex]; + colorMSAAImg = &context.GetSwapChain().GetSwapChainMSAAImages()[renderTarget.frameIndex]; + depthImg = &context.GetSwapChain().GetSwapChainDepthImages()[renderTarget.frameIndex]; + rtLayout = context.GetSwapChain().GetRenderTargetLayout(); + } + const VulkanImageBuffer* const sizeRef = colorImg != nullptr ? colorImg : depthImg; + const uint32_t width = sizeRef->GetWidth(); + const uint32_t height = sizeRef->GetHeight(); + const bool bHasDepth = (depthImg != nullptr); + + VkRenderingAttachmentInfoKHR colorAttachment{}; + if (!bDepthOnly) + { + colorAttachment.sType = VkStructureType::VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; + const bool bMSAA = (colorMSAAImg != nullptr); + colorAttachment.imageView = bMSAA ? colorMSAAImg->GetImageView() : colorImg->GetImageView(); + colorAttachment.imageLayout = VkImageLayout::VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + colorAttachment.loadOp = bClearColor ? VkAttachmentLoadOp::VK_ATTACHMENT_LOAD_OP_CLEAR : VkAttachmentLoadOp::VK_ATTACHMENT_LOAD_OP_LOAD; + colorAttachment.storeOp = bStoreColor ? VkAttachmentStoreOp::VK_ATTACHMENT_STORE_OP_STORE : VkAttachmentStoreOp::VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachment.clearValue.color = { { 0.f, 0.f, 0.f, 1.f } }; + if (bMSAA) + { + colorAttachment.resolveImageView = colorImg->GetImageView(); + colorAttachment.resolveImageLayout = VkImageLayout::VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + colorAttachment.resolveMode = VkResolveModeFlagBitsKHR::VK_RESOLVE_MODE_AVERAGE_BIT_KHR; + } + } + VkRenderingAttachmentInfoKHR depthAttachment{}; + if (bHasDepth) + { + depthAttachment.sType = VkStructureType::VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; + depthAttachment.imageView = depthImg->GetImageView(); + depthAttachment.imageLayout = VkImageLayout::VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + depthAttachment.loadOp = bClearDepth ? VkAttachmentLoadOp::VK_ATTACHMENT_LOAD_OP_CLEAR : VkAttachmentLoadOp::VK_ATTACHMENT_LOAD_OP_LOAD; + depthAttachment.storeOp = bStoreDepth ? VkAttachmentStoreOp::VK_ATTACHMENT_STORE_OP_STORE : VkAttachmentStoreOp::VK_ATTACHMENT_STORE_OP_DONT_CARE; + depthAttachment.clearValue.depthStencil = { 1.0f, 0 }; + } + + VkRenderingInfoKHR renderingInfo{}; + renderingInfo.sType = VkStructureType::VK_STRUCTURE_TYPE_RENDERING_INFO_KHR; + renderingInfo.colorAttachmentCount = bDepthOnly ? 0 : 1; + renderingInfo.pColorAttachments = bDepthOnly ? nullptr : &colorAttachment; + renderingInfo.pDepthAttachment = bHasDepth ? &depthAttachment : nullptr; + // depth only는 스텐실 지원x + renderingInfo.pStencilAttachment = (bHasDepth && !bDepthOnly) ? &depthAttachment : nullptr; + renderingInfo.renderArea = { { 0, 0 }, { width, height } }; + renderingInfo.layerCount = 1; + + static PFN_vkCmdBeginRenderingKHR pfnBegin = (PFN_vkCmdBeginRenderingKHR)vkGetDeviceProcAddr(context.GetDevice(), "vkCmdBeginRenderingKHR"); + pfnBegin(buffer, &renderingInfo); + bBeginRender = true; + + renderState = RenderState{}; + renderState.layout = rtLayout; + renderState.camera = renderTarget.camera; + renderState.targetWidth = width; + renderState.targetHeight = height; + renderState.bDepthOnly = bDepthOnly; + } + + SH_RENDER_API void VulkanCommandBuffer::SetViewport(int x, int y, int width, int height) + { + VkViewport viewport{}; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + viewport.x = static_cast(x); + viewport.y = static_cast(y); + viewport.width = static_cast(width); + viewport.height = static_cast(height); + + vkCmdSetViewport(buffer, 0, 1, &viewport); + } + + SH_RENDER_API void VulkanCommandBuffer::SetScissor(uint32_t x, uint32_t y, uint32_t width, uint32_t height) + { + VkRect2D rect; + rect.offset.x = x; + rect.offset.y = y; + rect.extent.width = width; + rect.extent.height = height; + + vkCmdSetScissor(buffer, 0, 1, &rect); + } + + SH_RENDER_API void VulkanCommandBuffer::DrawMesh(const Drawable& drawable, core::Name passName) + { + if (!bBeginRender || renderState.camera == nullptr) + return; + + const Material& mat = *drawable.GetMaterial(); + const Mesh& mesh = *drawable.GetMesh(); + const Mesh::Topology topology = drawable.GetTopology(); + const bool bSkinned = drawable.IsSkinnedMesh(); + const Shader* const shader = mat.GetShader(); + if (!core::IsValid(shader)) + return; + + const std::vector>* passes = shader->GetShaderPasses(passName); + if (passes == nullptr) + return; + + std::optional cameraOffsetOpt = VulkanCameraBuffers::GetInstance()->GetDynamicOffset(*renderState.camera); + if (!cameraOffsetOpt.has_value()) + return; + const uint32_t cameraOffset = cameraOffsetOpt.value(); + + for (const ShaderPass& pass : *passes) + { + if (pass.IsPendingKill()) + continue; + + const VulkanShaderPass& vkPass = static_cast(pass); + VkPipelineLayout pipelineLayout = vkPass.GetPipelineLayout(); + const std::vector* constantData = mat.GetConstantData(pass); + const uint32_t setSize = static_cast(vkPass.GetSetCount()); + VulkanPipelineManager::PipelineHandle handle = + context.GetPipelineManager().GetOrCreatePipelineHandle(vkPass, renderState.layout, topology, bSkinned, constantData); + + if (handle.index != renderState.lastPipelineIdx || handle.generation != renderState.lastPipelineGen) + { + context.GetPipelineManager().BindPipeline(buffer, handle); + renderState.lastPipelineIdx = handle.index; + renderState.lastPipelineGen = handle.generation; + } + // set 0=Camera, 1=Object, 2=Material + + if (setSize > 0) + BindCameraSet(mat, pass, pipelineLayout, cameraOffset); + if (setSize > 2) + BindMaterialSet(mat, pass, pipelineLayout); + if (setSize > 1) + BindObjectSet(drawable, pass, pipelineLayout); + + if (pass.HasConstantUniform()) + { + vkCmdPushConstants(buffer, pipelineLayout, + VkShaderStageFlagBits::VK_SHADER_STAGE_VERTEX_BIT, + 0, sizeof(glm::mat4), + &drawable.GetModelMatrix(core::ThreadType::Render)); + } + + BindMesh(mesh, drawable.GetSubMeshIndex(), bSkinned); + } + } + SH_RENDER_API void VulkanCommandBuffer::DrawMeshBatch(const std::vector& drawables, core::Name passName) + { + if (drawables.empty()) + return; + + const Material& mat = *drawables.front()->GetMaterial(); + const Mesh::Topology topology = drawables.front()->GetTopology(); + const bool bSkinned = drawables.front()->IsSkinnedMesh(); + + const Shader* const shader = mat.GetShader(); + if (!core::IsValid(shader)) + return; + + const std::vector>* passes = shader->GetShaderPasses(passName); + if (passes == nullptr) + return; + + std::optional cameraOffsetOpt = VulkanCameraBuffers::GetInstance()->GetDynamicOffset(*renderState.camera); + if (!cameraOffsetOpt.has_value()) + return; + const uint32_t cameraOffset = cameraOffsetOpt.value(); + + for (const ShaderPass& pass : *passes) + { + if (pass.IsPendingKill()) + continue; + + const VulkanShaderPass& vkPass = static_cast(pass); + VkPipelineLayout pipelineLayout = vkPass.GetPipelineLayout(); + const std::vector* constantData = mat.GetConstantData(pass); + const uint32_t setSize = static_cast(vkPass.GetSetCount()); + VulkanPipelineManager::PipelineHandle handle = + context.GetPipelineManager().GetOrCreatePipelineHandle(vkPass, renderState.layout, topology, bSkinned, constantData); + + if (handle.index != renderState.lastPipelineIdx || handle.generation != renderState.lastPipelineGen) + { + context.GetPipelineManager().BindPipeline(buffer, handle); + renderState.lastPipelineIdx = handle.index; + renderState.lastPipelineGen = handle.generation; + } + // set 0=Camera, 1=Object, 2=Material + + if (setSize > 0) + BindCameraSet(mat, pass, pipelineLayout, cameraOffset); + if (setSize > 2) + BindMaterialSet(mat, pass, pipelineLayout); + + for (const Drawable* drawable : drawables) + { + if (setSize > 1) + BindObjectSet(*drawable, pass, pipelineLayout); + + if (pass.HasConstantUniform()) + { + vkCmdPushConstants(buffer, pipelineLayout, + VkShaderStageFlagBits::VK_SHADER_STAGE_VERTEX_BIT, + 0, sizeof(glm::mat4), + &drawable->GetModelMatrix(core::ThreadType::Render)); + } + + const Mesh& mesh = *drawable->GetMesh(); + BindMesh(mesh, drawable->GetSubMeshIndex(), bSkinned); + } + } + } + SH_RENDER_API void VulkanCommandBuffer::EmitBarrier(const std::vector& barriers) + { + for (const BarrierInfo& barrier : barriers) + { + VulkanImageBuffer* imgBuffer = nullptr; + VulkanImageBuffer* msaaBuffer = nullptr; + + if (barrier.target.index() == 0) + { + imgBuffer = (static_cast(std::get<0>(barrier.target)->GetTextureBuffer())); + msaaBuffer = (static_cast(std::get<0>(barrier.target)->GetMSAABuffer())); + } + else + { + const uint32_t imgIdx = std::get<1>(barrier.target); + imgBuffer = &context.GetSwapChain().GetSwapChainImages()[imgIdx]; + msaaBuffer = &context.GetSwapChain().GetSwapChainMSAAImages()[imgIdx]; + } + + VkImageLayout srcLayout, dstLayout; + VkPipelineStageFlags srcStage, dstStage; + VkAccessFlags srcAccess, dstAccess; + + auto mapUsageFn = + [&]( + ResourceUsage u, + VkImageLayout& layout, + VkPipelineStageFlags& stage, + VkAccessFlags& access) + { + switch (u) + { + case ResourceUsage::Undefined: + layout = VkImageLayout::VK_IMAGE_LAYOUT_UNDEFINED; + stage = VkPipelineStageFlagBits::VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + access = 0; + return; + case ResourceUsage::ColorAttachment: + layout = VkImageLayout::VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + stage = VkPipelineStageFlagBits::VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + access = VkAccessFlagBits::VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VkAccessFlagBits::VK_ACCESS_COLOR_ATTACHMENT_READ_BIT; + return; + case ResourceUsage::SampledRead: + layout = VkImageLayout::VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + stage = VkPipelineStageFlagBits::VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT; + access = VkAccessFlagBits::VK_ACCESS_SHADER_READ_BIT; + return; + case ResourceUsage::Present: + layout = VkImageLayout::VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + stage = VkPipelineStageFlagBits::VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; + access = 0; + return; + case ResourceUsage::TransferSrc: + layout = VkImageLayout::VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + stage = VkPipelineStageFlagBits::VK_PIPELINE_STAGE_TRANSFER_BIT; + access = VkAccessFlagBits::VK_ACCESS_TRANSFER_READ_BIT; + return; + case ResourceUsage::DepthStencilAttachment: + layout = VkImageLayout::VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + stage = VkPipelineStageFlagBits::VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | + VkPipelineStageFlagBits::VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; + access = VkAccessFlagBits::VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | + VkAccessFlagBits::VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT; + return; + case ResourceUsage::DepthStencilSampledRead: + layout = VkImageLayout::VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + stage = VkPipelineStageFlagBits::VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + access = VkAccessFlagBits::VK_ACCESS_SHADER_READ_BIT; + return; + } + }; + + mapUsageFn(barrier.lastUsage, srcLayout, srcStage, srcAccess); + mapUsageFn(barrier.curUsage, dstLayout, dstStage, dstAccess); + + VulkanImageBuffer::BarrierCommand(buffer, *imgBuffer, srcLayout, dstLayout, srcStage, dstStage, srcAccess, dstAccess); + + if (msaaBuffer != nullptr) + { + // MSAA 버퍼를 다른 용도로 쓸 일이 없을듯? + //if (barrier.curUsage != ResourceUsage::Present) + //VulkanImageBuffer::BarrierCommand(cmd.GetCommandBuffer(), *msaaBuffer, srcLayout, dstLayout, srcStage, dstStage, srcAccess, dstAccess); + } + } + } SH_RENDER_API auto VulkanCommandBuffer::Create(VkCommandPool pool, VkCommandBufferLevel level) -> VkResult { Clear(); @@ -91,13 +466,15 @@ namespace sh::render::vk End(); return VkResult::VK_SUCCESS; } - SH_RENDER_API auto VulkanCommandBuffer::ResetCommand(VkCommandBufferResetFlags flags) -> VkResult + SH_RENDER_API void VulkanCommandBuffer::Reset() { - if (buffer == VK_NULL_HANDLE) - return VkResult::VK_ERROR_UNKNOWN; - VkResult result = vkResetCommandBuffer(buffer, flags); + if (buffer == VK_NULL_HANDLE) + return; + VkResult result = vkResetCommandBuffer(buffer, 0); assert(result == VkResult::VK_SUCCESS); - return result; + + bBeginRender = false; + renderState = RenderState{}; } SH_RENDER_API auto VulkanCommandBuffer::GetOrCreateFence() -> VkFence { @@ -150,22 +527,6 @@ namespace sh::render::vk { signalSemaphores.push_back(s); } - SH_RENDER_API auto VulkanCommandBuffer::GetWaitSemaphores() const -> const std::vector& - { - return waitSemaphores; - } - SH_RENDER_API auto VulkanCommandBuffer::GetSignalSemaphores() const -> const std::vector& - { - return signalSemaphores; - } - SH_RENDER_API auto VulkanCommandBuffer::GetCommandBuffer() const -> VkCommandBuffer - { - return buffer; - } - SH_RENDER_API auto VulkanCommandBuffer::GetCommandPool() const -> VkCommandPool - { - return cmdPool; - } SH_RENDER_API void VulkanCommandBuffer::Clear() { ResetSyncObjects(); @@ -180,37 +541,73 @@ namespace sh::render::vk cmdPool = VK_NULL_HANDLE; } - void VulkanCommandBuffer::Begin(bool bOnetime) + void VulkanCommandBuffer::BindCameraSet(const Material& mat, const ShaderPass& pass, VkPipelineLayout pipelineLayout, uint32_t cameraOffset) { - if (buffer == VK_NULL_HANDLE) - return; - - VkCommandBufferBeginInfo beginInfo{}; - beginInfo.sType = VkStructureType::VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; - beginInfo.flags = bOnetime ? VkCommandBufferUsageFlagBits::VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT : 0; - beginInfo.pInheritanceInfo = nullptr; + VulkanDescriptorSet* const cameraUBO = static_cast( + mat.GetMaterialData().GetShaderBinding(pass, UniformStructLayout::Usage::Camera)); + VkDescriptorSet cameraSet = cameraUBO ? cameraUBO->GetVkDescriptorSet() : context.GetEmptyDescriptorSet(); + const uint32_t dynamicCount = cameraUBO ? 1 : 0; + vkCmdBindDescriptorSets(buffer, VkPipelineBindPoint::VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, + static_cast(UniformStructLayout::Usage::Camera), + 1, &cameraSet, dynamicCount, &cameraOffset); + } + void VulkanCommandBuffer::BindMaterialSet(const Material& mat, const ShaderPass& pass, VkPipelineLayout pipelineLayout) + { + VulkanDescriptorSet* const matUBO = static_cast( + mat.GetMaterialData().GetShaderBinding(pass, UniformStructLayout::Usage::Material)); + VkDescriptorSet matSet = matUBO ? matUBO->GetVkDescriptorSet() : context.GetEmptyDescriptorSet(); + vkCmdBindDescriptorSets(buffer, VkPipelineBindPoint::VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, + static_cast(UniformStructLayout::Usage::Material), + 1, &matSet, 0, nullptr); + } + void VulkanCommandBuffer::BindObjectSet(const Drawable& drawable, const ShaderPass& pass, VkPipelineLayout pipelineLayout) + { + VulkanDescriptorSet* const objUBO = static_cast( + drawable.GetMaterialData().GetShaderBinding(pass, UniformStructLayout::Usage::Object)); + VkDescriptorSet objSet = objUBO ? objUBO->GetVkDescriptorSet() : context.GetEmptyDescriptorSet(); + vkCmdBindDescriptorSets(buffer, VkPipelineBindPoint::VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, + static_cast(UniformStructLayout::Usage::Object), + 1, &objSet, 0, nullptr); + } + void VulkanCommandBuffer::BindMesh(const Mesh& mesh, uint32_t subMeshIdx, bool bSkinned) + { + uint32_t indexCount = 0; + uint32_t firstIndex = 0; + const std::vector& subMeshes = mesh.GetSubMeshes(); + const uint32_t subMeshIndex = subMeshIdx; + if (subMeshIndex < static_cast(subMeshes.size())) + { + indexCount = static_cast(subMeshes[subMeshIndex].indexCount); + firstIndex = static_cast(subMeshes[subMeshIndex].indexOffset); + } + else + { + indexCount = static_cast(mesh.GetIndices().size()); + firstIndex = 0; + } - VkResult result = vkBeginCommandBuffer(buffer, &beginInfo); - assert(result == VkResult::VK_SUCCESS); - if (result != VkResult::VK_SUCCESS) + if (bSkinned) { - const std::string err = fmt::format("Failed vkBeginCommandBuffer: {}", string_VkResult(result)); - SH_ERROR(err); - throw std::runtime_error{ err }; + const SkinnedMesh* const skinned = static_cast(&mesh); + const VulkanSkinnedVertexBuffer* vkSkinnedVB = + static_cast(skinned->GetVertexBuffer()); + VkBuffer buffers[2] = { + vkSkinnedVB->GetVertexBuffer().GetBuffer(), + vkSkinnedVB->GetBoneBuffer().GetBuffer() + }; + VkDeviceSize offsets[2] = { 0, 0 }; + vkCmdBindVertexBuffers(buffer, 0, 2, buffers, offsets); + vkCmdBindIndexBuffer(buffer, vkSkinnedVB->GetIndexBuffer().GetBuffer(), 0, VK_INDEX_TYPE_UINT32); } - } - void VulkanCommandBuffer::End() - { - if (buffer == VK_NULL_HANDLE) - return; - VkResult result = vkEndCommandBuffer(buffer); - assert(result == VkResult::VK_SUCCESS); - assert(result == VkResult::VK_SUCCESS); - if (result != VkResult::VK_SUCCESS) + else { - const std::string err = fmt::format("Failed vkEndCommandBuffer: {}", string_VkResult(result)); - SH_ERROR(err); - throw std::runtime_error{ err }; + const VulkanVertexBuffer* vkVB = static_cast(mesh.GetVertexBuffer()); + VkBuffer vbs[1] = { vkVB->GetVertexBuffer().GetBuffer() }; + VkDeviceSize offsets[1] = { 0 }; + vkCmdBindVertexBuffers(buffer, 0, 1, vbs, offsets); + vkCmdBindIndexBuffer(buffer, vkVB->GetIndexBuffer().GetBuffer(), 0, VkIndexType::VK_INDEX_TYPE_UINT32); } + + vkCmdDrawIndexed(buffer, indexCount, 1, firstIndex, 0, 0); } -} +}//namespace \ No newline at end of file diff --git a/src/Render/VulkanImpl/VulkanCommandBufferPool.cpp b/src/Render/VulkanImpl/VulkanCommandBufferPool.cpp index c60b3b7f..825eb753 100644 --- a/src/Render/VulkanImpl/VulkanCommandBufferPool.cpp +++ b/src/Render/VulkanImpl/VulkanCommandBufferPool.cpp @@ -122,7 +122,7 @@ namespace sh::render::vk { if (td.id == ad.tid) { - ad.ptr->ResetCommand(); + ad.ptr->Reset(); ad.ptr->ResetSyncObjects(); td.commands[ad.type].cmds.push(std::move(ad.ptr)); return; diff --git a/src/Render/VulkanImpl/VulkanContext.cpp b/src/Render/VulkanImpl/VulkanContext.cpp index 0eaaff02..1543f46d 100644 --- a/src/Render/VulkanImpl/VulkanContext.cpp +++ b/src/Render/VulkanImpl/VulkanContext.cpp @@ -7,7 +7,6 @@ #include "VulkanDescriptorPool.h" #include "VulkanPipelineManager.h" #include "VulkanComputePipelineManager.h" -#include "VulkanRenderImpl.h" #include "Core/Util.h" #include "Core/Logger.h" @@ -330,7 +329,6 @@ namespace sh::render::vk pipelineManager = std::make_unique(*this); computePipelineManager = std::make_unique(*this); - renderImpl = std::make_unique(*this); } SH_RENDER_API void VulkanContext::Clear() { @@ -390,11 +388,6 @@ namespace sh::render::vk return viewportEnd; } - SH_RENDER_API auto VulkanContext::GetRenderImpl() const -> IRenderImpl& - { - return *renderImpl; - } - SH_RENDER_API auto VulkanContext::ReSizing() -> bool { //swapChain->DestroySwapChain(); diff --git a/src/Render/VulkanImpl/VulkanRenderImpl.cpp b/src/Render/VulkanImpl/VulkanRenderImpl.cpp deleted file mode 100644 index 611436a9..00000000 --- a/src/Render/VulkanImpl/VulkanRenderImpl.cpp +++ /dev/null @@ -1,497 +0,0 @@ -#include "VulkanRenderImpl.h" -#include "VulkanCommandBuffer.h" -#include "VulkanSwapChain.h" -#include "VulkanDescriptorSet.h" -#include "VulkanVertexBuffer.h" -#include "VulkanSkinnedVertexBuffer.h" -#include "VulkanPipelineManager.h" -#include "VulkanShaderPass.h" -#include "../Material.h" -#include "../ShaderPass.h" -#include "../Drawable.h" -#include "../SkinnedMesh.h" - -#include "Core/Reflection.hpp" - -namespace sh::render::vk -{ - VulkanRenderImpl::VulkanRenderImpl(VulkanContext& context) : - ctx(context) - { - cameraManager = VulkanCameraBuffers::GetInstance(); - } - SH_RENDER_API void VulkanRenderImpl::EmitBarrier(CommandBuffer& _cmd, const std::vector& barriers) const - { - auto& cmd = static_cast(_cmd); - for (const BarrierInfo& barrier : barriers) - { - VulkanImageBuffer* imgBuffer = nullptr; - VulkanImageBuffer* msaaBuffer = nullptr; - - if (barrier.target.index() == 0) - { - imgBuffer = (static_cast(std::get<0>(barrier.target)->GetTextureBuffer())); - msaaBuffer = (static_cast(std::get<0>(barrier.target)->GetMSAABuffer())); - } - else - { - const uint32_t imgIdx = std::get<1>(barrier.target); - imgBuffer = &ctx.GetSwapChain().GetSwapChainImages()[imgIdx]; - msaaBuffer = &ctx.GetSwapChain().GetSwapChainMSAAImages()[imgIdx]; - } - - VkImageLayout srcLayout, dstLayout; - VkPipelineStageFlags srcStage, dstStage; - VkAccessFlags srcAccess, dstAccess; - - auto mapUsageFn = - [&]( - ResourceUsage u, - VkImageLayout& layout, - VkPipelineStageFlags& stage, - VkAccessFlags& access) - { - switch (u) - { - case ResourceUsage::Undefined: - layout = VkImageLayout::VK_IMAGE_LAYOUT_UNDEFINED; - stage = VkPipelineStageFlagBits::VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - access = 0; - return; - case ResourceUsage::ColorAttachment: - layout = VkImageLayout::VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - stage = VkPipelineStageFlagBits::VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - access = VkAccessFlagBits::VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VkAccessFlagBits::VK_ACCESS_COLOR_ATTACHMENT_READ_BIT; - return; - case ResourceUsage::SampledRead: - layout = VkImageLayout::VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - stage = VkPipelineStageFlagBits::VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT; - access = VkAccessFlagBits::VK_ACCESS_SHADER_READ_BIT; - return; - case ResourceUsage::Present: - layout = VkImageLayout::VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - stage = VkPipelineStageFlagBits::VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; - access = 0; - return; - case ResourceUsage::TransferSrc: - layout = VkImageLayout::VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; - stage = VkPipelineStageFlagBits::VK_PIPELINE_STAGE_TRANSFER_BIT; - access = VkAccessFlagBits::VK_ACCESS_TRANSFER_READ_BIT; - return; - case ResourceUsage::DepthStencilAttachment: - layout = VkImageLayout::VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - stage = VkPipelineStageFlagBits::VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | - VkPipelineStageFlagBits::VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; - access = VkAccessFlagBits::VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | - VkAccessFlagBits::VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT; - return; - case ResourceUsage::DepthStencilSampledRead: - layout = VkImageLayout::VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - stage = VkPipelineStageFlagBits::VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; - access = VkAccessFlagBits::VK_ACCESS_SHADER_READ_BIT; - return; - } - }; - - mapUsageFn(barrier.lastUsage, srcLayout, srcStage, srcAccess); - mapUsageFn(barrier.curUsage, dstLayout, dstStage, dstAccess); - - VulkanImageBuffer::BarrierCommand(cmd.GetCommandBuffer(), *imgBuffer, srcLayout, dstLayout, srcStage, dstStage, srcAccess, dstAccess); - - if (msaaBuffer != nullptr) - { - // MSAA 버퍼를 다른 용도로 쓸 일이 없을듯? - //if (barrier.curUsage != ResourceUsage::Present) - //VulkanImageBuffer::BarrierCommand(cmd.GetCommandBuffer(), *msaaBuffer, srcLayout, dstLayout, srcStage, dstStage, srcAccess, dstAccess); - } - } - } - namespace - { - struct ResolvedTarget - { - VulkanImageBuffer* color = nullptr; - VulkanImageBuffer* colorMSAA = nullptr; - VulkanImageBuffer* depth = nullptr; - RenderTargetLayout layout{}; - uint32_t width = 0; - uint32_t height = 0; - bool bSwapChain = false; - bool bDepthOnly = false; - }; - - auto ResolveTarget(VulkanContext& ctx, const RenderTarget& renderData) -> ResolvedTarget - { - ResolvedTarget target{}; - if (core::IsValid(renderData.target)) - { - target.bSwapChain = false; - target.bDepthOnly = renderData.target->IsDepthOnly(); - target.color = target.bDepthOnly ? nullptr : static_cast(renderData.target->GetTextureBuffer()); - target.colorMSAA = static_cast(renderData.target->GetMSAABuffer()); - target.depth = static_cast(renderData.target->GetDepthBuffer()); - target.layout = renderData.target->GetLayout(); - - VulkanImageBuffer* const sizeRef = target.color != nullptr ? target.color : target.depth; - target.width = sizeRef->GetWidth(); - target.height = sizeRef->GetHeight(); - } - else - { - target.bSwapChain = true; - const uint32_t idx = renderData.frameIndex; - target.colorMSAA = &ctx.GetSwapChain().GetSwapChainMSAAImages()[idx]; - target.color = &ctx.GetSwapChain().GetSwapChainImages()[idx]; - target.depth = &ctx.GetSwapChain().GetSwapChainDepthImages()[idx]; - target.layout = ctx.GetSwapChain().GetRenderTargetLayout(); - target.width = target.color->GetWidth(); - target.height = target.color->GetHeight(); - } - return target; - } - - auto BuildViewport(VulkanContext& ctx, const ResolvedTarget& target) -> VkViewport - { - VkViewport viewport{}; - viewport.minDepth = 0.0f; - viewport.maxDepth = 1.0f; - if (target.bSwapChain) - { - const float w = ctx.GetViewportEnd().x - ctx.GetViewportStart().x; - const float h = ctx.GetViewportEnd().y - ctx.GetViewportStart().y; - const float surfW = static_cast(ctx.GetSwapChain().GetSwapChainSize().width); - const float surfH = static_cast(ctx.GetSwapChain().GetSwapChainSize().height); - viewport.x = ctx.GetViewportStart().x; - viewport.y = ctx.GetViewportEnd().y; - viewport.width = std::min(w, surfW); - viewport.height = -std::min(h, surfH); - } - else - { - viewport.x = 0.f; - viewport.y = static_cast(target.height); - viewport.width = static_cast(target.width); - viewport.height = -static_cast(target.height); - } - return viewport; - } - - auto BuildScissor(VulkanContext& ctx, const ResolvedTarget& target) -> VkRect2D - { - VkRect2D scissor{}; - scissor.offset = { 0, 0 }; - scissor.extent = target.bSwapChain ? ctx.GetSwapChain().GetSwapChainSize() : VkExtent2D{ target.width, target.height }; - return scissor; - } - - auto BuildColorAttachment(const ResolvedTarget& target, const DrawList& drawList, bool bStoreImage) -> VkRenderingAttachmentInfoKHR - { - VkRenderingAttachmentInfoKHR info{}; - info.sType = VkStructureType::VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; - const bool bMSAA = (target.colorMSAA != nullptr); - info.imageView = bMSAA ? target.colorMSAA->GetImageView() : target.color->GetImageView(); - info.imageLayout = VkImageLayout::VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - info.loadOp = drawList.bClearColor ? VkAttachmentLoadOp::VK_ATTACHMENT_LOAD_OP_CLEAR : VkAttachmentLoadOp::VK_ATTACHMENT_LOAD_OP_LOAD; - info.storeOp = bStoreImage ? VkAttachmentStoreOp::VK_ATTACHMENT_STORE_OP_STORE : VkAttachmentStoreOp::VK_ATTACHMENT_STORE_OP_DONT_CARE; - info.clearValue.color = { { 0.f, 0.f, 0.f, 1.f } }; - if (bMSAA) - { - info.resolveImageView = target.color->GetImageView(); - info.resolveImageLayout = VkImageLayout::VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - info.resolveMode = VkResolveModeFlagBitsKHR::VK_RESOLVE_MODE_AVERAGE_BIT_KHR; - } - return info; - } - - auto BuildDepthAttachment(const ResolvedTarget& target, const DrawList& drawList, bool bStoreImage) -> VkRenderingAttachmentInfoKHR - { - VkRenderingAttachmentInfoKHR info{}; - info.sType = VkStructureType::VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; - info.imageView = target.depth->GetImageView(); - info.imageLayout = VkImageLayout::VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - info.loadOp = drawList.bClearDepth ? VkAttachmentLoadOp::VK_ATTACHMENT_LOAD_OP_CLEAR : VkAttachmentLoadOp::VK_ATTACHMENT_LOAD_OP_LOAD; - info.storeOp = bStoreImage ? VkAttachmentStoreOp::VK_ATTACHMENT_STORE_OP_STORE : VkAttachmentStoreOp::VK_ATTACHMENT_STORE_OP_DONT_CARE; - info.clearValue.depthStencil = { 1.0f, 0 }; - return info; - } - - auto HasDrawables(const DrawList& drawList) -> bool - { - if (std::holds_alternative>(drawList.renderData)) - return !std::get>(drawList.renderData).empty(); - - return !std::get>(drawList.renderData).empty(); - } - }//anonymous namespace - - SH_RENDER_API void VulkanRenderImpl::RecordCommand(CommandBuffer& _cmd, const core::Name& passName, const RenderTarget& renderData, const DrawList& drawList, bool bStoreImage) const - { - // 드로우 목적이 아닌 경우 - if (renderData.camera == nullptr) - { - for (auto& call : drawList.drawCall) - call(_cmd); - return; - } - - VulkanCommandBuffer& cmd = static_cast(_cmd); - VkCommandBuffer commandBuffer = cmd.GetCommandBuffer(); - - const ResolvedTarget target = ResolveTarget(ctx, renderData); - const VkViewport viewport = BuildViewport(ctx, target); - const VkRect2D scissor = BuildScissor(ctx, target); - - VkRenderingAttachmentInfoKHR colorAttach{}; - VkRenderingAttachmentInfoKHR depthAttach{}; - if (!target.bDepthOnly) - colorAttach = BuildColorAttachment(target, drawList, bStoreImage); - const bool bHasDepth = (target.depth != nullptr); - if (bHasDepth) - depthAttach = BuildDepthAttachment(target, drawList, bStoreImage); - - VkRenderingInfoKHR renderingInfo{}; - renderingInfo.sType = VkStructureType::VK_STRUCTURE_TYPE_RENDERING_INFO_KHR; - renderingInfo.colorAttachmentCount = target.bDepthOnly ? 0 : 1; - renderingInfo.pColorAttachments = target.bDepthOnly ? nullptr : &colorAttach; - renderingInfo.pDepthAttachment = bHasDepth ? &depthAttach : nullptr; - // depth-only RT는 view aspect=DEPTH-only이므로 stencil 첨부는 비활성화 - renderingInfo.pStencilAttachment = (bHasDepth && !target.bDepthOnly) ? &depthAttach : nullptr; - renderingInfo.renderArea = { { 0, 0 }, { target.width, target.height } }; - renderingInfo.layerCount = 1; - - static PFN_vkCmdBeginRenderingKHR pfnBegin = (PFN_vkCmdBeginRenderingKHR)vkGetDeviceProcAddr(ctx.GetDevice(), "vkCmdBeginRenderingKHR"); - static PFN_vkCmdEndRenderingKHR pfnEnd = (PFN_vkCmdEndRenderingKHR) vkGetDeviceProcAddr(ctx.GetDevice(), "vkCmdEndRenderingKHR"); - - // 5) 기록 - pfnBegin(commandBuffer, &renderingInfo); - vkCmdSetViewport(commandBuffer, 0, 1, &viewport); - vkCmdSetScissor(commandBuffer, 0, 1, &scissor); - - if (HasDrawables(drawList)) - RenderDrawable(cmd, passName, renderData, drawList, target.layout); - - for (auto& call : drawList.drawCall) - call(_cmd); - - pfnEnd(commandBuffer); - } - - void VulkanRenderImpl::BindCameraSet(VulkanCommandBuffer& cmd, VkPipelineLayout layout, const ShaderPass& pass, const Material& mat, uint32_t cameraOffset) const - { - // 카메라 데이터는 다이나믹 디스크립터셋 - VulkanDescriptorSet* const cameraUBO = static_cast( - mat.GetMaterialData().GetShaderBinding(pass, UniformStructLayout::Usage::Camera)); - - VkDescriptorSet cameraSet = cameraUBO ? cameraUBO->GetVkDescriptorSet() : ctx.GetEmptyDescriptorSet(); - const uint32_t dynamicCount = cameraUBO ? 1 : 0; - - vkCmdBindDescriptorSets(cmd.GetCommandBuffer(), - VkPipelineBindPoint::VK_PIPELINE_BIND_POINT_GRAPHICS, layout, - static_cast(UniformStructLayout::Usage::Camera), - 1, &cameraSet, dynamicCount, &cameraOffset); - } - void VulkanRenderImpl::BindMaterialSet(VulkanCommandBuffer& cmd, VkPipelineLayout layout, const ShaderPass& pass, const Material& mat) const - { - VulkanDescriptorSet* const materialUniformBuffer = static_cast( - mat.GetMaterialData().GetShaderBinding(pass, UniformStructLayout::Usage::Material)); - - VkDescriptorSet materialDescriptorSet = materialUniformBuffer ? materialUniformBuffer->GetVkDescriptorSet() : ctx.GetEmptyDescriptorSet(); - - vkCmdBindDescriptorSets(cmd.GetCommandBuffer(), - VkPipelineBindPoint::VK_PIPELINE_BIND_POINT_GRAPHICS, layout, - static_cast(UniformStructLayout::Usage::Material), - 1, &materialDescriptorSet, 0, nullptr); - } - void VulkanRenderImpl::BindObjectSet(VulkanCommandBuffer& cmd, VkPipelineLayout layout, const ShaderPass& pass, Drawable& drawable) const - { - VulkanDescriptorSet* const objectUniformBuffer = static_cast( - drawable.GetMaterialData().GetShaderBinding(pass, UniformStructLayout::Usage::Object)); - - VkDescriptorSet objectDescriptorSet = objectUniformBuffer ? objectUniformBuffer->GetVkDescriptorSet() : ctx.GetEmptyDescriptorSet(); - - vkCmdBindDescriptorSets(cmd.GetCommandBuffer(), - VkPipelineBindPoint::VK_PIPELINE_BIND_POINT_GRAPHICS, layout, - static_cast(UniformStructLayout::Usage::Object), - 1, &objectDescriptorSet, 0, nullptr); - } - void VulkanRenderImpl::DrawMesh(VulkanCommandBuffer& cmd, const ShaderPass& pass, const Mesh& mesh, int subMeshIndex) const - { - uint32_t indexCount; - uint32_t firstIndex; - const auto& subMeshes = mesh.GetSubMeshes(); - if (subMeshIndex >= 0 && subMeshIndex < static_cast(subMeshes.size())) - { - indexCount = static_cast(subMeshes[subMeshIndex].indexCount); - firstIndex = static_cast(subMeshes[subMeshIndex].indexOffset); - } - else - { - indexCount = static_cast(mesh.GetIndices().size()); - firstIndex = 0; - } - - const SkinnedMesh* skinnedMesh = core::reflection::Cast(&mesh); - if (skinnedMesh) - { - const VulkanSkinnedVertexBuffer* vkSkinnedVB = - static_cast(skinnedMesh->GetVertexBuffer()); - - VkBuffer buffers[2] = { - vkSkinnedVB->GetVertexBuffer().GetBuffer(), - vkSkinnedVB->GetBoneBuffer().GetBuffer() - }; - VkDeviceSize offsets[2] = { 0, 0 }; - vkCmdBindVertexBuffers(cmd.GetCommandBuffer(), 0, 2, buffers, offsets); - vkCmdBindIndexBuffer(cmd.GetCommandBuffer(), vkSkinnedVB->GetIndexBuffer().GetBuffer(), 0, VK_INDEX_TYPE_UINT32); - } - else - { - const VulkanVertexBuffer* vkVertexBuffer = static_cast(mesh.GetVertexBuffer()); - - std::array buffers = { vkVertexBuffer->GetVertexBuffer().GetBuffer() }; - - VkDeviceSize offsets[1] = { 0 }; - vkCmdBindVertexBuffers(cmd.GetCommandBuffer(), 0, 1, buffers.data(), offsets); - vkCmdBindIndexBuffer(cmd.GetCommandBuffer(), vkVertexBuffer->GetIndexBuffer().GetBuffer(), 0, VkIndexType::VK_INDEX_TYPE_UINT32); - } - vkCmdDrawIndexed(cmd.GetCommandBuffer(), indexCount, 1, firstIndex, 0, 0); - } - void VulkanRenderImpl::RenderDrawable(VulkanCommandBuffer& cmd, const core::Name& passName, const RenderTarget& renderData, const DrawList& drawList, const RenderTargetLayout& layout) const - { - std::optional cameraOffsetOpt = cameraManager->GetDynamicOffset(*renderData.camera); - if (!cameraOffsetOpt.has_value()) - return; - - VulkanPipelineManager::PipelineHandle lastPipelineHandle{ 0xffffffff, 0xffffffff }; - if (drawList.renderData.index() == 0) - { - for (const DrawList::RenderGroup& renderGroup : std::get<0>(drawList.renderData)) - { - const Material* mat = renderGroup.material; - assert(mat); - - const auto passVectorPtr = renderGroup.material->GetShader()->GetShaderPasses(passName); - if (passVectorPtr == nullptr) - continue; - - // 첫 번째 유효 drawable의 mesh로 스킨드 여부 판단 - bool bGroupSkinned = false; - for (auto drawable : renderGroup.drawables) - { - if (drawable && drawable->GetMesh()) - { - bGroupSkinned = (core::reflection::Cast(drawable->GetMesh()) != nullptr); - break; - } - } - - for (const ShaderPass& pass : *passVectorPtr) - { - if (pass.IsPendingKill()) - continue; - - const std::vector* constantData = mat->GetConstantData(pass); - - auto pipelineHandle = ctx.GetPipelineManager(). - GetOrCreatePipelineHandle(static_cast(pass), layout, renderGroup.topology, bGroupSkinned, constantData); - - if (lastPipelineHandle != pipelineHandle) - { - ctx.GetPipelineManager().BindPipeline(cmd.GetCommandBuffer(), pipelineHandle); - lastPipelineHandle = pipelineHandle; - } - VkPipelineLayout pipelineLayout = static_cast(pass).GetPipelineLayout(); - uint32_t setSize = static_cast(pass).GetSetCount(); - - // set = 0 카메라 - // set = 1 객체 고유 - // set = 2 메테리얼 - if (setSize > 0) - BindCameraSet(cmd, pipelineLayout, pass, *mat, cameraOffsetOpt.value()); - if (setSize > 2) - BindMaterialSet(cmd, pipelineLayout, pass, *mat); - - for (auto drawable : renderGroup.drawables) - { - if (drawable == nullptr) - continue; - const Mesh* mesh = drawable->GetMesh(); - if (!core::IsValid(mesh)) - continue; - - if (setSize > 1) - BindObjectSet(cmd, pipelineLayout, pass, *drawable); - - if (pass.HasConstantUniform()) - vkCmdPushConstants(cmd.GetCommandBuffer(), pipelineLayout, - VkShaderStageFlagBits::VK_SHADER_STAGE_VERTEX_BIT, - 0, sizeof(glm::mat4), - &drawable->GetModelMatrix(core::ThreadType::Render)); - - DrawMesh(cmd, pass, *mesh, drawable->GetSubMeshIndex()); - } - } - } - } - else - { - for (const DrawList::RenderItem& renderItem : std::get<1>(drawList.renderData)) - { - const Material* mat = renderItem.material; - assert(mat); - - const auto passVectorPtr = mat->GetShader()->GetShaderPasses(passName); - if (passVectorPtr == nullptr) - continue; - - bool bItemSkinned = false; - if (renderItem.drawable && renderItem.drawable->GetMesh()) - bItemSkinned = (core::reflection::Cast(renderItem.drawable->GetMesh()) != nullptr); - - for (const ShaderPass& pass : *passVectorPtr) - { - if (pass.IsPendingKill()) - continue; - - const std::vector* constantData = mat->GetConstantData(pass); - - auto pipelineHandle = ctx.GetPipelineManager(). - GetOrCreatePipelineHandle(static_cast(pass), layout, renderItem.topology, bItemSkinned, constantData); - - if (lastPipelineHandle != pipelineHandle) - { - ctx.GetPipelineManager().BindPipeline(cmd.GetCommandBuffer(), pipelineHandle); - lastPipelineHandle = pipelineHandle; - } - VkPipelineLayout pipelineLayout = static_cast(pass).GetPipelineLayout(); - uint32_t setSize = static_cast(pass).GetSetCount(); - - // set = 0 카메라 - // set = 1 객체 고유 - // set = 2 메테리얼 - if (setSize > 0) - BindCameraSet(cmd, pipelineLayout, pass, *mat, cameraOffsetOpt.value()); - if (setSize > 2) - BindMaterialSet(cmd, pipelineLayout, pass, *mat); - - if (renderItem.drawable == nullptr) - continue; - const Mesh* mesh = renderItem.drawable->GetMesh(); - if (!core::IsValid(mesh)) - continue; - - if (setSize > 1) - BindObjectSet(cmd, pipelineLayout, pass, *renderItem.drawable); - - if (pass.HasConstantUniform()) - vkCmdPushConstants(cmd.GetCommandBuffer(), pipelineLayout, - VkShaderStageFlagBits::VK_SHADER_STAGE_VERTEX_BIT, - 0, sizeof(glm::mat4), - &renderItem.drawable->GetModelMatrix(core::ThreadType::Render)); - - DrawMesh(cmd, pass, *mesh, renderItem.drawable->GetSubMeshIndex()); - } - } - } - } -}//namespace \ No newline at end of file From e32bb7a30bee8f41a0331100756b8bb4819bd5e9 Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Fri, 1 May 2026 07:47:30 +0900 Subject: [PATCH 21/60] fix(UIPass): Prevent clearing of color target --- src/Game/GameRenderer.cpp | 2 +- src/Game/UIPass.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Game/GameRenderer.cpp b/src/Game/GameRenderer.cpp index 8cdc962b..38823c03 100644 --- a/src/Game/GameRenderer.cpp +++ b/src/Game/GameRenderer.cpp @@ -14,7 +14,7 @@ namespace sh::game } SH_GAME_API void GameRenderer::Init() { - AddShadowPass(); + //AddShadowPass(); AddRenderPass(core::Name{ "Opaque" }, render::RenderQueue::Opaque); AddRenderPass(); AddRenderPass("UI", render::RenderQueue::UI); diff --git a/src/Game/UIPass.cpp b/src/Game/UIPass.cpp index a2f6b15f..2d6b94be 100644 --- a/src/Game/UIPass.cpp +++ b/src/Game/UIPass.cpp @@ -25,7 +25,8 @@ namespace sh::game SH_GAME_API void UIPass::Record(render::CommandBuffer& cmd, const render::IRenderContext& ctx, const render::RenderTarget& renderData) { - ScriptableRenderPass::Record(cmd, ctx, renderData); + cmd.SetRenderTarget(renderData, false, true, false, false); + SetViewportScissor(cmd, ctx, renderData); ImGui_ImplVulkan_RenderDrawData(&gui->GetDrawData(), static_cast(cmd).GetCommandBuffer()); } }//namespace \ No newline at end of file From 4b75f13f75a453c1bc68f3ebfd32bf3e62077e58 Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Fri, 1 May 2026 07:45:52 +0900 Subject: [PATCH 22/60] fix(MeshRenderer): Call SetMaterial() to create PropertyBlock when mats property changes --- src/Game/Component/Render/MeshRenderer.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Game/Component/Render/MeshRenderer.cpp b/src/Game/Component/Render/MeshRenderer.cpp index 972ab39e..fe2284d4 100644 --- a/src/Game/Component/Render/MeshRenderer.cpp +++ b/src/Game/Component/Render/MeshRenderer.cpp @@ -108,14 +108,15 @@ namespace sh::game } else if (prop.GetName() == core::Util::ConstexprHash("mats")) { - CreateDrawable(true); + for (std::size_t i = 0; i < mats.size(); ++i) + SetMaterial(i, mats[i]); } else if (prop.GetName() == core::Util::ConstexprHash("renderTag")) { - for (auto* d : drawables) + for (render::Drawable* drawable : drawables) { - if (d != nullptr) - d->SetRenderTagId(renderTag); + if (drawable != nullptr) + drawable->SetRenderTagId(renderTag); } } } From 353fc47309c2be9d5943196cae2563fcd231e0f8 Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Fri, 1 May 2026 07:46:18 +0900 Subject: [PATCH 23/60] fix(SkinnedMeshRenderer): Call InitIBM() when property changes --- .../Component/Render/SkinnedMeshRenderer.h | 2 ++ .../Component/Render/SkinnedMeshRenderer.cpp | 28 +++++++++++-------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/include/Game/Component/Render/SkinnedMeshRenderer.h b/include/Game/Component/Render/SkinnedMeshRenderer.h index 77b5e3eb..15bfdb0a 100644 --- a/include/Game/Component/Render/SkinnedMeshRenderer.h +++ b/include/Game/Component/Render/SkinnedMeshRenderer.h @@ -21,6 +21,7 @@ namespace sh::game SH_GAME_API ~SkinnedMeshRenderer(); SH_GAME_API void Awake() override; + SH_GAME_API void OnPropertyChanged(const core::reflection::Property& prop) override; SH_GAME_API void SetSkinnedMesh(render::SkinnedMesh* mesh); SH_GAME_API void SetBones(std::vector bones); @@ -32,6 +33,7 @@ namespace sh::game private: void ComputeBoneMatrices(); void UploadBoneMatrices(); + void InitIBM(); private: PROPERTY(bones, core::PropertyOption::invisible) std::vector bones; diff --git a/src/Game/Component/Render/SkinnedMeshRenderer.cpp b/src/Game/Component/Render/SkinnedMeshRenderer.cpp index e9052068..1f11faab 100644 --- a/src/Game/Component/Render/SkinnedMeshRenderer.cpp +++ b/src/Game/Component/Render/SkinnedMeshRenderer.cpp @@ -24,6 +24,13 @@ namespace sh::game inverseBindMatrices = skinnedMesh->GetInverseBindMatrices(); } + SH_GAME_API void SkinnedMeshRenderer::OnPropertyChanged(const core::reflection::Property& prop) + { + Super::OnPropertyChanged(prop); + if (prop.GetName() == core::Util::ConstexprHash("bones")) + InitIBM(); + } + SH_GAME_API void SkinnedMeshRenderer::SetSkinnedMesh(render::SkinnedMesh* mesh) { SetMesh(mesh); @@ -31,11 +38,7 @@ namespace sh::game SH_GAME_API void SkinnedMeshRenderer::SetBones(std::vector boneTransforms) { bones = std::move(boneTransforms); - finalBoneMatrices.resize(bones.size()); - - const render::SkinnedMesh* const skinnedMesh = static_cast(GetMesh()); - if (core::IsValid(skinnedMesh)) - inverseBindMatrices = skinnedMesh->GetInverseBindMatrices(); + InitIBM(); } SH_GAME_API void SkinnedMeshRenderer::UpdateDrawable() @@ -54,13 +57,6 @@ namespace sh::game if (finalBoneMatrices.size() < bones.size()) finalBoneMatrices.resize(bones.size()); - //for (std::size_t i = 0; i < jointCount; ++i) - //{ - // if (!core::IsValid(bones[i])) - // continue; - // finalBoneMatrices[i] = bones[i]->localToWorldMatrix * inverseBindMatrices[i]; - //} - const auto calcMatricesFn = [this](std::size_t start, std::size_t end) { @@ -130,4 +126,12 @@ namespace sh::game } } } + void SkinnedMeshRenderer::InitIBM() + { + finalBoneMatrices.resize(bones.size()); + + const render::SkinnedMesh* const skinnedMesh = static_cast(GetMesh()); + if (core::IsValid(skinnedMesh)) + inverseBindMatrices = skinnedMesh->GetInverseBindMatrices(); + } }//namespace From 9bcf088d0ddee4866fc8a2b0450488f1c58acaa1 Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Fri, 1 May 2026 07:48:48 +0900 Subject: [PATCH 24/60] style(Name): Reorder functions in header file --- include/Core/Name.h | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/include/Core/Name.h b/include/Core/Name.h index cc8e1d11..c0a4682d 100644 --- a/include/Core/Name.h +++ b/include/Core/Name.h @@ -10,16 +10,6 @@ namespace sh::core /// @brief 같은 문자열은 같은 주소를 가르키며 생성 시 스레드 안전하다. class Name { - private: - friend struct std::hash; - - SH_CORE_API static std::unordered_map map; - SH_CORE_API static std::shared_mutex mu; - -#if SH_DEBUG - std::string debugString; -#endif - std::size_t hash; public: SH_CORE_API Name(std::string_view str); SH_CORE_API Name(const Name& other) noexcept; @@ -37,13 +27,23 @@ namespace sh::core SH_CORE_API operator const std::string& () const; SH_CORE_API auto ToString() const -> const std::string&; + private: + friend struct std::hash; + + SH_CORE_API static std::unordered_map map; + SH_CORE_API static std::shared_mutex mu; + +#if SH_DEBUG + std::string debugString; +#endif + std::size_t hash; }; - static auto operator==(std::string_view str, const sh::core::Name& name) -> bool + inline static auto operator==(std::string_view str, const sh::core::Name& name) -> bool { return name == str; } - static auto operator!=(std::string_view str, const sh::core::Name& name) -> bool + inline static auto operator!=(std::string_view str, const sh::core::Name& name) -> bool { return name != str; } @@ -59,4 +59,4 @@ namespace std return name.hash; } }; -} +}//namespace \ No newline at end of file From 0cc92de9ab956d7c021971848c36bd008f518f8c Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Fri, 1 May 2026 07:49:31 +0900 Subject: [PATCH 25/60] feat(BuildSystem): Copy engine executables during build --- include/Editor/BuildSystem.h | 1 + src/Editor/BuildSystem.cpp | 90 ++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+) diff --git a/include/Editor/BuildSystem.h b/include/Editor/BuildSystem.h index a0455179..de8b2246 100644 --- a/include/Editor/BuildSystem.h +++ b/include/Editor/BuildSystem.h @@ -32,6 +32,7 @@ namespace sh::editor void ExtractUUIDs(std::unordered_set& set, const core::Json& world); void PackingAssets(core::AssetBundle& bundle, game::World& world); void ExportGameManager(const std::filesystem::path& outputPath); + void CopyRuntimeBinaries(const std::filesystem::path& outputPath); private: Project* currentProject = nullptr; diff --git a/src/Editor/BuildSystem.cpp b/src/Editor/BuildSystem.cpp index cadb6a93..8b81104b 100644 --- a/src/Editor/BuildSystem.cpp +++ b/src/Editor/BuildSystem.cpp @@ -85,6 +85,96 @@ namespace sh::editor bundle.SaveBundle(outputPath / "assets.bundle"); ExportGameManager(outputPath / "gameManager.bin"); + + CopyRuntimeBinaries(outputPath); + } + + void BuildSystem::CopyRuntimeBinaries(const std::filesystem::path& outputPath) + { + const std::filesystem::path engineDir = core::FileSystem::GetExecutableDirectory(); + +#if _WIN32 + constexpr const char* exeExt = ".exe"; + constexpr const char* libExt = ".dll"; + constexpr const char* libPrefix = ""; +#else + constexpr const char* exeExt = ""; + constexpr const char* libExt = ".so"; + constexpr const char* libPrefix = "lib"; +#endif + const std::vector executables = { + "ShellGame" + }; + const std::vector libraries = { + "ShellEngineCore", + "ShellEngineWindow", + "ShellEngineRender", + "ShellEnginePhysics", + "ShellEngineGame", + "ShellEngineNetwork", + "ShellEngineSound" + }; +#if _WIN32 + const std::vector extraFiles = { + "OpenAL32.dll" + }; +#else + const std::vector extraFiles = { + "libopenal.so" + }; +#endif + const std::vector directories = { + "fonts" + }; + + std::error_code ec; + if (!std::filesystem::exists(outputPath)) + std::filesystem::create_directories(outputPath, ec); + + auto copyIfMissingFn = [&](const std::filesystem::path& src, const std::filesystem::path& dst) + { + if (!std::filesystem::exists(src)) + { + SH_WARN_FORMAT("Runtime binary not found in engine path: {}", src.u8string()); + return; + } + std::error_code copyEc; + std::filesystem::copy_file(src, dst, std::filesystem::copy_options::overwrite_existing, copyEc); + if (copyEc) + SH_ERROR_FORMAT("Failed to copy {} -> {}: {}", src.u8string(), dst.u8string(), copyEc.message()); + }; + + for (const std::string& name : executables) + { + const std::string fileName = name + exeExt; + copyIfMissingFn(engineDir / fileName, outputPath / fileName); + } + for (const std::string& name : libraries) + { + const std::string fileName = libPrefix + name + libExt; + copyIfMissingFn(engineDir / fileName, outputPath / fileName); + } + for (const std::string& fileName : extraFiles) + { + copyIfMissingFn(engineDir / fileName, outputPath / fileName); + } + for (const std::string& dirName : directories) + { + const std::filesystem::path src = engineDir / dirName; + const std::filesystem::path dst = outputPath / dirName; + if (!std::filesystem::exists(src)) + { + SH_WARN_FORMAT("Runtime directory not found in engine path: {}", src.u8string()); + continue; + } + std::error_code copyEc; + std::filesystem::copy(src, dst, + std::filesystem::copy_options::recursive | + std::filesystem::copy_options::overwrite_existing, + copyEc); + if (copyEc) + SH_ERROR_FORMAT("Failed to copy directory {} -> {}: {}", src.u8string(), dst.u8string(), copyEc.message()); + } } void BuildSystem::ExtractUUIDs(std::unordered_set& set, const core::Json& worldJson) { From 44c9ecfaad45f7e5545036ab4f0b860d80933877 Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Sun, 3 May 2026 08:10:42 +0900 Subject: [PATCH 26/60] Update Multithreading.md --- Multithreading.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/Multithreading.md b/Multithreading.md index 71b1207f..03e329de 100644 --- a/Multithreading.md +++ b/Multithreading.md @@ -58,7 +58,7 @@ SH_RENDER_API void Renderer::Sync() std::swap(drawcall[core::ThreadType::Game], drawcall[core::ThreadType::Render]); } ``` -> swap방식은 메모리를 추가로 먹지만 매우 빠르게 작동하니 성능에 중요한 곳에서 사용하면 좋습니다. +> swap방식은 메모리를 추가로 먹지만 매우 빠르게 작동하니 성능에 중요한 곳에서 사용하면 됩니다. # 스레드 풀 core모듈의 ThreadPool클래스를 이용하여 작업을 할당하고 future로 값을 받아올 수 있습니다. @@ -89,3 +89,18 @@ for (auto& renderPipeline : renderPipelines) for (auto& futureCommand : futureCommands) recordedCommands.push_back(futureCommand.get()); ``` +# MPSC큐 +MSPC큐는 Multi Producer Single Consumer큐의 약자로, 다수의 스레드에서 쓰기 작업을 수행하고, 하나의 스레드에서 읽기 작업을 하는데 특화된 락-프리 큐입니다. + +```cpp +core::LockFreeMPSCQueue requestID; +// 다수의 스레드에서 실행 +requestID.Push(threadID); +// 하나의 스레드에서 읽어오기 +requestID.Drain( + [this](int threadID) + { + SH_INFO_FORMAT("Request: {}", threadID); + } +) +``` From 4023d2c9af1bad03fb7e4605d1defbb3f5af429c Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Mon, 4 May 2026 05:28:31 +0900 Subject: [PATCH 27/60] fix(ArrayView): const keyword position is incorrect --- include/Core/ArrayView.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/include/Core/ArrayView.hpp b/include/Core/ArrayView.hpp index fd49d469..b92b3a3f 100644 --- a/include/Core/ArrayView.hpp +++ b/include/Core/ArrayView.hpp @@ -9,17 +9,17 @@ namespace sh::core class ArrayView { private: - const T* ptr; + T* const ptr; std::size_t len; public: - ArrayView(const T* ptr, std::size_t len) noexcept : + ArrayView(T* ptr, std::size_t len) noexcept : ptr(ptr), len(len) {} auto operator[](int i) const noexcept -> T const& { return ptr[i]; } auto size() const noexcept -> std::size_t { return len; } - auto begin() noexcept -> const T* { return ptr; } - auto end() noexcept -> const T* { return ptr + len; } + auto begin() noexcept -> T* { return ptr; } + auto end() noexcept -> T* { return ptr + len; } }; -} \ No newline at end of file +}//namespace \ No newline at end of file From 6934c50311d7da109255b114d2837494aadf8739 Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Mon, 4 May 2026 05:27:13 +0900 Subject: [PATCH 28/60] refact: Overhaul render pipeline to support shadow mapping MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - render::Camera를 RenderData와 RenderViewer로 분리, 매프레임 제출 되게 변경, 여러 뷰 지원(ex.그림자 캐스팅) - render::Camera클래스 제거 후 카메라 컴포넌트로 통합, VulkanCameraBuffer 클래스 제거 - Renderer는 카메라 데이터를 소유하지 않게 변경 - RenderData들을 관리하는 RenderDataManager클래스 추가 - UIPass 이름을 명확하게 GUIPass로 변경 - EditorRenderer가 GameRenderer를 상속하게 변경 - Renderer에 CreateContext, DestroyContext, Reset 함수 추가 --- include/Core/Name.h | 18 +- include/Editor/EditorOutlinePass.h | 7 +- include/Editor/EditorPostOutlinePass.h | 4 +- include/Editor/EditorRenderer.h | 26 +- include/Game/Component/Render/Camera.h | 89 ++-- .../Game/Component/Render/DirectionalLight.h | 44 +- include/Game/{UIPass.h => GUIPass.h} | 11 +- include/Game/GameManager.h | 4 + include/Game/GameRenderer.h | 14 +- include/Game/UICamera.h | 16 - include/Game/World.h | 52 +-- include/Render/BufferFactory.h | 2 + include/Render/Camera.h | 108 ----- include/Render/CommandBuffer.h | 11 +- include/Render/IRenderContext.h | 5 + include/Render/RenderData.h | 66 ++- include/Render/RenderDataManager.h | 62 +++ include/Render/RenderPass/CopyPass.h | 4 +- include/Render/RenderPass/TransparentPass.h | 20 +- include/Render/RenderTarget.h | 39 -- include/Render/RenderTexture.h | 1 - include/Render/Renderer.h | 45 +- include/Render/ScriptableRenderPass.h | 17 +- include/Render/ScriptableRenderer.h | 14 +- .../Render/VulkanImpl/VulkanCameraBuffers.h | 42 -- .../Render/VulkanImpl/VulkanCommandBuffer.h | 17 +- include/Render/VulkanImpl/VulkanContext.h | 45 +- include/Render/VulkanImpl/VulkanPipeline.h | 2 +- .../Render/VulkanImpl/VulkanPipelineManager.h | 8 +- include/Render/VulkanImpl/VulkanRenderer.h | 29 +- include/Render/VulkanImpl/VulkanSwapChain.h | 4 +- src/Core/Name.cpp | 26 -- src/Editor/Component/EditorUI.cpp | 1 + src/Editor/EditorOutlinePass.cpp | 36 +- src/Editor/EditorPostOutlinePass.cpp | 18 +- src/Editor/EditorRenderer.cpp | 65 ++- src/Editor/EditorResource.cpp | 1 + src/Editor/EditorWorld.cpp | 23 +- src/Editor/UI/AssetExplorer.cpp | 2 + src/Editor/UI/ProjectExplorer.cpp | 1 + src/Editor/UI/Viewport.cpp | 5 +- src/EngineInit.cpp | 1 + src/Game/Component/Phys/BoxCollider.cpp | 4 +- src/Game/Component/Phys/CapsuleCollider.cpp | 5 +- src/Game/Component/Phys/ConvexCollider.cpp | 4 +- src/Game/Component/Phys/RigidBody.cpp | 8 +- src/Game/Component/Phys/SphereCollider.cpp | 4 +- src/Game/Component/Render/Camera.cpp | 207 +++------ .../Component/Render/DirectionalLight.cpp | 118 ++++-- src/Game/Component/Render/EditorCamera.cpp | 7 +- src/Game/Component/Render/LineRenderer.cpp | 1 + src/Game/Component/Render/MeshRenderer.cpp | 2 + src/Game/Component/Render/PickingCamera.cpp | 6 +- src/Game/Component/Render/TextRenderer.cpp | 2 + src/Game/GUIPass.cpp | 36 ++ src/Game/GameManager.cpp | 17 +- src/Game/GameRenderer.cpp | 72 +--- src/Game/UICamera.cpp | 27 -- src/Game/UIPass.cpp | 32 -- src/Game/World.cpp | 332 +++++++-------- src/Render/BufferFactory.cpp | 8 + src/Render/Camera.cpp | 209 --------- src/Render/MaterialData.cpp | 11 +- src/Render/RenderDataManager.cpp | 84 ++++ src/Render/RenderPass/CopyPass.cpp | 4 +- src/Render/RenderPass/ShadowMapPass.cpp | 5 - src/Render/RenderPass/TransparentPass.cpp | 55 ++- src/Render/RenderTarget.cpp | 1 - src/Render/Renderer.cpp | 73 +--- src/Render/ScriptableRenderPass.cpp | 70 +-- src/Render/ScriptableRenderer.cpp | 23 +- src/Render/VulkanImpl/VulkanCameraBuffers.cpp | 90 ---- src/Render/VulkanImpl/VulkanCommandBuffer.cpp | 55 ++- src/Render/VulkanImpl/VulkanContext.cpp | 397 +++++++++--------- src/Render/VulkanImpl/VulkanRenderer.cpp | 163 +++---- 75 files changed, 1260 insertions(+), 1877 deletions(-) rename include/Game/{UIPass.h => GUIPass.h} (53%) delete mode 100644 include/Game/UICamera.h delete mode 100644 include/Render/Camera.h create mode 100644 include/Render/RenderDataManager.h delete mode 100644 include/Render/RenderTarget.h delete mode 100644 include/Render/VulkanImpl/VulkanCameraBuffers.h create mode 100644 src/Game/GUIPass.cpp delete mode 100644 src/Game/UICamera.cpp delete mode 100644 src/Game/UIPass.cpp delete mode 100644 src/Render/Camera.cpp create mode 100644 src/Render/RenderDataManager.cpp delete mode 100644 src/Render/RenderTarget.cpp delete mode 100644 src/Render/VulkanImpl/VulkanCameraBuffers.cpp diff --git a/include/Core/Name.h b/include/Core/Name.h index c0a4682d..23f799e3 100644 --- a/include/Core/Name.h +++ b/include/Core/Name.h @@ -1,5 +1,6 @@ #pragma once #include "Export.h" +#include "Util.h" #include #include @@ -16,17 +17,18 @@ namespace sh::core SH_CORE_API Name(Name&& other) noexcept; SH_CORE_API ~Name(); + SH_CORE_API operator const std::string& () const; + SH_CORE_API auto ToString() const -> const std::string&; + SH_CORE_API auto operator=(const Name& other) noexcept -> Name&; SH_CORE_API auto operator=(Name&& other) noexcept -> Name&; - SH_CORE_API auto operator==(const Name& other) const -> bool; - SH_CORE_API auto operator!=(const Name& other) const -> bool; - SH_CORE_API auto operator==(const std::string_view str) const -> bool; - SH_CORE_API auto operator!=(const std::string_view str) const -> bool; - SH_CORE_API auto operator==(std::size_t hash) const -> bool; - SH_CORE_API auto operator!=(std::size_t hash) const -> bool; - SH_CORE_API operator const std::string& () const; - SH_CORE_API auto ToString() const -> const std::string&; + auto operator==(const Name& other) const -> bool { return hash == other.hash; } + auto operator!=(const Name& other) const -> bool { return hash != other.hash; } + auto operator==(const std::string_view str) const -> bool { return hash == core::Util::ConstexprHash(str); } + auto operator!=(const std::string_view str) const -> bool { return hash != core::Util::ConstexprHash(str); } + auto operator==(std::size_t hash) const -> bool { return this->hash == hash; } + auto operator!=(std::size_t hash) const -> bool { return this->hash != hash; } private: friend struct std::hash; diff --git a/include/Editor/EditorOutlinePass.h b/include/Editor/EditorOutlinePass.h index 8ee4a871..a301c690 100644 --- a/include/Editor/EditorOutlinePass.h +++ b/include/Editor/EditorOutlinePass.h @@ -3,6 +3,7 @@ #include "Core/SContainer.hpp" +#include "Render/RenderData.h" #include "Render/ScriptableRenderPass.h" #include "Game/Component/Render/Camera.h" @@ -22,9 +23,9 @@ namespace sh::editor SH_EDITOR_API void SetOutTexture(render::RenderTexture& tex); protected: - SH_EDITOR_API void Configure(const render::RenderTarget& renderData) override; - SH_EDITOR_API void Record(render::CommandBuffer& cmd, const render::IRenderContext& ctx, const render::RenderTarget& renderData) override; + SH_EDITOR_API void Configure(const render::RenderData& renderData) override; + SH_EDITOR_API void Record(render::CommandBuffer& cmd, const render::IRenderContext& ctx, const render::RenderData& renderData) override; private: - core::SObjWeakPtr output = nullptr; + render::RenderData rd; }; }//namespace \ No newline at end of file diff --git a/include/Editor/EditorPostOutlinePass.h b/include/Editor/EditorPostOutlinePass.h index f0aeaa2a..c8478ccd 100644 --- a/include/Editor/EditorPostOutlinePass.h +++ b/include/Editor/EditorPostOutlinePass.h @@ -19,8 +19,8 @@ namespace sh::editor SH_EDITOR_API ~EditorPostOutlinePass(); SH_EDITOR_API void SetOutlineMaterial(render::Material& mat); protected: - SH_EDITOR_API void Configure(const render::RenderTarget& renderData) override; - SH_EDITOR_API void Record(render::CommandBuffer& cmd, const render::IRenderContext& ctx, const render::RenderTarget& renderData) override; + SH_EDITOR_API void Configure(const render::RenderData& renderData) override; + SH_EDITOR_API void Record(render::CommandBuffer& cmd, const render::IRenderContext& ctx, const render::RenderData& renderData) override; private: const render::IRenderContext& ctx; diff --git a/include/Editor/EditorRenderer.h b/include/Editor/EditorRenderer.h index 1e352cdb..4a7d5c0e 100644 --- a/include/Editor/EditorRenderer.h +++ b/include/Editor/EditorRenderer.h @@ -12,9 +12,13 @@ namespace sh::game { class ImGUImpl; - class UIPass; + class GUIPass; class World; } +namespace sh::render +{ + class TransparentPass; +} namespace sh::editor { class EditorRenderer : public game::GameRenderer @@ -24,22 +28,16 @@ namespace sh::editor SH_EDITOR_API void Init() override; - SH_EDITOR_API auto GetOutlinePass() const -> EditorOutlinePass* { return outlinePass; } - SH_EDITOR_API auto GetPostOutlinePass() const -> EditorPostOutlinePass* { return postOutlinePass; } - - SH_EDITOR_API void SetImGUICamera(const render::Camera& camera); - SH_EDITOR_API void SetEditorCamera(const render::Camera& camera); - SH_EDITOR_API void SetPickingCamera(const render::Camera& camera); + auto GetOutlinePass() const -> EditorOutlinePass* { return outlinePass; } + auto GetPostOutlinePass() const -> EditorPostOutlinePass* { return postOutlinePass; } protected: - SH_EDITOR_API void Setup(const render::RenderTarget& data) override; + SH_EDITOR_API void Setup(const render::RenderData& data) override; private: - const render::Camera* editorCamera = nullptr; - const render::Camera* ImGUICamera = nullptr; - const render::Camera* pickingCamera = nullptr; - + render::ScriptableRenderPass* pickingPass = nullptr; + render::ScriptableRenderPass* opaquePass = nullptr; + render::TransparentPass* transparentPass = nullptr; + render::TransparentPass* uiPass = nullptr; EditorOutlinePass* outlinePass = nullptr; EditorPostOutlinePass* postOutlinePass = nullptr; - game::UIPass* uiPass = nullptr; - //render::TransparentPipeline* transParentPass = nullptr; }; }//namespace \ No newline at end of file diff --git a/include/Game/Component/Render/Camera.h b/include/Game/Component/Render/Camera.h index 7ce847db..d54294f4 100644 --- a/include/Game/Component/Render/Camera.h +++ b/include/Game/Component/Render/Camera.h @@ -3,9 +3,8 @@ #include "Game/Vector.h" #include "Game/Component/Component.h" -#include "Render/Renderer.h" +#include "Render/RenderData.h" #include "Render/RenderTexture.h" -#include "Render/Camera.h" #include "glm/mat4x4.hpp" @@ -33,65 +32,63 @@ namespace sh::game SH_GAME_API void BeginUpdate() override; SH_GAME_API void OnDestroy() override; - SH_GAME_API void SetActive(bool b) override; - - SH_GAME_API auto GetProjMatrix() const -> const glm::mat4&; - SH_GAME_API auto GetViewMatrix() const -> const glm::mat4&; + SH_GAME_API auto ScreenPointToRay(const Vec2& mousePos) const -> phys::Ray; + SH_GAME_API auto ScreenPointToRayOrtho(const Vec2& mousePos) const -> phys::Ray; - SH_GAME_API void SetDepth(int depth); - SH_GAME_API auto GetDepth() const -> int { return depth; } + SH_GAME_API void SetPriority(int priority); SH_GAME_API void SetFov(float degree); + void SetRenderTexture(render::RenderTexture* renderTexture) { this->renderTexture = renderTexture; } + void SetLookPos(const Vec3& pos) { lookPos = pos; } + void SetUpVector(const Vec3& up) { this->up = up; } + void SetProjection(Projection proj) { projection = proj; } + void SetNearPlane(float nearPlane) { this->nearPlane = nearPlane; } + void SetFarPlane(float farPlane) { this->farPlane = farPlane; } + void SetTag(const core::Name& tag) { renderData.tag = tag; } + auto GetPriority() const -> int { return priority; } /// @brief 세로 시야각을 가져오는 함수. /// @return Degree로 반환한다. - SH_GAME_API auto GetFov() const -> float { return fov; } + auto GetFov() const -> float { return fov; } /// @brief 가로 시야각을 가져오는 함수. /// @return Degree로 반환한다. - SH_GAME_API auto GetFovx() const -> float { return fovx; } - - SH_GAME_API void SetRenderTexture(render::RenderTexture* renderTexture); - SH_GAME_API auto GetRenderTexture() const -> render::RenderTexture*; - - SH_GAME_API auto GetNative() -> render::Camera&; - SH_GAME_API auto GetNative() const -> const render::Camera&; - - SH_GAME_API auto ScreenPointToRay(const Vec2& mousePos) const -> phys::Ray; - SH_GAME_API auto ScreenPointToRayOrtho(const Vec2& mousePos) const -> phys::Ray; - - SH_GAME_API void SetLookPos(const Vec3& pos); - SH_GAME_API auto GetLookPos() const -> const Vec3&; - SH_GAME_API void SetUpVector(const Vec3& up); - SH_GAME_API auto GetUpVector() const -> Vec3; - - SH_GAME_API void SetWidth(float width); - SH_GAME_API auto GetWidth() const -> float; - SH_GAME_API void SetHeight(float height); - SH_GAME_API auto GetHeight() const -> float; - - SH_GAME_API void SetProjection(Projection proj); - SH_GAME_API auto GetProjection() const -> Projection; - - SH_GAME_API void SetNearPlane(float near); - SH_GAME_API auto GetNearPlane() const -> float; - SH_GAME_API void SetFarPlane(float near); - SH_GAME_API auto GetFarPlane() const -> float; - - SH_GAME_API void OnPropertyChanged(const core::reflection::Property& prop) override; + auto GetFovx() const -> float { return fovx; } + auto GetWidth() const -> float { return width; } + auto GetHeight() const -> float { return height; } + auto GetNearPlane() const -> float { return nearPlane; } + auto GetFarPlane() const -> float { return farPlane; } + auto GetUpVector() const -> const Vec3& { return up; } + auto GetProjMatrix() const -> const glm::mat4& { return renderData.renderViewers.front().projMatrix; } + auto GetViewMatrix() const -> const glm::mat4& { return renderData.renderViewers.front().viewMatrix; } + auto GetLookPos() const -> const Vec3& { return lookPos; } + auto GetTag() const -> const core::Name& { return renderData.tag; } + auto GetProjection() const -> Projection { return projection; } + auto GetRenderTexture() const -> render::RenderTexture* { return renderTexture; } + protected: + SH_RENDER_API virtual void UpdateViewMatrix(); + SH_RENDER_API virtual void UpdateProjMatrix(); protected: - render::Camera camera; - PROPERTY(lookPos) Vec3 lookPos; private: PROPERTY(renderTexture) render::RenderTexture* renderTexture; - PROPERTY(depth) - int depth; + PROPERTY(priority) + int priority = 0; PROPERTY(fov) - float fov; - float fovx; + float fov = 60.f; + float fovRadians = 0.f; + float fovx = 0.f; PROPERTY(projection) Projection projection = Projection::Perspective; + Vec3 up{ 0.f, 1.f, 0.f }; + PROPERTY(nearPlane) + float nearPlane = 0.1f; + PROPERTY(farPlane) + float farPlane = 1000.f; + float width = 1.f; + float height = 1.f; + + render::RenderData renderData; }; -} \ No newline at end of file +}//namespace \ No newline at end of file diff --git a/include/Game/Component/Render/DirectionalLight.h b/include/Game/Component/Render/DirectionalLight.h index 812b8605..5434974b 100644 --- a/include/Game/Component/Render/DirectionalLight.h +++ b/include/Game/Component/Render/DirectionalLight.h @@ -3,11 +3,16 @@ #include "Game/ILight.h" #include "Game/Component/Component.h" -#include "Render/Camera.h" -#include "Render/RenderTexture.h" +#include "Render/RenderData.h" +#include "Render/ShadowMapManager.h" #include +namespace sh::render +{ + class RenderTexture; +} + namespace sh::game { class DirectionalLight : public Component, public ILight @@ -30,26 +35,25 @@ namespace sh::game SH_GAME_API void SetShadowBias(float bias) override; SH_GAME_API void SetShadowMapResolution(uint32_t res) override; - SH_GAME_API auto GetPos() const -> const Vec3 & override; - SH_GAME_API auto GetLightType() const -> ILight::Type override; - SH_GAME_API auto IsCastShadow() const -> bool override { return bCastShadow; } - SH_GAME_API auto GetShadowBias() const -> float override { return shadowBias; } - SH_GAME_API auto GetShadowMapResolution() const -> uint32_t override { return shadowMapResolution; } - SH_GAME_API auto GetDirection() const -> const Vec3& { return direction; } - SH_GAME_API auto GetIntensity() const -> float { return intensity; } - /// @brief 디렉셔널 라이트의 광원 공간 변환 행렬 (proj * view)을 계산한다. - /// @details 디렉셔널 라이트는 평행광이므로 직교 투영을 사용한다. 광원 위치는 씬 중심에서 광선 - /// 반대 방향으로 farPlane * 0.5 만큼 이동한 가상 위치를 사용한다. - /// 현재 구현은 단일 셰도우 맵 기준이며, CSM은 후속 작업으로 남겨둔다. SH_GAME_API auto GetLightSpaceMatrix() const -> glm::mat4; - - SH_GAME_API auto GetShadowCamera() -> render::Camera*; - SH_GAME_API auto GetShadowCamera() const -> const render::Camera*; + /// @brief 그림자 아틀라스 텍스처를 반환한다 (모든 광원이 공유) SH_GAME_API auto GetShadowMap() const -> render::RenderTexture*; + /// @brief 아틀라스 내에서 이 광원의 슬롯 정보를 반환한다. + SH_GAME_API auto GetShadowSlot() const -> render::ShadowMapManager::Slot; + + auto GetPos() const -> const Vec3 & override { return gameObject.transform->GetWorldPosition(); } + auto GetLightType() const -> ILight::Type override { return ILight::Type::Directional; } + auto IsCastShadow() const -> bool override { return bCastShadow; } + auto GetShadowBias() const -> float override { return shadowBias; } + auto GetShadowMapResolution() const -> uint32_t override { return shadowMapResolution; } + auto GetDirection() const -> const Vec3& { return direction; } + auto GetIntensity() const -> float { return intensity; } private: - /// @brief bCastShadow=true 시점에 호출. RT를 생성/빌드하고 카메라를 직교투영으로 설정. + /// @brief bCastShadow=true 시점에 호출. 매니저로부터 슬롯을 할당받고 카메라를 설정. void EnsureShadowResources(); + /// @brief 슬롯을 매니저에 반납하고 카메라 매핑을 해제. + void ReleaseShadowResources(); /// @brief 매 프레임 광원 방향에 맞춰 shadowCamera의 pos/lookAt을 갱신. void UpdateShadowCamera(); private: @@ -71,9 +75,7 @@ namespace sh::game PROPERTY(shadowFarPlane) float shadowFarPlane = 100.f; - PROPERTY(shadowMap, core::PropertyOption::invisible) - render::RenderTexture* shadowMap = nullptr; - - render::Camera shadowCamera; + render::RenderData renderData; + render::ShadowMapManager::SlotHandle shadowSlot = render::ShadowMapManager::InvalidSlot; }; }//namespace diff --git a/include/Game/UIPass.h b/include/Game/GUIPass.h similarity index 53% rename from include/Game/UIPass.h rename to include/Game/GUIPass.h index 760bfc17..ccdd80d7 100644 --- a/include/Game/UIPass.h +++ b/include/Game/GUIPass.h @@ -9,17 +9,18 @@ namespace sh::game { class ImGUImpl; - class UIPass : public render::ScriptableRenderPass + /// @brief ImGUI전용 패스 + class GUIPass : public render::ScriptableRenderPass { public: - SH_GAME_API UIPass(); + SH_GAME_API GUIPass(); SH_GAME_API void SetImGUIContext(ImGUImpl& gui); protected: - SH_GAME_API void Configure(const render::RenderTarget& renderData) override; - SH_GAME_API void Record(render::CommandBuffer& cmd, const render::IRenderContext& ctx, const render::RenderTarget& renderData) override; + SH_GAME_API void Configure(const render::RenderData& renderData) override; + SH_GAME_API void Record(render::CommandBuffer& cmd, const render::IRenderContext& ctx, const render::RenderData& renderData) override; public: - render::RenderTexture* viewportTexture = nullptr; + const render::RenderTexture* viewportTexture = nullptr; private: ImGUImpl* gui = nullptr; }; diff --git a/include/Game/GameManager.h b/include/Game/GameManager.h index 20c98896..0fb4b1ca 100644 --- a/include/Game/GameManager.h +++ b/include/Game/GameManager.h @@ -12,6 +12,8 @@ #include "Game/ImGUImpl.h" #include "Game/ComponentLoader.h" +#include "Render/RenderData.h" + #include #include #include @@ -101,6 +103,8 @@ namespace sh::game core::SVector defaultAssets; + render::RenderData guiRenderData; + bool bLoadingWorld = false; }; }//namespace \ No newline at end of file diff --git a/include/Game/GameRenderer.h b/include/Game/GameRenderer.h index a16b6fb8..abd6ee30 100644 --- a/include/Game/GameRenderer.h +++ b/include/Game/GameRenderer.h @@ -4,14 +4,13 @@ #include "Render/ScriptableRenderer.h" #include "Render/RenderPass/ShadowMapPass.h" -#include "Component/Render/Camera.h" - #include #include namespace sh::game { class ImGUImpl; class World; + class GUIPass; class GameRenderer : public render::ScriptableRenderer { @@ -19,11 +18,7 @@ namespace sh::game SH_GAME_API GameRenderer(render::IRenderContext& ctx, game::ImGUImpl& guictx, World& world); SH_GAME_API void Init() override; - SH_GAME_API void Setup(const render::RenderTarget& data) override; - - SH_GAME_API void SetUICamera(const render::Camera& camera); - SH_GAME_API void AddShadowCasterCamera(const render::Camera& camera); - SH_GAME_API void RemoveShadowCasterCamera(const render::Camera& camera); + SH_GAME_API void Setup(const render::RenderData& data) override; protected: SH_GAME_API void AddShadowPass(); protected: @@ -31,10 +26,7 @@ namespace sh::game World& world; game::ImGUImpl& guiCtx; + game::GUIPass* guiPass = nullptr; render::ShadowMapPass* shadowMapPass = nullptr; - std::unordered_map> allowedCamera; - std::unordered_map> ignoreCamera; - private: - const render::Camera* uiCamera = nullptr; }; }//namespace \ No newline at end of file diff --git a/include/Game/UICamera.h b/include/Game/UICamera.h deleted file mode 100644 index 14ceb764..00000000 --- a/include/Game/UICamera.h +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once -#include "Export.h" - -#include "Render/Camera.h" - -namespace sh::game -{ - class UICamera : public render::Camera - { - public: - SH_GAME_API UICamera(); - protected: - SH_GAME_API void UpdateViewMatrix() override; - SH_GAME_API void UpdateProjMatrix() override; - }; -}//namespace \ No newline at end of file diff --git a/include/Game/World.h b/include/Game/World.h index a070ff64..167d8c3d 100644 --- a/include/Game/World.h +++ b/include/Game/World.h @@ -2,9 +2,7 @@ #include "Export.h" #include "ComponentModule.h" #include "Octree.h" -#include "UICamera.h" #include "GameObject.h" -#include "Component/Render/Camera.h" #include "Core/NonCopyable.h" #include "Core/SObject.h" @@ -45,6 +43,7 @@ namespace sh::game { class Component; class ImGUImpl; + class Camera; class World : public sh::core::SObject, public sh::core::INonCopyable { @@ -58,6 +57,8 @@ namespace sh::game SH_GAME_API virtual ~World(); SH_GAME_API void OnDestroy() override; + SH_GAME_API auto Serialize() const -> core::Json override; + SH_GAME_API void Deserialize(const core::Json& json) override; SH_GAME_API virtual void Clear(); @@ -74,20 +75,13 @@ namespace sh::game /// @param name 이름 /// @return 못 찾을 시 nullptr, 찾을 시 게임 오브젝트 포인터 SH_GAME_API auto GetGameObject(std::string_view name) const -> GameObject*; - SH_GAME_API auto GetGameObjects() const -> const std::vector& { return objs; } - SH_GAME_API auto GetGameObjectPool() -> core::memory::MemoryPool&; /// @brief 게임 오브젝트가 할당된 메모리를 반환 큐에 넣는 함수. /// @param ptr 오브젝트 포인터 SH_GAME_API void PushDeAllocatedGameObject(GameObject* ptr); - SH_GAME_API void RegisterCamera(Camera* cam); - SH_GAME_API void UnRegisterCamera(Camera* cam); + SH_GAME_API void RegisterCamera(Camera& cam); + SH_GAME_API void UnRegisterCamera(Camera& cam); SH_GAME_API void SetMainCamera(Camera* cam); - SH_GAME_API auto GetMainCamera() const -> Camera*; - - SH_GAME_API auto GetPhysWorld() -> phys::PhysWorld*; - SH_GAME_API auto GetLightOctree() -> Octree&; - SH_GAME_API auto GetLightOctree() const -> const Octree&; SH_GAME_API virtual void Start(); SH_GAME_API virtual void Update(double deltaTime); @@ -100,41 +94,37 @@ namespace sh::game /// @param func 함수 SH_GAME_API void AddAfterSyncTask(const std::function& func); - SH_GAME_API auto Serialize() const->core::Json override; - SH_GAME_API void Deserialize(const core::Json& json) override; - SH_GAME_API void SaveWorldPoint(const core::Json& json, std::string_view name = "default"); SH_GAME_API void SaveWorldPoint(core::Json&& json, std::string_view name = "default"); - SH_GAME_API void LoadWorldPoint(); SH_GAME_API void LoadWorldPoint(const std::string& name); - SH_GAME_API auto GetWorldPoint() const -> const core::Json*; SH_GAME_API auto GetWorldPoint(const std::string& name) const -> const core::Json*; SH_GAME_API auto GetWorldPoint() -> core::Json*; SH_GAME_API auto GetWorldPoint(const std::string& name) -> core::Json*; - SH_GAME_API void ClearWorldPoints(); SH_GAME_API void ClearWorldPoint(const std::string& name = "default"); - SH_GAME_API auto GetUiContext() const -> ImGUImpl&; - SH_GAME_API void PublishEvent(const core::IEvent& event); SH_GAME_API void SubscribeEvent(core::ISubscriber& subscriber); SH_GAME_API void Play(); SH_GAME_API void Stop(); - SH_GAME_API auto GetCameras() const -> const core::SVector& { return cameras; } - SH_GAME_API auto IsPlaying() const -> bool; - SH_GAME_API auto IsStart() const -> bool; - SH_GAME_API auto IsLoaded() const -> bool; - SH_GAME_API auto GetUICamera() -> UICamera& { return uiCamera; } - SH_GAME_API auto GetUICamera() const -> const UICamera& { return uiCamera; } - SH_GAME_API virtual void ReallocateUUIDS(); - SH_GAME_API auto GetFixedAccumulator() const -> double { return dtAccumulator; } + auto GetUiContext() const -> ImGUImpl& { return *imgui; } + auto GetPhysWorld() -> phys::PhysWorld& { return physWorld; } + auto GetLightOctree() -> Octree& { return lightOctree; } + auto GetLightOctree() const -> const Octree& { return lightOctree; } + auto GetMainCamera() const -> Camera* { return mainCamera; } + auto GetGameObjects() const -> const std::vector& { return objs; } + auto GetGameObjectPool() -> core::memory::MemoryPool& { return objPool; } + auto GetCameras() const -> const std::vector& { return cameras; } + auto IsPlaying() const -> bool { return bPlaying; } + auto IsStart() const -> bool { return bOnStart; } + auto IsLoaded() const -> bool { return bLoaded; } + auto GetFixedAccumulator() const -> double { return dtAccumulator; } protected: SH_GAME_API void CleanObjs(); private: @@ -148,7 +138,7 @@ namespace sh::game core::EventBus eventBus; std::unordered_map objIdx; std::vector objs; - core::SVector cameras; + std::vector cameras; std::unique_ptr customRenderer; private: @@ -162,7 +152,7 @@ namespace sh::game double dt = 0.0; double dtAccumulator = 0.0; - PROPERTY(mainCamera) + PROPERTY(mainCamera, core::PropertyOption::sobjPtr) Camera* mainCamera = nullptr; phys::PhysWorld physWorld; @@ -175,8 +165,6 @@ namespace sh::game std::unordered_map savePoints; - UICamera uiCamera; - core::EventSubscriber physEventSubscriber; bool bStartLoop = false; @@ -185,4 +173,4 @@ namespace sh::game bool bLoaded = false; bool bOnStart = false; }; -} \ No newline at end of file +}//namespace \ No newline at end of file diff --git a/include/Render/BufferFactory.h b/include/Render/BufferFactory.h index 5fe21695..b03917e3 100644 --- a/include/Render/BufferFactory.h +++ b/include/Render/BufferFactory.h @@ -29,6 +29,8 @@ namespace sh::render SH_RENDER_API static auto Create(const IRenderContext& context, const CreateInfo& info) -> std::unique_ptr; SH_RENDER_API static auto CreateShaderBinding(const IRenderContext& context, const ShaderPass& shader, UniformStructLayout::Usage usage) -> std::unique_ptr; SH_RENDER_API static auto CreateShaderBinding(const IRenderContext& context, const ComputeShader& shader) -> std::unique_ptr; + + SH_RENDER_API static auto GetBufferAlignment(const IRenderContext& context) -> std::size_t; private: static auto CreateVkUniformBuffer(const vk::VulkanContext& context, const CreateInfo& info) -> std::unique_ptr; }; diff --git a/include/Render/Camera.h b/include/Render/Camera.h deleted file mode 100644 index ea1148c9..00000000 --- a/include/Render/Camera.h +++ /dev/null @@ -1,108 +0,0 @@ -#pragma once -#include "Export.h" -#include "IRenderContext.h" -#include "RenderTexture.h" - -#include "Core/ISyncable.h" - -#include - -#include - -#ifdef near -#undef near -#endif -#ifdef far -#undef far -#endif - -namespace sh::render -{ - /// @brief 렌더러용 카메라. - class Camera : public core::ISyncable - { - public: - SH_RENDER_API Camera(); - - SH_RENDER_API void SetRenderTexture(RenderTexture* framebuffer); - SH_RENDER_API auto GetRenderTexture() const -> RenderTexture*; - - SH_RENDER_API void SetPriority(int priority); - SH_RENDER_API auto GetPriority() const -> int; - - SH_RENDER_API auto GetProjMatrix(core::ThreadType thr) const -> const glm::mat4&; - SH_RENDER_API auto GetViewMatrix(core::ThreadType thr) const -> const glm::mat4&; - - SH_RENDER_API void SetPos(const glm::vec3& pos); - SH_RENDER_API void SetLookPos(const glm::vec3& pos); - SH_RENDER_API void SetUpVector(const glm::vec3& up); - SH_RENDER_API void SetFovRadian(float rad); - SH_RENDER_API void SetFov(float degree); - SH_RENDER_API void SetNearPlane(float near); - SH_RENDER_API void SetFarPlane(float far); - SH_RENDER_API void SetWidth(float width); - SH_RENDER_API void SetHeight(float height); - SH_RENDER_API void SetOrthographic(bool bEnable); - SH_RENDER_API void SetCustomProjectionMatrix(const glm::mat4* matrix); - - SH_RENDER_API void SetRenderTagMask(uint32_t mask); - SH_RENDER_API void AddRenderTagMask(uint32_t tagId); - SH_RENDER_API void RemoveRenderTagMask(uint32_t tagId); - SH_RENDER_API auto GetRenderTagMask() const -> uint32_t; - /// @brief 렌더 태그가 mask에 통과 하는지 검증하는 함수. - /// @param tagId 태그 id - /// @return 통과 하면 true 아니면 false - SH_RENDER_API auto CheckRenderTag(uint32_t tagId) const -> bool; - - SH_RENDER_API void SetActive(bool bActive); - SH_RENDER_API auto GetActive() const -> bool; - - SH_RENDER_API auto GetPos(core::ThreadType thr) const -> const glm::vec3& { return pos[thr]; } - SH_RENDER_API auto GetLookPos(core::ThreadType thr) const -> const glm::vec3& { return to[thr]; } - SH_RENDER_API auto GetUpVector(core::ThreadType thr) const -> const glm::vec3& { return up[thr]; } - SH_RENDER_API auto GetFovRadian(core::ThreadType thr) const -> float { return fovRadians[thr]; } - SH_RENDER_API auto GetNearPlane(core::ThreadType thr) const -> float { return nearPlane[thr]; } - SH_RENDER_API auto GetFarPlane(core::ThreadType thr) const -> float { return farPlane[thr]; } - SH_RENDER_API auto GetWidth(core::ThreadType thr) const -> float { return width[thr]; } - SH_RENDER_API auto GetHeight(core::ThreadType thr) const -> float { return height[thr]; } - - SH_RENDER_API auto IsOrtho() const -> bool { return !bPerspective; } - protected: - SH_RENDER_API void SyncDirty() override; - SH_RENDER_API void Sync() override; - /// @brief dirtyMask |= 256; 후 SyncDirty호출 필요 - SH_RENDER_API virtual void UpdateProjMatrix(); - /// @brief dirtyMask |= 256; 후 SyncDirty호출 필요 - SH_RENDER_API virtual void UpdateViewMatrix(); - public: - struct alignas(16) BufferData - { - glm::mat4 matView; - glm::mat4 matProj; - }; - const uint32_t id; - protected: - core::SyncArray pos; - core::SyncArray to; - core::SyncArray up; - - core::SyncArray fovRadians = { 0.0f, 0.f }; - core::SyncArray nearPlane = { 0.1f, 0.1f }; - core::SyncArray farPlane = { 1000.f, 1000.f }; - core::SyncArray width = { 1.f, 1.f }; - core::SyncArray height = { 1.f, 1.f }; - core::SyncArray bufferData; - - uint32_t dirtyMask = 0; - private: - SH_RENDER_API static uint32_t nextId; - RenderTexture* renderTexture; - - int priority; - uint32_t renderTagMask = 1; - - bool bActive = true; - bool bDirty = false; - bool bPerspective = true; - }; -} //namespace \ No newline at end of file diff --git a/include/Render/CommandBuffer.h b/include/Render/CommandBuffer.h index 3a8d808e..1ff81772 100644 --- a/include/Render/CommandBuffer.h +++ b/include/Render/CommandBuffer.h @@ -11,7 +11,7 @@ namespace sh::render class Material; class Drawable; class ShaderPass; - struct RenderTarget; + struct RenderData; struct BarrierInfo; class CommandBuffer : public core::INonCopyable @@ -25,12 +25,11 @@ namespace sh::render virtual void Blit(RenderTexture& src, int x, int y, IBuffer& dst) = 0; virtual void Dispatch(const ComputeShader& shader, uint32_t x, uint32_t y, uint32_t z) = 0; - virtual void SetRenderTarget(const RenderTarget& renderTarget, bool bClearColor = true, bool bClearDepth = true, bool bStoreColor = false, bool bStoreDepth = false) = 0; - virtual void SetViewport(int x, int y, int width, int height) = 0; + virtual void SetRenderData(const RenderData& renderData, bool bClearColor = true, bool bClearDepth = true, bool bStoreColor = false, bool bStoreDepth = false) = 0; + virtual void SetViewport(uint32_t x, uint32_t y, uint32_t width, uint32_t height) = 0; virtual void SetScissor(uint32_t x, uint32_t y, uint32_t width, uint32_t height) = 0; - virtual void DrawMeshBatch(const std::vector& drawables, core::Name passName) = 0; - virtual void DrawMesh(const Drawable& drawable, core::Name passName) = 0; + virtual void DrawMeshBatch(const std::vector& drawables, core::Name passName, std::size_t viewerIdx = 0) = 0; + virtual void DrawMesh(const Drawable& drawable, core::Name passName, std::size_t viewerIdx = 0) = 0; virtual void EmitBarrier(const std::vector& barriers) = 0; - }; }//namespace \ No newline at end of file diff --git a/include/Render/IRenderContext.h b/include/Render/IRenderContext.h index 72c23d44..662ddfca 100644 --- a/include/Render/IRenderContext.h +++ b/include/Render/IRenderContext.h @@ -8,6 +8,8 @@ namespace sh::render { + class RenderDataManager; + enum class RenderAPI { OpenGL, @@ -29,5 +31,8 @@ namespace sh::render virtual void SetViewport(const glm::vec2& start, const glm::vec2& end) = 0; virtual auto GetViewportStart() const -> const glm::vec2& = 0; virtual auto GetViewportEnd() const -> const glm::vec2& = 0; + + virtual auto GetRenderDataManager() const -> const RenderDataManager& = 0; + virtual auto GetRenderDataManager() -> RenderDataManager& = 0; }; }//namespace \ No newline at end of file diff --git a/include/Render/RenderData.h b/include/Render/RenderData.h index a00a072d..95c7e1d6 100644 --- a/include/Render/RenderData.h +++ b/include/Render/RenderData.h @@ -1,11 +1,19 @@ #pragma once #include "Mesh.h" +#include "Texture.h" #include "Core/SContainer.hpp" +#include "Core/Name.h" #include #include #include + +namespace sh +{ + template + using Ref = std::reference_wrapper; +} namespace sh::render { class Camera; @@ -14,12 +22,39 @@ namespace sh::render class Material; class CommandBuffer; - struct RenderTarget + struct RenderViewer { - uint32_t frameIndex; - const Camera* camera; - const RenderTexture* target; - const std::vector* drawables; + glm::mat4 viewMatrix; + glm::mat4 projMatrix; + glm::vec3 pos; + glm::vec3 to; + + glm::uvec4 viewportRect; + glm::uvec4 viewportScissor; + std::size_t offset = 0; + }; + + struct RenderData + { + int priority = 0; + uint32_t frameIndex = 0; + const RenderTexture* target = nullptr; + core::Name tag{ "Camera" }; + + std::vector renderViewers; + const std::vector* drawables = nullptr; + }; + + struct RenderTargetLayout + { + TextureFormat format; + TextureFormat depthFormat; + bool bUseMSAA = false; + + auto operator==(const RenderTargetLayout& other) const -> bool + { + return format == other.format && depthFormat == other.depthFormat && bUseMSAA == other.bUseMSAA; + } }; //struct DrawList @@ -58,4 +93,23 @@ namespace sh::render ResourceUsage lastUsage; ResourceUsage curUsage; }; -}//namespace \ No newline at end of file +}//namespace + +namespace std +{ + template<> + struct hash + { + auto operator()(const sh::render::RenderTargetLayout& layout) const -> std::size_t + { + std::hash intHasher{}; + std::hash boolHasher{}; + + std::size_t hash = intHasher(static_cast(layout.format)); + hash = sh::core::Util::CombineHash(hash, intHasher(static_cast(layout.depthFormat))); + hash = sh::core::Util::CombineHash(hash, boolHasher(layout.bUseMSAA)); + + return hash; + } + }; +}//namespace std diff --git a/include/Render/RenderDataManager.h b/include/Render/RenderDataManager.h new file mode 100644 index 00000000..ead773b3 --- /dev/null +++ b/include/Render/RenderDataManager.h @@ -0,0 +1,62 @@ +#pragma once +#include "Export.h" +#include "RenderData.h" +#include "IBuffer.h" +#include "IRenderThrMethod.h" + +#include "Core/LockFreeMPSCQueue.h" +#include "Core/ArrayView.hpp" + +#include +#include +#include +#include +namespace sh::render +{ + class IRenderContext; + + /// @brief 카메라와 같은 렌더뷰어들을 관리하는 매니저. 스레드 안전하다. + class RenderDataManager + { + public: + SH_RENDER_API RenderDataManager(); + SH_RENDER_API ~RenderDataManager(); + + SH_RENDER_API void Init(const IRenderContext& ctx); + + SH_RENDER_API void PushRenderData(const RenderData& renderData); + + SH_RENDER_API auto GetBuffer() const -> const IBuffer* { return buffer.get(); } + protected: + SH_RENDER_API void ClearBuffer(); + SH_RENDER_API void ClearRenderViews(); + SH_RENDER_API void UploadToGPU(); + SH_RENDER_API auto GetRenderDatas() -> core::ArrayView; + public: + struct BufferData + { + glm::mat4 view; + glm::mat4 proj; + glm::vec3 pos; + }; + private: + friend struct IRenderThrMethod; + const IRenderContext* ctx = nullptr; + + core::LockFreeMPSCQueue> renderDataQueue; + std::vector renderDatas; + + std::unique_ptr buffer; + std::size_t alignment = 256; + std::size_t renderDatasSize = 0; + }; + + template<> + struct IRenderThrMethod + { + static void ClearBuffer(RenderDataManager& manager) { manager.ClearBuffer(); } + static void ClearRenderViews(RenderDataManager& manager) { manager.ClearRenderViews(); } + static void UploadToGPU(RenderDataManager& manager) { manager.UploadToGPU(); } + static auto GetRenderDatas(RenderDataManager& manager) -> core::ArrayView { return manager.GetRenderDatas(); } + }; +}//namespace \ No newline at end of file diff --git a/include/Render/RenderPass/CopyPass.h b/include/Render/RenderPass/CopyPass.h index eeca7a99..abb92e4a 100644 --- a/include/Render/RenderPass/CopyPass.h +++ b/include/Render/RenderPass/CopyPass.h @@ -14,8 +14,8 @@ namespace sh::render public: SH_RENDER_API CopyPass(); protected: - SH_RENDER_API void Configure(const RenderTarget& renderData) override; - SH_RENDER_API void Record(CommandBuffer& cmd, const IRenderContext& ctx, const RenderTarget& renderData) override; + SH_RENDER_API void Configure(const RenderData& renderData) override; + SH_RENDER_API void Record(CommandBuffer& cmd, const IRenderContext& ctx, const RenderData& renderData) override; SH_RENDER_API void EnqueCopyImagePixelToBuffer(RenderTexture& rt, int x, int y, IBuffer& buffer); SH_RENDER_API auto IsEmpty() const -> bool { return cpyDatas.empty(); } diff --git a/include/Render/RenderPass/TransparentPass.h b/include/Render/RenderPass/TransparentPass.h index 23d0a0bd..c6058b4c 100644 --- a/include/Render/RenderPass/TransparentPass.h +++ b/include/Render/RenderPass/TransparentPass.h @@ -1,16 +1,28 @@ #pragma once -#include "../Export.h" -#include "../ScriptableRenderPass.h" +#include "Render/Export.h" +#include "Render/ScriptableRenderPass.h" +#include "Render/Mesh.h" +#include namespace sh::render { + class Material; + class Drawables; /// @brief 단순히 우선 순위 정렬기능만 추가된 패스 class TransparentPass : public ScriptableRenderPass { public: SH_RENDER_API TransparentPass(std::string_view name = "Transparent", RenderQueue renderQueue = RenderQueue::Transparent); - SH_RENDER_API void Configure(const RenderTarget& renderData) override; - SH_RENDER_API void Record(CommandBuffer& cmd, const IRenderContext& ctx, const RenderTarget& renderData) override; + SH_RENDER_API void Configure(const RenderData& renderData) override; + SH_RENDER_API void Record(CommandBuffer& cmd, const IRenderContext& ctx, const RenderData& renderData) override; + private: + struct RenderItem + { + const Material* material; + Mesh::Topology topology; + const Drawable* drawable; + }; + std::vector items; }; }//namespace \ No newline at end of file diff --git a/include/Render/RenderTarget.h b/include/Render/RenderTarget.h deleted file mode 100644 index 215216d6..00000000 --- a/include/Render/RenderTarget.h +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once -#include "Texture.h" - -#include "Core/Util.h" - -#include -namespace sh::render -{ - struct RenderTargetLayout - { - TextureFormat format; - TextureFormat depthFormat; - bool bUseMSAA = false; - - auto operator==(const RenderTargetLayout& other) const -> bool - { - return format == other.format && depthFormat == other.depthFormat && bUseMSAA == other.bUseMSAA; - } - }; -}//namespace - -namespace std -{ - template<> - struct hash - { - auto operator()(const sh::render::RenderTargetLayout& layout) const -> std::size_t - { - std::hash intHasher{}; - std::hash boolHasher{}; - - std::size_t hash = intHasher(static_cast(layout.format)); - hash = sh::core::Util::CombineHash(hash, intHasher(static_cast(layout.depthFormat))); - hash = sh::core::Util::CombineHash(hash, boolHasher(layout.bUseMSAA)); - - return hash; - } - }; -} diff --git a/include/Render/RenderTexture.h b/include/Render/RenderTexture.h index 738e7de0..4f010277 100644 --- a/include/Render/RenderTexture.h +++ b/include/Render/RenderTexture.h @@ -1,7 +1,6 @@ #pragma once #include "Export.h" #include "Texture.h" -#include "RenderTarget.h" #include "RenderData.h" #include "IRenderThrMethod.h" diff --git a/include/Render/Renderer.h b/include/Render/Renderer.h index 228e9a25..cb12dd07 100644 --- a/include/Render/Renderer.h +++ b/include/Render/Renderer.h @@ -1,6 +1,5 @@ #pragma once #include "Export.h" -#include "Camera.h" #include "ScriptableRenderer.h" #include "Core/ISyncable.h" @@ -41,8 +40,12 @@ namespace sh::render SH_RENDER_API void SyncDirty() override; SH_RENDER_API void Sync() override; - SH_RENDER_API virtual bool Init(sh::window::Window& win); + SH_RENDER_API virtual void CreateContext(const window::Window& win) = 0; + SH_RENDER_API virtual void DestroyContext() = 0; + SH_RENDER_API virtual bool Init(window::Window& win); SH_RENDER_API virtual bool Resizing() = 0; + /// @brief 렌더러 상태를 초기화 한다. + SH_RENDER_API virtual void Reset(); SH_RENDER_API virtual void Clear(); SH_RENDER_API virtual void Render(); @@ -53,12 +56,13 @@ namespace sh::render SH_RENDER_API virtual auto GetWidth() const -> uint32_t = 0; SH_RENDER_API virtual auto GetHeight() const -> uint32_t = 0; SH_RENDER_API virtual void WaitForCurrentFrame() = 0; - SH_RENDER_API virtual auto GetContext() const->IRenderContext* = 0; + SH_RENDER_API virtual auto GetContext() const -> IRenderContext* = 0; /// @brief 드로우 객체를 큐에 집어 넣는다. /// @brief 큐에 들어간 객체는 렌더 스레드 프레임 시작 시 반영된다. /// @param drawable 드로우 객체 포인터 SH_RENDER_API void PushDrawAble(Drawable* drawable); + SH_RENDER_API void PushRenderData(const RenderData& renderData); SH_RENDER_API auto GetWindow() const -> sh::window::Window&; @@ -67,56 +71,33 @@ namespace sh::render SH_RENDER_API auto IsPause() const -> bool; SH_RENDER_API void SetScriptableRenderer(ScriptableRenderer& renderer); - SH_RENDER_API auto GetScriptableRenderer() const -> ScriptableRenderer* { return renderer; } - /// @brief 카메라를 추가한다. 렌더 스레드에서 반영된다. - /// @param camera 카메라 참조 - SH_RENDER_API void AddCamera(const Camera& camera); - /// @brief 카메라를 제거한다. 렌더 스레드에서 반영된다. - /// @param camera 카메라 참조 - SH_RENDER_API void RemoveCamera(const Camera& camera); - - SH_RENDER_API auto GetDrawCall(core::ThreadType thread) const -> uint32_t; + auto GetDrawCall(core::ThreadType thread) const -> uint32_t { return drawcall[static_cast(thread)]; } /// @brief 현재 렌더러가 돌아가는 스레드의 번호를 반환한다. 한번이라도 렌더링을 한 후에 갱신된다. - SH_RENDER_API auto GetThreadId() const -> std::thread::id; + auto GetThreadId() const -> std::thread::id { return threadId; } + auto GetScriptableRenderer() const -> ScriptableRenderer* { return renderer; } protected: - virtual void OnCameraAdded(const Camera* camera) {}; - virtual void OnCameraRemoved(const Camera* camera) {}; - SH_RENDER_API void SetDrawCallCount(uint32_t drawcall); SH_RENDER_API void DrainRenderCommands(); protected: - struct CameraCompare - { - bool operator()(const Camera* left, const Camera* right) const - { - if (left->GetPriority() == right->GetPriority()) - return left->id < right->id; - return left->GetPriority() < right->GetPriority(); - } - }; struct RenderCommand { enum class Type { - PushDrawable, - AddCamera, - RemoveCamera + PushDrawable }; Type type = Type::PushDrawable; - std::variant, const Camera*> data; + core::SObjWeakPtr data; }; core::LockFreeMPSCQueue renderCommands; std::vector drawables; - std::set cameras; - std::vector> drawCalls; std::atomic_bool bPause; ScriptableRenderer* renderer = nullptr; private: - sh::window::Window* window; + window::Window* window; core::SyncArray drawcall; diff --git a/include/Render/ScriptableRenderPass.h b/include/Render/ScriptableRenderPass.h index f4abf4cc..9e84aa79 100644 --- a/include/Render/ScriptableRenderPass.h +++ b/include/Render/ScriptableRenderPass.h @@ -40,8 +40,8 @@ namespace sh::render template<> struct IRenderThrMethod { - static void Configure(ScriptableRenderPass& pass, const RenderTarget& renderData); - static void Record(ScriptableRenderPass& pass, CommandBuffer& cmd, const IRenderContext& ctx, const RenderTarget& renderTarget); + static void Configure(ScriptableRenderPass& pass, const RenderData& renderData); + static void Record(ScriptableRenderPass& pass, CommandBuffer& cmd, const IRenderContext& ctx, const RenderData& renderTarget); /// @brief Configure 이후에 호출해야 정확한 렌더콜 갯수를 알 수 있음 /// @param pass 패스 /// @return 렌더콜 횟수 @@ -64,12 +64,13 @@ namespace sh::render bool bSkinned = false; std::vector drawables; }; - SH_RENDER_API virtual void Configure(const RenderTarget& renderData); - SH_RENDER_API virtual void Record(CommandBuffer& cmd, const IRenderContext& ctx, const RenderTarget& renderData); + SH_RENDER_API virtual void Configure(const RenderData& renderData); + SH_RENDER_API virtual void Record(CommandBuffer& cmd, const IRenderContext& ctx, const RenderData& renderData); SH_RENDER_API auto CreateRenderBatch(const std::vector& drawables) const -> std::vector; - SH_RENDER_API void SetImageUsages(const RenderTarget& renderData); - SH_RENDER_API void SetViewportScissor(CommandBuffer& cmd, const IRenderContext& ctx, const RenderTarget& renderTarget); + SH_RENDER_API void SetImageUsages(const RenderData& renderData); + SH_RENDER_API void SetImageUsages(const std::vector& drawables); + SH_RENDER_API void SetViewportScissor(CommandBuffer& cmd, const IRenderContext& ctx, const RenderViewer& renderViewer); /// @brief Configure 이후에 호출해야 정확한 렌더콜 갯수를 알 수 있음 SH_RENDER_API auto GetRenderCallCount() const -> uint32_t { return renderCallCount; } @@ -83,11 +84,11 @@ namespace sh::render uint32_t renderCallCount = 0; }; - inline void IRenderThrMethod::Configure(ScriptableRenderPass& pass, const RenderTarget& renderData) + inline void IRenderThrMethod::Configure(ScriptableRenderPass& pass, const RenderData& renderData) { pass.Configure(renderData); } - inline void IRenderThrMethod::Record(ScriptableRenderPass& pass, CommandBuffer& cmd, const IRenderContext& ctx, const RenderTarget& renderData) + inline void IRenderThrMethod::Record(ScriptableRenderPass& pass, CommandBuffer& cmd, const IRenderContext& ctx, const RenderData& renderData) { pass.Record(cmd, ctx, renderData); } diff --git a/include/Render/ScriptableRenderer.h b/include/Render/ScriptableRenderer.h index bee9287a..1e28bc52 100644 --- a/include/Render/ScriptableRenderer.h +++ b/include/Render/ScriptableRenderer.h @@ -21,8 +21,8 @@ namespace sh::render template<> struct IRenderThrMethod { - static void Setup(ScriptableRenderer& renderer, const RenderTarget& data); - static void Execute(ScriptableRenderer& renderer, const RenderTarget& data); + static void Setup(ScriptableRenderer& renderer, const RenderData& data); + static void Execute(ScriptableRenderer& renderer, const RenderData& data); static void ExecuteTransfer(ScriptableRenderer& renderer, uint32_t imgIdx); static void EnqueRenderPass(ScriptableRenderer& renderer, ScriptableRenderPass& pass); static void CallReadbacks(ScriptableRenderer& renderer); @@ -50,17 +50,17 @@ namespace sh::render SH_RENDER_API auto ReadRenderTextureAsync(RenderTexture& rt, int x, int y) -> std::future>; SH_RENDER_API void Dispatch(const ComputeShader& computeShader, uint32_t x, uint32_t y, uint32_t z); - SH_RENDER_API auto GetRecordedCommands() const -> const std::vector& { return recordedCmds; } SH_RENDER_API auto HasPass(const core::Name& passName) const -> bool; + auto GetRecordedCommands() const -> const std::vector& { return recordedCmds; } template>, typename... Args> auto AddRenderPass(Args&&... args) -> T&; protected: SH_RENDER_API void SyncDirty() override; SH_RENDER_API void Sync() override; - SH_RENDER_API virtual void Setup(const RenderTarget& data); - SH_RENDER_API virtual void Execute(const RenderTarget& data); + SH_RENDER_API virtual void Setup(const RenderData& data); + SH_RENDER_API virtual void Execute(const RenderData& data); SH_RENDER_API void ExecuteTransfer(uint32_t imgIdx); SH_RENDER_API void CallReadbacks(); @@ -113,11 +113,11 @@ namespace sh::render return *ptr; } - inline void IRenderThrMethod::Setup(ScriptableRenderer& renderer, const RenderTarget& data) + inline void IRenderThrMethod::Setup(ScriptableRenderer& renderer, const RenderData& data) { renderer.Setup(data); } - inline void IRenderThrMethod::Execute(ScriptableRenderer& renderer, const RenderTarget& data) + inline void IRenderThrMethod::Execute(ScriptableRenderer& renderer, const RenderData& data) { renderer.Execute(data); } diff --git a/include/Render/VulkanImpl/VulkanCameraBuffers.h b/include/Render/VulkanImpl/VulkanCameraBuffers.h deleted file mode 100644 index 47046e1c..00000000 --- a/include/Render/VulkanImpl/VulkanCameraBuffers.h +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once -#include "Render/Export.h" -#include "Render/Camera.h" -#include "VulkanContext.h" -#include "VulkanBuffer.h" - -#include "Core/Singleton.hpp" - -#include -#include -#include -#include -#include -namespace sh::render::vk -{ - class VulkanCameraBuffers : public core::Singleton - { - friend core::Singleton; - private: - const VulkanContext* context = nullptr; - - std::vector cams; - uint32_t cap = 10; - std::stack emptyidx; - - std::unique_ptr cameraData; - - std::size_t dynamicAlignment; - private: - VulkanCameraBuffers() = default; - public: - SH_RENDER_API void Init(const VulkanContext& context); - SH_RENDER_API void Clear(); - - SH_RENDER_API void AddCamera(const Camera& camera); - SH_RENDER_API void RemoveCamera(const Camera& camera); - SH_RENDER_API void UploadDataToGPU(const Camera& camera); - - SH_RENDER_API auto GetDynamicOffset(const Camera& camera) const -> std::optional; - SH_RENDER_API auto GetCameraBuffer() const -> const VulkanBuffer&; - }; -}//namespace \ No newline at end of file diff --git a/include/Render/VulkanImpl/VulkanCommandBuffer.h b/include/Render/VulkanImpl/VulkanCommandBuffer.h index 7fdc56f2..0238f6dc 100644 --- a/include/Render/VulkanImpl/VulkanCommandBuffer.h +++ b/include/Render/VulkanImpl/VulkanCommandBuffer.h @@ -1,7 +1,7 @@ #pragma once -#include "../Export.h" -#include "../CommandBuffer.h" -#include "../RenderTarget.h" +#include "Render/Export.h" +#include "Render/CommandBuffer.h" +#include "Render/RenderData.h" #include "VulkanConfig.h" #include "Core/NonCopyable.h" @@ -12,6 +12,7 @@ namespace sh::render { class Camera; class Mesh; + struct RenderView; } namespace sh::render::vk { @@ -52,11 +53,11 @@ namespace sh::render::vk SH_RENDER_API void Blit(RenderTexture& src, int x, int y, IBuffer& dst) override; SH_RENDER_API void Dispatch(const ComputeShader& shader, uint32_t x, uint32_t y, uint32_t z) override; - SH_RENDER_API void SetRenderTarget(const RenderTarget& renderTarget, bool bClearColor = true, bool bClearDepth = true, bool bStoreColor = false, bool bStoreDepth = false) override; - SH_RENDER_API void SetViewport(int x, int y, int width, int height) override; + SH_RENDER_API void SetRenderData(const RenderData& renderData, bool bClearColor = true, bool bClearDepth = true, bool bStoreColor = false, bool bStoreDepth = false) override; + SH_RENDER_API void SetViewport(uint32_t x, uint32_t y, uint32_t width, uint32_t height) override; SH_RENDER_API void SetScissor(uint32_t x, uint32_t y, uint32_t width, uint32_t height) override; - SH_RENDER_API void DrawMesh(const Drawable& drawable, core::Name passName) override; - SH_RENDER_API void DrawMeshBatch(const std::vector& drawables, core::Name passName) override; + SH_RENDER_API void DrawMesh(const Drawable& drawable, core::Name passName, std::size_t viewerIdx = 0) override; + SH_RENDER_API void DrawMeshBatch(const std::vector& drawables, core::Name passName, std::size_t viewerIdx = 0) override; SH_RENDER_API void EmitBarrier(const std::vector& barriers) override; SH_RENDER_API auto GetOrCreateFence() -> VkFence; @@ -85,7 +86,7 @@ namespace sh::render::vk struct RenderState { RenderTargetLayout layout{}; - const Camera* camera = nullptr; + const RenderData* renderData = nullptr; uint32_t targetWidth = 0; uint32_t targetHeight = 0; diff --git a/include/Render/VulkanImpl/VulkanContext.h b/include/Render/VulkanImpl/VulkanContext.h index dfc64c18..b20670e8 100644 --- a/include/Render/VulkanImpl/VulkanContext.h +++ b/include/Render/VulkanImpl/VulkanContext.h @@ -1,6 +1,7 @@ #pragma once #include "Render/Export.h" #include "Render/IRenderContext.h" +#include "Render/RenderDataManager.h" #include "VulkanConfig.h" #include "Core/ISyncable.h" @@ -39,15 +40,17 @@ namespace sh::render::vk SH_RENDER_API void Init() override; SH_RENDER_API void Clear() override; - SH_RENDER_API auto GetRenderAPIType() const -> RenderAPI override { return RenderAPI::Vulkan; } + auto GetRenderAPIType() const -> RenderAPI override { return RenderAPI::Vulkan; } SH_RENDER_API auto AllocateCommandBuffer(bool bCompute) -> CommandBuffer* override; SH_RENDER_API void DeallocateCommandBuffer(CommandBuffer& cmd) override; SH_RENDER_API void SubmitCommand(CommandBuffer& cmd) override; SH_RENDER_API void SetViewport(const glm::vec2& start, const glm::vec2& end) override; - SH_RENDER_API auto GetViewportStart() const -> const glm::vec2 & override; - SH_RENDER_API auto GetViewportEnd() const -> const glm::vec2 & override; + auto GetViewportStart() const -> const glm::vec2& override { return viewportStart; } + auto GetViewportEnd() const -> const glm::vec2& override { return viewportEnd; } + auto GetRenderDataManager() const -> const RenderDataManager & override { return renderDataManager; } + auto GetRenderDataManager() -> RenderDataManager& override { return renderDataManager; } SH_RENDER_API auto ReSizing() -> bool; @@ -58,23 +61,23 @@ namespace sh::render::vk /// @brief 멀티 샘플링의 샘플을 지정한다. 기기에서 지원하지 않는다면 최대 지원하는 샘플 수로 지정된다. /// @param sample 샘플 수 SH_RENDER_API void SetSampleCount(VkSampleCountFlagBits sample); - SH_RENDER_API auto GetSampleCount() const -> VkSampleCountFlagBits { return sample; } - - SH_RENDER_API auto GetInstance() const -> VkInstance { return instance; } - SH_RENDER_API auto GetGPU() const -> VkPhysicalDevice { return gpu; } - SH_RENDER_API auto GetGPUName() const -> std::string_view { return gpuProp.deviceName; } - SH_RENDER_API auto GetGPUProperty() const -> const VkPhysicalDeviceProperties& { return gpuProp; } - SH_RENDER_API auto GetDevice() const -> VkDevice { return device; } - SH_RENDER_API auto GetSwapChain() const -> VulkanSwapChain& { return *swapChain; } - SH_RENDER_API auto GetCommandBufferPool() const -> VulkanCommandBufferPool& { return *cmdPool; } - SH_RENDER_API auto GetQueueManager() const -> VulkanQueueManager& { return *queueManager; } - SH_RENDER_API auto GetDescriptorPool() const -> VulkanDescriptorPool& { return *descPool; } - SH_RENDER_API auto GetAllocator() const -> VmaAllocator { return allocator; } - SH_RENDER_API auto GetPipelineManager() const -> VulkanPipelineManager& { return *pipelineManager; } - SH_RENDER_API auto GetComputePipelineManager() const -> VulkanComputePipelineManager& { return *computePipelineManager; } - SH_RENDER_API auto GetEmptyDescriptorSetLayout() const -> VkDescriptorSetLayout { return emptyDescLayout; } - SH_RENDER_API auto GetEmptyDescriptorSet() const -> VkDescriptorSet { return emptyDescSet; } + SH_RENDER_API auto GetMaxSampleCount() const -> VkSampleCountFlagBits; + auto GetSampleCount() const -> VkSampleCountFlagBits { return sample; } + auto GetInstance() const -> VkInstance { return instance; } + auto GetGPU() const -> VkPhysicalDevice { return gpu; } + auto GetGPUName() const -> std::string_view { return gpuProp.deviceName; } + auto GetGPUProperty() const -> const VkPhysicalDeviceProperties& { return gpuProp; } + auto GetDevice() const -> VkDevice { return device; } + auto GetSwapChain() const -> VulkanSwapChain& { return *swapChain; } + auto GetCommandBufferPool() const -> VulkanCommandBufferPool& { return *cmdPool; } + auto GetQueueManager() const -> VulkanQueueManager& { return *queueManager; } + auto GetDescriptorPool() const -> VulkanDescriptorPool& { return *descPool; } + auto GetAllocator() const -> VmaAllocator { return allocator; } + auto GetPipelineManager() const -> VulkanPipelineManager& { return *pipelineManager; } + auto GetComputePipelineManager() const -> VulkanComputePipelineManager& { return *computePipelineManager; } + auto GetEmptyDescriptorSetLayout() const -> VkDescriptorSetLayout { return emptyDescLayout; } + auto GetEmptyDescriptorSet() const -> VkDescriptorSet { return emptyDescSet; } private: void PrepareValidationLayer(); void CreateDebugInfo(); @@ -137,8 +140,10 @@ namespace sh::render::vk mutable std::shared_mutex commandPoolMutex; mutable std::shared_mutex commandBufferMutex; + RenderDataManager renderDataManager; + bool bInit = false; bool bFindValidationLayer = false; bool bEnableValidationLayers = false; }; -} \ No newline at end of file +}//namespace \ No newline at end of file diff --git a/include/Render/VulkanImpl/VulkanPipeline.h b/include/Render/VulkanImpl/VulkanPipeline.h index 37334614..4215b496 100644 --- a/include/Render/VulkanImpl/VulkanPipeline.h +++ b/include/Render/VulkanImpl/VulkanPipeline.h @@ -1,8 +1,8 @@ #pragma once #include "Render/Export.h" #include "Render/ShaderEnum.h" +#include "Render/RenderData.h" #include "VulkanConfig.h" -#include "../RenderTarget.h" #include "Core/NonCopyable.h" diff --git a/include/Render/VulkanImpl/VulkanPipelineManager.h b/include/Render/VulkanImpl/VulkanPipelineManager.h index ca91840e..a6bca01f 100644 --- a/include/Render/VulkanImpl/VulkanPipelineManager.h +++ b/include/Render/VulkanImpl/VulkanPipelineManager.h @@ -1,10 +1,10 @@ #pragma once -#include "../Export.h" +#include "Render/Export.h" +#include "Render/Mesh.h" +#include "Render/StencilState.h" +#include "Render/RenderData.h" #include "VulkanPipeline.h" #include "VulkanConfig.h" -#include "../Mesh.h" -#include "../StencilState.h" -#include "../RenderTarget.h" #include "Core/SContainer.hpp" #include "Core/Util.h" diff --git a/include/Render/VulkanImpl/VulkanRenderer.h b/include/Render/VulkanImpl/VulkanRenderer.h index 274a28da..be9fd7d4 100644 --- a/include/Render/VulkanImpl/VulkanRenderer.h +++ b/include/Render/VulkanImpl/VulkanRenderer.h @@ -1,5 +1,5 @@ #pragma once -#include "../Renderer.h" +#include "Render/Renderer.h" #include "VulkanConfig.h" #include "Core/NonCopyable.h" @@ -18,7 +18,6 @@ namespace sh::render::vk { class VulkanContext; - class VulkanCameraBuffers; class VulkanCommandBuffer; class VulkanRenderer : @@ -28,31 +27,26 @@ namespace sh::render::vk SH_RENDER_API VulkanRenderer(); SH_RENDER_API ~VulkanRenderer(); - SH_RENDER_API bool Init(sh::window::Window& win) override; + SH_RENDER_API void CreateContext(const window::Window& win) override; + SH_RENDER_API void DestroyContext() override; + SH_RENDER_API bool Init(window::Window& win) override; SH_RENDER_API bool Resizing() override; SH_RENDER_API void Clear() override; - SH_RENDER_API bool IsInit() const override; + SH_RENDER_API bool IsInit() const override { return isInit; } SH_RENDER_API void Render() override; SH_RENDER_API void WaitForCurrentFrame() override; - SH_RENDER_API auto GetCurrentFrame() const -> int; SH_RENDER_API auto GetWidth() const -> uint32_t override; SH_RENDER_API auto GetHeight() const -> uint32_t override; - - SH_RENDER_API auto GetTimelineSemaphore() const -> VkSemaphore; - /// @brief [원자적] 첫번째 패스의 타임라인 세마포어의 값을 가져온다. - /// @return 타임라인 세마포어 값 - SH_RENDER_API auto GetTimelineValue() const -> uint64_t; - - SH_RENDER_API auto GetContext() const -> IRenderContext* override; SH_RENDER_API void Sync() override; - protected: - void OnCameraAdded(const Camera* camera) override; - void OnCameraRemoved(const Camera* camera) override; + + SH_RENDER_API auto GetContext() const -> IRenderContext* override; + auto GetTimelineSemaphore() const -> VkSemaphore { return timelineSemaphore; } + auto GetCurrentFrame() const -> int { return currentFrame; } private: auto CreateSyncObjects() -> VkResult; void DestroySyncObjects(); @@ -63,14 +57,9 @@ namespace sh::render::vk VkSemaphore renderFinishedSemaphore = VK_NULL_HANDLE; VkSemaphore timelineSemaphore = VK_NULL_HANDLE; - uint64_t timelineValue = 0; - std::atomic timelineValueAtomic = 0; - std::vector frameFences; VkFence inFlightFence; - VulkanCameraBuffers* camManager = nullptr; - int currentFrame; bool isInit = false; diff --git a/include/Render/VulkanImpl/VulkanSwapChain.h b/include/Render/VulkanImpl/VulkanSwapChain.h index bc5479f2..2c4cc602 100644 --- a/include/Render/VulkanImpl/VulkanSwapChain.h +++ b/include/Render/VulkanImpl/VulkanSwapChain.h @@ -1,6 +1,6 @@ #pragma once -#include "../Export.h" -#include "../RenderTarget.h" +#include "Render/Export.h" +#include "Render/RenderData.h" #include "VulkanConfig.h" #include "VulkanImageBuffer.h" diff --git a/src/Core/Name.cpp b/src/Core/Name.cpp index 6288b019..bba4fd2a 100644 --- a/src/Core/Name.cpp +++ b/src/Core/Name.cpp @@ -1,5 +1,4 @@ #include "Name.h" -#include "Util.h" #include namespace sh::core @@ -60,31 +59,6 @@ namespace sh::core #endif return *this; } - SH_CORE_API auto Name::operator==(const Name& other) const -> bool - { - return hash == other.hash; - } - SH_CORE_API auto Name::operator!=(const Name& other) const -> bool - { - return hash != other.hash; - } - SH_CORE_API auto Name::operator==(const std::string_view str) const -> bool - { - return hash == core::Util::ConstexprHash(str); - } - SH_CORE_API auto Name::operator!=(const std::string_view str) const -> bool - { - return hash != core::Util::ConstexprHash(str); - } - SH_CORE_API auto Name::operator==(std::size_t hash) const -> bool - { - return this->hash == hash; - } - SH_CORE_API auto Name::operator!=(std::size_t hash) const -> bool - { - return this->hash != hash; - } - SH_CORE_API Name::operator const std::string& () const { return ToString(); diff --git a/src/Editor/Component/EditorUI.cpp b/src/Editor/Component/EditorUI.cpp index 438edb04..af2fddbc 100644 --- a/src/Editor/Component/EditorUI.cpp +++ b/src/Editor/Component/EditorUI.cpp @@ -12,6 +12,7 @@ #include "Game/Asset/FontGenerator.h" #include "Game/Input.h" +#include "Render/Renderer.h" #include "Render/RenderTexture.h" #include diff --git a/src/Editor/EditorOutlinePass.cpp b/src/Editor/EditorOutlinePass.cpp index 45a53117..926e9f6f 100644 --- a/src/Editor/EditorOutlinePass.cpp +++ b/src/Editor/EditorOutlinePass.cpp @@ -3,7 +3,7 @@ #include "Core/GarbageCollection.h" #include "Render/RenderTexture.h" - +#include "Render/CommandBuffer.h" namespace sh::editor { EditorOutlinePass::EditorOutlinePass() : @@ -15,28 +15,30 @@ namespace sh::editor } SH_EDITOR_API void EditorOutlinePass::SetOutTexture(render::RenderTexture& tex) { - output = &tex; + rd.target = &tex; + rd.renderViewers.resize(1); } - SH_EDITOR_API void EditorOutlinePass::Configure(const render::RenderTarget& renderData) + SH_EDITOR_API void EditorOutlinePass::Configure(const render::RenderData& renderData) { - render::RenderTarget rd; - rd.camera = renderData.camera; - rd.frameIndex = renderData.frameIndex; - rd.target = output.Get(); - rd.drawables = renderData.drawables; - - ScriptableRenderPass::Configure(rd); + renderTextures.clear(); + renderTextures[rd.target] = render::ResourceUsage::ColorAttachment; + if (renderData.drawables != nullptr) + { + SetImageUsages(*renderData.drawables); + renderBatches = CreateRenderBatch(*renderData.drawables); + } } - SH_EDITOR_API void EditorOutlinePass::Record(render::CommandBuffer& cmd, const render::IRenderContext& ctx, const render::RenderTarget& renderData) + SH_EDITOR_API void EditorOutlinePass::Record(render::CommandBuffer& cmd, const render::IRenderContext& ctx, const render::RenderData& renderData) { - render::RenderTarget rd; - rd.camera = renderData.camera; - rd.frameIndex = renderData.frameIndex; - rd.target = output.Get(); + if (renderData.renderViewers.empty()) + return; + rd.renderViewers[0] = renderData.renderViewers.front(); + rd.drawables = renderData.drawables; - SetViewportScissor(cmd, ctx, rd); - cmd.SetRenderTarget(rd, true, true, true, true); + cmd.SetRenderData(rd, true, true, true, true); + cmd.SetViewport(0, 0, rd.target->GetSize().x, rd.target->GetSize().y); + cmd.SetScissor(0, 0, rd.target->GetSize().x, rd.target->GetSize().y); if (rd.drawables == nullptr) return; diff --git a/src/Editor/EditorPostOutlinePass.cpp b/src/Editor/EditorPostOutlinePass.cpp index 438b65d7..3f623fe1 100644 --- a/src/Editor/EditorPostOutlinePass.cpp +++ b/src/Editor/EditorPostOutlinePass.cpp @@ -36,7 +36,7 @@ namespace sh::editor this->mat = &mat; } - SH_EDITOR_API void EditorPostOutlinePass::Configure(const render::RenderTarget& renderData) + SH_EDITOR_API void EditorPostOutlinePass::Configure(const render::RenderData& renderData) { if (drawable == nullptr && mat != nullptr) { @@ -45,18 +45,26 @@ namespace sh::editor core::GarbageCollection::GetInstance()->SetRootSet(drawable); } + + renderTextures.clear(); + renderTextures[renderData.target] = render::ResourceUsage::ColorAttachment; if (mat != nullptr) { for (auto& [name, rt] : mat->GetCachedRenderTextures()) renderTextures[rt] = render::ResourceUsage::SampledRead; } } - SH_EDITOR_API void EditorPostOutlinePass::Record(render::CommandBuffer& cmd, const render::IRenderContext& ctx, const render::RenderTarget& renderData) + SH_EDITOR_API void EditorPostOutlinePass::Record(render::CommandBuffer& cmd, const render::IRenderContext& ctx, const render::RenderData& renderData) { if (!core::IsValid(drawable->GetMesh()) || !core::IsValid(drawable->GetMaterial())) return; - cmd.SetRenderTarget(renderData, false, false, false, false); - SetViewportScissor(cmd, ctx, renderData); - cmd.DrawMesh(*drawable, passName); + cmd.SetRenderData(renderData, false, false, false, false); + std::size_t viewerIdx = 0; + for (const render::RenderViewer& viewer : renderData.renderViewers) + { + SetViewportScissor(cmd, ctx, viewer); + cmd.DrawMesh(*drawable, passName, viewerIdx); + ++viewerIdx; + } } }//namespace \ No newline at end of file diff --git a/src/Editor/EditorRenderer.cpp b/src/Editor/EditorRenderer.cpp index da66f31a..93c274c2 100644 --- a/src/Editor/EditorRenderer.cpp +++ b/src/Editor/EditorRenderer.cpp @@ -1,6 +1,6 @@ #include "EditorRenderer.h" -#include "Game/UIPass.h" +#include "Game/GUIPass.h" #include "Render/RenderPass/TransparentPass.h" @@ -13,49 +13,42 @@ namespace sh::editor } SH_EDITOR_API void EditorRenderer::Init() { - AddRenderPass(core::Name{ "EditorPicking" }, render::RenderQueue::Picking); + pickingPass = &AddRenderPass(core::Name{ "EditorPicking" }, render::RenderQueue::Picking); outlinePass = &AddRenderPass(); //AddShadowPass(); - AddRenderPass(core::Name{ "Opaque" }, render::RenderQueue::Opaque); - AddRenderPass(); - AddRenderPass("UI", render::RenderQueue::Transparent); + opaquePass = &AddRenderPass(core::Name{ "Opaque" }, render::RenderQueue::Opaque); + transparentPass = &AddRenderPass(); + uiPass = &AddRenderPass("UI", render::RenderQueue::Transparent); postOutlinePass = &AddRenderPass(ctx); - uiPass = &AddRenderPass(); - uiPass->SetImGUIContext(guiCtx); + guiPass = &AddRenderPass(); + guiPass->SetImGUIContext(guiCtx); } - SH_EDITOR_API void EditorRenderer::SetImGUICamera(const render::Camera& camera) + SH_EDITOR_API void EditorRenderer::Setup(const render::RenderData& data) { - ImGUICamera = &camera; - allowedCamera[core::Name{ "ImGUI" }].insert(&camera); - for (const std::unique_ptr& pass : allPasses) + if (data.tag == "ImGUI") { - if (pass->passName == "ImGUI") - continue; - ignoreCamera[pass->passName].insert(&camera); + EnqueRenderPass(*guiPass); + return; } - } - SH_EDITOR_API void EditorRenderer::SetEditorCamera(const render::Camera& camera) - { - editorCamera = &camera; - allowedCamera[outlinePass->passName].insert(&camera); - allowedCamera[postOutlinePass->passName].insert(&camera); - - uiPass->viewportTexture = editorCamera->GetRenderTexture(); - } - SH_EDITOR_API void EditorRenderer::SetPickingCamera(const render::Camera& camera) - { - pickingCamera = &camera; - allowedCamera[core::Name{ "EditorPicking" }].insert(&camera); - for (const std::unique_ptr& pass : allPasses) + if (data.tag == "EditorCamera") { - if (pass->passName == "EditorPicking") - continue; - ignoreCamera[pass->passName].insert(&camera); + guiPass->viewportTexture = data.target; + EnqueRenderPass(*outlinePass); + EnqueRenderPass(*opaquePass); + EnqueRenderPass(*transparentPass); + EnqueRenderPass(*uiPass); + EnqueRenderPass(*postOutlinePass); + return; + } + if (data.tag == "PickingCamera") + { + EnqueRenderPass(*pickingPass); + return; + } + if (data.tag == "ImGUI") + { + EnqueRenderPass(*guiPass); + return; } - } - - SH_EDITOR_API void EditorRenderer::Setup(const render::RenderTarget& data) - { - GameRenderer::Setup(data); } }//namespace \ No newline at end of file diff --git a/src/Editor/EditorResource.cpp b/src/Editor/EditorResource.cpp index 29cab40d..d0906ad4 100644 --- a/src/Editor/EditorResource.cpp +++ b/src/Editor/EditorResource.cpp @@ -4,6 +4,7 @@ #include "Core/AssetExporter.h" #include "Core/GarbageCollection.h" +#include "Render/Renderer.h" #include "Render/VulkanImpl/VulkanContext.h" #include "Render/VulkanImpl/VulkanShaderPassBuilder.h" #include "Render/Material.h" diff --git a/src/Editor/EditorWorld.cpp b/src/Editor/EditorWorld.cpp index f6f0ae5f..1417139e 100644 --- a/src/Editor/EditorWorld.cpp +++ b/src/Editor/EditorWorld.cpp @@ -9,6 +9,7 @@ #include "Core/Name.h" +#include "Render/Renderer.h" #include "Render/Mesh/Grid.h" #include "Render/Model.h" @@ -84,22 +85,17 @@ namespace sh::editor renderer.SetScriptableRenderer(*customRenderer); EditorRenderer& editorRenderer = static_cast(*customRenderer); editorRenderer.Init(); - - auto& uiCamera = GetUICamera(); - uiCamera.SetPriority(1000); - renderer.AddCamera(uiCamera); - - editorRenderer.SetImGUICamera(uiCamera); } SH_EDITOR_API void EditorWorld::InitResource() { Super::InitResource(); - auto* editorRenderer = static_cast(customRenderer.get()); + EditorRenderer* const editorRenderer = static_cast(customRenderer.get()); // editorRenderer가 없으면 addtive로 추가된 월드임 if (editorRenderer != nullptr) { - auto viewportPtr = static_cast(core::SObjectManager::GetInstance()->GetSObject(core::UUID{ "180635b4e4d1a98ebb0064ab47dc452a" })); + render::RenderTexture* const viewportPtr = + static_cast(core::SObjectManager::GetInstance()->GetSObject(core::UUID{ "180635b4e4d1a98ebb0064ab47dc452a" })); if (!core::IsValid(viewportPtr)) { if (viewportPtr != nullptr && viewportPtr->IsPendingKill()) @@ -117,24 +113,21 @@ namespace sh::editor else viewportTexture = viewportPtr; - game::GameObject* camObj = AddGameObject("EditorCamera"); + game::GameObject* const camObj = AddGameObject("EditorCamera"); camObj->transform->SetPosition({ 2.f, 2.f, 2.f }); camObj->hideInspector = true; camObj->bNotSave = true; editorCamera = camObj->AddComponent(); editorCamera->SetRenderTexture(viewportTexture); - editorCamera->GetNative().SetActive(true); - editorRenderer->SetEditorCamera(editorCamera->GetNative()); + editorCamera->SetTag(core::Name{ "EditorCamera" }); - auto PickingCamObj = AddGameObject("PickingCamera"); + game::GameObject* const PickingCamObj = AddGameObject("PickingCamera"); PickingCamObj->bNotSave = true; PickingCamObj->transform->SetParent(camObj->transform); PickingCamObj->transform->SetPosition({ 0.f, 0.f, 0.f }); pickingCamera = PickingCamObj->AddComponent(); pickingCamera->SetFollowCamera(editorCamera); - pickingCamera->GetNative().SetActive(true); - - editorRenderer->SetPickingCamera(pickingCamera->GetNative()); + pickingCamera->SetTag(core::Name{ "PickingCamera" }); auto outlineTexture = EditorResource::GetInstance()->GetTexture("OutlineTexture"); assert(outlineTexture); diff --git a/src/Editor/UI/AssetExplorer.cpp b/src/Editor/UI/AssetExplorer.cpp index 328f1037..b5e2cd42 100644 --- a/src/Editor/UI/AssetExplorer.cpp +++ b/src/Editor/UI/AssetExplorer.cpp @@ -1,6 +1,8 @@ #include "UI/AssetExplorer.h" #include "EditorWorld.h" +#include "Render/Renderer.h" + #include "Game/ImGUImpl.h" #include "Window/Window.h" diff --git a/src/Editor/UI/ProjectExplorer.cpp b/src/Editor/UI/ProjectExplorer.cpp index bfeecc4f..ea35856e 100644 --- a/src/Editor/UI/ProjectExplorer.cpp +++ b/src/Editor/UI/ProjectExplorer.cpp @@ -8,6 +8,7 @@ #include "Core/FileSystem.h" +#include "Render/Renderer.h" #include "Render/Font.h" #include "Game/GUITexture.h" diff --git a/src/Editor/UI/Viewport.cpp b/src/Editor/UI/Viewport.cpp index 8f894d76..789e1662 100644 --- a/src/Editor/UI/Viewport.cpp +++ b/src/Editor/UI/Viewport.cpp @@ -14,6 +14,7 @@ #include "Game/Component/Render/PickingRenderer.h" #include "Game/GameManager.h" +#include "Render/Renderer.h" #include "Render/RenderTexture.h" #include "External/imgui/ImGuizmo.h" @@ -261,9 +262,7 @@ namespace sh::editor std::sort(cams.begin(), cams.end(), [](const game::Camera* left, const game::Camera* right) { - if (left->GetDepth() == right->GetDepth()) - return left->GetNative().id < right->GetNative().id; - return left->GetDepth() < right->GetDepth(); + return left->GetPriority() < right->GetPriority(); } ); diff --git a/src/EngineInit.cpp b/src/EngineInit.cpp index 7a30c26c..cfd43fe2 100644 --- a/src/EngineInit.cpp +++ b/src/EngineInit.cpp @@ -166,6 +166,7 @@ namespace sh SH_INFO("Renderer initialization"); renderer = std::make_unique(); + renderer->CreateContext(*window); renderer->Init(*window); renderer->GetContext()->SetViewport({ 150.f, 0.f }, { window->GetWidth() - 150.f, window->GetHeight() - 180}); diff --git a/src/Game/Component/Phys/BoxCollider.cpp b/src/Game/Component/Phys/BoxCollider.cpp index cfa95568..07cfa7d5 100644 --- a/src/Game/Component/Phys/BoxCollider.cpp +++ b/src/Game/Component/Phys/BoxCollider.cpp @@ -18,7 +18,7 @@ namespace sh::game { impl = std::make_unique(); - auto ctx = reinterpret_cast(world.GetPhysWorld()->GetContext()); + auto ctx = reinterpret_cast(world.GetPhysWorld().GetContext()); Vec3 halfSize = size / 2.0f; impl->shape = ctx->createBoxShape(reactphysics3d::Vector3{ halfSize.x, halfSize.y, halfSize.z }); @@ -88,7 +88,7 @@ namespace sh::game if (impl->shape != nullptr) { SH_INFO("DestroyShape()"); - auto ctx = reinterpret_cast(world.GetPhysWorld()->GetContext()); + auto ctx = reinterpret_cast(world.GetPhysWorld().GetContext()); ctx->destroyBoxShape(impl->shape); impl->shape = nullptr; } diff --git a/src/Game/Component/Phys/CapsuleCollider.cpp b/src/Game/Component/Phys/CapsuleCollider.cpp index 8c35c1fc..d74434dc 100644 --- a/src/Game/Component/Phys/CapsuleCollider.cpp +++ b/src/Game/Component/Phys/CapsuleCollider.cpp @@ -5,6 +5,7 @@ #include "Game/GameObject.h" #include "Game/World.h" +#include "Render/Renderer.h" #include "Render/Mesh/CapsuleMesh.h" #include "reactphysics3d/reactphysics3d.h" @@ -20,7 +21,7 @@ namespace sh::game { impl = std::make_unique(); - auto ctx = reinterpret_cast(world.GetPhysWorld()->GetContext()); + auto ctx = reinterpret_cast(world.GetPhysWorld().GetContext()); impl->shape = ctx->createCapsuleShape(radius, height); if(IsEditor()) @@ -119,7 +120,7 @@ namespace sh::game if (impl->shape != nullptr) { SH_INFO("DestroyShape()"); - auto ctx = reinterpret_cast(world.GetPhysWorld()->GetContext()); + auto ctx = reinterpret_cast(world.GetPhysWorld().GetContext()); ctx->destroyCapsuleShape(impl->shape); impl->shape = nullptr; } diff --git a/src/Game/Component/Phys/ConvexCollider.cpp b/src/Game/Component/Phys/ConvexCollider.cpp index 27f493e7..847865a8 100644 --- a/src/Game/Component/Phys/ConvexCollider.cpp +++ b/src/Game/Component/Phys/ConvexCollider.cpp @@ -90,7 +90,7 @@ namespace sh::game return; lastMesh = mesh; - auto ctx = reinterpret_cast(world.GetPhysWorld()->GetContext()); + auto ctx = reinterpret_cast(world.GetPhysWorld().GetContext()); const auto& meshVerts = mesh->GetVertex(); const reactphysics3d::VertexArray verts{ meshVerts.data(), sizeof(render::Mesh::Vertex), static_cast(meshVerts.size()), reactphysics3d::VertexArray::DataType::VERTEX_FLOAT_TYPE }; @@ -115,7 +115,7 @@ namespace sh::game SH_GAME_API void ConvexCollider::DestroyShape() { - auto ctx = reinterpret_cast(world.GetPhysWorld()->GetContext()); + auto ctx = reinterpret_cast(world.GetPhysWorld().GetContext()); if (impl->shape != nullptr) ctx->destroyConvexMeshShape(impl->shape); if (impl->convexMesh != nullptr) diff --git a/src/Game/Component/Phys/RigidBody.cpp b/src/Game/Component/Phys/RigidBody.cpp index 747a2bdb..6d31d7f1 100644 --- a/src/Game/Component/Phys/RigidBody.cpp +++ b/src/Game/Component/Phys/RigidBody.cpp @@ -21,8 +21,8 @@ namespace sh::game SH_GAME_API RigidBody::RigidBody(GameObject& owner) : Component(owner) { - auto ctx = reinterpret_cast(gameObject.world.GetPhysWorld()->GetContext()); - auto world = reinterpret_cast(gameObject.world.GetPhysWorld()->GetNative()); + auto ctx = reinterpret_cast(gameObject.world.GetPhysWorld().GetContext()); + auto world = reinterpret_cast(gameObject.world.GetPhysWorld().GetNative()); reactphysics3d::Vector3 physPos{ gameObject.transform->position.x, gameObject.transform->position.y, gameObject.transform->position.z }; auto& quat = gameObject.transform->GetQuat(); @@ -80,7 +80,7 @@ namespace sh::game { nativeMap.erase(impl->rigidbody); - auto physWorld = reinterpret_cast(gameObject.world.GetPhysWorld()->GetNative()); + auto physWorld = reinterpret_cast(gameObject.world.GetPhysWorld().GetNative()); physWorld->destroyRigidBody(impl->rigidbody); impl->rigidbody = nullptr; @@ -289,7 +289,7 @@ namespace sh::game if (this == &other) return true; - auto world = reinterpret_cast(gameObject.world.GetPhysWorld()->GetNative()); + auto world = reinterpret_cast(gameObject.world.GetPhysWorld().GetNative()); return world->testOverlap(impl->rigidbody, other.impl->rigidbody); } SH_GAME_API auto RigidBody::GetRigidBodyUsingHandle(RigidBodyHandle handle) -> RigidBody* diff --git a/src/Game/Component/Phys/SphereCollider.cpp b/src/Game/Component/Phys/SphereCollider.cpp index 663fe743..ea1e2f2f 100644 --- a/src/Game/Component/Phys/SphereCollider.cpp +++ b/src/Game/Component/Phys/SphereCollider.cpp @@ -18,7 +18,7 @@ namespace sh::game { impl = std::make_unique(); - auto ctx = reinterpret_cast(world.GetPhysWorld()->GetContext()); + auto ctx = reinterpret_cast(world.GetPhysWorld().GetContext()); impl->shape = ctx->createSphereShape(radius); if (IsEditor()) { @@ -88,7 +88,7 @@ namespace sh::game if (impl->shape != nullptr) { SH_INFO("DestroyShape()"); - auto ctx = reinterpret_cast(world.GetPhysWorld()->GetContext()); + auto ctx = reinterpret_cast(world.GetPhysWorld().GetContext()); ctx->destroySphereShape(impl->shape); impl->shape = nullptr; } diff --git a/src/Game/Component/Render/Camera.cpp b/src/Game/Component/Render/Camera.cpp index 796e7eb7..06a04c63 100644 --- a/src/Game/Component/Render/Camera.cpp +++ b/src/Game/Component/Render/Camera.cpp @@ -1,50 +1,56 @@ #include "Component/Render/Camera.h" #include "World.h" +#include "Render/Renderer.h" #include "Render/IRenderContext.h" +#include "Render/RenderData.h" #include "Physics/Ray.h" #include "glm/gtc/matrix_transform.hpp" #include - namespace sh::game { Camera::Camera(GameObject& owner) : Component(owner), - - fov(60.f), fovx(0.f), - camera(), depth(0), - renderTexture(nullptr) { - SetFov(60); - owner.world.RegisterCamera(this); - camera.SetNearPlane(0.1f); - camera.SetFarPlane(1000.f); + fovRadians = glm::radians(fov); + owner.world.RegisterCamera(*this); + renderData.renderViewers.resize(1); canPlayInEditor = true; } - Camera::~Camera() - { - } + Camera::~Camera() = default; SH_GAME_API void Camera::BeginUpdate() { + render::RenderViewer& viewer = renderData.renderViewers[0]; if (renderTexture == nullptr) { - glm::vec2 size = gameObject.world.renderer.GetContext()->GetViewportEnd() - gameObject.world.renderer.GetContext()->GetViewportStart(); - camera.SetWidth(size.x); - camera.SetHeight(size.y); + const glm::vec2 size = gameObject.world.renderer.GetContext()->GetViewportEnd() - gameObject.world.renderer.GetContext()->GetViewportStart(); + width = size.x; + height = size.y; + viewer.viewportRect = { 0, 0, size.x, size.y }; + viewer.viewportScissor = { 0, 0, size.x, size.y }; } else { - glm::vec2 size = renderTexture->GetSize(); - camera.SetWidth(size.x); - camera.SetHeight(size.y); + const glm::vec2 size = renderTexture->GetSize(); + width = size.x; + height = size.y; + viewer.viewportRect = { 0, 0, size.x, size.y }; + viewer.viewportScissor = { 0, 0, size.x, size.y }; + + renderData.target = renderTexture; } - camera.SetPos(gameObject.transform->GetWorldPosition()); - camera.SetLookPos(lookPos); + gameObject.transform->UpdateMatrix(); + viewer.pos = gameObject.transform->GetWorldPosition(); + viewer.to = lookPos; + UpdateViewMatrix(); + UpdateProjMatrix(); + + world.renderer.PushRenderData(renderData); } SH_GAME_API void Camera::OnDestroy() { @@ -52,93 +58,37 @@ namespace sh::game { gameObject.world.SetMainCamera(nullptr); } - gameObject.world.UnRegisterCamera(this); + gameObject.world.UnRegisterCamera(*this); Super::OnDestroy(); } - SH_GAME_API void Camera::SetActive(bool b) - { - Super::SetActive(b); - camera.SetActive(b); - } - - SH_GAME_API auto Camera::GetProjMatrix() const -> const glm::mat4& - { - return camera.GetProjMatrix(core::ThreadType::Game); - } - - SH_GAME_API auto Camera::GetViewMatrix() const -> const glm::mat4& - { - return camera.GetViewMatrix(core::ThreadType::Game); - } - - SH_GAME_API void Camera::SetDepth(int depth) - { - this->depth = depth; - camera.SetPriority(depth); - } - SH_GAME_API void Camera::SetFov(float degree) - { - const float aspect = camera.GetWidth(core::ThreadType::Game) / camera.GetHeight(core::ThreadType::Game); - fov = degree; - fovx = glm::degrees(glm::atan(glm::tan(glm::radians(fov) * 0.5f) * aspect) * 2.f); - - camera.SetFov(degree); - } - - SH_GAME_API void Camera::SetRenderTexture(render::RenderTexture* renderTexture) - { - this->renderTexture = renderTexture; - camera.SetRenderTexture(renderTexture); - } - SH_GAME_API auto Camera::GetRenderTexture() const -> render::RenderTexture* - { - return renderTexture; - } - - SH_GAME_API auto Camera::GetNative() -> render::Camera& - { - return camera; - } - - SH_GAME_API auto Camera::GetNative() const -> const render::Camera& - { - return camera; - } - SH_GAME_API auto Camera::ScreenPointToRay(const Vec2& mousePos) const -> phys::Ray { - float w = camera.GetWidth(core::ThreadType::Game); - float h = camera.GetHeight(core::ThreadType::Game); - float aspect = w / h; + const float aspect = width / height; - float ndcX = 2.f * mousePos.x / w - 1.0f; - float ndcY = 1.0f - 2.f * mousePos.y / h; + const float ndcX = 2.f * mousePos.x / width - 1.0f; + const float ndcY = 1.0f - 2.f * mousePos.y / height; glm::vec4 camCoord{ 0.f, 0.f, 0.f, 1.f }; camCoord.x = aspect * ndcX; camCoord.y = ndcY; - camCoord.z = -1.f / glm::tan(camera.GetFovRadian(core::ThreadType::Game) / 2.f); + camCoord.z = -1.f / glm::tan(fovRadians / 2.f); - glm::vec3 worldCoord{ glm::inverse(camera.GetViewMatrix(core::ThreadType::Game)) * camCoord }; + glm::vec3 worldCoord{ glm::inverse(GetViewMatrix()) * camCoord }; glm::vec3 dir = glm::normalize(worldCoord - glm::vec3{ gameObject.transform->position }); return phys::Ray(gameObject.transform->position, dir); } - SH_GAME_API auto Camera::ScreenPointToRayOrtho(const Vec2& mousePos) const -> phys::Ray { - float w = camera.GetWidth(core::ThreadType::Game); - float h = camera.GetHeight(core::ThreadType::Game); - - float ndcX = 2.f * (mousePos.x / w) - 1.f; - float ndcY = 1.f - 2.f * (mousePos.y / h); + const float ndcX = 2.f * (mousePos.x / width) - 1.f; + const float ndcY = 1.f - 2.f * (mousePos.y / height); // Vulkan z_ndc = 0 ~ 1 glm::vec4 ndcNear(ndcX, ndcY, 0.f, 1.f); glm::vec4 ndcFar(ndcX, ndcY, 1.f, 1.f); - glm::mat4 invViewProj = glm::inverse(camera.GetProjMatrix(core::ThreadType::Game) * camera.GetViewMatrix(core::ThreadType::Game)); + glm::mat4 invViewProj = glm::inverse(GetProjMatrix() * GetViewMatrix()); glm::vec4 worldNear4 = invViewProj * ndcNear; glm::vec4 worldFar4 = invViewProj * ndcFar; @@ -150,78 +100,29 @@ namespace sh::game return phys::Ray(worldNear, dir); } - - SH_GAME_API void Camera::SetLookPos(const Vec3& pos) - { - lookPos = pos; - } - SH_GAME_API auto Camera::GetLookPos() const -> const Vec3& - { - return lookPos; - } - SH_GAME_API void Camera::SetUpVector(const Vec3& up) - { - camera.SetUpVector(up); - } - SH_GAME_API auto Camera::GetUpVector() const -> Vec3 - { - return camera.GetUpVector(core::ThreadType::Game); - } - SH_GAME_API void Camera::SetWidth(float width) - { - camera.SetWidth(width); - } - SH_GAME_API auto Camera::GetWidth() const -> float - { - return camera.GetWidth(core::ThreadType::Game); - } - SH_GAME_API void Camera::SetHeight(float height) - { - camera.SetHeight(height); - } - SH_GAME_API auto Camera::GetHeight() const -> float - { - return camera.GetHeight(core::ThreadType::Game); - } - - SH_GAME_API void Camera::SetProjection(Projection proj) - { - projection = proj; - if (proj == Projection::Perspective) - camera.SetOrthographic(false); - else - camera.SetOrthographic(true); - } - - SH_GAME_API auto Camera::GetProjection() const -> Projection + SH_GAME_API void Camera::SetFov(float degree) { - return projection; + const float aspect = width / height; + fov = degree; + fovx = glm::degrees(glm::atan(glm::tan(glm::radians(fov) * 0.5f) * aspect) * 2.f); + fovRadians = glm::radians(fov); } - SH_GAME_API void Camera::SetNearPlane(float near) - { - camera.SetNearPlane(near); - } - SH_GAME_API auto Camera::GetNearPlane() const -> float - { - return camera.GetNearPlane(core::ThreadType::Game); - } - SH_GAME_API void Camera::SetFarPlane(float far) + SH_GAME_API void Camera::UpdateViewMatrix() { - camera.SetFarPlane(far); + renderData.renderViewers.front().viewMatrix = glm::lookAt(glm::vec3{ gameObject.transform->GetWorldPosition() }, glm::vec3{ lookPos }, glm::vec3{ up }); } - SH_GAME_API auto Camera::GetFarPlane() const -> float + SH_GAME_API void Camera::UpdateProjMatrix() { - return camera.GetFarPlane(core::ThreadType::Game); - } - - SH_GAME_API void Camera::OnPropertyChanged(const core::reflection::Property& prop) - { - if (prop.GetName() == core::Util::ConstexprHash("depth")) - SetDepth(depth); - else if (prop.GetName() == core::Util::ConstexprHash("fov")) - SetFov(fov); - else if (prop.GetName() == core::Util::ConstexprHash("projection")) - SetProjection(projection); + if (projection == Projection::Perspective) + { + renderData.renderViewers.front().projMatrix = glm::perspectiveFovRH_ZO(fovRadians, width, height, nearPlane, farPlane); + } + else + { + const float dis = glm::length(glm::vec3{ lookPos } - glm::vec3{ gameObject.transform->GetWorldPosition() }) / 2.0f; + const float aspect = width / height; + renderData.renderViewers.front().projMatrix = glm::orthoRH_ZO(-dis * aspect, dis * aspect, -dis, dis, nearPlane, farPlane); + } } -} \ No newline at end of file +}//namespace \ No newline at end of file diff --git a/src/Game/Component/Render/DirectionalLight.cpp b/src/Game/Component/Render/DirectionalLight.cpp index c4a91e45..a07d01ad 100644 --- a/src/Game/Component/Render/DirectionalLight.cpp +++ b/src/Game/Component/Render/DirectionalLight.cpp @@ -5,7 +5,12 @@ #include "Core/SObject.h" +#include "Render/Renderer.h" +#include "Render/ShadowMapManager.h" +#include "Render/RenderTexture.h" + #include +#include namespace sh::game { @@ -13,12 +18,14 @@ namespace sh::game Component(owner) { world.GetLightOctree().Insert(*this); - + + renderData.priority = 1000; + GameRenderer* const srPtr = dynamic_cast(world.renderer.GetScriptableRenderer()); if (srPtr != nullptr) { - world.renderer.AddCamera(shadowCamera); - srPtr->AddShadowCasterCamera(shadowCamera); + //world.renderer.AddCamera(shadowCamera); + //srPtr->AddShadowCasterCamera(shadowCamera); } else { @@ -39,17 +46,19 @@ namespace sh::game SH_GAME_API void DirectionalLight::BeginUpdate() { Super::BeginUpdate(); - if (bCastShadow && shadowMap != nullptr) + if (bCastShadow && shadowSlot != render::ShadowMapManager::InvalidSlot) UpdateShadowCamera(); } SH_GAME_API void DirectionalLight::OnDestroy() { + ReleaseShadowResources(); + GameRenderer* const srPtr = dynamic_cast(world.renderer.GetScriptableRenderer()); if (srPtr != nullptr) { - srPtr->RemoveShadowCasterCamera(shadowCamera); - world.renderer.RemoveCamera(shadowCamera); + //srPtr->RemoveShadowCasterCamera(shadowCamera); + //world.renderer.RemoveCamera(shadowCamera); } world.GetLightOctree().Erase(*this); Super::OnDestroy(); @@ -59,15 +68,24 @@ namespace sh::game { if (prop.GetName() == "bCastShadow") { - if (bCastShadow && shadowMap == nullptr) - EnsureShadowResources(); - // bCastShadow=false로 바뀌어도 RT는 보존(토글 시 재할당 비용 회피). - // 메모리 회수가 필요하면 별도 정리 API를 도입할 것. + if (bCastShadow) + { + if (shadowSlot == render::ShadowMapManager::InvalidSlot) + EnsureShadowResources(); + } + else + { + ReleaseShadowResources(); + } } else if (prop.GetName() == "shadowMapResolution") { - if (shadowMap != nullptr) - shadowMap->SetSize(shadowMapResolution, shadowMapResolution); + if (bCastShadow) + { + // 슬롯 크기는 변경 불가. 해제 후 재할당으로 새 해상도 슬롯을 받는다. + ReleaseShadowResources(); + EnsureShadowResources(); + } } } @@ -83,20 +101,15 @@ namespace sh::game { direction = dir; } - SH_GAME_API auto DirectionalLight::GetPos() const -> const Vec3& - { - return gameObject.transform->GetWorldPosition(); - } - - SH_GAME_API auto DirectionalLight::GetLightType() const -> ILight::Type - { - return ILight::Type::Directional; - } SH_GAME_API void DirectionalLight::SetCastShadow(bool b) { + if (bCastShadow == b) + return; bCastShadow = b; - if (bCastShadow && shadowMap == nullptr) + if (bCastShadow) EnsureShadowResources(); + else + ReleaseShadowResources(); } SH_GAME_API void DirectionalLight::SetShadowBias(float bias) { @@ -104,9 +117,14 @@ namespace sh::game } SH_GAME_API void DirectionalLight::SetShadowMapResolution(uint32_t res) { + if (shadowMapResolution == res) + return; shadowMapResolution = res; - if (shadowMap != nullptr) - shadowMap->SetSize(res, res); + if (bCastShadow) + { + ReleaseShadowResources(); + EnsureShadowResources(); + } } SH_GAME_API auto DirectionalLight::GetLightSpaceMatrix() const -> glm::mat4 @@ -122,45 +140,57 @@ namespace sh::game const glm::mat4 proj = glm::ortho(-shadowOrthoSize, shadowOrthoSize, -shadowOrthoSize, shadowOrthoSize, shadowNearPlane, shadowFarPlane); return proj * view; } - - SH_GAME_API auto DirectionalLight::GetShadowCamera() -> render::Camera* + SH_GAME_API auto DirectionalLight::GetShadowMap() const -> render::RenderTexture* { - return bCastShadow ? &shadowCamera : nullptr; + if (!bCastShadow) + return nullptr; + return render::ShadowMapManager::GetInstance()->GetAtlas(); } - SH_GAME_API auto DirectionalLight::GetShadowCamera() const -> const render::Camera* + SH_GAME_API auto DirectionalLight::GetShadowSlot() const -> render::ShadowMapManager::Slot { - return bCastShadow ? &shadowCamera : nullptr; - } - SH_GAME_API auto DirectionalLight::GetShadowMap() const -> render::RenderTexture* - { - return bCastShadow ? shadowMap : nullptr; + if (!bCastShadow || shadowSlot == render::ShadowMapManager::InvalidSlot) + return render::ShadowMapManager::Slot{}; + return render::ShadowMapManager::GetInstance()->GetSlot(shadowSlot); } void DirectionalLight::EnsureShadowResources() { - if (shadowMap == nullptr) + render::ShadowMapManager* mgr = render::ShadowMapManager::GetInstance(); + + if (shadowSlot == render::ShadowMapManager::InvalidSlot) { - render::RenderTargetLayout layout{}; - layout.format = render::TextureFormat::None; - layout.depthFormat = render::TextureFormat::D32; - layout.bUseMSAA = false; - - shadowMap = core::SObject::Create(layout); - shadowMap->SetSize(shadowMapResolution, shadowMapResolution); - shadowMap->Build(*world.renderer.GetContext()); - shadowMap->SetName("DirectionalShadowMap"); + shadowSlot = mgr->AllocateSlot(shadowMapResolution); + if (shadowSlot == render::ShadowMapManager::InvalidSlot) + { + SH_ERROR("DirectionalLight: failed to allocate shadow slot"); + return; + } + mgr->RegisterCameraSlot(&shadowCamera, shadowSlot); } + render::RenderTexture* atlas = mgr->GetAtlas(); + shadowCamera.SetOrthographic(true); shadowCamera.SetWidth(shadowOrthoSize * 2.f); shadowCamera.SetHeight(shadowOrthoSize * 2.f); shadowCamera.SetNearPlane(shadowNearPlane); shadowCamera.SetFarPlane(shadowFarPlane); - shadowCamera.SetRenderTexture(shadowMap); + shadowCamera.SetRenderTexture(atlas); UpdateShadowCamera(); } + void DirectionalLight::ReleaseShadowResources() + { + if (shadowSlot == render::ShadowMapManager::InvalidSlot) + return; + render::ShadowMapManager* mgr = render::ShadowMapManager::GetInstance(); + mgr->UnregisterCameraSlot(&shadowCamera); + mgr->ReleaseSlot(shadowSlot); + shadowSlot = render::ShadowMapManager::InvalidSlot; + shadowCamera.SetRenderTexture(nullptr); + } + void DirectionalLight::UpdateShadowCamera() { const glm::vec3 dir = glm::normalize(glm::vec3{ direction.x, direction.y, direction.z }); diff --git a/src/Game/Component/Render/EditorCamera.cpp b/src/Game/Component/Render/EditorCamera.cpp index 2b7d915e..359ebb19 100644 --- a/src/Game/Component/Render/EditorCamera.cpp +++ b/src/Game/Component/Render/EditorCamera.cpp @@ -16,7 +16,6 @@ namespace sh::game lastLookPos(lookPos), middleDragRight(), middleDragUp(), rotationSpeed(0.2f), moveSpeed(0.02f) { - auto& type = GetStaticType(); } SH_GAME_API void EditorCamera::Start() @@ -26,7 +25,7 @@ namespace sh::game const float pitch = glm::degrees(asin(forward.y)); xdir = pitch; ydir = yaw; - camera.SetFarPlane(10000.0f); + SetFarPlane(10'000.f); } SH_GAME_API void EditorCamera::BeginUpdate() @@ -210,9 +209,9 @@ namespace sh::game glm::vec3 v = gameObject.transform->GetWorldPosition() - lookPos; //glm::vec3 up = glm::cross(glm::vec3{ 1.0f, 0.f, 0.f }, glm::normalize(v)); - Vec3 up = camera.GetUpVector(core::ThreadType::Game); + Vec3 up = GetUpVector(); up.y = (xdir >= 90 && xdir <= 270) ? -1 : 1; - camera.SetUpVector(up); + SetUpVector(up); } void EditorCamera::UpdateCameraPosition() diff --git a/src/Game/Component/Render/LineRenderer.cpp b/src/Game/Component/Render/LineRenderer.cpp index 865151c1..5ea34f97 100644 --- a/src/Game/Component/Render/LineRenderer.cpp +++ b/src/Game/Component/Render/LineRenderer.cpp @@ -1,6 +1,7 @@ #include "Component/Render/LineRenderer.h" #include "Component/Render/Camera.h" +#include "Render/Renderer.h" #include "Render/Mesh.h" #include "World.h" diff --git a/src/Game/Component/Render/MeshRenderer.cpp b/src/Game/Component/Render/MeshRenderer.cpp index fe2284d4..34e1072a 100644 --- a/src/Game/Component/Render/MeshRenderer.cpp +++ b/src/Game/Component/Render/MeshRenderer.cpp @@ -4,6 +4,8 @@ #include "World.h" +#include "Render/Renderer.h" + #include #include diff --git a/src/Game/Component/Render/PickingCamera.cpp b/src/Game/Component/Render/PickingCamera.cpp index 6478486a..ab9230c6 100644 --- a/src/Game/Component/Render/PickingCamera.cpp +++ b/src/Game/Component/Render/PickingCamera.cpp @@ -3,6 +3,8 @@ #include "Game/World.h" #include "Game/GameObject.h" +#include "Render/Renderer.h" + #include namespace sh::game { @@ -25,8 +27,6 @@ namespace sh::game renderTex = core::SObject::Create(rt); renderTex->SetSize(1024, 768); renderTex->Build(*world.renderer.GetContext()); - camera.SetWidth(1024); - camera.SetHeight(768); renderTex->SetName("PickingFramebuffer"); SetRenderTexture(renderTex); @@ -75,8 +75,6 @@ namespace sh::game SH_GAME_API void PickingCamera::SetTextureSize(const Vec2& size) { - camera.SetWidth(size.x); - camera.SetHeight(size.y); renderTex->SetSize(size.x, size.y); } diff --git a/src/Game/Component/Render/TextRenderer.cpp b/src/Game/Component/Render/TextRenderer.cpp index c1198505..a886c046 100644 --- a/src/Game/Component/Render/TextRenderer.cpp +++ b/src/Game/Component/Render/TextRenderer.cpp @@ -3,6 +3,8 @@ #include "Core/Util.h" +#include "Render/Renderer.h" + #include "Render/Model.h" namespace sh::game { diff --git a/src/Game/GUIPass.cpp b/src/Game/GUIPass.cpp new file mode 100644 index 00000000..aeefca97 --- /dev/null +++ b/src/Game/GUIPass.cpp @@ -0,0 +1,36 @@ +#include "GUIPass.h" +#include "ImGUImpl.h" + +#include "Render/IRenderContext.h" +#include "Render/VulkanImpl/VulkanCommandBuffer.h" + +#include "External/imgui/backends/imgui_impl_vulkan.h" + +namespace sh::game +{ + GUIPass::GUIPass() : + render::ScriptableRenderPass(core::Name{ "ImGUI" }, render::RenderQueue::UI) + { + } + SH_GAME_API void GUIPass::SetImGUIContext(ImGUImpl& gui) + { + this->gui = &gui; + } + SH_GAME_API void GUIPass::Configure(const render::RenderData& renderData) + { + renderTextures.clear(); + renderTextures[renderData.target] = render::ResourceUsage::ColorAttachment; + if (viewportTexture != nullptr) + renderTextures[viewportTexture] = render::ResourceUsage::SampledRead; + } + + SH_GAME_API void GUIPass::Record(render::CommandBuffer& cmd, const render::IRenderContext& ctx, const render::RenderData& renderData) + { + cmd.SetRenderData(renderData, false, true, false, false); + for (const render::RenderViewer& viewer : renderData.renderViewers) + { + SetViewportScissor(cmd, ctx, viewer); + ImGui_ImplVulkan_RenderDrawData(&gui->GetDrawData(), static_cast(cmd).GetCommandBuffer()); + } + } +}//namespace \ No newline at end of file diff --git a/src/Game/GameManager.cpp b/src/Game/GameManager.cpp index 8862e47b..bf058275 100644 --- a/src/Game/GameManager.cpp +++ b/src/Game/GameManager.cpp @@ -3,6 +3,7 @@ #include "GameObject.h" #include "ComponentModule.h" #include "ImGUImpl.h" +#include "Component/Render/Camera.h" #include "AssetLoaderFactory.h" #include "Asset/WorldAsset.h" @@ -17,6 +18,8 @@ #include "Core/ModuleLoader.h" #include "Core/ThreadSyncManager.h" +#include "Render/Renderer.h" + #include "Sound/SoundSystem.h" #include @@ -30,6 +33,17 @@ namespace sh::game immortalWorld = core::SObject::Create(renderer, gui); immortalWorld->SetName("immortalWorld"); core::GarbageCollection::GetInstance()->SetRootSet(immortalWorld); + + render::RenderViewer uiViewer{}; + uiViewer.projMatrix = + glm::orthoRH_ZO(0.f, 13.66f, 0.0f, 7.68f, 0.01f, 1000.f); + uiViewer.viewMatrix = glm::mat4{ 1.f }; + uiViewer.viewportRect = { 0, 0, renderer.GetWidth(), renderer.GetHeight() }; + uiViewer.viewportScissor = uiViewer.viewportRect; + + guiRenderData.priority = -100; + guiRenderData.renderViewers.push_back(uiViewer); + guiRenderData.tag = core::Name{ "ImGUI" }; } SH_GAME_API auto GameManager::GetRenderer() const -> render::Renderer& { @@ -79,6 +93,7 @@ namespace sh::game worldPtr->Update(dt); immortalWorld->Update(dt); gui->End(); + renderer->PushRenderData(guiRenderData); UpdateSoundListener(); @@ -123,7 +138,7 @@ namespace sh::game worldPtr->Clear(); worlds.clear(); - renderer->Clear(); + renderer->Reset(); gui->ClearDrawData(); loadingSingleWorld->SetupRenderer(); diff --git a/src/Game/GameRenderer.cpp b/src/Game/GameRenderer.cpp index 38823c03..d39a64b2 100644 --- a/src/Game/GameRenderer.cpp +++ b/src/Game/GameRenderer.cpp @@ -1,8 +1,11 @@ #include "GameRenderer.h" -#include "UIPass.h" +#include "GUIPass.h" #include "World.h" #include "Render/RenderPass/TransparentPass.h" +#include "Render/ShadowMapManager.h" + +#include namespace sh::game { GameRenderer::GameRenderer(render::IRenderContext& ctx, game::ImGUImpl& guictx, World& world) : @@ -11,6 +14,7 @@ namespace sh::game world(world), guiCtx(guictx) { + //render::ShadowMapManager::GetInstance()->Init(ctx); } SH_GAME_API void GameRenderer::Init() { @@ -18,71 +22,23 @@ namespace sh::game AddRenderPass(core::Name{ "Opaque" }, render::RenderQueue::Opaque); AddRenderPass(); AddRenderPass("UI", render::RenderQueue::UI); - AddRenderPass().SetImGUIContext(guiCtx); - } - SH_GAME_API void GameRenderer::Setup(const render::RenderTarget& data) - { - for (const std::unique_ptr& pass : allPasses) - { - auto allowedIt = allowedCamera.find(pass->passName); - if (allowedIt != allowedCamera.end()) - { - const std::set& allowedCams = allowedIt->second; - bool bAllowed = allowedCams.find(data.camera) != allowedCams.end(); - if (!bAllowed) - continue; - } - - auto it = ignoreCamera.find(pass->passName); - if (it == ignoreCamera.end()) - { - EnqueRenderPass(*pass); - continue; - } - const std::set& ignoreCams = it->second; - bool bIgnore = ignoreCams.find(data.camera) != ignoreCams.end(); - if (bIgnore) - continue; - EnqueRenderPass(*pass); - } - } - SH_GAME_API void GameRenderer::SetUICamera(const render::Camera& camera) - { - uiCamera = &camera; - allowedCamera[core::Name{ "UI" }].insert(&camera); - allowedCamera[core::Name{ "ImGUI" }].insert(&camera); - for (const std::unique_ptr& pass : allPasses) - { - if (pass->passName == "UI" || pass->passName == "ImGUI") - continue; - ignoreCamera[pass->passName].insert(&camera); - } + guiPass = &AddRenderPass(); + guiPass->SetImGUIContext(guiCtx); } - - SH_GAME_API void GameRenderer::AddShadowCasterCamera(const render::Camera& camera) + SH_GAME_API void GameRenderer::Setup(const render::RenderData& data) { - allowedCamera[core::Name{ "ShadowMapPass" }].insert(&camera); - for (const std::unique_ptr& pass : allPasses) + if (data.tag == "ImGUI") { - if (pass->passName == "ShadowMapPass") - continue; - ignoreCamera[pass->passName].insert(&camera); + if (guiPass != nullptr) + EnqueRenderPass(*guiPass); + return; } - } - SH_GAME_API void GameRenderer::RemoveShadowCasterCamera(const render::Camera& camera) - { - allowedCamera[core::Name{ "ShadowMapPass" }].erase(&camera); for (const std::unique_ptr& pass : allPasses) { - if (pass->passName == "ShadowMapPass") + if (pass.get() == guiPass) continue; - ignoreCamera[pass->passName].erase(&camera); + EnqueRenderPass(*pass); } } - SH_GAME_API void GameRenderer::AddShadowPass() - { - shadowMapPass = &AddRenderPass(); - allowedCamera.insert({ shadowMapPass->passName, {} }); - } }//namespace diff --git a/src/Game/UICamera.cpp b/src/Game/UICamera.cpp deleted file mode 100644 index 052349a2..00000000 --- a/src/Game/UICamera.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include "UICamera.h" - -#include "glm/gtc/matrix_transform.hpp" -namespace sh::game -{ - UICamera::UICamera() - { - SetOrthographic(true); - pos[core::ThreadType::Render] = pos[core::ThreadType::Game] = { 0.f, 0.f, 0.0f }; - to[core::ThreadType::Render] = to[core::ThreadType::Game] = { 0.f, 0.f, -1.0f }; - - bufferData[core::ThreadType::Game].matProj = - glm::orthoRH_ZO(0.f, 13.66f, 0.0f, 7.68f, 0.01f, 1000.f); - bufferData[core::ThreadType::Game].matView = glm::mat4{ 1.f }; - bufferData[core::ThreadType::Render] = bufferData[core::ThreadType::Game]; - } - SH_GAME_API void UICamera::UpdateViewMatrix() - { - } - SH_GAME_API void UICamera::UpdateProjMatrix() - { - bufferData[core::ThreadType::Game].matProj = - glm::orthoRH_ZO(0.f, width[core::ThreadType::Game], 0.0f, height[core::ThreadType::Game], nearPlane[core::ThreadType::Game], farPlane[core::ThreadType::Game]); - dirtyMask |= 256; - SyncDirty(); - } -}//namepsace \ No newline at end of file diff --git a/src/Game/UIPass.cpp b/src/Game/UIPass.cpp deleted file mode 100644 index 2d6b94be..00000000 --- a/src/Game/UIPass.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "UIPass.h" -#include "ImGUImpl.h" - -#include "Render/IRenderContext.h" -#include "Render/VulkanImpl/VulkanCommandBuffer.h" - -#include "External/imgui/backends/imgui_impl_vulkan.h" - -namespace sh::game -{ - UIPass::UIPass() : - render::ScriptableRenderPass(core::Name{ "ImGUI" }, render::RenderQueue::UI) - { - } - SH_GAME_API void UIPass::SetImGUIContext(ImGUImpl& gui) - { - this->gui = &gui; - } - SH_GAME_API void UIPass::Configure(const render::RenderTarget& renderData) - { - renderTextures[renderData.target] = render::ResourceUsage::ColorAttachment; - if (viewportTexture != nullptr) - renderTextures[viewportTexture] = render::ResourceUsage::SampledRead; - } - - SH_GAME_API void UIPass::Record(render::CommandBuffer& cmd, const render::IRenderContext& ctx, const render::RenderTarget& renderData) - { - cmd.SetRenderTarget(renderData, false, true, false, false); - SetViewportScissor(cmd, ctx, renderData); - ImGui_ImplVulkan_RenderDrawData(&gui->GetDrawData(), static_cast(cmd).GetCommandBuffer()); - } -}//namespace \ No newline at end of file diff --git a/src/Game/World.cpp b/src/Game/World.cpp index 358db35b..83cea5c5 100644 --- a/src/Game/World.cpp +++ b/src/Game/World.cpp @@ -13,6 +13,8 @@ #include "Core/SObjectManager.h" #include "Core/Asset.h" +#include "Render/Renderer.h" + #include #include @@ -107,7 +109,136 @@ namespace sh::game Clear(); Super::OnDestroy(); } + SH_GAME_API auto World::Serialize() const -> core::Json + { + if (IsLoaded()) + { + core::Json mainJson = Super::Serialize(); + core::Json& objsJson = mainJson["objs"]; + for (auto obj : objs) + { + if (obj->bNotSave || !core::IsValid(obj)) + continue; + objsJson.push_back(obj->Serialize()); + } + return mainJson; + } + const core::Json* worldPointPtr = GetWorldPoint(); + if (worldPointPtr == nullptr || worldPointPtr->empty() || worldPointPtr->is_discarded()) + return core::Json(); + + return *worldPointPtr; + } + SH_GAME_API void World::Deserialize(const core::Json& json) + { + bLoaded = true; + Super::Deserialize(json); + + if (!json.contains("objs")) + { + bLoaded = false; + return; + } + + bool hasCustomRenderer = customRenderer != nullptr; + CleanObjs(); + if (hasCustomRenderer) + SetupRenderer(); + InitResource(); + core::SObjectManager* objManager = core::SObjectManager::GetInstance(); + // 유효한 참조를 위해 두번 로드하는 과정을 거친다. + // 생성만 하는 과정 + for (const auto& objJson : json["objs"]) + { + const std::string& name = objJson["name"].get_ref(); + core::UUID objuuid{ objJson["uuid"].get_ref() }; + + auto sobj = objManager->GetSObject(objuuid); + GameObject* gameObj = nullptr; + if (core::IsValid(sobj)) // 이미 월드 상에 존재 할 경우 + { + gameObj = static_cast(sobj); + // 모든 컴포넌트 제거 + for (int i = 0; i < gameObj->GetComponents().size(); ++i) + { + Component* component = gameObj->GetComponents()[i]; + if (component == nullptr) + continue; + component->SetUUID(core::UUID::Generate()); + component->Destroy(); + } + } + else + { + if (sobj != nullptr) // pending kill + sobj->SetUUID(core::UUID::Generate()); + gameObj = this->AddGameObject(name); + gameObj->SetUUID(objuuid); + } + + for (auto& compJson : objJson["Components"]) + { + std::string name{ compJson["name"].get() }; + std::string type{ compJson["type"].get() }; + core::UUID uuid{ compJson["uuid"].get() }; + if (type == "Transform") // 트랜스폼은 게임오브젝트 생성 시 이미 만들어져있다. + { + if (gameObj->transform->GetUUID() != uuid) + { + // 실패 했다면 이미 해당 트랜스폼이 존재하는 상태 + if (!gameObj->transform->SetUUID(uuid)) + { + auto obj = core::SObjectManager::GetInstance()->GetSObject(uuid); + // 보류 상태라면 UUID 변경 + if (obj != nullptr && obj->IsPendingKill()) + { + obj->SetUUID(core::UUID::Generate()); + gameObj->transform->SetUUID(uuid); + } + } + } + continue; + } + + auto compType = ComponentModule::GetInstance()->GetComponent(name); + if (compType == nullptr) + { + SH_ERROR_FORMAT("Not found component - {}", type); + continue; + } + Component* component = compType->Create(*gameObj); + // 실패 했다면 이미 해당 컴포넌트가 존재하는 상태 (PendingKill상태 일 수도 있음) + if (!component->SetUUID(core::UUID{ uuid })) + { + auto obj = core::SObjectManager::GetInstance()->GetSObject(uuid); + // 보류 상태라면 UUID 변경 + if (obj != nullptr && obj->IsPendingKill()) + { + obj->SetUUID(core::UUID::Generate()); + component->SetUUID(core::UUID{ uuid }); + } + } + gameObj->AddComponent(component); + } + } + // 역 직렬화 + for (const auto& objJson : json["objs"]) + { + GameObject* obj = static_cast(objManager->GetSObject(core::UUID{ objJson["uuid"].get() })); + obj->Deserialize(objJson); + } + for (auto obj : objs) + { + if (!obj->activeSelf) + obj->PropagateEnable(); + } + for (auto& obj : addedObjs) + { + if (!obj->activeSelf) + obj->PropagateEnable(); + } + } SH_GAME_API void World::Clear() { CleanObjs(); @@ -125,17 +256,17 @@ namespace sh::game } SH_GAME_API void World::SetupRenderer() { - uiCamera.SetPriority(1000); + //uiCamera.SetPriority(1000); if (customRenderer == nullptr) { - renderer.AddCamera(uiCamera); + //renderer.AddCamera(uiCamera); customRenderer = std::make_unique(*renderer.GetContext(), GetUiContext(), *this); customRenderer->Init(); renderer.SetScriptableRenderer(*customRenderer); } - static_cast(customRenderer.get())->SetUICamera(uiCamera); + //static_cast(customRenderer.get())->SetUICamera(uiCamera); } SH_GAME_API void World::InitResource() { @@ -230,17 +361,10 @@ namespace sh::game return nullptr; } - - SH_GAME_API auto World::GetGameObjectPool() -> core::memory::MemoryPool& - { - return objPool; - } - SH_GAME_API void World::PushDeAllocatedGameObject(GameObject* ptr) { deallocatedObjs.push(ptr); } - SH_GAME_API void World::Start() { bOnStart = true; @@ -248,6 +372,7 @@ namespace sh::game SH_GAME_API void World::Update(double deltaTime) { dt = deltaTime; + if (!bStartLoop) bStartLoop = true; @@ -351,28 +476,22 @@ namespace sh::game } } - SH_GAME_API void World::RegisterCamera(Camera* cam) + SH_GAME_API void World::RegisterCamera(Camera& cam) { - if (!core::IsValid(cam)) + if (cam.IsPendingKill()) return; for (int i = 0; i < cameras.size(); ++i) { - if (cameras[i] == cam) + if (cameras[i] == &cam) return; } - cameras.push_back(cam); - renderer.AddCamera(cam->GetNative()); - - eventBus.Publish(events::CameraEvent{ *cam, events::CameraEvent::Type::Added }); + cameras.push_back(&cam); + eventBus.Publish(events::CameraEvent{ cam, events::CameraEvent::Type::Added }); } - SH_GAME_API void World::UnRegisterCamera(Camera* cam) + SH_GAME_API void World::UnRegisterCamera(Camera& cam) { - if (cam == nullptr) - return; - cameras.erase(std::remove(cameras.begin(), cameras.end(), cam), cameras.end()); // O(n) - renderer.RemoveCamera(cam->GetNative()); - - eventBus.Publish(events::CameraEvent{ *cam, events::CameraEvent::Type::Removed }); + cameras.erase(std::remove(cameras.begin(), cameras.end(), &cam), cameras.end()); // O(n) + eventBus.Publish(events::CameraEvent{ cam, events::CameraEvent::Type::Removed }); } SH_GAME_API void World::SetMainCamera(Camera* cam) { @@ -380,24 +499,6 @@ namespace sh::game return; mainCamera = cam; } - SH_GAME_API auto World::GetMainCamera() const -> Camera* - { - return mainCamera; - } - - SH_GAME_API auto World::GetPhysWorld() -> phys::PhysWorld* - { - return &physWorld; - } - SH_GAME_API auto World::GetLightOctree() -> Octree& - { - return lightOctree; - } - SH_GAME_API auto World::GetLightOctree() const -> const Octree& - { - return lightOctree; - } - SH_GAME_API void World::AddBeforeSyncTask(const std::function& func) { beforeSyncTasks.push(func); @@ -407,137 +508,6 @@ namespace sh::game { afterSyncTasks.push(func); } - - SH_GAME_API auto World::Serialize() const -> core::Json - { - if (IsLoaded()) - { - core::Json mainJson = Super::Serialize(); - core::Json& objsJson = mainJson["objs"]; - for (auto obj : objs) - { - if (obj->bNotSave || !core::IsValid(obj)) - continue; - objsJson.push_back(obj->Serialize()); - } - return mainJson; - } - const core::Json* worldPointPtr = GetWorldPoint(); - if (worldPointPtr == nullptr || worldPointPtr->empty() || worldPointPtr->is_discarded()) - return core::Json(); - - return *worldPointPtr; - } - SH_GAME_API void World::Deserialize(const core::Json& json) - { - bLoaded = true; - Super::Deserialize(json); - - if (!json.contains("objs")) - { - bLoaded = false; - return; - } - - bool hasCustomRenderer = customRenderer != nullptr; - CleanObjs(); - if (hasCustomRenderer) - SetupRenderer(); - InitResource(); - - core::SObjectManager* objManager = core::SObjectManager::GetInstance(); - // 유효한 참조를 위해 두번 로드하는 과정을 거친다. - // 생성만 하는 과정 - for (const auto& objJson : json["objs"]) - { - const std::string& name = objJson["name"].get_ref(); - core::UUID objuuid{ objJson["uuid"].get_ref() }; - - auto sobj = objManager->GetSObject(objuuid); - GameObject* gameObj = nullptr; - if (core::IsValid(sobj)) // 이미 월드 상에 존재 할 경우 - { - gameObj = static_cast(sobj); - // 모든 컴포넌트 제거 - for (int i = 0; i < gameObj->GetComponents().size(); ++i) - { - Component* component = gameObj->GetComponents()[i]; - if (component == nullptr) - continue; - component->SetUUID(core::UUID::Generate()); - component->Destroy(); - } - } - else - { - if (sobj != nullptr) // pending kill - sobj->SetUUID(core::UUID::Generate()); - gameObj = this->AddGameObject(name); - gameObj->SetUUID(objuuid); - } - - for (auto& compJson : objJson["Components"]) - { - std::string name{ compJson["name"].get() }; - std::string type{ compJson["type"].get() }; - core::UUID uuid{ compJson["uuid"].get() }; - if (type == "Transform") // 트랜스폼은 게임오브젝트 생성 시 이미 만들어져있다. - { - if (gameObj->transform->GetUUID() != uuid) - { - // 실패 했다면 이미 해당 트랜스폼이 존재하는 상태 - if (!gameObj->transform->SetUUID(uuid)) - { - auto obj = core::SObjectManager::GetInstance()->GetSObject(uuid); - // 보류 상태라면 UUID 변경 - if (obj != nullptr && obj->IsPendingKill()) - { - obj->SetUUID(core::UUID::Generate()); - gameObj->transform->SetUUID(uuid); - } - } - } - continue; - } - - auto compType = ComponentModule::GetInstance()->GetComponent(name); - if (compType == nullptr) - { - SH_ERROR_FORMAT("Not found component - {}", type); - continue; - } - Component* component = compType->Create(*gameObj); - // 실패 했다면 이미 해당 컴포넌트가 존재하는 상태 (PendingKill상태 일 수도 있음) - if (!component->SetUUID(core::UUID{ uuid })) - { - auto obj = core::SObjectManager::GetInstance()->GetSObject(uuid); - // 보류 상태라면 UUID 변경 - if (obj != nullptr && obj->IsPendingKill()) - { - obj->SetUUID(core::UUID::Generate()); - component->SetUUID(core::UUID{ uuid }); - } - } - gameObj->AddComponent(component); - } - } - // 역 직렬화 - for (const auto& objJson : json["objs"]) - { - GameObject* obj = static_cast(objManager->GetSObject(core::UUID{ objJson["uuid"].get() })); - obj->Deserialize(objJson); - } - for (auto obj : objs) - { - if (!obj->activeSelf) - obj->PropagateEnable(); - } - for (auto& obj : addedObjs) - { - if (!obj->activeSelf) - obj->PropagateEnable(); - } - } SH_GAME_API void World::SaveWorldPoint(const core::Json& json, std::string_view name) { savePoints[std::string{ name }] = json; @@ -593,10 +563,6 @@ namespace sh::game { savePoints.erase(name); } - SH_GAME_API auto World::GetUiContext() const -> ImGUImpl& - { - return *imgui; - } SH_GAME_API void World::PublishEvent(const core::IEvent& event) { eventBus.Publish(event); @@ -621,18 +587,6 @@ namespace sh::game bOnStart = false; eventBus.Publish(events::WorldEvent{ events::WorldEvent::Type::Stop }); } - SH_GAME_API auto World::IsPlaying() const -> bool - { - return bPlaying; - } - SH_GAME_API auto World::IsStart() const -> bool - { - return bOnStart; - } - SH_GAME_API auto World::IsLoaded() const -> bool - { - return bLoaded; - } SH_GAME_API void World::ReallocateUUIDS() { auto changeObjUUIDfn = diff --git a/src/Render/BufferFactory.cpp b/src/Render/BufferFactory.cpp index c11acf2c..f4852e11 100644 --- a/src/Render/BufferFactory.cpp +++ b/src/Render/BufferFactory.cpp @@ -66,4 +66,12 @@ namespace sh::render assert(false); return nullptr; } + SH_RENDER_API auto BufferFactory::GetBufferAlignment(const IRenderContext& context) -> std::size_t + { + if (context.GetRenderAPIType() == RenderAPI::Vulkan) + { + return static_cast(static_cast(context).GetGPUProperty().limits.minUniformBufferOffsetAlignment); + } + return 256u; + } }//namespace \ No newline at end of file diff --git a/src/Render/Camera.cpp b/src/Render/Camera.cpp deleted file mode 100644 index 176e80f1..00000000 --- a/src/Render/Camera.cpp +++ /dev/null @@ -1,209 +0,0 @@ -#include "Camera.h" - -#include "Core/ThreadSyncManager.h" - -#include "glm/gtc/matrix_transform.hpp" - -#include - -namespace sh::render -{ - uint32_t Camera::nextId = 0; - - SH_RENDER_API void Camera::SyncDirty() - { - if (bDirty) - return; - bDirty = true; - - core::ThreadSyncManager::PushSyncable(*this); - } - SH_RENDER_API void Camera::Sync() - { - if (dirtyMask & 1) - std::swap(pos[core::ThreadType::Render], pos[core::ThreadType::Game]); - if (dirtyMask & 2) - std::swap(to[core::ThreadType::Render], to[core::ThreadType::Game]); - if (dirtyMask & 4) - std::swap(up[core::ThreadType::Render], up[core::ThreadType::Game]); - if (dirtyMask & 8) - std::swap(fovRadians[core::ThreadType::Render], fovRadians[core::ThreadType::Game]); - if (dirtyMask & 16) - std::swap(nearPlane[core::ThreadType::Render], nearPlane[core::ThreadType::Game]); - if (dirtyMask & 32) - std::swap(farPlane[core::ThreadType::Render], farPlane[core::ThreadType::Game]); - if (dirtyMask & 64) - std::swap(width[core::ThreadType::Render], width[core::ThreadType::Game]); - if (dirtyMask & 128) - std::swap(height[core::ThreadType::Render], height[core::ThreadType::Game]); - if (dirtyMask & 256) - std::swap(bufferData[core::ThreadType::Render], bufferData[core::ThreadType::Game]); - dirtyMask = 0; - bDirty = false; - } - Camera::Camera() : - id(nextId++), - renderTexture(nullptr), priority(0) - { - fovRadians[core::ThreadType::Game] = glm::radians(60.0f); - pos[core::ThreadType::Game] = glm::vec3(0.0f, 0.0f, 0.0f); - to[core::ThreadType::Game] = glm::vec3(0.0f, 0.0f, 1.0f); - up[core::ThreadType::Game] = glm::vec3(0.0f, 1.0f, 0.0f); - - fovRadians[core::ThreadType::Render] = glm::radians(60.0f); - pos[core::ThreadType::Render] = glm::vec3(0.0f, 0.0f, 0.0f); - to[core::ThreadType::Render] = glm::vec3(0.0f, 0.0f, 1.0f); - up[core::ThreadType::Render] = glm::vec3(0.0f, 1.0f, 0.0f); - } - SH_RENDER_API void Camera::SetRenderTexture(RenderTexture* framebuffer) - { - this->renderTexture = framebuffer; - } - SH_RENDER_API auto Camera::GetRenderTexture() const -> RenderTexture* - { - return renderTexture; - } - - SH_RENDER_API void Camera::SetPriority(int priority) - { - this->priority = priority; - } - SH_RENDER_API auto Camera::GetPriority() const -> int - { - return priority; - } - SH_RENDER_API auto Camera::GetProjMatrix(core::ThreadType thr) const -> const glm::mat4& - { - return bufferData[thr].matProj; - } - SH_RENDER_API auto Camera::GetViewMatrix(core::ThreadType thr) const -> const glm::mat4& - { - return bufferData[thr].matView; - } - SH_RENDER_API void Camera::SetPos(const glm::vec3& pos) - { - this->pos[core::ThreadType::Game] = pos; - dirtyMask |= 1; - UpdateProjMatrix(); - UpdateViewMatrix(); - SyncDirty(); - } - SH_RENDER_API void Camera::SetLookPos(const glm::vec3& pos) - { - this->to[core::ThreadType::Game] = pos; - dirtyMask |= 2; - UpdateProjMatrix(); - UpdateViewMatrix(); - SyncDirty(); - } - SH_RENDER_API void Camera::SetUpVector(const glm::vec3& up) - { - this->up[core::ThreadType::Game] = up; - dirtyMask |= 4; - UpdateViewMatrix(); - SyncDirty(); - } - SH_RENDER_API void sh::render::Camera::SetFovRadian(float rad) - { - fovRadians[core::ThreadType::Game] = rad; - dirtyMask |= 8; - UpdateProjMatrix(); - SyncDirty(); - } - SH_RENDER_API void Camera::SetFov(float degree) - { - fovRadians[core::ThreadType::Game] = glm::radians(degree); - dirtyMask |= 8; - UpdateProjMatrix(); - SyncDirty(); - } - SH_RENDER_API void Camera::SetNearPlane(float near) - { - nearPlane[core::ThreadType::Game] = near; - dirtyMask |= 16; - UpdateProjMatrix(); - SyncDirty(); - } - SH_RENDER_API void Camera::SetFarPlane(float far) - { - farPlane[core::ThreadType::Game] = far; - dirtyMask |= 32; - UpdateProjMatrix(); - SyncDirty(); - } - SH_RENDER_API void Camera::SetWidth(float width) - { - this->width[core::ThreadType::Game] = width; - dirtyMask |= 64; - UpdateProjMatrix(); - SyncDirty(); - } - SH_RENDER_API void Camera::SetHeight(float height) - { - this->height[core::ThreadType::Game] = height; - dirtyMask |= 128; - UpdateProjMatrix(); - SyncDirty(); - } - SH_RENDER_API void Camera::SetOrthographic(bool bEnable) - { - bPerspective = !bEnable; - } - SH_RENDER_API void Camera::SetRenderTagMask(uint32_t mask) - { - renderTagMask = mask; - } - SH_RENDER_API void Camera::AddRenderTagMask(uint32_t tagId) - { - renderTagMask |= tagId; - } - SH_RENDER_API void Camera::RemoveRenderTagMask(uint32_t tagId) - { - assert(renderTagMask & tagId); - renderTagMask ^= tagId; - } - SH_RENDER_API auto Camera::GetRenderTagMask() const -> uint32_t - { - return renderTagMask; - } - SH_RENDER_API auto Camera::CheckRenderTag(uint32_t tagId) const -> bool - { - return renderTagMask & tagId; - } - SH_RENDER_API void Camera::SetActive(bool bActive) - { - this->bActive = bActive; - } - SH_RENDER_API auto Camera::GetActive() const -> bool - { - return bActive; - } - void Camera::UpdateProjMatrix() - { - if (bPerspective) - { - bufferData[core::ThreadType::Game].matProj = - glm::perspectiveFovRH_ZO( - fovRadians[core::ThreadType::Game], - width[core::ThreadType::Game], - height[core::ThreadType::Game], - nearPlane[core::ThreadType::Game], - farPlane[core::ThreadType::Game]); - } - else - { - const float dis = glm::length(to[core::ThreadType::Game] - pos[core::ThreadType::Game]) / 2.0f; - const float aspect = width[core::ThreadType::Game] / height[core::ThreadType::Game]; - bufferData[core::ThreadType::Game].matProj = - glm::orthoRH_ZO(-dis * aspect, dis * aspect, -dis, dis, nearPlane[core::ThreadType::Game], farPlane[core::ThreadType::Game]); - } - dirtyMask |= 256; - SyncDirty(); - } - void Camera::UpdateViewMatrix() - { - bufferData[core::ThreadType::Game].matView = glm::lookAt(pos[core::ThreadType::Game], to[core::ThreadType::Game], up[core::ThreadType::Game]); - dirtyMask |= 256; - SyncDirty(); - } -}//namespace render \ No newline at end of file diff --git a/src/Render/MaterialData.cpp b/src/Render/MaterialData.cpp index bb52215a..21eac9c0 100644 --- a/src/Render/MaterialData.cpp +++ b/src/Render/MaterialData.cpp @@ -4,11 +4,11 @@ #include "BufferFactory.h" #include "IRenderContext.h" -#include "VulkanCameraBuffers.h" - #include "Core/ThreadSyncManager.h" #include "Core/Logger.h" +#include "Render/RenderDataManager.h" + namespace sh::render { MaterialData::MaterialData(MaterialData&& other) noexcept : @@ -223,12 +223,7 @@ namespace sh::render } else { - assert(context.GetRenderAPIType() == RenderAPI::Vulkan); // 언젠가 API 추가되면 빼기 - // 카메라 데이터 - if (context.GetRenderAPIType() == RenderAPI::Vulkan) - { - passData.shaderBindings[set]->Link(binding, vk::VulkanCameraBuffers::GetInstance()->GetCameraBuffer(), 128); - } + passData.shaderBindings[set]->Link(binding, *context.GetRenderDataManager().GetBuffer(), sizeof(RenderDataManager::BufferData)); } } } diff --git a/src/Render/RenderDataManager.cpp b/src/Render/RenderDataManager.cpp new file mode 100644 index 00000000..baa89593 --- /dev/null +++ b/src/Render/RenderDataManager.cpp @@ -0,0 +1,84 @@ +#include "RenderDataManager.h" +#include "BufferFactory.h" + +#include "Core/Util.h" +namespace sh::render +{ + RenderDataManager::RenderDataManager() = default; + RenderDataManager::~RenderDataManager() = default; + + SH_RENDER_API void RenderDataManager::Init(const IRenderContext& ctx) + { + if (this->ctx != nullptr) + return; + + this->ctx = &ctx; + alignment = core::Util::AlignTo(sizeof(BufferData), BufferFactory::GetBufferAlignment(ctx));; + + const std::size_t cap = 100; + + BufferFactory::CreateInfo info{}; + info.size = cap * alignment; + info.bDynamic = false; + info.bGPUOnly = false; + + buffer = BufferFactory::Create(ctx, info); + } + SH_RENDER_API void RenderDataManager::PushRenderData(const RenderData& renderTarget) + { + renderDataQueue.Push(renderTarget); + } + SH_RENDER_API void RenderDataManager::ClearBuffer() + { + buffer.reset(); + } + SH_RENDER_API void RenderDataManager::ClearRenderViews() + { + renderDataQueue.Clear(); + renderDatas.clear(); + } + SH_RENDER_API void RenderDataManager::UploadToGPU() + { + std::size_t offset = 0; + std::size_t i = 0; + renderDatasSize = 0; + renderDataQueue.Drain( + [&](Ref& renderTarget) + { + if (offset + sizeof(BufferData) > buffer->GetSize()) + return; + + if (renderDatas.size() <= i) // 메모) 힙 할당 최소화 하기 위해 이렇게 + renderDatas.resize(i + 1); + + renderDatas[i] = renderTarget.get(); + + for (RenderViewer& viewer : renderDatas[i].renderViewers) + { + BufferData data; + data.view = viewer.viewMatrix; + data.proj = viewer.projMatrix; + data.pos = viewer.pos; + + buffer->SetData(&data, offset, sizeof(BufferData)); + + viewer.offset = offset; + offset += alignment; + } + ++i; + } + ); + renderDatasSize = i; + std::sort(renderDatas.begin(), renderDatas.begin() + renderDatasSize, + [](const RenderData& left, const RenderData& right) + { + return left.priority > right.priority; + } + ); + } + + SH_RENDER_API auto RenderDataManager::GetRenderDatas() -> core::ArrayView + { + return core::ArrayView{renderDatas.data(), renderDatasSize}; + } +}//namespace \ No newline at end of file diff --git a/src/Render/RenderPass/CopyPass.cpp b/src/Render/RenderPass/CopyPass.cpp index ba380c78..3ce8276a 100644 --- a/src/Render/RenderPass/CopyPass.cpp +++ b/src/Render/RenderPass/CopyPass.cpp @@ -9,7 +9,7 @@ namespace sh::render { } - SH_RENDER_API void CopyPass::Configure(const RenderTarget& renderData) + SH_RENDER_API void CopyPass::Configure(const RenderData& renderData) { if (cpyDatas.empty()) return; @@ -18,7 +18,7 @@ namespace sh::render for (const CopyData& cpyData : cpyDatas) renderTextures[cpyData.src] = ResourceUsage::TransferSrc; } - SH_RENDER_API void CopyPass::Record(CommandBuffer& cmd, const IRenderContext& ctx, const RenderTarget& renderData) + SH_RENDER_API void CopyPass::Record(CommandBuffer& cmd, const IRenderContext& ctx, const RenderData& renderData) { if (cpyDatas.empty()) return; diff --git a/src/Render/RenderPass/ShadowMapPass.cpp b/src/Render/RenderPass/ShadowMapPass.cpp index fa7856b5..2fed9401 100644 --- a/src/Render/RenderPass/ShadowMapPass.cpp +++ b/src/Render/RenderPass/ShadowMapPass.cpp @@ -1,9 +1,4 @@ #include "RenderPass/ShadowMapPass.h" -#include "RenderTexture.h" -#include "Camera.h" -#include "Drawable.h" -#include "Material.h" -#include "Shader.h" #include "IRenderContext.h" namespace sh::render diff --git a/src/Render/RenderPass/TransparentPass.cpp b/src/Render/RenderPass/TransparentPass.cpp index 09c591e3..ffb56247 100644 --- a/src/Render/RenderPass/TransparentPass.cpp +++ b/src/Render/RenderPass/TransparentPass.cpp @@ -1,6 +1,6 @@ #include "RenderPass/TransparentPass.h" -#include "Camera.h" #include "Drawable.h" +#include "CommandBuffer.h" #define GLM_ENABLE_EXPERIMENTAL #include "glm/gtx/norm.hpp" @@ -13,26 +13,18 @@ namespace sh::render { } - SH_RENDER_API void TransparentPass::Configure(const RenderTarget& renderData) + SH_RENDER_API void TransparentPass::Configure(const RenderData& renderData) { SetImageUsages(renderData); } - SH_RENDER_API void TransparentPass::Record(CommandBuffer& cmd, const IRenderContext& ctx, const RenderTarget& renderTarget) + SH_RENDER_API void TransparentPass::Record(CommandBuffer& cmd, const IRenderContext& ctx, const RenderData& renderTarget) { - cmd.SetRenderTarget(renderTarget, false, false, true, false); - SetViewportScissor(cmd, ctx, renderTarget); + cmd.SetRenderData(renderTarget, false, false, true, false); if (renderTarget.drawables == nullptr) return; - struct RenderItem - { - const Material* material; - Mesh::Topology topology; - const Drawable* drawable; - }; - - std::vector items; + items.clear(); for (const Drawable* drawable : *renderTarget.drawables) { RenderItem item{}; @@ -42,21 +34,26 @@ namespace sh::render items.push_back(item); } - const glm::vec3& camPos = renderTarget.camera->GetPos(core::ThreadType::Render); - const glm::vec3& camTo = renderTarget.camera->GetLookPos(core::ThreadType::Render); - const glm::vec3 to = glm::normalize(camTo - camPos); - std::stable_sort(items.begin(), items.end(), - [&camPos, &to](const RenderItem& left, const RenderItem& right) -> bool - { - const glm::vec3 posLeft = left.drawable->GetModelMatrix(core::ThreadType::Render)[3]; - const glm::vec3 posRight = right.drawable->GetModelMatrix(core::ThreadType::Render)[3]; - const float leftLen = glm::dot(to, posLeft - camPos); - const float rightLen = glm::dot(to, posRight - camPos); - return leftLen > rightLen; - } - ); - - for (const RenderItem& item : items) - cmd.DrawMesh(*item.drawable, passName); + std::size_t idx = 0; + for (const RenderViewer& viewer : renderTarget.renderViewers) + { + const glm::vec3& camPos = viewer.pos; + const glm::vec3& camTo = viewer.to; + const glm::vec3 to = glm::normalize(camTo - camPos); + std::stable_sort(items.begin(), items.end(), + [&camPos, &to](const RenderItem& left, const RenderItem& right) -> bool + { + const glm::vec3 posLeft = left.drawable->GetModelMatrix(core::ThreadType::Render)[3]; + const glm::vec3 posRight = right.drawable->GetModelMatrix(core::ThreadType::Render)[3]; + const float leftLen = glm::dot(to, posLeft - camPos); + const float rightLen = glm::dot(to, posRight - camPos); + return leftLen > rightLen; + } + ); + + for (const RenderItem& item : items) + cmd.DrawMesh(*item.drawable, passName, idx); + ++idx; + } } }//namespace \ No newline at end of file diff --git a/src/Render/RenderTarget.cpp b/src/Render/RenderTarget.cpp deleted file mode 100644 index 16919920..00000000 --- a/src/Render/RenderTarget.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "RenderTarget.h" \ No newline at end of file diff --git a/src/Render/Renderer.cpp b/src/Render/Renderer.cpp index f79cdb70..5c05324a 100644 --- a/src/Render/Renderer.cpp +++ b/src/Render/Renderer.cpp @@ -1,5 +1,6 @@ #include "Renderer.h" #include "Drawable.h" +#include "RenderDataManager.h" #include "Core/ThreadSyncManager.h" @@ -12,9 +13,7 @@ namespace sh::render drawcall({ 0, 0 }) { } - Renderer::~Renderer() - { - } + Renderer::~Renderer() = default; SH_RENDER_API void Renderer::SyncDirty() { @@ -36,18 +35,27 @@ namespace sh::render SH_RENDER_API bool Renderer::Init(sh::window::Window& win) { window = &win; + IRenderContext* ctx = GetContext(); + assert(ctx != nullptr); + if (ctx == nullptr) + { + SH_ERROR("Create context first!"); + return false; + } return true; } - SH_RENDER_API void Renderer::Clear() + SH_RENDER_API void Renderer::Reset() { renderer = nullptr; renderCommands.Clear(); drawables.clear(); drawcall = core::SyncArray{}; - cameras.clear(); - - drawCalls.clear(); + IRenderThrMethod::ClearRenderViews(GetContext()->GetRenderDataManager()); + } + SH_RENDER_API void Renderer::Clear() + { + Reset(); } SH_RENDER_API void Renderer::Render() @@ -58,6 +66,7 @@ namespace sh::render drawables.clear(); DrainRenderCommands(); + IRenderThrMethod::UploadToGPU(GetContext()->GetRenderDataManager()); } SH_RENDER_API void Renderer::Pause(bool b) { @@ -74,7 +83,10 @@ namespace sh::render cmd.data = drawable; renderCommands.Push(std::move(cmd)); } - + SH_RENDER_API void Renderer::PushRenderData(const RenderData& renderData) + { + GetContext()->GetRenderDataManager().PushRenderData(renderData); + } SH_RENDER_API auto Renderer::GetWindow() const -> sh::window::Window& { assert(window); @@ -90,30 +102,6 @@ namespace sh::render { this->renderer = &renderer; } - SH_RENDER_API void Renderer::AddCamera(const Camera& camera) - { - RenderCommand cmd{}; - cmd.type = RenderCommand::Type::AddCamera; - cmd.data = &camera; - renderCommands.Push(std::move(cmd)); - } - SH_RENDER_API void Renderer::RemoveCamera(const Camera& camera) - { - RenderCommand cmd{}; - cmd.type = RenderCommand::Type::RemoveCamera; - cmd.data = &camera; - renderCommands.Push(std::move(cmd)); - } - - SH_RENDER_API auto Renderer::GetDrawCall(core::ThreadType thread) const -> uint32_t - { - return drawcall[static_cast(thread)]; - } - SH_RENDER_API auto Renderer::GetThreadId() const -> std::thread::id - { - return threadId; - } - SH_RENDER_API void Renderer::SetDrawCallCount(uint32_t drawcall) { this->drawcall[static_cast(core::ThreadType::Render)] = drawcall; @@ -130,31 +118,12 @@ namespace sh::render { case RenderCommand::Type::PushDrawable: { - Drawable* drawable = std::get<0>(cmd.data).Get(); + Drawable* const drawable = cmd.data.Get(); if (!core::IsValid(drawable) || !drawable->CheckAssetValid()) return; drawables.push_back(drawable); break; } - case RenderCommand::Type::AddCamera: - { - const Camera* camera = std::get<1>(cmd.data); - if (camera == nullptr) - return; - auto [it, inserted] = cameras.insert(camera); - if (inserted) - OnCameraAdded(camera); - break; - } - case RenderCommand::Type::RemoveCamera: - { - const Camera* camera = std::get<1>(cmd.data); - if (camera == nullptr) - return; - if (cameras.erase(camera) != 0) - OnCameraRemoved(camera); - break; - } } } ); diff --git a/src/Render/ScriptableRenderPass.cpp b/src/Render/ScriptableRenderPass.cpp index 3300e3d8..3172c8d9 100644 --- a/src/Render/ScriptableRenderPass.cpp +++ b/src/Render/ScriptableRenderPass.cpp @@ -13,7 +13,7 @@ namespace sh::render renderQueue(renderQueue) { } - SH_RENDER_API void ScriptableRenderPass::Configure(const RenderTarget& renderData) + SH_RENDER_API void ScriptableRenderPass::Configure(const RenderData& renderData) { if (renderData.drawables != nullptr) { @@ -21,19 +21,19 @@ namespace sh::render } SetImageUsages(renderData); } - SH_RENDER_API void ScriptableRenderPass::Record(CommandBuffer& cmd, const IRenderContext& ctx, const RenderTarget& renderTarget) + SH_RENDER_API void ScriptableRenderPass::Record(CommandBuffer& cmd, const IRenderContext& ctx, const RenderData& renderData) { - cmd.SetRenderTarget(renderTarget, true, true, true, true); - SetViewportScissor(cmd, ctx, renderTarget); - - if (renderTarget.drawables == nullptr) + cmd.SetRenderData(renderData, true, true, true, true); + if (renderData.drawables == nullptr) return; - - for (const RenderBatch& batch : renderBatches) + std::size_t viewerIdx = 0; + for (const RenderViewer& viewer : renderData.renderViewers) { - cmd.DrawMeshBatch(batch.drawables, passName); + SetViewportScissor(cmd, ctx, viewer); + for (const RenderBatch& batch : renderBatches) + cmd.DrawMeshBatch(batch.drawables, passName, viewerIdx); + ++viewerIdx; } - //ctx.GetRenderImpl().RecordCommand(cmd, passName, renderTarget, drawList, bStoreImage); } SH_RENDER_API auto ScriptableRenderPass::CreateRenderBatch(const std::vector& drawables) const -> std::vector @@ -59,12 +59,28 @@ namespace sh::render std::unordered_map groupIndex; groupIndex.reserve(drawables.size()); + std::map materialFilter; for (const Drawable* drawable : drawables) { if (!core::IsValid(drawable) || !drawable->CheckAssetValid()) continue; const Material* const mat = drawable->GetMaterial(); + if (auto it = materialFilter.find(mat); it == materialFilter.end()) + { + if (mat->GetShader()->GetShaderPasses(passName) == nullptr) + { + materialFilter.insert({ mat, false }); + continue; + } + materialFilter.insert({ mat, true }); + } + else + { + if (!it->second) + continue; + } + Mesh::Topology topology = drawable->GetTopology(core::ThreadType::Render); const bool bSkinned = drawable->IsSkinnedMesh(); @@ -88,7 +104,7 @@ namespace sh::render } return groups; } - SH_RENDER_API void ScriptableRenderPass::SetImageUsages(const RenderTarget& renderData) + SH_RENDER_API void ScriptableRenderPass::SetImageUsages(const RenderData& renderData) { renderTextures.clear(); if (renderData.target != nullptr && IsDepthTexture(renderData.target->GetTextureFormat())) @@ -96,10 +112,12 @@ namespace sh::render else renderTextures[renderData.target] = ResourceUsage::ColorAttachment; - if (renderData.drawables == nullptr) - return; - - for (const Drawable* drawable : *renderData.drawables) + if (renderData.drawables != nullptr) + SetImageUsages(*renderData.drawables); + } + SH_RENDER_API void ScriptableRenderPass::SetImageUsages(const std::vector& drawables) + { + for (const Drawable* drawable : drawables) { if (drawable == nullptr || !drawable->CheckAssetValid()) continue; @@ -124,25 +142,9 @@ namespace sh::render } } } - SH_RENDER_API void ScriptableRenderPass::SetViewportScissor(CommandBuffer& cmd, const IRenderContext& ctx, const RenderTarget& renderTarget) + SH_RENDER_API void ScriptableRenderPass::SetViewportScissor(CommandBuffer& cmd, const IRenderContext& ctx, const RenderViewer& renderViewer) { - if (renderTarget.target == nullptr) - { - const int x = static_cast(ctx.GetViewportStart().x); - const int y = static_cast(ctx.GetViewportEnd().y); - const int w = static_cast(ctx.GetViewportEnd().x - ctx.GetViewportStart().x); - const int h = static_cast(ctx.GetViewportEnd().y - ctx.GetViewportStart().y); - cmd.SetViewport(x, y, w, -h); - cmd.SetScissor(0, 0, static_cast(w), static_cast(h)); - } - else - { - const int x = 0; - const int y = static_cast(renderTarget.target->GetSize().y); - const int w = static_cast(renderTarget.target->GetSize().x); - const int h = y; - cmd.SetViewport(x, y, w, -h); - cmd.SetScissor(0, 0, static_cast(w), static_cast(h)); - } + cmd.SetViewport(renderViewer.viewportRect.x, renderViewer.viewportRect.y, renderViewer.viewportRect.z, renderViewer.viewportRect.w); + cmd.SetScissor(renderViewer.viewportScissor.x, renderViewer.viewportScissor.y, renderViewer.viewportScissor.z, renderViewer.viewportScissor.w); } }//namespace \ No newline at end of file diff --git a/src/Render/ScriptableRenderer.cpp b/src/Render/ScriptableRenderer.cpp index 5182ec6a..2d43e652 100644 --- a/src/Render/ScriptableRenderer.cpp +++ b/src/Render/ScriptableRenderer.cpp @@ -85,12 +85,12 @@ namespace sh::render bSyncDirty = false; } - SH_RENDER_API void ScriptableRenderer::Setup(const RenderTarget& data) + SH_RENDER_API void ScriptableRenderer::Setup(const RenderData& data) { for (const std::unique_ptr& pass : allPasses) activePasses.push_back(pass.get()); } - SH_RENDER_API void ScriptableRenderer::Execute(const RenderTarget& data) + SH_RENDER_API void ScriptableRenderer::Execute(const RenderData& data) { renderCallCount = 0; std::stable_sort(activePasses.begin(), activePasses.end(), @@ -145,13 +145,11 @@ namespace sh::render } SH_RENDER_API void ScriptableRenderer::ExecuteTransfer(uint32_t imgIdx) { - RenderTarget target{}; - target.camera = nullptr; - target.target = nullptr; - target.drawables = nullptr; - target.frameIndex = imgIdx; + RenderData data{}; + data.frameIndex = imgIdx; + data.drawables = nullptr; - IRenderThrMethod::Configure(*cpyPass, target); + IRenderThrMethod::Configure(*cpyPass, data); std::vector preBarriers = BuildBarrierInfo(*cpyPass, imgIdx); std::vector postBarriers; @@ -167,17 +165,10 @@ namespace sh::render swapChainStates[imgIdx] = ResourceUsage::Present; } - - RenderTarget rt{}; - rt.frameIndex = imgIdx; - rt.camera = nullptr; - rt.target = nullptr; - rt.drawables = nullptr; - CommandBuffer* cmd = ctx.AllocateCommandBuffer(false); cmd->Begin(true); cmd->EmitBarrier(preBarriers); - IRenderThrMethod::Record(*cpyPass, *cmd, ctx, rt); + IRenderThrMethod::Record(*cpyPass, *cmd, ctx, data); cmd->EmitBarrier(postBarriers); cmd->End(); diff --git a/src/Render/VulkanImpl/VulkanCameraBuffers.cpp b/src/Render/VulkanImpl/VulkanCameraBuffers.cpp deleted file mode 100644 index 6d967bc5..00000000 --- a/src/Render/VulkanImpl/VulkanCameraBuffers.cpp +++ /dev/null @@ -1,90 +0,0 @@ -#include "VulkanCameraBuffers.h" - -#include "Core/Util.h" - -namespace sh::render::vk -{ - void VulkanCameraBuffers::Init(const VulkanContext& context) - { - this->context = &context; - - cameraData = std::make_unique(context); - std::size_t minUboAlignment = context.GetGPUProperty().limits.minUniformBufferOffsetAlignment; - - dynamicAlignment = core::Util::AlignTo(sizeof(Camera::BufferData), minUboAlignment); - - std::size_t bufferSize = cap * dynamicAlignment; - - cameraData->Create(bufferSize, - VkBufferUsageFlagBits::VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, - VkSharingMode::VK_SHARING_MODE_EXCLUSIVE, - VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, - true); - } - SH_RENDER_API void VulkanCameraBuffers::Clear() - { - cams.clear(); - emptyidx = std::stack{}; - } - void VulkanCameraBuffers::AddCamera(const Camera& camera) - { - assert(cams.size() < cap); - - auto it = std::find(cams.begin(), cams.end(), &camera); - if (it != cams.end()) - return; - - if (emptyidx.empty()) - { - cams.push_back(&camera); - } - else - { - uint32_t idx = emptyidx.top(); - emptyidx.pop(); - cams[idx] = &camera; - } - } - void VulkanCameraBuffers::RemoveCamera(const Camera& camera) - { - auto it = std::find(cams.begin(), cams.end(), &camera); - if (it == cams.end()) - return; - *it = nullptr; - emptyidx.push(std::distance(cams.begin(), it)); - } - void VulkanCameraBuffers::UploadDataToGPU(const Camera& camera) - { - auto it = std::find(cams.begin(), cams.end(), &camera); - if (it == cams.end()) - return; - auto offsetOpt = GetDynamicOffset(camera); - if (offsetOpt.has_value()) - { - //Camera::BufferData* data = reinterpret_cast((std::size_t)cameraData->GetData() + offsetOpt.value()); - Camera::BufferData data; - data.matView = camera.GetViewMatrix(core::ThreadType::Render); - data.matProj = camera.GetProjMatrix(core::ThreadType::Render); - cameraData->SetData(&data, offsetOpt.value(), sizeof(Camera::BufferData)); - } - } - auto VulkanCameraBuffers::GetDynamicOffset(const Camera& camera) const -> std::optional - { - auto it = std::find_if(cams.begin(), cams.end(), - [&](const Camera* other) - { - if (other == nullptr) - return false; - return (other->id == camera.id); - } - ); - if (it == cams.end()) - return {}; - uint32_t idx = std::distance(cams.begin(), it); - return idx * dynamicAlignment; - } - SH_RENDER_API auto sh::render::vk::VulkanCameraBuffers::GetCameraBuffer() const -> const VulkanBuffer& - { - return *cameraData.get(); - } -}//namespace \ No newline at end of file diff --git a/src/Render/VulkanImpl/VulkanCommandBuffer.cpp b/src/Render/VulkanImpl/VulkanCommandBuffer.cpp index 309225d9..08f56c29 100644 --- a/src/Render/VulkanImpl/VulkanCommandBuffer.cpp +++ b/src/Render/VulkanImpl/VulkanCommandBuffer.cpp @@ -8,7 +8,6 @@ #include "VulkanComputePipelineManager.h" #include "VulkanSwapChain.h" #include "VulkanShaderPass.h" -#include "VulkanCameraBuffers.h" #include "VulkanVertexBuffer.h" #include "VulkanSkinnedVertexBuffer.h" #include "Render/RenderTexture.h" @@ -19,7 +18,6 @@ #include "Render/SkinnedMesh.h" #include "Render/Drawable.h" #include "Render/ShaderPass.h" -#include "Render/Camera.h" #include "Render/RenderData.h" #include "Core/Reflection.hpp" @@ -127,7 +125,7 @@ namespace sh::render::vk vkCmdDispatch(buffer, x, y, z); } - SH_RENDER_API void VulkanCommandBuffer::SetRenderTarget(const RenderTarget& renderTarget, bool bClearColor, bool bClearDepth, bool bStoreColor, bool bStoreDepth) + SH_RENDER_API void VulkanCommandBuffer::SetRenderData(const RenderData& renderData, bool bClearColor, bool bClearDepth, bool bStoreColor, bool bStoreDepth) { if (bBeginRender) { @@ -136,23 +134,25 @@ namespace sh::render::vk bBeginRender = false; } - const bool bDepthOnly = renderTarget.target == nullptr ? false : renderTarget.target->IsDepthOnly(); + const RenderTexture* const target = renderData.target; + + const bool bDepthOnly = target == nullptr ? false : target->IsDepthOnly(); const VulkanImageBuffer* colorImg = nullptr; const VulkanImageBuffer* colorMSAAImg = nullptr; const VulkanImageBuffer* depthImg = nullptr; RenderTargetLayout rtLayout{}; - if (renderTarget.target != nullptr) + if (target != nullptr) { - colorImg = bDepthOnly ? nullptr : static_cast(renderTarget.target->GetTextureBuffer()); - colorMSAAImg = static_cast(renderTarget.target->GetMSAABuffer()); - depthImg = static_cast(renderTarget.target->GetDepthBuffer()); - rtLayout = renderTarget.target->GetLayout(); + colorImg = bDepthOnly ? nullptr : static_cast(target->GetTextureBuffer()); + colorMSAAImg = static_cast(target->GetMSAABuffer()); + depthImg = static_cast(target->GetDepthBuffer()); + rtLayout = target->GetLayout(); } else { - colorImg = &context.GetSwapChain().GetSwapChainImages()[renderTarget.frameIndex]; - colorMSAAImg = &context.GetSwapChain().GetSwapChainMSAAImages()[renderTarget.frameIndex]; - depthImg = &context.GetSwapChain().GetSwapChainDepthImages()[renderTarget.frameIndex]; + colorImg = &context.GetSwapChain().GetSwapChainImages()[renderData.frameIndex]; + colorMSAAImg = &context.GetSwapChain().GetSwapChainMSAAImages()[renderData.frameIndex]; + depthImg = &context.GetSwapChain().GetSwapChainDepthImages()[renderData.frameIndex]; rtLayout = context.GetSwapChain().GetRenderTargetLayout(); } const VulkanImageBuffer* const sizeRef = colorImg != nullptr ? colorImg : depthImg; @@ -204,21 +204,21 @@ namespace sh::render::vk renderState = RenderState{}; renderState.layout = rtLayout; - renderState.camera = renderTarget.camera; + renderState.renderData = &renderData; renderState.targetWidth = width; renderState.targetHeight = height; renderState.bDepthOnly = bDepthOnly; } - SH_RENDER_API void VulkanCommandBuffer::SetViewport(int x, int y, int width, int height) + SH_RENDER_API void VulkanCommandBuffer::SetViewport(uint32_t x, uint32_t y, uint32_t width, uint32_t height) { VkViewport viewport{}; viewport.minDepth = 0.0f; viewport.maxDepth = 1.0f; viewport.x = static_cast(x); - viewport.y = static_cast(y); + viewport.y = static_cast(height) - static_cast(y); viewport.width = static_cast(width); - viewport.height = static_cast(height); + viewport.height = -static_cast(height); vkCmdSetViewport(buffer, 0, 1, &viewport); } @@ -234,10 +234,15 @@ namespace sh::render::vk vkCmdSetScissor(buffer, 0, 1, &rect); } - SH_RENDER_API void VulkanCommandBuffer::DrawMesh(const Drawable& drawable, core::Name passName) + SH_RENDER_API void VulkanCommandBuffer::DrawMesh(const Drawable& drawable, core::Name passName, std::size_t viewerIdx) { - if (!bBeginRender || renderState.camera == nullptr) + if (!bBeginRender || renderState.renderData == nullptr) + return; + if (renderState.renderData->renderViewers.size() <= viewerIdx) + { + assert(renderState.renderData->renderViewers.size() <= viewerIdx); return; + } const Material& mat = *drawable.GetMaterial(); const Mesh& mesh = *drawable.GetMesh(); @@ -251,10 +256,7 @@ namespace sh::render::vk if (passes == nullptr) return; - std::optional cameraOffsetOpt = VulkanCameraBuffers::GetInstance()->GetDynamicOffset(*renderState.camera); - if (!cameraOffsetOpt.has_value()) - return; - const uint32_t cameraOffset = cameraOffsetOpt.value(); + const uint32_t cameraOffset = renderState.renderData->renderViewers[viewerIdx].offset; for (const ShaderPass& pass : *passes) { @@ -294,9 +296,9 @@ namespace sh::render::vk BindMesh(mesh, drawable.GetSubMeshIndex(), bSkinned); } } - SH_RENDER_API void VulkanCommandBuffer::DrawMeshBatch(const std::vector& drawables, core::Name passName) + SH_RENDER_API void VulkanCommandBuffer::DrawMeshBatch(const std::vector& drawables, core::Name passName, std::size_t viewerIdx) { - if (drawables.empty()) + if (drawables.empty() || renderState.renderData == nullptr) return; const Material& mat = *drawables.front()->GetMaterial(); @@ -311,10 +313,7 @@ namespace sh::render::vk if (passes == nullptr) return; - std::optional cameraOffsetOpt = VulkanCameraBuffers::GetInstance()->GetDynamicOffset(*renderState.camera); - if (!cameraOffsetOpt.has_value()) - return; - const uint32_t cameraOffset = cameraOffsetOpt.value(); + const uint32_t cameraOffset = renderState.renderData->renderViewers[viewerIdx].offset; for (const ShaderPass& pass : *passes) { diff --git a/src/Render/VulkanImpl/VulkanContext.cpp b/src/Render/VulkanImpl/VulkanContext.cpp index 1543f46d..0a5e6bfb 100644 --- a/src/Render/VulkanImpl/VulkanContext.cpp +++ b/src/Render/VulkanImpl/VulkanContext.cpp @@ -41,6 +41,196 @@ namespace sh::render::vk SH_INFO_FORMAT("{}", pCallbackData->pMessage); return VK_FALSE; } + + VulkanContext::VulkanContext(const sh::window::Window& window) : + window(window), + sample(VkSampleCountFlagBits::VK_SAMPLE_COUNT_4_BIT) + { + bEnableValidationLayers = core::Util::IsDebug(); + + requestedDeviceExtension = + { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME + }; + } + VulkanContext::~VulkanContext() + { + } + + SH_RENDER_API void VulkanContext::Init() + { + if (bInit) + return; + bInit = true; + + layers = std::make_unique(); + layers->Query(); + + if (bEnableValidationLayers) + PrepareValidationLayer(); + + PrepareSurfaceExtension(); + + CreateInstance(); + + if (bEnableValidationLayers) + InitDebugMessenger(); + + swapChain = std::make_unique(*this); + swapChain->CreateSurface(window); + + GetPhysicalDevices(); + + gpu = SelectPhysicalDevice(); + assert(gpu != nullptr); + if (gpu == nullptr) + throw std::runtime_error("Can't find suitable GPU"); + vkGetPhysicalDeviceProperties(gpu, &gpuProp); + + queueManager = std::make_unique(*this); + queueManager->QueryFamilies(swapChain->GetSurface()); + + CreateDevice(gpu); + + queueManager->FetchQueues(); + + CreateAllocator(); + + swapChain->CreateSwapChain( + queueManager->GetFamilyIndex(VulkanQueueManager::Role::Graphics), + queueManager->GetFamilyIndex(VulkanQueueManager::Role::Present), + false); + + CreateCommandPool(); + + descPool = std::make_unique(device); + CreateEmptyDescriptor(); + + pipelineManager = std::make_unique(*this); + computePipelineManager = std::make_unique(*this); + + renderDataManager.Init(*this); + } + SH_RENDER_API void VulkanContext::Clear() + { + bInit = false; + + vkDeviceWaitIdle(device); + + pipelineManager.reset(); + if (emptyDescLayout) + { + vkDestroyDescriptorSetLayout(device, emptyDescLayout, nullptr); + emptyDescLayout = nullptr; + } + descPool.reset(); + DestroyCommandPool(); + swapChain.reset(); + queueManager.reset(); + DestroyAllocator(); + DestroyDevice(); + if (bEnableValidationLayers) + DestroyDebugMessenger(); + gpu = VK_NULL_HANDLE; + gpus.clear(); + DestroyInstance(); + } + SH_RENDER_API auto VulkanContext::AllocateCommandBuffer(bool bCompute) -> CommandBuffer* + { + VkQueueFlagBits flag = VkQueueFlagBits::VK_QUEUE_GRAPHICS_BIT; + if (bCompute) + flag = VkQueueFlagBits::VK_QUEUE_COMPUTE_BIT; + VulkanCommandBuffer* const cmd = GetCommandBufferPool().AllocateCommandBuffer(std::this_thread::get_id(), flag); + return cmd; + } + SH_RENDER_API void VulkanContext::DeallocateCommandBuffer(CommandBuffer& cmd) + { + GetCommandBufferPool().DeallocateCommandBuffer(static_cast(cmd)); + } + SH_RENDER_API void VulkanContext::SubmitCommand(CommandBuffer& cmd) + { + VulkanCommandBuffer& vkCmd = static_cast(cmd); + VkFence fence = vkCmd.GetOrCreateFence(); + GetQueueManager().Submit(VulkanQueueManager::Role::Compute, vkCmd, fence); + vkWaitForFences(device, 1, &fence, true, std::numeric_limits::max()); + } + SH_RENDER_API void VulkanContext::SetViewport(const glm::vec2& start, const glm::vec2& end) + { + viewportStart = start; + viewportEnd = end; + } + SH_RENDER_API auto VulkanContext::ReSizing() -> bool + { + //swapChain->DestroySwapChain(); + swapChain->CreateSwapChain( + queueManager->GetFamilyIndex(VulkanQueueManager::Role::Graphics), + queueManager->GetFamilyIndex(VulkanQueueManager::Role::Present), + false); + return true; + } + SH_RENDER_API void VulkanContext::PrintLayers() + { + for (auto& prop : layers->GetLayerProperties()) + { + SH_INFO_FORMAT("LayerName: {} - {}", prop.properties.layerName, prop.properties.description); + for (auto& ext : prop.extensions) + SH_INFO_FORMAT("ExtensionName: {}", ext.extensionName); + } + SH_INFO("-----Vulkan Extensions-----"); + for (auto& i : layers->GetVulkanExtensions()) + SH_INFO_FORMAT("ExtensionName: {}", i.extensionName); + + SH_INFO("-----GPU Layer------"); + for (auto& i : layers->GetGPULayerProperties()) + { + SH_INFO_FORMAT("LayerName: {} - {}", i.properties.layerName, i.properties.description); + for (auto& ext : i.extensions) + SH_INFO_FORMAT("ExtensionName: {}", ext.extensionName); + } + SH_INFO("-----GPU Extensions------"); + for (auto& i : layers->GetGPUExtensions()) + SH_INFO_FORMAT("ExtensionName: {}", i.extensionName); + } + SH_RENDER_API auto VulkanContext::FindSupportedDepthFormat(bool bUseStencil) const -> VkFormat + { + const std::array formats = { VkFormat::VK_FORMAT_D32_SFLOAT_S8_UINT, VkFormat::VK_FORMAT_D24_UNORM_S8_UINT, VkFormat::VK_FORMAT_D16_UNORM_S8_UINT }; + + auto feature = VkFormatFeatureFlagBits::VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT; + for (VkFormat format : formats) + { + VkFormatProperties props; + vkGetPhysicalDeviceFormatProperties(gpu, format, &props); + + if (props.optimalTilingFeatures & feature) + return format; + } + + throw std::runtime_error("Failed to find supported Depth format!"); + } + SH_RENDER_API void VulkanContext::SetSampleCount(VkSampleCountFlagBits sample) + { + VkSampleCountFlagBits maxSample = GetMaxSampleCount(); + if (sample > maxSample) + sample = maxSample; + + this->sample = sample; + } + SH_RENDER_API auto VulkanContext::GetMaxSampleCount() const -> VkSampleCountFlagBits + { + VkSampleCountFlags supportedSampleCount = + std::min(gpuProp.limits.framebufferColorSampleCounts, gpuProp.limits.framebufferDepthSampleCounts); + + if (supportedSampleCount & VkSampleCountFlagBits::VK_SAMPLE_COUNT_64_BIT) return VkSampleCountFlagBits::VK_SAMPLE_COUNT_64_BIT; + if (supportedSampleCount & VkSampleCountFlagBits::VK_SAMPLE_COUNT_32_BIT) return VkSampleCountFlagBits::VK_SAMPLE_COUNT_32_BIT; + if (supportedSampleCount & VkSampleCountFlagBits::VK_SAMPLE_COUNT_16_BIT) return VkSampleCountFlagBits::VK_SAMPLE_COUNT_16_BIT; + if (supportedSampleCount & VkSampleCountFlagBits::VK_SAMPLE_COUNT_8_BIT) return VkSampleCountFlagBits::VK_SAMPLE_COUNT_8_BIT; + if (supportedSampleCount & VkSampleCountFlagBits::VK_SAMPLE_COUNT_4_BIT) return VkSampleCountFlagBits::VK_SAMPLE_COUNT_4_BIT; + if (supportedSampleCount & VkSampleCountFlagBits::VK_SAMPLE_COUNT_2_BIT) return VkSampleCountFlagBits::VK_SAMPLE_COUNT_2_BIT; + + return VkSampleCountFlagBits::VK_SAMPLE_COUNT_1_BIT; + } + void VulkanContext::CreateDebugInfo() { //validationEnables.push_back(VkValidationFeatureEnableEXT::VK_VALIDATION_FEATURE_ENABLE_GPU_ASSISTED_EXT); @@ -165,9 +355,9 @@ namespace sh::render::vk bool swapchainExSupport = layers->FindGPUExtension(gpu, VK_KHR_SWAPCHAIN_EXTENSION_NAME); - if (prop.deviceType == VkPhysicalDeviceType::VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU || - prop.deviceType == VkPhysicalDeviceType::VK_PHYSICAL_DEVICE_TYPE_CPU && - swapchainExSupport && + if (prop.deviceType == VkPhysicalDeviceType::VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU || + prop.deviceType == VkPhysicalDeviceType::VK_PHYSICAL_DEVICE_TYPE_CPU && + swapchainExSupport && swapChain->IsSwapChainSupport(gpu)) return gpu; } @@ -241,8 +431,8 @@ namespace sh::render::vk void VulkanContext::CreateCommandPool() { cmdPool = std::make_unique( - *this, - queueManager->GetFamilyIndex(VulkanQueueManager::Role::Graphics), + *this, + queueManager->GetFamilyIndex(VulkanQueueManager::Role::Graphics), queueManager->GetFamilyIndex(VulkanQueueManager::Role::Transfer), queueManager->GetFamilyIndex(VulkanQueueManager::Role::Compute)); } @@ -261,201 +451,4 @@ namespace sh::render::vk emptyDescSet = descPool->AllocateDescriptorSet(emptyDescLayout); } - VulkanContext::VulkanContext(const sh::window::Window& window) : - window(window), - sample(VkSampleCountFlagBits::VK_SAMPLE_COUNT_4_BIT) - { - bEnableValidationLayers = core::Util::IsDebug(); - - requestedDeviceExtension = - { - VK_KHR_SWAPCHAIN_EXTENSION_NAME, - VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME - }; - } - VulkanContext::~VulkanContext() - { - - } - - SH_RENDER_API void VulkanContext::Init() - { - if (bInit) - return; - bInit = true; - - layers = std::make_unique(); - layers->Query(); - - if (bEnableValidationLayers) - PrepareValidationLayer(); - - PrepareSurfaceExtension(); - - CreateInstance(); - - if (bEnableValidationLayers) - InitDebugMessenger(); - - swapChain = std::make_unique(*this); - swapChain->CreateSurface(window); - - GetPhysicalDevices(); - - gpu = SelectPhysicalDevice(); - assert(gpu != nullptr); - if (gpu == nullptr) - throw std::runtime_error("Can't find suitable GPU"); - vkGetPhysicalDeviceProperties(gpu, &gpuProp); - - queueManager = std::make_unique(*this); - queueManager->QueryFamilies(swapChain->GetSurface()); - - CreateDevice(gpu); - - queueManager->FetchQueues(); - - CreateAllocator(); - - swapChain->CreateSwapChain( - queueManager->GetFamilyIndex(VulkanQueueManager::Role::Graphics), - queueManager->GetFamilyIndex(VulkanQueueManager::Role::Present), - false); - - CreateCommandPool(); - - descPool = std::make_unique(device); - CreateEmptyDescriptor(); - - pipelineManager = std::make_unique(*this); - computePipelineManager = std::make_unique(*this); - } - SH_RENDER_API void VulkanContext::Clear() - { - bInit = false; - - vkDeviceWaitIdle(device); - - pipelineManager.reset(); - if (emptyDescLayout) - { - vkDestroyDescriptorSetLayout(device, emptyDescLayout, nullptr); - emptyDescLayout = nullptr; - } - descPool.reset(); - DestroyCommandPool(); - swapChain.reset(); - queueManager.reset(); - DestroyAllocator(); - DestroyDevice(); - if (bEnableValidationLayers) - DestroyDebugMessenger(); - gpu = VK_NULL_HANDLE; - gpus.clear(); - DestroyInstance(); - } - SH_RENDER_API auto VulkanContext::AllocateCommandBuffer(bool bCompute) -> CommandBuffer* - { - VkQueueFlagBits flag = VkQueueFlagBits::VK_QUEUE_GRAPHICS_BIT; - if (bCompute) - flag = VkQueueFlagBits::VK_QUEUE_COMPUTE_BIT; - VulkanCommandBuffer* const cmd = GetCommandBufferPool().AllocateCommandBuffer(std::this_thread::get_id(), flag); - return cmd; - } - SH_RENDER_API void VulkanContext::DeallocateCommandBuffer(CommandBuffer& cmd) - { - GetCommandBufferPool().DeallocateCommandBuffer(static_cast(cmd)); - } - SH_RENDER_API void VulkanContext::SubmitCommand(CommandBuffer& cmd) - { - VulkanCommandBuffer& vkCmd = static_cast(cmd); - VkFence fence = vkCmd.GetOrCreateFence(); - GetQueueManager().Submit(VulkanQueueManager::Role::Compute, vkCmd, fence); - vkWaitForFences(device, 1, &fence, true, std::numeric_limits::max()); - } - SH_RENDER_API void VulkanContext::SetViewport(const glm::vec2& start, const glm::vec2& end) - { - viewportStart = start; - viewportEnd = end; - } - - SH_RENDER_API auto VulkanContext::GetViewportStart() const -> const glm::vec2& - { - return viewportStart; - } - SH_RENDER_API auto VulkanContext::GetViewportEnd() const -> const glm::vec2& - { - return viewportEnd; - } - - SH_RENDER_API auto VulkanContext::ReSizing() -> bool - { - //swapChain->DestroySwapChain(); - swapChain->CreateSwapChain( - queueManager->GetFamilyIndex(VulkanQueueManager::Role::Graphics), - queueManager->GetFamilyIndex(VulkanQueueManager::Role::Present), - false); - return true; - } - SH_RENDER_API void VulkanContext::PrintLayers() - { - for (auto& prop : layers->GetLayerProperties()) - { - SH_INFO_FORMAT("LayerName: {} - {}", prop.properties.layerName, prop.properties.description); - for (auto& ext : prop.extensions) - SH_INFO_FORMAT("ExtensionName: {}", ext.extensionName); - } - SH_INFO("-----Vulkan Extensions-----"); - for (auto& i : layers->GetVulkanExtensions()) - SH_INFO_FORMAT("ExtensionName: {}", i.extensionName); - - SH_INFO("-----GPU Layer------"); - for (auto& i : layers->GetGPULayerProperties()) - { - SH_INFO_FORMAT("LayerName: {} - {}", i.properties.layerName, i.properties.description); - for (auto& ext : i.extensions) - SH_INFO_FORMAT("ExtensionName: {}", ext.extensionName); - } - SH_INFO("-----GPU Extensions------"); - for (auto& i : layers->GetGPUExtensions()) - SH_INFO_FORMAT("ExtensionName: {}", i.extensionName); - } - SH_RENDER_API auto VulkanContext::FindSupportedDepthFormat(bool bUseStencil) const -> VkFormat - { - const std::array formats = { VkFormat::VK_FORMAT_D32_SFLOAT_S8_UINT, VkFormat::VK_FORMAT_D24_UNORM_S8_UINT, VkFormat::VK_FORMAT_D16_UNORM_S8_UINT }; - - auto feature = VkFormatFeatureFlagBits::VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT; - for (VkFormat format : formats) - { - VkFormatProperties props; - vkGetPhysicalDeviceFormatProperties(gpu, format, &props); - - if (props.optimalTilingFeatures & feature) - return format; - } - - throw std::runtime_error("Failed to find supported Depth format!"); - } - SH_RENDER_API void VulkanContext::SetSampleCount(VkSampleCountFlagBits sample) - { - VkSampleCountFlagBits maxSample = GetMaxSampleCount(); - if (sample > maxSample) - sample = maxSample; - - this->sample = sample; - } - SH_RENDER_API auto VulkanContext::GetMaxSampleCount() const -> VkSampleCountFlagBits - { - VkSampleCountFlags supportedSampleCount = - std::min(gpuProp.limits.framebufferColorSampleCounts, gpuProp.limits.framebufferDepthSampleCounts); - - if (supportedSampleCount & VkSampleCountFlagBits::VK_SAMPLE_COUNT_64_BIT) return VkSampleCountFlagBits::VK_SAMPLE_COUNT_64_BIT; - if (supportedSampleCount & VkSampleCountFlagBits::VK_SAMPLE_COUNT_32_BIT) return VkSampleCountFlagBits::VK_SAMPLE_COUNT_32_BIT; - if (supportedSampleCount & VkSampleCountFlagBits::VK_SAMPLE_COUNT_16_BIT) return VkSampleCountFlagBits::VK_SAMPLE_COUNT_16_BIT; - if (supportedSampleCount & VkSampleCountFlagBits::VK_SAMPLE_COUNT_8_BIT) return VkSampleCountFlagBits::VK_SAMPLE_COUNT_8_BIT; - if (supportedSampleCount & VkSampleCountFlagBits::VK_SAMPLE_COUNT_4_BIT) return VkSampleCountFlagBits::VK_SAMPLE_COUNT_4_BIT; - if (supportedSampleCount & VkSampleCountFlagBits::VK_SAMPLE_COUNT_2_BIT) return VkSampleCountFlagBits::VK_SAMPLE_COUNT_2_BIT; - - return VkSampleCountFlagBits::VK_SAMPLE_COUNT_1_BIT; - } }//namespace \ No newline at end of file diff --git a/src/Render/VulkanImpl/VulkanRenderer.cpp b/src/Render/VulkanImpl/VulkanRenderer.cpp index 15cbf45a..003e8532 100644 --- a/src/Render/VulkanImpl/VulkanRenderer.cpp +++ b/src/Render/VulkanImpl/VulkanRenderer.cpp @@ -3,7 +3,6 @@ #include "VulkanSwapChain.h" #include "VulkanQueueManager.h" #include "VulkanPipelineManager.h" -#include "VulkanCameraBuffers.h" #include "VulkanCommandBufferPool.h" #include "Drawable.h" @@ -34,80 +33,31 @@ namespace sh::render::vk if (isInit) { Clear(); - - camManager->Destroy(); DestroySyncObjects(); - context->Clear(); } } SH_RENDER_API void VulkanRenderer::Clear() { vkDeviceWaitIdle(context->GetDevice()); - - frameFences.clear(); - camManager->Clear(); - Renderer::Clear(); + frameFences.clear(); } - auto VulkanRenderer::CreateSyncObjects() -> VkResult - { - VkSemaphoreCreateInfo semaphoreInfo{}; - semaphoreInfo.sType = VkStructureType::VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; - - VkFenceCreateInfo fenceInfo{}; - fenceInfo.sType = VkStructureType::VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; - fenceInfo.flags = 0; - - VkResult result; - result = vkCreateSemaphore(context->GetDevice(), &semaphoreInfo, nullptr, &imageAvailableSemaphore); - if (result != VK_SUCCESS) return result; - - result = vkCreateSemaphore(context->GetDevice(), &semaphoreInfo, nullptr, &renderFinishedSemaphore); - if (result != VK_SUCCESS) return result; - - VkSemaphoreTypeCreateInfo timelineCreateInfo{}; - timelineCreateInfo.sType = VkStructureType::VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO; - timelineCreateInfo.semaphoreType = VkSemaphoreType::VK_SEMAPHORE_TYPE_TIMELINE; - timelineCreateInfo.initialValue = 0; - semaphoreInfo.pNext = &timelineCreateInfo; - - result = vkCreateSemaphore(context->GetDevice(), &semaphoreInfo, nullptr, &timelineSemaphore); - if (result != VK_SUCCESS) return result; - - result = vkCreateFence(context->GetDevice(), &fenceInfo, nullptr, &inFlightFence); - if (result != VK_SUCCESS) return result; - - return VK_SUCCESS; - } - - void VulkanRenderer::DestroySyncObjects() + void VulkanRenderer::CreateContext(const window::Window& win) { - vkDestroySemaphore(context->GetDevice(), imageAvailableSemaphore, nullptr); - vkDestroySemaphore(context->GetDevice(), renderFinishedSemaphore, nullptr); - vkDestroySemaphore(context->GetDevice(), timelineSemaphore, nullptr); - vkDestroyFence(context->GetDevice(), inFlightFence, nullptr); - } - void VulkanRenderer::OnCameraAdded(const Camera* camera) - { - VulkanCameraBuffers::GetInstance()->AddCamera(*camera); + context = std::make_unique(win); + context->Init(); } - void VulkanRenderer::OnCameraRemoved(const Camera* camera) + SH_RENDER_API void VulkanRenderer::DestroyContext() { - VulkanCameraBuffers::GetInstance()->RemoveCamera(*camera); + context->Clear(); + context.reset(); } - SH_RENDER_API bool VulkanRenderer::Init(sh::window::Window& win) { Renderer::Init(win); - context = std::make_unique(win); - context->Init(); - - camManager = VulkanCameraBuffers::GetInstance(); - camManager->Init(*context); - if (CreateSyncObjects() != VkResult::VK_SUCCESS) return false; @@ -128,11 +78,6 @@ namespace sh::render::vk return context->ReSizing(); } - SH_RENDER_API bool VulkanRenderer::IsInit() const - { - return isInit; - } - SH_RENDER_API void VulkanRenderer::WaitForCurrentFrame() { vkDeviceWaitIdle(context->GetDevice()); @@ -142,14 +87,11 @@ namespace sh::render::vk { using SignalSemaphore = VulkanCommandBuffer::SignalSemaphore; using WaitSemaphore = VulkanCommandBuffer::WaitSemaphore; - Renderer::Render(); if (!isInit || bPause.load(std::memory_order::memory_order_acquire)) return; if (renderer == nullptr) return; - if (cameras.size() == 0) - return; // 스왑체인에서 이미지를 가져오고, 변경 사항이 있다면 리사이징 uint32_t imgIdx; @@ -169,39 +111,19 @@ namespace sh::render::vk } assert(result == VkResult::VK_SUCCESS); - // 카메라 데이터 GPU에 업로드 - std::vector cams{}; - cams.reserve(cameras.size()); - for (const Camera* camera : cameras) - { - if (!camera->GetActive()) - continue; - camManager->UploadDataToGPU(*camera); - cams.push_back(camera); - } + Renderer::Render(); - uint32_t renderCallCount = 0; - for (const Camera* cam : cams) + core::ArrayView& renderDatas = IRenderThrMethod::GetRenderDatas(context->GetRenderDataManager()); + std::size_t viewIdx = 0; + for (RenderData& rd : renderDatas) { - std::vector filteredDrawables; - filteredDrawables.reserve(drawables.size()); - - RenderTarget rt{}; - rt.frameIndex = imgIdx; - rt.camera = cam; - rt.target = cam->GetRenderTexture(); - rt.drawables = &filteredDrawables; - - for (Drawable* drawable : drawables) - { - if (cam->CheckRenderTag(drawable->GetRenderTagId())) - filteredDrawables.push_back(drawable); - } - IRenderThrMethod::Setup(*renderer, rt); - IRenderThrMethod::Execute(*renderer, rt); // ScriptableRenderer에 등록된 패스들을 렌더큐 순서대로, 병렬적으로 커맨드에 기록 - renderCallCount += IRenderThrMethod::GetRenderCallCount(*renderer); + rd.frameIndex = imgIdx; + rd.drawables = &drawables; + + IRenderThrMethod::Setup(*renderer, rd); + IRenderThrMethod::Execute(*renderer, rd); // ScriptableRenderer에 등록된 패스들을 렌더큐 순서대로, 병렬적으로 커맨드에 기록 } - SetDrawCallCount(renderCallCount); + IRenderThrMethod::ExecuteTransfer(*renderer, imgIdx); const std::vector& recordedCmds = renderer->GetRecordedCommands(); @@ -277,10 +199,6 @@ namespace sh::render::vk IRenderThrMethod::ResetSubmittedCommands(*renderer, *context); } - SH_RENDER_API auto VulkanRenderer::GetCurrentFrame() const -> int - { - return currentFrame; - } SH_RENDER_API auto VulkanRenderer::GetWidth() const -> uint32_t { return context->GetSwapChain().GetSwapChainSize().width; @@ -289,14 +207,6 @@ namespace sh::render::vk { return context->GetSwapChain().GetSwapChainSize().height; } - SH_RENDER_API auto VulkanRenderer::GetTimelineSemaphore() const -> VkSemaphore - { - return timelineSemaphore; - } - SH_RENDER_API auto VulkanRenderer::GetTimelineValue() const -> uint64_t - { - return timelineValueAtomic.load(std::memory_order::memory_order_acquire); - } SH_RENDER_API auto VulkanRenderer::GetContext() const -> IRenderContext* { return context.get(); @@ -305,5 +215,44 @@ namespace sh::render::vk { Renderer::Sync(); } + + auto VulkanRenderer::CreateSyncObjects() -> VkResult + { + VkSemaphoreCreateInfo semaphoreInfo{}; + semaphoreInfo.sType = VkStructureType::VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + VkFenceCreateInfo fenceInfo{}; + fenceInfo.sType = VkStructureType::VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = 0; + + VkResult result; + result = vkCreateSemaphore(context->GetDevice(), &semaphoreInfo, nullptr, &imageAvailableSemaphore); + if (result != VK_SUCCESS) return result; + + result = vkCreateSemaphore(context->GetDevice(), &semaphoreInfo, nullptr, &renderFinishedSemaphore); + if (result != VK_SUCCESS) return result; + + VkSemaphoreTypeCreateInfo timelineCreateInfo{}; + timelineCreateInfo.sType = VkStructureType::VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO; + timelineCreateInfo.semaphoreType = VkSemaphoreType::VK_SEMAPHORE_TYPE_TIMELINE; + timelineCreateInfo.initialValue = 0; + semaphoreInfo.pNext = &timelineCreateInfo; + + result = vkCreateSemaphore(context->GetDevice(), &semaphoreInfo, nullptr, &timelineSemaphore); + if (result != VK_SUCCESS) return result; + + result = vkCreateFence(context->GetDevice(), &fenceInfo, nullptr, &inFlightFence); + if (result != VK_SUCCESS) return result; + + return VK_SUCCESS; + } + + void VulkanRenderer::DestroySyncObjects() + { + vkDestroySemaphore(context->GetDevice(), imageAvailableSemaphore, nullptr); + vkDestroySemaphore(context->GetDevice(), renderFinishedSemaphore, nullptr); + vkDestroySemaphore(context->GetDevice(), timelineSemaphore, nullptr); + vkDestroyFence(context->GetDevice(), inFlightFence, nullptr); + } }//namespace From dc1be1e8679255e4577f11deb000e7e0934182da Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Mon, 4 May 2026 05:37:25 +0900 Subject: [PATCH 29/60] feat(ShadowMapPass): Finalize ShadowMapPass implementation --- .../Game/Component/Render/DirectionalLight.h | 28 +-- include/Game/GameRenderer.h | 9 +- include/Game/World.h | 4 + include/Render/RenderPass/ShadowMapPass.h | 14 +- include/Render/ShadowMapManager.h | 84 +++++++++ src/Editor/EditorRenderer.cpp | 8 +- src/Editor/EditorWorld.cpp | 15 +- .../Component/Render/DirectionalLight.cpp | 161 +++++------------- src/Game/GameRenderer.cpp | 2 - src/Game/World.cpp | 13 +- src/Render/RenderPass/ShadowMapPass.cpp | 34 ++++ src/Render/ScriptableRenderPass.cpp | 3 +- src/Render/ShadowMapManager.cpp | 161 ++++++++++++++++++ 13 files changed, 386 insertions(+), 150 deletions(-) create mode 100644 include/Render/ShadowMapManager.h create mode 100644 src/Render/ShadowMapManager.cpp diff --git a/include/Game/Component/Render/DirectionalLight.h b/include/Game/Component/Render/DirectionalLight.h index 5434974b..86201ccc 100644 --- a/include/Game/Component/Render/DirectionalLight.h +++ b/include/Game/Component/Render/DirectionalLight.h @@ -15,7 +15,7 @@ namespace sh::render namespace sh::game { - class DirectionalLight : public Component, public ILight + class DirectionalLight : public Component, public ILight, public render::IShadowCaster { COMPONENT(DirectionalLight) public: @@ -23,7 +23,6 @@ namespace sh::game SH_GAME_API ~DirectionalLight(); SH_GAME_API void Awake() override; - SH_GAME_API void BeginUpdate() override; SH_GAME_API void OnDestroy() override; SH_GAME_API void OnPropertyChanged(const core::reflection::Property& prop) override; @@ -35,27 +34,29 @@ namespace sh::game SH_GAME_API void SetShadowBias(float bias) override; SH_GAME_API void SetShadowMapResolution(uint32_t res) override; - /// @brief 디렉셔널 라이트의 광원 공간 변환 행렬 (proj * view)을 계산한다. - SH_GAME_API auto GetLightSpaceMatrix() const -> glm::mat4; /// @brief 그림자 아틀라스 텍스처를 반환한다 (모든 광원이 공유) SH_GAME_API auto GetShadowMap() const -> render::RenderTexture*; /// @brief 아틀라스 내에서 이 광원의 슬롯 정보를 반환한다. SH_GAME_API auto GetShadowSlot() const -> render::ShadowMapManager::Slot; + /// @brief 디렉셔널 라이트의 광원 공간 변환 행렬 (proj * view)을 반환한다. + SH_GAME_API auto GetLightSpaceMatrix() const -> glm::mat4; + + SH_GAME_API auto GetShadowMapResolution() const -> uint32_t override { return shadowMapResolution; } + SH_GAME_API auto GetShadowViewMatrix() const -> glm::mat4 override; + SH_GAME_API auto GetShadowProjMatrix() const -> glm::mat4 override; + SH_GAME_API auto GetShadowPos() const -> glm::vec3 override; + SH_GAME_API auto GetShadowLookAt() const -> glm::vec3 override; - auto GetPos() const -> const Vec3 & override { return gameObject.transform->GetWorldPosition(); } + SH_GAME_API auto GetPos() const -> const Vec3& override; auto GetLightType() const -> ILight::Type override { return ILight::Type::Directional; } auto IsCastShadow() const -> bool override { return bCastShadow; } auto GetShadowBias() const -> float override { return shadowBias; } - auto GetShadowMapResolution() const -> uint32_t override { return shadowMapResolution; } auto GetDirection() const -> const Vec3& { return direction; } auto GetIntensity() const -> float { return intensity; } private: - /// @brief bCastShadow=true 시점에 호출. 매니저로부터 슬롯을 할당받고 카메라를 설정. - void EnsureShadowResources(); - /// @brief 슬롯을 매니저에 반납하고 카메라 매핑을 해제. - void ReleaseShadowResources(); - /// @brief 매 프레임 광원 방향에 맞춰 shadowCamera의 pos/lookAt을 갱신. - void UpdateShadowCamera(); + /// @brief bCastShadow 변경 시 월드의 ShadowMapManager에 등록/해제한다. + void RegisterToShadowManager(); + void UnregisterFromShadowManager(); private: PROPERTY(direction) Vec3 direction{ -1.f, -1.f, -1.f }; @@ -75,7 +76,6 @@ namespace sh::game PROPERTY(shadowFarPlane) float shadowFarPlane = 100.f; - render::RenderData renderData; - render::ShadowMapManager::SlotHandle shadowSlot = render::ShadowMapManager::InvalidSlot; + bool bRegistered = false; }; }//namespace diff --git a/include/Game/GameRenderer.h b/include/Game/GameRenderer.h index abd6ee30..53e83a7a 100644 --- a/include/Game/GameRenderer.h +++ b/include/Game/GameRenderer.h @@ -2,16 +2,19 @@ #include "Export.h" #include "Render/ScriptableRenderer.h" -#include "Render/RenderPass/ShadowMapPass.h" #include #include +namespace sh::render +{ + class ShadowMapPass; +} namespace sh::game { class ImGUImpl; class World; class GUIPass; - + class GameRenderer : public render::ScriptableRenderer { public: @@ -19,8 +22,6 @@ namespace sh::game SH_GAME_API void Init() override; SH_GAME_API void Setup(const render::RenderData& data) override; - protected: - SH_GAME_API void AddShadowPass(); protected: render::IRenderContext& renderCtx; World& world; diff --git a/include/Game/World.h b/include/Game/World.h index 167d8c3d..1d9fb7d6 100644 --- a/include/Game/World.h +++ b/include/Game/World.h @@ -37,6 +37,7 @@ namespace sh::render { class Renderer; class ScriptableRenderer; + class ShadowMapManager; } namespace sh::game @@ -118,6 +119,8 @@ namespace sh::game auto GetLightOctree() -> Octree& { return lightOctree; } auto GetLightOctree() const -> const Octree& { return lightOctree; } auto GetMainCamera() const -> Camera* { return mainCamera; } + auto GetShadowMapManager() -> render::ShadowMapManager& { return *shadowMapManager; } + auto GetShadowMapManager() const -> const render::ShadowMapManager& { return *shadowMapManager; } auto GetGameObjects() const -> const std::vector& { return objs; } auto GetGameObjectPool() -> core::memory::MemoryPool& { return objPool; } auto GetCameras() const -> const std::vector& { return cameras; } @@ -141,6 +144,7 @@ namespace sh::game std::vector cameras; std::unique_ptr customRenderer; + std::unique_ptr shadowMapManager; private: core::GarbageCollection* gc; ImGUImpl* imgui = nullptr; diff --git a/include/Render/RenderPass/ShadowMapPass.h b/include/Render/RenderPass/ShadowMapPass.h index c6c1a488..dfb5c293 100644 --- a/include/Render/RenderPass/ShadowMapPass.h +++ b/include/Render/RenderPass/ShadowMapPass.h @@ -2,17 +2,19 @@ #include "../Export.h" #include "../ScriptableRenderPass.h" -#include - -#include +#include namespace sh::render { - class Camera; - class RenderTexture; - + /// @brief 그림자 아틀라스에 광원의 깊이 정보를 기록하는 패스 class ShadowMapPass : public ScriptableRenderPass { public: SH_RENDER_API ShadowMapPass(); + + SH_RENDER_API void Configure(const RenderData& renderData) override; + SH_RENDER_API void Record(CommandBuffer& cmd, const IRenderContext& ctx, const RenderData& renderData) override; + private: + uint32_t lastFrameIndex = ~0u; + uint32_t callsThisFrame = 0; }; }//namespace diff --git a/include/Render/ShadowMapManager.h b/include/Render/ShadowMapManager.h new file mode 100644 index 00000000..8fd1d3d4 --- /dev/null +++ b/include/Render/ShadowMapManager.h @@ -0,0 +1,84 @@ +#pragma once +#include "Export.h" +#include "RenderData.h" + +#include "Core/NonCopyable.h" + +#include +#include + +#include +#include +#include +#include + +namespace sh::render +{ + class IRenderContext; + class RenderTexture; + class Renderer; + class ShelfPacker; + + /// @brief 그림자 캐스터가 구현하는 인터페이스 + class IShadowCaster + { + public: + virtual ~IShadowCaster() = default; + virtual auto GetShadowMapResolution() const -> uint32_t = 0; + virtual auto GetShadowViewMatrix() const -> glm::mat4 = 0; + virtual auto GetShadowProjMatrix() const -> glm::mat4 = 0; + virtual auto GetShadowPos() const -> glm::vec3 = 0; + virtual auto GetShadowLookAt() const -> glm::vec3 = 0; + }; + + /// @brief 월드 단위의 그림자 아틀라스 매니저. 스레드 안전하다. + class ShadowMapManager : public core::INonCopyable + { + public: + struct Slot + { + glm::uvec2 offset{ 0u, 0u }; + glm::uvec2 size{ 0u, 0u }; + bool valid = false; + }; + public: + SH_RENDER_API ShadowMapManager(); + SH_RENDER_API ~ShadowMapManager(); + + SH_RENDER_API void Init(IRenderContext& ctx, uint32_t atlasSize = 4096); + /// @brief 등록된 캐스터/슬롯/아틀라스를 해제한다. + SH_RENDER_API void Clear(); + + /// @brief 그림자 캐스터를 등록한다. 같은 객체의 중복 등록은 무시된다. + SH_RENDER_API void Register(IShadowCaster& caster); + SH_RENDER_API void Unregister(IShadowCaster& caster); + + /// @brief 등록된 캐스터들을 모아 RenderData를 만들어 Renderer에 푸시한다. + /// @brief 매 프레임 1회 호출하면 된다. 슬롯은 이 시점에 ShelfPacker로 새로 패킹된다. + SH_RENDER_API void Submit(Renderer& renderer); + + /// @brief 캐스터에 할당된 슬롯 정보를 반환한다. + /// @brief Submit 호출 후 유효하다. 매 프레임 슬롯 위치가 바뀔 수 있으므로 매번 조회한다. + SH_RENDER_API auto GetSlot(const IShadowCaster& caster) const -> Slot; + /// @brief 캐스터의 광원 공간 변환 행렬(proj * view)을 반환한다. + /// @brief Submit 호출 후 유효하다. + SH_RENDER_API auto GetLightSpaceMatrix(const IShadowCaster& caster) const -> glm::mat4; + + auto GetAtlas() const -> RenderTexture* { return atlas; } + auto GetAtlasSize() const -> uint32_t { return atlasSize; } + private: + void EnsureAtlas(); + private: + IRenderContext* ctx = nullptr; + uint32_t atlasSize = 4096; + RenderTexture* atlas = nullptr; + + std::unique_ptr packer; + + std::vector casters; + std::unordered_map casterSlots; + std::unordered_map casterLightSpace; + + RenderData renderData; + }; +}//namespace diff --git a/src/Editor/EditorRenderer.cpp b/src/Editor/EditorRenderer.cpp index 93c274c2..5a0ff6a9 100644 --- a/src/Editor/EditorRenderer.cpp +++ b/src/Editor/EditorRenderer.cpp @@ -3,6 +3,7 @@ #include "Game/GUIPass.h" #include "Render/RenderPass/TransparentPass.h" +#include "Render/RenderPass/ShadowMapPass.h" #include namespace sh::editor @@ -15,7 +16,7 @@ namespace sh::editor { pickingPass = &AddRenderPass(core::Name{ "EditorPicking" }, render::RenderQueue::Picking); outlinePass = &AddRenderPass(); - //AddShadowPass(); + shadowMapPass = &AddRenderPass(); opaquePass = &AddRenderPass(core::Name{ "Opaque" }, render::RenderQueue::Opaque); transparentPass = &AddRenderPass(); uiPass = &AddRenderPass("UI", render::RenderQueue::Transparent); @@ -50,5 +51,10 @@ namespace sh::editor EnqueRenderPass(*guiPass); return; } + if (data.tag == "Shadow") + { + EnqueRenderPass(*shadowMapPass); + return; + } } }//namespace \ No newline at end of file diff --git a/src/Editor/EditorWorld.cpp b/src/Editor/EditorWorld.cpp index 1417139e..6c766792 100644 --- a/src/Editor/EditorWorld.cpp +++ b/src/Editor/EditorWorld.cpp @@ -12,6 +12,7 @@ #include "Render/Renderer.h" #include "Render/Mesh/Grid.h" #include "Render/Model.h" +#include "Render/ShadowMapManager.h" #include "Editor/Component/EditorControl.h" @@ -81,10 +82,16 @@ namespace sh::editor SH_EDITOR_API void EditorWorld::SetupRenderer() { - customRenderer = std::make_unique(*renderer.GetContext(), GetUiContext(), *this); - renderer.SetScriptableRenderer(*customRenderer); - EditorRenderer& editorRenderer = static_cast(*customRenderer); - editorRenderer.Init(); + if (customRenderer == nullptr) + { + customRenderer = std::make_unique(*renderer.GetContext(), GetUiContext(), *this); + EditorRenderer& editorRenderer = static_cast(*customRenderer); + editorRenderer.Init(); + + renderer.SetScriptableRenderer(*customRenderer); + } + if (shadowMapManager != nullptr) + shadowMapManager->Init(*renderer.GetContext()); } SH_EDITOR_API void EditorWorld::InitResource() { diff --git a/src/Game/Component/Render/DirectionalLight.cpp b/src/Game/Component/Render/DirectionalLight.cpp index a07d01ad..fcc6cd8a 100644 --- a/src/Game/Component/Render/DirectionalLight.cpp +++ b/src/Game/Component/Render/DirectionalLight.cpp @@ -1,7 +1,6 @@ #include "Component/Render/DirectionalLight.h" #include "World.h" -#include "GameRenderer.h" #include "Core/SObject.h" @@ -10,7 +9,6 @@ #include "Render/RenderTexture.h" #include -#include namespace sh::game { @@ -18,20 +16,6 @@ namespace sh::game Component(owner) { world.GetLightOctree().Insert(*this); - - renderData.priority = 1000; - - GameRenderer* const srPtr = dynamic_cast(world.renderer.GetScriptableRenderer()); - if (srPtr != nullptr) - { - //world.renderer.AddCamera(shadowCamera); - //srPtr->AddShadowCasterCamera(shadowCamera); - } - else - { - SH_ERROR("ScriptableRenderer is not GameRenderer!"); - } - canPlayInEditor = true; } DirectionalLight::~DirectionalLight() = default; @@ -40,26 +24,12 @@ namespace sh::game { Super::Awake(); if (bCastShadow) - EnsureShadowResources(); - } - - SH_GAME_API void DirectionalLight::BeginUpdate() - { - Super::BeginUpdate(); - if (bCastShadow && shadowSlot != render::ShadowMapManager::InvalidSlot) - UpdateShadowCamera(); + RegisterToShadowManager(); } SH_GAME_API void DirectionalLight::OnDestroy() { - ReleaseShadowResources(); - - GameRenderer* const srPtr = dynamic_cast(world.renderer.GetScriptableRenderer()); - if (srPtr != nullptr) - { - //srPtr->RemoveShadowCasterCamera(shadowCamera); - //world.renderer.RemoveCamera(shadowCamera); - } + UnregisterFromShadowManager(); world.GetLightOctree().Erase(*this); Super::OnDestroy(); } @@ -69,23 +39,9 @@ namespace sh::game if (prop.GetName() == "bCastShadow") { if (bCastShadow) - { - if (shadowSlot == render::ShadowMapManager::InvalidSlot) - EnsureShadowResources(); - } + RegisterToShadowManager(); else - { - ReleaseShadowResources(); - } - } - else if (prop.GetName() == "shadowMapResolution") - { - if (bCastShadow) - { - // 슬롯 크기는 변경 불가. 해제 후 재할당으로 새 해상도 슬롯을 받는다. - ReleaseShadowResources(); - EnsureShadowResources(); - } + UnregisterFromShadowManager(); } } @@ -107,9 +63,9 @@ namespace sh::game return; bCastShadow = b; if (bCastShadow) - EnsureShadowResources(); + RegisterToShadowManager(); else - ReleaseShadowResources(); + UnregisterFromShadowManager(); } SH_GAME_API void DirectionalLight::SetShadowBias(float bias) { @@ -117,90 +73,67 @@ namespace sh::game } SH_GAME_API void DirectionalLight::SetShadowMapResolution(uint32_t res) { - if (shadowMapResolution == res) - return; shadowMapResolution = res; - if (bCastShadow) - { - ReleaseShadowResources(); - EnsureShadowResources(); - } } - SH_GAME_API auto DirectionalLight::GetLightSpaceMatrix() const -> glm::mat4 - { - const glm::vec3 dir = glm::normalize(glm::vec3{ direction.x, direction.y, direction.z }); - const glm::vec3 sceneCenter = glm::vec3{ 0.f, 0.f, 0.f }; - const float distance = shadowFarPlane * 0.5f; - const glm::vec3 lightPos = sceneCenter - dir * distance; - - const glm::vec3 worldUp = (glm::abs(dir.y) > 0.99f) ? glm::vec3{ 0.f, 0.f, 1.f } : glm::vec3{ 0.f, 1.f, 0.f }; - - const glm::mat4 view = glm::lookAt(lightPos, lightPos + dir, worldUp); - const glm::mat4 proj = glm::ortho(-shadowOrthoSize, shadowOrthoSize, -shadowOrthoSize, shadowOrthoSize, shadowNearPlane, shadowFarPlane); - return proj * view; - } SH_GAME_API auto DirectionalLight::GetShadowMap() const -> render::RenderTexture* { if (!bCastShadow) return nullptr; - return render::ShadowMapManager::GetInstance()->GetAtlas(); + return world.GetShadowMapManager().GetAtlas(); } SH_GAME_API auto DirectionalLight::GetShadowSlot() const -> render::ShadowMapManager::Slot { - if (!bCastShadow || shadowSlot == render::ShadowMapManager::InvalidSlot) + if (!bCastShadow) return render::ShadowMapManager::Slot{}; - return render::ShadowMapManager::GetInstance()->GetSlot(shadowSlot); + return world.GetShadowMapManager().GetSlot(*this); } - - void DirectionalLight::EnsureShadowResources() - { - render::ShadowMapManager* mgr = render::ShadowMapManager::GetInstance(); - - if (shadowSlot == render::ShadowMapManager::InvalidSlot) - { - shadowSlot = mgr->AllocateSlot(shadowMapResolution); - if (shadowSlot == render::ShadowMapManager::InvalidSlot) - { - SH_ERROR("DirectionalLight: failed to allocate shadow slot"); - return; - } - mgr->RegisterCameraSlot(&shadowCamera, shadowSlot); - } - - render::RenderTexture* atlas = mgr->GetAtlas(); - - shadowCamera.SetOrthographic(true); - shadowCamera.SetWidth(shadowOrthoSize * 2.f); - shadowCamera.SetHeight(shadowOrthoSize * 2.f); - shadowCamera.SetNearPlane(shadowNearPlane); - shadowCamera.SetFarPlane(shadowFarPlane); - shadowCamera.SetRenderTexture(atlas); - - UpdateShadowCamera(); - } - - void DirectionalLight::ReleaseShadowResources() + SH_GAME_API auto DirectionalLight::GetLightSpaceMatrix() const -> glm::mat4 { - if (shadowSlot == render::ShadowMapManager::InvalidSlot) - return; - render::ShadowMapManager* mgr = render::ShadowMapManager::GetInstance(); - mgr->UnregisterCameraSlot(&shadowCamera); - mgr->ReleaseSlot(shadowSlot); - shadowSlot = render::ShadowMapManager::InvalidSlot; - shadowCamera.SetRenderTexture(nullptr); + return GetShadowProjMatrix() * GetShadowViewMatrix(); } - void DirectionalLight::UpdateShadowCamera() + SH_GAME_API auto DirectionalLight::GetShadowViewMatrix() const -> glm::mat4 { const glm::vec3 dir = glm::normalize(glm::vec3{ direction.x, direction.y, direction.z }); - const glm::vec3 sceneCenter = glm::vec3{ 0.f, 0.f, 0.f }; + const glm::vec3 sceneCenter{ 0.f, 0.f, 0.f }; const float distance = shadowFarPlane * 0.5f; const glm::vec3 lightPos = sceneCenter - dir * distance; const glm::vec3 worldUp = (glm::abs(dir.y) > 0.99f) ? glm::vec3{ 0.f, 0.f, 1.f } : glm::vec3{ 0.f, 1.f, 0.f }; + return glm::lookAt(lightPos, lightPos + dir, worldUp); + } + SH_GAME_API auto DirectionalLight::GetShadowProjMatrix() const -> glm::mat4 + { + return glm::orthoRH_ZO(-shadowOrthoSize, shadowOrthoSize, -shadowOrthoSize, shadowOrthoSize, shadowNearPlane, shadowFarPlane); + } + SH_GAME_API auto DirectionalLight::GetShadowPos() const -> glm::vec3 + { + const glm::vec3 dir = glm::normalize(glm::vec3{ direction.x, direction.y, direction.z }); + const glm::vec3 sceneCenter{ 0.f, 0.f, 0.f }; + const float distance = shadowFarPlane * 0.5f; + return sceneCenter - dir * distance; + } + SH_GAME_API auto DirectionalLight::GetShadowLookAt() const -> glm::vec3 + { + return glm::vec3{ 0.f, 0.f, 0.f }; + } + SH_GAME_API auto DirectionalLight::GetPos() const -> const Vec3& + { + return gameObject.transform->GetWorldPosition(); + } - shadowCamera.SetPos(lightPos); - shadowCamera.SetLookPos(sceneCenter); - shadowCamera.SetUpVector(worldUp); + void DirectionalLight::RegisterToShadowManager() + { + if (bRegistered) + return; + world.GetShadowMapManager().Register(*this); + bRegistered = true; + } + void DirectionalLight::UnregisterFromShadowManager() + { + if (!bRegistered) + return; + world.GetShadowMapManager().Unregister(*this); + bRegistered = false; } }//namespace diff --git a/src/Game/GameRenderer.cpp b/src/Game/GameRenderer.cpp index d39a64b2..6f355040 100644 --- a/src/Game/GameRenderer.cpp +++ b/src/Game/GameRenderer.cpp @@ -3,7 +3,6 @@ #include "World.h" #include "Render/RenderPass/TransparentPass.h" -#include "Render/ShadowMapManager.h" #include namespace sh::game @@ -14,7 +13,6 @@ namespace sh::game world(world), guiCtx(guictx) { - //render::ShadowMapManager::GetInstance()->Init(ctx); } SH_GAME_API void GameRenderer::Init() { diff --git a/src/Game/World.cpp b/src/Game/World.cpp index 83cea5c5..b11eaba3 100644 --- a/src/Game/World.cpp +++ b/src/Game/World.cpp @@ -14,6 +14,7 @@ #include "Core/Asset.h" #include "Render/Renderer.h" +#include "Render/ShadowMapManager.h" #include #include @@ -29,6 +30,8 @@ namespace sh::game SetName("World"); gc = core::GarbageCollection::GetInstance(); + shadowMapManager = std::make_unique(); + physEventSubscriber.SetCallback( [](const phys::PhysicsEvent& evt) { @@ -241,6 +244,8 @@ namespace sh::game } SH_GAME_API void World::Clear() { + if (shadowMapManager != nullptr) + shadowMapManager->Clear(); CleanObjs(); while (!deallocatedObjs.empty()) @@ -256,17 +261,15 @@ namespace sh::game } SH_GAME_API void World::SetupRenderer() { - //uiCamera.SetPriority(1000); if (customRenderer == nullptr) { - //renderer.AddCamera(uiCamera); customRenderer = std::make_unique(*renderer.GetContext(), GetUiContext(), *this); customRenderer->Init(); renderer.SetScriptableRenderer(*customRenderer); } - - //static_cast(customRenderer.get())->SetUICamera(uiCamera); + if (shadowMapManager != nullptr) + shadowMapManager->Init(*renderer.GetContext()); } SH_GAME_API void World::InitResource() { @@ -456,6 +459,8 @@ namespace sh::game continue; obj->LateUpdate(); } + if (shadowMapManager != nullptr) + shadowMapManager->Submit(renderer); } SH_GAME_API void World::BeforeSync() diff --git a/src/Render/RenderPass/ShadowMapPass.cpp b/src/Render/RenderPass/ShadowMapPass.cpp index 2fed9401..db2ae667 100644 --- a/src/Render/RenderPass/ShadowMapPass.cpp +++ b/src/Render/RenderPass/ShadowMapPass.cpp @@ -1,4 +1,5 @@ #include "RenderPass/ShadowMapPass.h" +#include "CommandBuffer.h" #include "IRenderContext.h" namespace sh::render @@ -7,4 +8,37 @@ namespace sh::render ScriptableRenderPass(core::Name{ "ShadowMapPass" }, RenderQueue::BeforeRendering) { } + + SH_RENDER_API void ShadowMapPass::Configure(const RenderData& renderData) + { + ScriptableRenderPass::Configure(renderData); + } + SH_RENDER_API void ShadowMapPass::Record(CommandBuffer& cmd, const IRenderContext& ctx, const RenderData& renderData) + { + if (renderData.target == nullptr) + return; + + // 한 프레임 내 첫 그림자 호출에서만 아틀라스를 클리어하고, 이후엔 LOAD하여 다른 광원의 슬롯 결과를 보존 + if (renderData.frameIndex != lastFrameIndex) + { + lastFrameIndex = renderData.frameIndex; + callsThisFrame = 0; + } + const bool bClearDepth = (callsThisFrame == 0); + ++callsThisFrame; + + cmd.SetRenderData(renderData, false, bClearDepth, false, true); + + if (renderData.drawables == nullptr) + return; + + std::size_t viewerIdx = 0; + for (const RenderViewer& viewer : renderData.renderViewers) + { + SetViewportScissor(cmd, ctx, viewer); + for (const RenderBatch& batch : renderBatches) + cmd.DrawMeshBatch(batch.drawables, passName, viewerIdx); + ++viewerIdx; + } + } }//namespace diff --git a/src/Render/ScriptableRenderPass.cpp b/src/Render/ScriptableRenderPass.cpp index 3172c8d9..0d28a5d5 100644 --- a/src/Render/ScriptableRenderPass.cpp +++ b/src/Render/ScriptableRenderPass.cpp @@ -107,7 +107,8 @@ namespace sh::render SH_RENDER_API void ScriptableRenderPass::SetImageUsages(const RenderData& renderData) { renderTextures.clear(); - if (renderData.target != nullptr && IsDepthTexture(renderData.target->GetTextureFormat())) + + if (renderData.target != nullptr && renderData.target->IsDepthOnly()) renderTextures[renderData.target] = ResourceUsage::DepthStencilAttachment; else renderTextures[renderData.target] = ResourceUsage::ColorAttachment; diff --git a/src/Render/ShadowMapManager.cpp b/src/Render/ShadowMapManager.cpp new file mode 100644 index 00000000..8bd13001 --- /dev/null +++ b/src/Render/ShadowMapManager.cpp @@ -0,0 +1,161 @@ +#include "ShadowMapManager.h" +#include "RenderTexture.h" +#include "RenderData.h" +#include "Renderer.h" +#include "ShelfPacker.h" +#include "Formats.hpp" +#include "IRenderContext.h" + +#include "Core/Name.h" +#include "Core/SObject.h" +#include "Core/GarbageCollection.h" +#include "Core/Logger.h" + +#include + +namespace sh::render +{ + ShadowMapManager::ShadowMapManager() = default; + ShadowMapManager::~ShadowMapManager() + { + Clear(); + } + + SH_RENDER_API void ShadowMapManager::Init(IRenderContext& ctx, uint32_t atlasSize) + { + this->ctx = &ctx; + this->atlasSize = atlasSize; + packer = std::make_unique(static_cast(atlasSize), static_cast(atlasSize), 0); + + renderData.tag = core::Name{ "Shadow" }; + renderData.priority = 1000; // 다른 패스보다 먼저 실행되도록 + } + + SH_RENDER_API void ShadowMapManager::Clear() + { + if (atlas != nullptr) + { + core::GarbageCollection::GetInstance()->RemoveRootSet(atlas); + atlas = nullptr; + } + casters.clear(); + casterSlots.clear(); + casterLightSpace.clear(); + renderData = RenderData{}; + packer.reset(); + ctx = nullptr; + } + + void ShadowMapManager::EnsureAtlas() + { + if (atlas != nullptr) + return; + if (ctx == nullptr) + { + SH_ERROR("ShadowMapManager: context is not initialized"); + return; + } + RenderTargetLayout layout{}; + layout.format = TextureFormat::None; + layout.depthFormat = TextureFormat::D32; + layout.bUseMSAA = false; + + atlas = core::SObject::Create(layout); + atlas->SetSize(atlasSize, atlasSize); + atlas->Build(*ctx); + atlas->SetName("ShadowAtlas"); + // 매니저는 SObject가 아니므로 GC가 회수하지 않도록 루트셋에 등록 + core::GarbageCollection::GetInstance()->SetRootSet(atlas); + } + + SH_RENDER_API void ShadowMapManager::Register(IShadowCaster& caster) + { + if (std::find(casters.begin(), casters.end(), &caster) != casters.end()) + return; + casters.push_back(&caster); + } + + SH_RENDER_API void ShadowMapManager::Unregister(IShadowCaster& caster) + { + casters.erase(std::remove(casters.begin(), casters.end(), &caster), casters.end()); + casterSlots.erase(&caster); + casterLightSpace.erase(&caster); + } + + SH_RENDER_API void ShadowMapManager::Submit(Renderer& renderer) + { + if (casters.empty()) + return; + + EnsureAtlas(); + if (atlas == nullptr || packer == nullptr) + return; + + packer->Reset(); + casterSlots.clear(); + casterLightSpace.clear(); + + renderData.renderViewers.clear(); + renderData.target = atlas; + renderData.renderViewers.reserve(casters.size()); + + for (const IShadowCaster* caster : casters) + { + if (caster == nullptr) + continue; + + const uint32_t res = caster->GetShadowMapResolution(); + if (res == 0) + continue; + + int x = 0, y = 0; + if (!packer->Alloc(static_cast(res), static_cast(res), x, y)) + { + SH_ERROR_FORMAT("ShadowMapManager: out of atlas space (atlas {}, requested {})", + atlasSize, res); + continue; + } + + Slot slot{}; + slot.offset = glm::uvec2{ static_cast(x), static_cast(y) }; + slot.size = glm::uvec2{ res, res }; + slot.valid = true; + + const glm::mat4 view = caster->GetShadowViewMatrix(); + const glm::mat4 proj = caster->GetShadowProjMatrix(); + + casterSlots.emplace(caster, slot); + casterLightSpace.emplace(caster, proj * view); + + RenderViewer viewer{}; + viewer.viewMatrix = view; + viewer.projMatrix = proj; + viewer.pos = caster->GetShadowPos(); + viewer.to = caster->GetShadowLookAt(); + viewer.viewportRect = glm::uvec4{ slot.offset.x, slot.offset.y, slot.size.x, slot.size.y }; + viewer.viewportScissor = viewer.viewportRect; + renderData.renderViewers.push_back(viewer); + } + + if (renderData.renderViewers.empty()) + return; + + renderer.PushRenderData(renderData); + } + + SH_RENDER_API auto ShadowMapManager::GetSlot(const IShadowCaster& caster) const -> Slot + { + auto it = casterSlots.find(&caster); + if (it == casterSlots.end()) + return Slot{}; + return it->second; + } + + SH_RENDER_API auto ShadowMapManager::GetLightSpaceMatrix(const IShadowCaster& caster) const -> glm::mat4 + { + auto it = casterLightSpace.find(&caster); + if (it == casterLightSpace.end()) + return glm::mat4{ 1.f }; + return it->second; + } +}//namespace From a2c670030aa5147cfb9bea3dbaa54714a450fc1a Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Mon, 4 May 2026 12:11:04 +0900 Subject: [PATCH 30/60] fix(Camera): Change SH_RENDER_API to SH_GAME_API --- include/Game/Component/Render/Camera.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/Game/Component/Render/Camera.h b/include/Game/Component/Render/Camera.h index d54294f4..8748984c 100644 --- a/include/Game/Component/Render/Camera.h +++ b/include/Game/Component/Render/Camera.h @@ -64,8 +64,8 @@ namespace sh::game auto GetProjection() const -> Projection { return projection; } auto GetRenderTexture() const -> render::RenderTexture* { return renderTexture; } protected: - SH_RENDER_API virtual void UpdateViewMatrix(); - SH_RENDER_API virtual void UpdateProjMatrix(); + SH_GAME_API virtual void UpdateViewMatrix(); + SH_GAME_API virtual void UpdateProjMatrix(); protected: PROPERTY(lookPos) Vec3 lookPos; From 7ef789a73265a3374b48b5d5bbfa950b1e0f2625 Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Mon, 4 May 2026 12:13:25 +0900 Subject: [PATCH 31/60] feat(ShaderParser): Change Light buffer to use ssbo --- include/Game/Component/Render/MeshRenderer.h | 11 +- include/Render/ShaderAST.h | 23 ++- include/Render/ShaderPass.h | 14 +- include/Render/UniformStructLayout.h | 16 +- src/Game/Component/Render/MeshRenderer.cpp | 34 +++-- src/Render/ShaderAST.cpp | 148 +++++++++++++------ src/Render/ShaderParser.cpp | 50 +++++-- src/Render/ShaderPass.cpp | 64 ++++---- 8 files changed, 243 insertions(+), 117 deletions(-) diff --git a/include/Game/Component/Render/MeshRenderer.h b/include/Game/Component/Render/MeshRenderer.h index 58449e6d..ec361b1b 100644 --- a/include/Game/Component/Render/MeshRenderer.h +++ b/include/Game/Component/Render/MeshRenderer.h @@ -69,7 +69,7 @@ namespace sh::game std::memcpy(uniformData.data() + offset, &data, sizeof(T)); } - void FillLightStruct(render::Drawable& drawable, render::Shader& shader) const; + void FillLightStruct(render::Drawable& drawable, render::Shader& shader); protected: PROPERTY(drawables, core::PropertyOption::invisible, core::PropertyOption::noSave) std::vector drawables; @@ -78,12 +78,13 @@ namespace sh::game PROPERTY(mats) std::vector mats; private: - struct Light + struct alignas(16) Light { - alignas(16) int lightCount = 0; - alignas(16) glm::vec4 lightPos[10]; - alignas(16) glm::vec4 other[10]; + glm::vec4 pos; + glm::vec4 other; + glm::mat4 lightSpaceMatrix; }; + std::vector lightDatas; render::AABB worldAABB; std::vector> propertyBlocks; diff --git a/include/Render/ShaderAST.h b/include/Render/ShaderAST.h index 9e742e5e..1dac46b9 100644 --- a/include/Render/ShaderAST.h +++ b/include/Render/ShaderAST.h @@ -40,12 +40,14 @@ namespace sh::render Local }; + struct StructNode; struct VariableNode : core::ISerializable { VariableType type = VariableType::Int; uint32_t arraySize = 1; // 배열 원소 개수 (단일 변수면 1) std::string name; std::string defaultValue; + std::string structType; VariableAttribute attribute = VariableAttribute::None; bool bDynamicArray = false; // SSBO 끝의 가변 배열 (arraySize 무시) @@ -54,7 +56,10 @@ namespace sh::render type(type), arraySize(arraySize), name(std::move(name)) { } - static auto MakeDynamicArray(VariableType type, std::string name) -> VariableNode + SH_RENDER_API auto Serialize() const -> core::Json override; + SH_RENDER_API void Deserialize(const core::Json& json) override; + + inline static auto MakeDynamicArray(VariableType type, std::string name) -> VariableNode { VariableNode v; v.type = type; @@ -62,6 +67,12 @@ namespace sh::render v.name = std::move(name); return v; } + SH_RENDER_API static auto MakeDynamicArray(StructNode& structNode, std::string name) -> VariableNode; + }; + struct StructNode : core::ISerializable + { + std::string name; + std::vector vars; SH_RENDER_API auto Serialize() const -> core::Json override; SH_RENDER_API void Deserialize(const core::Json& json) override; @@ -119,6 +130,7 @@ namespace sh::render StageType type; std::vector in; std::vector out; + std::vector structs; std::vector buffers; std::vector declaration; std::vector functions; @@ -128,6 +140,8 @@ namespace sh::render SH_RENDER_API auto Serialize() const -> core::Json override; SH_RENDER_API void Deserialize(const core::Json& json) override; + + SH_RENDER_API auto GetStructNode(const std::string& structName) const -> const StructNode*; }; struct PassNode : core::ISerializable @@ -181,5 +195,12 @@ namespace sh::render SH_RENDER_API auto Serialize() const -> core::Json override; SH_RENDER_API void Deserialize(const core::Json& json) override; }; + + struct alignas(16) LightBuffer + { + glm::vec4 pos; + glm::vec4 other; + glm::mat4 lightSpaceMatrix; + }; }; }//namespace \ No newline at end of file diff --git a/include/Render/ShaderPass.h b/include/Render/ShaderPass.h index f9f0af39..277dba90 100644 --- a/include/Render/ShaderPass.h +++ b/include/Render/ShaderPass.h @@ -50,6 +50,13 @@ namespace sh::render SH_RENDER_API virtual void Clear() = 0; SH_RENDER_API virtual void Build() = 0; + /// @brief 셰이더 바이너리 데이터를 직렬화 한다. + /// @return 직렬화 된 json + SH_RENDER_API auto Serialize() const -> core::Json override; + /// @brief 셰이더 바이너리 데이터를 역직렬화 한다. + /// @param json 직렬화 된 json + SH_RENDER_API void Deserialize(const core::Json& json) override; + SH_RENDER_API auto HasUniformMember(const std::string& name, ShaderStage stage) const -> const UniformStructLayout*; SH_RENDER_API auto GetStencilState() const -> const StencilState& { return stencilState; } @@ -80,13 +87,6 @@ namespace sh::render SH_RENDER_API auto GetAttribute(const std::string& name) const -> std::optional; SH_RENDER_API void StoreShaderCode(ShaderCode&& shaderCode); - - /// @brief 셰이더 바이너리 데이터를 직렬화 한다. - /// @return 직렬화 된 json - SH_RENDER_API auto Serialize() const -> core::Json override; - /// @brief 셰이더 바이너리 데이터를 역직렬화 한다. - /// @param json 직렬화 된 json - SH_RENDER_API void Deserialize(const core::Json& json) override; protected: ShaderPass(const ShaderAST::PassNode& passNode, ShaderType type); ShaderPass(ShaderPass&& other) noexcept; diff --git a/include/Render/UniformStructLayout.h b/include/Render/UniformStructLayout.h index 72e298cc..b6e5e09b 100644 --- a/include/Render/UniformStructLayout.h +++ b/include/Render/UniformStructLayout.h @@ -21,14 +21,14 @@ namespace sh::render public: enum class Layout : uint8_t { - STD140, // UBO 기본 - STD430 // SSBO 기본 + STD140, // UBO + STD430 // SSBO }; /// @brief 버퍼 타입 enum class Kind : uint8_t { - Uniform, // UBO — std140 권장 - Storage, // SSBO — std430 권장, 가변 배열 가능 + Uniform, // UBO + Storage, // SSBO Sampler, PushConstant // 푸시 상수 }; @@ -54,7 +54,7 @@ namespace sh::render struct LayoutInfo { uint32_t baseAlignment; // 오프셋의 정렬 단위 - uint32_t bytes; // 실제 차지하는 총 크기(패딩 포함) + uint32_t bytes; // 실제 차지하는 총 크기(패딩 포함) }; public: SH_RENDER_API UniformStructLayout(std::string name, uint32_t binding, Usage type, ShaderStage stage, Kind kind = Kind::Uniform); @@ -116,6 +116,8 @@ namespace sh::render return { 16, 48 }; else if constexpr (std::is_same_v) return { 16, 64 }; + else if constexpr (std::is_class_v) + return { alignof(T), sizeof(T)}; #if defined(_MSC_VER) else static_assert(std::_Always_false, "Unknown type for GetLayoutInfo: " __FUNCSIG__); @@ -157,7 +159,9 @@ namespace sh::render } else { - const LayoutInfo info = GetLayoutInfo(); + LayoutInfo info = GetLayoutInfo(); + if (layout == Layout::STD140 && std::is_class_v) + info.baseAlignment = 16; const bool isArray = (count > 1 || bDynamic); // STD140: 배열 원소 stride는 최소 16바이트 정렬 diff --git a/src/Game/Component/Render/MeshRenderer.cpp b/src/Game/Component/Render/MeshRenderer.cpp index 34e1072a..5dfa66a5 100644 --- a/src/Game/Component/Render/MeshRenderer.cpp +++ b/src/Game/Component/Render/MeshRenderer.cpp @@ -428,13 +428,18 @@ namespace sh::game } UpdatePropertyBlockData(); } - void MeshRenderer::FillLightStruct(render::Drawable& drawable, render::Shader& shader) const + void MeshRenderer::FillLightStruct(render::Drawable& drawable, render::Shader& shader) { - Light lightStruct{}; - auto lights = world.GetLightOctree().Query(worldAABB); - int idx = 0; - for (int i = 0; i < std::min((int)lights.size(), 10); ++i) + const std::vector& lights = world.GetLightOctree().Query(worldAABB); + + lightDatas.clear(); + lightDatas.resize(sizeof(int) * 4 + lights.size() * sizeof(Light), 0); // int 3개는 패딩 + const int count = static_cast(lights.size()); + std::memcpy(lightDatas.data(), &count, sizeof(int)); + std::size_t offset = sizeof(int) * 4; + for (int i = 0; i < count; ++i) { + Light lightStruct{}; const ILight* light = static_cast(lights[i]); if (light->GetLightType() == ILight::Type::Point) { @@ -442,9 +447,8 @@ namespace sh::game if (!core::IsValid(pointLight)) continue; const Vec3& pos = pointLight->gameObject.transform->GetWorldPosition(); - lightStruct.lightPos[idx] = { pos.x, pos.y, pos.z, 0.f }; - lightStruct.lightPos[idx].w = pointLight->GetRadius(); - lightStruct.other[idx].w = 1; + lightStruct.pos = { pos.x, pos.y, pos.z, pointLight->GetRadius() }; + lightStruct.other.w = 1; } else if (light->GetLightType() == ILight::Type::Directional) { @@ -452,20 +456,20 @@ namespace sh::game if (!core::IsValid(dirLight)) continue; const Vec3& dir = dirLight->GetDirection(); - lightStruct.lightPos[idx] = { dir.x, dir.y, dir.z, dirLight->GetIntensity() }; - lightStruct.other[idx].w = 0; + lightStruct.pos = { dir.x, dir.y, dir.z, dirLight->GetIntensity() }; + lightStruct.other.w = 0; } - ++idx; + std::memcpy(lightDatas.data() + offset, &lightStruct, sizeof(Light)); + offset += sizeof(Light); } - lightStruct.lightCount = idx; - for (auto& lightingPass : shader.GetAllShaderPass()) + for (const render::Shader::LightingPassData& lightingPassData : shader.GetAllShaderPass()) { - for (render::ShaderPass& pass : lightingPass.passes) + for (const render::ShaderPass& pass : lightingPassData.passes) { if (pass.IsPendingKill() || pass.GetLightingBinding() == -1) continue; - drawable.GetMaterialData().SetBindingData(pass, render::UniformStructLayout::Usage::Object, pass.GetLightingBinding(), &lightStruct, sizeof(Light)); + drawable.GetMaterialData().SetBindingData(pass, render::UniformStructLayout::Usage::Object, pass.GetLightingBinding(), lightDatas.data(), lightDatas.size()); } } } diff --git a/src/Render/ShaderAST.cpp b/src/Render/ShaderAST.cpp index 84b01908..7ede9d63 100644 --- a/src/Render/ShaderAST.cpp +++ b/src/Render/ShaderAST.cpp @@ -2,10 +2,11 @@ namespace sh::render { - auto ShaderAST::VariableNode::Serialize() const -> core::Json + SH_RENDER_API auto ShaderAST::VariableNode::Serialize() const -> core::Json { core::Json json; json["type"] = static_cast(type); + json["structType"] = structType; json["arraySize"] = arraySize; json["name"] = name; json["default"] = defaultValue; @@ -13,10 +14,11 @@ namespace sh::render json["dynamicArray"] = bDynamicArray; return json; } - - void ShaderAST::VariableNode::Deserialize(const core::Json& json) + SH_RENDER_API void ShaderAST::VariableNode::Deserialize(const core::Json& json) { type = static_cast(json.at("type").get()); + if (json.contains("structType")) + structType = json["structType"].get_ref(); if (json.contains("arraySize")) { arraySize = json.at("arraySize").get(); @@ -27,8 +29,42 @@ namespace sh::render defaultValue = json["default"].get(); attribute = static_cast(json.at("attribute").get()); } + SH_RENDER_API auto ShaderAST::VariableNode::MakeDynamicArray(StructNode& structNode, std::string name) -> VariableNode + { + VariableNode v; + v.type = VariableType::Struct; + v.structType = structNode.name; + v.bDynamicArray = true; + v.name = std::move(name); + return v; + } - auto ShaderAST::LayoutNode::Serialize() const -> core::Json + SH_RENDER_API auto ShaderAST::StructNode::Serialize() const -> core::Json + { + core::Json json; + json["name"] = name; + core::Json& varsJson = json["vars"]; + for (const VariableNode& varNode : vars) + varsJson.push_back(varNode.Serialize()); + return json; + } + SH_RENDER_API void ShaderAST::StructNode::Deserialize(const core::Json& json) + { + if (json.contains("name")) + name = json["name"].get_ref(); + if (json.contains("vars")) + { + const core::Json& varsJson = json["vars"]; + vars.reserve(varsJson.size()); + for (const core::Json& varJson : varsJson) + { + VariableNode varNode; + varNode.Deserialize(varJson); + vars.push_back(std::move(varNode)); + } + } + } + SH_RENDER_API auto ShaderAST::LayoutNode::Serialize() const -> core::Json { core::Json json; json["binding"] = binding; @@ -36,13 +72,12 @@ namespace sh::render return json; } - void ShaderAST::LayoutNode::Deserialize(const core::Json& json) + SH_RENDER_API void ShaderAST::LayoutNode::Deserialize(const core::Json& json) { binding = json.at("binding").get(); var.Deserialize(json.at("var")); } - - auto ShaderAST::BufferNode::Serialize() const -> core::Json + SH_RENDER_API auto ShaderAST::BufferNode::Serialize() const -> core::Json { core::Json json; json["bufferType"] = static_cast(bufferType); @@ -59,8 +94,7 @@ namespace sh::render json["vars"] = std::move(varArray); return json; } - - void ShaderAST::BufferNode::Deserialize(const core::Json& json) + SH_RENDER_API void ShaderAST::BufferNode::Deserialize(const core::Json& json) { set = json.at("set").get(); binding = json.at("binding").get(); @@ -81,8 +115,7 @@ namespace sh::render vars.push_back(std::move(var)); } } - - auto ShaderAST::StageNode::Serialize() const -> core::Json + SH_RENDER_API auto ShaderAST::StageNode::Serialize() const -> core::Json { core::Json json; json["type"] = static_cast(type); @@ -102,6 +135,11 @@ namespace sh::render outArray.push_back(o.Serialize()); json["out"] = std::move(outArray); + // structs + core::Json& structsJson = json["structs"]; + for (const StructNode& structNode : structs) + structsJson.push_back(structNode.Serialize()); + // uniforms core::Json bufferArray = core::Json::array(); for (const auto& u : buffers) @@ -117,7 +155,7 @@ namespace sh::render return json; } - void ShaderAST::StageNode::Deserialize(const core::Json& json) + SH_RENDER_API void ShaderAST::StageNode::Deserialize(const core::Json& json) { type = static_cast(json.at("type").get()); lightingBinding = json.value("lightingBinding", -1); @@ -126,39 +164,71 @@ namespace sh::render // in in.clear(); - for (const auto& i : json.at("in")) + out.clear(); + if (json.contains("in")) { - LayoutNode l; - l.Deserialize(i); - in.push_back(std::move(l)); + for (const auto& i : json.at("in")) + { + LayoutNode l; + l.Deserialize(i); + in.push_back(std::move(l)); + } } // out out.clear(); - for (const auto& o : json.at("out")) + if (json.contains("out")) { - LayoutNode l; - l.Deserialize(o); - out.push_back(std::move(l)); + for (const auto& o : json.at("out")) + { + LayoutNode l; + l.Deserialize(o); + out.push_back(std::move(l)); + } + } + + // structs + structs.clear(); + if (json.contains("structs")) + { + for (const core::Json& structJson : json.at("structs")) + { + StructNode structNode; + structNode.Deserialize(structJson); + structs.push_back(std::move(structNode)); + } } // uniforms buffers.clear(); - for (const auto& u : json.at("buffers")) + if (json.contains("buffers")) { - BufferNode ubo; - ubo.Deserialize(u); - buffers.push_back(std::move(ubo)); + for (const auto& u : json.at("buffers")) + { + BufferNode ubo; + ubo.Deserialize(u); + buffers.push_back(std::move(ubo)); + } } // declaration - declaration = json.at("declaration").get>(); + if (json.contains("declaration")) + declaration = json.at("declaration").get>(); // functions - functions = json.at("functions").get>(); + if (json.contains("functions")) + functions = json.at("functions").get>(); } - - auto ShaderAST::PassNode::Serialize() const -> core::Json + SH_RENDER_API auto ShaderAST::StageNode::GetStructNode(const std::string& structName) const -> const StructNode* + { + for (const StructNode& structNode : structs) + { + if (structNode.name == structName) + return &structNode; + } + return nullptr; + } + SH_RENDER_API auto ShaderAST::PassNode::Serialize() const -> core::Json { core::Json json; json["name"] = name; @@ -184,8 +254,7 @@ namespace sh::render return json; } - - void ShaderAST::PassNode::Deserialize(const core::Json& json) + SH_RENDER_API void ShaderAST::PassNode::Deserialize(const core::Json& json) { name = json.at("name").get(); lightingPass = json.at("lightingPass").get(); @@ -218,22 +287,19 @@ namespace sh::render stages.push_back(std::move(stage)); } } - - auto ShaderAST::VersionNode::Serialize() const -> core::Json + SH_RENDER_API auto ShaderAST::VersionNode::Serialize() const -> core::Json { core::Json json; json["versionNumber"] = versionNumber; json["profile"] = profile; return json; } - - void ShaderAST::VersionNode::Deserialize(const core::Json& json) + SH_RENDER_API void ShaderAST::VersionNode::Deserialize(const core::Json& json) { versionNumber = json.at("versionNumber").get(); profile = json.at("profile").get(); } - - auto ShaderAST::ShaderNode::Serialize() const -> core::Json + SH_RENDER_API auto ShaderAST::ShaderNode::Serialize() const -> core::Json { core::Json json; json["version"] = version.Serialize(); @@ -253,8 +319,7 @@ namespace sh::render return json; } - - void ShaderAST::ShaderNode::Deserialize(const core::Json& json) + SH_RENDER_API void ShaderAST::ShaderNode::Deserialize(const core::Json& json) { version.Deserialize(json.at("version")); shaderName = json.at("shaderName").get(); @@ -275,8 +340,7 @@ namespace sh::render passes.push_back(std::move(pass)); } } - - auto ShaderAST::ComputeShaderNode::Serialize() const -> core::Json + SH_RENDER_API auto ShaderAST::ComputeShaderNode::Serialize() const -> core::Json { core::Json json; json["version"] = version.Serialize(); @@ -295,8 +359,7 @@ namespace sh::render json["functions"] = functions; return json; } - - void ShaderAST::ComputeShaderNode::Deserialize(const core::Json& json) + SH_RENDER_API void ShaderAST::ComputeShaderNode::Deserialize(const core::Json& json) { version.Deserialize(json.at("version")); shaderName = json.at("shaderName").get(); @@ -315,5 +378,4 @@ namespace sh::render declaration = json.at("declaration").get>(); functions = json.at("functions").get>(); } - }//namespace \ No newline at end of file diff --git a/src/Render/ShaderParser.cpp b/src/Render/ShaderParser.cpp index 9c486afd..5e84fab9 100644 --- a/src/Render/ShaderParser.cpp +++ b/src/Render/ShaderParser.cpp @@ -713,16 +713,21 @@ namespace sh::render [](const ShaderAST::BufferNode& ubo) { return ubo.name == "LIGHT"; }); if (it == stageNode.buffers.end()) { - ShaderAST::BufferNode uboNode{}; - uboNode.bufferType = ShaderAST::BufferType::Uniform; - uboNode.name = "LIGHT"; - uboNode.set = static_cast(UniformStructLayout::Usage::Object); - uboNode.binding = lastObjectUniformBinding++; - uboNode.vars.push_back(ShaderAST::VariableNode{ ShaderAST::VariableType::Int, 1, "count" }); - uboNode.vars.push_back(ShaderAST::VariableNode{ ShaderAST::VariableType::Vec4, 10, "pos" }); - uboNode.vars.push_back(ShaderAST::VariableNode{ ShaderAST::VariableType::Vec4, 10, "other" }); - stageNode.buffers.push_back(std::move(uboNode)); - stageNode.lightingBinding = uboNode.binding; + ShaderAST::StructNode& structNode = stageNode.structs.emplace_back(); + structNode.name = "Light"; + structNode.vars.push_back(ShaderAST::VariableNode{ ShaderAST::VariableType::Vec4, 1, "pos" }); + structNode.vars.push_back(ShaderAST::VariableNode{ ShaderAST::VariableType::Vec4, 1, "other" }); + structNode.vars.push_back(ShaderAST::VariableNode{ ShaderAST::VariableType::Mat4, 1, "lightSpaceMatrix" }); + + ShaderAST::BufferNode& ssboNode = stageNode.buffers.emplace_back(); + ssboNode.bufferType = ShaderAST::BufferType::Storage; + ssboNode.name = "LIGHT"; + ssboNode.set = static_cast(UniformStructLayout::Usage::Object); + ssboNode.binding = lastObjectUniformBinding++; + ssboNode.vars.push_back(ShaderAST::VariableNode{ ShaderAST::VariableType::Int, 1, "count" }); + ssboNode.vars.push_back(ShaderAST::VariableNode::MakeDynamicArray(structNode, "lights")); + + stageNode.lightingBinding = ssboNode.binding; uboit = refreshUboIt(); } usingLIGHT = true; @@ -1036,7 +1041,20 @@ namespace sh::render for (auto& out : stageNode.out) code += fmt::format("layout(location = {}) out {} {};\n", out.binding, VariableTypeToString(out.var.type), out.var.name); - for (auto& uniform : stageNode.buffers) + // 구조체 선언 + for (const ShaderAST::StructNode& structNode : stageNode.structs) + { + std::string variables; + for (const ShaderAST::VariableNode& varNode : structNode.vars) + { + assert(varNode.type != ShaderAST::VariableType::Struct); + variables += fmt::format("{} {};\n", VariableTypeToString(varNode.type), varNode.name); + } + code += fmt::format("struct {} {{ {} }};\n", structNode.name, variables); + } + + // 버퍼 선언 + for (const ShaderAST::BufferNode& uniform : stageNode.buffers) { if (uniform.bufferType == ShaderAST::BufferType::Sampler) { @@ -1045,14 +1063,20 @@ namespace sh::render } std::string uniformMembers; bool bDynamicArrayFlag = false; - for (auto& member : uniform.vars) + for (const ShaderAST::VariableNode& member : uniform.vars) { if (member.bDynamicArray) { assert(!bDynamicArrayFlag); if (bDynamicArrayFlag) throw ShaderParserException{ "SSBO must have at most one dynamic array!" }; - uniformMembers += fmt::format("{} {}[];\n", VariableTypeToString(member.type), member.name); + + std::string varType; + if (member.type != ShaderAST::VariableType::Struct) + varType = VariableTypeToString(member.type); + else + varType = member.structType; + uniformMembers += fmt::format("{} {}[];\n", varType, member.name); bDynamicArrayFlag = true; } else if (member.arraySize == 1) diff --git a/src/Render/ShaderPass.cpp b/src/Render/ShaderPass.cpp index 677fc6e5..7b2f2501 100644 --- a/src/Render/ShaderPass.cpp +++ b/src/Render/ShaderPass.cpp @@ -88,6 +88,30 @@ namespace sh::render return *this; } + SH_RENDER_API auto ShaderPass::Serialize() const -> core::Json + { + core::Json mainJson = Super::Serialize(); + core::Json& shaderPassJson = mainJson["shaderPass"]; + + shaderPassJson["vertShaderData"] = shaderCode.vert; + shaderPassJson["fragShaderData"] = shaderCode.frag; + + return mainJson; + } + SH_RENDER_API void ShaderPass::Deserialize(const core::Json& json) + { + Super::Deserialize(json); + + if (!json.contains("shaderPass")) + return; + + const core::Json& shaderPassJson = json["shaderPass"]; + if (shaderPassJson.contains("vertShaderData")) + shaderCode.vert = shaderPassJson["vertShaderData"].get>(); + if (shaderPassJson.contains("fragShaderData")) + shaderCode.frag = shaderPassJson["fragShaderData"].get>(); + } + SH_RENDER_API auto ShaderPass::HasUniformMember(const std::string& name, ShaderStage stage) const -> const UniformStructLayout* { if (stage == ShaderStage::Vertex) @@ -151,29 +175,6 @@ namespace sh::render { this->shaderCode = std::move(shaderCode); } - SH_RENDER_API auto ShaderPass::Serialize() const -> core::Json - { - core::Json mainJson = Super::Serialize(); - core::Json& shaderPassJson = mainJson["shaderPass"]; - - shaderPassJson["vertShaderData"] = shaderCode.vert; - shaderPassJson["fragShaderData"] = shaderCode.frag; - - return mainJson; - } - SH_RENDER_API void ShaderPass::Deserialize(const core::Json& json) - { - Super::Deserialize(json); - - if (!json.contains("shaderPass")) - return; - - const auto& shaderPassJson = json["shaderPass"]; - if (shaderPassJson.contains("vertShaderData")) - shaderCode.vert = shaderPassJson["vertShaderData"].get>(); - if (shaderPassJson.contains("fragShaderData")) - shaderCode.frag = shaderPassJson["fragShaderData"].get>(); - } void ShaderPass::AddUniformLayout(ShaderStage stage, const UniformStructLayout& layout) { @@ -201,15 +202,15 @@ namespace sh::render } void ShaderPass::FillAttributes(const render::ShaderAST::PassNode& passNode) { - for (auto& stage : passNode.stages) + for (const ShaderAST::StageNode& stage : passNode.stages) { - auto stageType = (stage.type == render::ShaderAST::StageType::Vertex) ? + const ShaderStage stageType = (stage.type == render::ShaderAST::StageType::Vertex) ? render::ShaderStage::Vertex : render::ShaderStage::Fragment; if (stage.type == render::ShaderAST::StageType::Vertex) { - for (auto& in : stage.in) + for (const ShaderAST::LayoutNode& in : stage.in) { switch (in.var.type) { @@ -242,7 +243,7 @@ namespace sh::render if (kind != UniformStructLayout::Kind::Sampler) { - for (auto& var : bufferNode.vars) + for (const ShaderAST::VariableNode& var : bufferNode.vars) { auto addFn = [&](auto typeTag) { @@ -261,6 +262,15 @@ namespace sh::render case render::ShaderAST::VariableType::Mat3: addFn(glm::mat3{}); break; case render::ShaderAST::VariableType::Float: addFn(float{}); break; case render::ShaderAST::VariableType::Int: addFn(int{}); break; + case render::ShaderAST::VariableType::Struct: + { + struct alignas(16) Dummy + { + }; + if (var.bDynamicArray) + uniformLayout.AddDynamicArrayMember(var.name); + break; + } } } } From c5217ee5fc689f753c5c6b4eb9b139cfe596b475 Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Mon, 4 May 2026 12:15:56 +0900 Subject: [PATCH 32/60] fix(ShaderLoader): Correct shader loader logic --- include/Render/Shader.h | 2 +- src/Game/Asset/ShaderLoader.cpp | 10 ++++------ src/Render/Shader.cpp | 30 +++++++++++++++++------------- 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/include/Render/Shader.h b/include/Render/Shader.h index e4b4a1a4..c14ca822 100644 --- a/include/Render/Shader.h +++ b/include/Render/Shader.h @@ -37,7 +37,7 @@ namespace sh::render std::vector> passes; }; public: - SH_RENDER_API Shader(ShaderCreateInfo&& shaderCreateInfo); + SH_RENDER_API Shader(ShaderCreateInfo shaderCreateInfo); SH_RENDER_API ~Shader(); SH_RENDER_API void OnDestroy() override; diff --git a/src/Game/Asset/ShaderLoader.cpp b/src/Game/Asset/ShaderLoader.cpp index b180b40d..cbdbc6fd 100644 --- a/src/Game/Asset/ShaderLoader.cpp +++ b/src/Game/Asset/ShaderLoader.cpp @@ -137,7 +137,7 @@ namespace sh::game for (int i = 0; i < shaderCI.shaderNode.passes.size(); ++i) { - const auto& passNode = shaderCI.shaderNode.passes[i]; + const render::ShaderAST::PassNode& passNode = shaderCI.shaderNode.passes[i]; const core::Json& shaderPassObjJson = *(shaderJson["passes"].begin() + i); const core::Json& shaderPassJson = shaderPassObjJson["shaderPass"]; @@ -146,15 +146,13 @@ namespace sh::game if (shaderPassJson.contains("fragShaderData")) passBuilder->SetData(render::ShaderPassBuilder::shaderType::Fragment, shaderPassJson["fragShaderData"].get>()); - render::ShaderPass* shaderPass = passBuilder->Build(passNode); + render::ShaderPass* const shaderPass = passBuilder->Build(passNode); if (shaderPass == nullptr) return nullptr; - shaderPass->Deserialize(shaderPassObjJson); - shaderCI.passes.push_back(shaderPass); } - auto shader = core::SObject::Create(std::move(shaderCI)); - shader->Deserialize(shaderObjJson); + render::Shader* const shader = core::SObject::Create(std::move(shaderCI)); + shader->SetUUID(asset.GetAssetUUID()); return shader; } diff --git a/src/Render/Shader.cpp b/src/Render/Shader.cpp index 3c35c51e..9577edbc 100644 --- a/src/Render/Shader.cpp +++ b/src/Render/Shader.cpp @@ -1,17 +1,15 @@ #include "Shader.h" #include "ShaderCreateInfo.h" -#include - namespace sh::render { - Shader::Shader(ShaderCreateInfo&& shaderCreateInfo) + Shader::Shader(ShaderCreateInfo shaderCreateInfo) { SetName(shaderCreateInfo.shaderNode.shaderName); AddShaderPasses(std::move(shaderCreateInfo.passes)); - for (auto& prop : shaderCreateInfo.shaderNode.properties) + for (const ShaderAST::VariableNode& prop : shaderCreateInfo.shaderNode.properties) { const bool bLocal = prop.attribute == ShaderAST::VariableAttribute::Local ? true : false; switch (prop.type) @@ -56,16 +54,13 @@ namespace sh::render core::Json& json = mainJson["shader"]; json["AST"] = shaderNode.Serialize(); - - std::set uniquePass; - for (const auto& [name, passVec] : passDatas) + core::Json& passesJson = json["passes"]; + for (const ShaderPass* pass : passes) { - for (ShaderPass& pass : passVec) - uniquePass.insert(&pass); + if (pass == nullptr) + continue; + passesJson.push_back(pass->Serialize()); } - for (const ShaderPass* pass : uniquePass) - json["passes"].push_back(pass->Serialize()); - return mainJson; } SH_RENDER_API void Shader::Deserialize(const core::Json& json) @@ -76,9 +71,18 @@ namespace sh::render return; const core::Json& shaderJson = json["shader"]; - if (shaderJson.contains("AST")) shaderNode.Deserialize(shaderJson["AST"]); + if (shaderJson.contains("passes")) + { + std::size_t idx = 0; + for (const core::Json& passJson : shaderJson["passes"]) + { + if (passes.size() >= idx) + break; + passes[idx++]->Deserialize(passJson); + } + } } SH_RENDER_API auto Shader::GetShaderPasses(const core::Name& lightingPassName) const -> const std::vector>* From 3be8d3a3bfffc03a0a1949acaafb57652146dd08 Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Mon, 4 May 2026 12:17:21 +0900 Subject: [PATCH 33/60] feat(Renderer): Use RenderCommand when SetScriptableRenderer() is called for safety --- include/Render/Renderer.h | 13 ++++--------- src/Render/Renderer.cpp | 21 ++++++++++----------- src/Render/VulkanImpl/VulkanRenderer.cpp | 5 ++--- 3 files changed, 16 insertions(+), 23 deletions(-) diff --git a/include/Render/Renderer.h b/include/Render/Renderer.h index cb12dd07..f60bfb6f 100644 --- a/include/Render/Renderer.h +++ b/include/Render/Renderer.h @@ -37,9 +37,6 @@ namespace sh::render SH_RENDER_API Renderer(); SH_RENDER_API virtual ~Renderer(); - SH_RENDER_API void SyncDirty() override; - SH_RENDER_API void Sync() override; - SH_RENDER_API virtual void CreateContext(const window::Window& win) = 0; SH_RENDER_API virtual void DestroyContext() = 0; SH_RENDER_API virtual bool Init(window::Window& win); @@ -77,17 +74,15 @@ namespace sh::render auto GetThreadId() const -> std::thread::id { return threadId; } auto GetScriptableRenderer() const -> ScriptableRenderer* { return renderer; } protected: + SH_RENDER_API void SyncDirty() override; + SH_RENDER_API void Sync() override; + SH_RENDER_API void SetDrawCallCount(uint32_t drawcall); SH_RENDER_API void DrainRenderCommands(); protected: struct RenderCommand { - enum class Type - { - PushDrawable - }; - Type type = Type::PushDrawable; - core::SObjWeakPtr data; + std::variant, ScriptableRenderer*> data; }; core::LockFreeMPSCQueue renderCommands; diff --git a/src/Render/Renderer.cpp b/src/Render/Renderer.cpp index 5c05324a..900cd05d 100644 --- a/src/Render/Renderer.cpp +++ b/src/Render/Renderer.cpp @@ -77,11 +77,7 @@ namespace sh::render { if (!core::IsValid(drawable)) return; - - RenderCommand cmd{}; - cmd.type = RenderCommand::Type::PushDrawable; - cmd.data = drawable; - renderCommands.Push(std::move(cmd)); + renderCommands.Push(RenderCommand{ drawable }); } SH_RENDER_API void Renderer::PushRenderData(const RenderData& renderData) { @@ -100,7 +96,7 @@ namespace sh::render SH_RENDER_API void Renderer::SetScriptableRenderer(ScriptableRenderer& renderer) { - this->renderer = &renderer; + renderCommands.Push(RenderCommand{ &renderer }); } SH_RENDER_API void Renderer::SetDrawCallCount(uint32_t drawcall) { @@ -114,16 +110,19 @@ namespace sh::render renderCommands.Drain( [this](RenderCommand& cmd) { - switch (cmd.type) - { - case RenderCommand::Type::PushDrawable: + if (std::holds_alternative>(cmd.data)) { - Drawable* const drawable = cmd.data.Get(); + Drawable* const drawable = std::get>(cmd.data).Get(); if (!core::IsValid(drawable) || !drawable->CheckAssetValid()) return; drawables.push_back(drawable); - break; } + else + { + ScriptableRenderer* sr = std::get(cmd.data); + if (sr == nullptr) + return; + renderer = sr; } } ); diff --git a/src/Render/VulkanImpl/VulkanRenderer.cpp b/src/Render/VulkanImpl/VulkanRenderer.cpp index 003e8532..3a47b4e4 100644 --- a/src/Render/VulkanImpl/VulkanRenderer.cpp +++ b/src/Render/VulkanImpl/VulkanRenderer.cpp @@ -90,6 +90,8 @@ namespace sh::render::vk if (!isInit || bPause.load(std::memory_order::memory_order_acquire)) return; + + Renderer::Render(); if (renderer == nullptr) return; @@ -110,9 +112,6 @@ namespace sh::render::vk return; } assert(result == VkResult::VK_SUCCESS); - - Renderer::Render(); - core::ArrayView& renderDatas = IRenderThrMethod::GetRenderDatas(context->GetRenderDataManager()); std::size_t viewIdx = 0; for (RenderData& rd : renderDatas) From 4760808d440d2a17fdab8cd444c472ba8f38897a Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Mon, 4 May 2026 12:18:06 +0900 Subject: [PATCH 34/60] chore: Safe code --- src/Editor/EditorPostOutlinePass.cpp | 2 +- src/Editor/EditorWorld.cpp | 12 ++++-------- src/Game/Component/Render/PickingCamera.cpp | 7 +++++-- src/Game/World.cpp | 10 +++------- 4 files changed, 13 insertions(+), 18 deletions(-) diff --git a/src/Editor/EditorPostOutlinePass.cpp b/src/Editor/EditorPostOutlinePass.cpp index 3f623fe1..83ba9354 100644 --- a/src/Editor/EditorPostOutlinePass.cpp +++ b/src/Editor/EditorPostOutlinePass.cpp @@ -56,7 +56,7 @@ namespace sh::editor } SH_EDITOR_API void EditorPostOutlinePass::Record(render::CommandBuffer& cmd, const render::IRenderContext& ctx, const render::RenderData& renderData) { - if (!core::IsValid(drawable->GetMesh()) || !core::IsValid(drawable->GetMaterial())) + if (drawable == nullptr || !core::IsValid(drawable->GetMesh()) || !core::IsValid(drawable->GetMaterial())) return; cmd.SetRenderData(renderData, false, false, false, false); std::size_t viewerIdx = 0; diff --git a/src/Editor/EditorWorld.cpp b/src/Editor/EditorWorld.cpp index 6c766792..0037ea3f 100644 --- a/src/Editor/EditorWorld.cpp +++ b/src/Editor/EditorWorld.cpp @@ -82,14 +82,10 @@ namespace sh::editor SH_EDITOR_API void EditorWorld::SetupRenderer() { - if (customRenderer == nullptr) - { - customRenderer = std::make_unique(*renderer.GetContext(), GetUiContext(), *this); - EditorRenderer& editorRenderer = static_cast(*customRenderer); - editorRenderer.Init(); - - renderer.SetScriptableRenderer(*customRenderer); - } + customRenderer = std::make_unique(*renderer.GetContext(), GetUiContext(), *this); + EditorRenderer& editorRenderer = static_cast(*customRenderer); + editorRenderer.Init(); + renderer.SetScriptableRenderer(*customRenderer); if (shadowMapManager != nullptr) shadowMapManager->Init(*renderer.GetContext()); } diff --git a/src/Game/Component/Render/PickingCamera.cpp b/src/Game/Component/Render/PickingCamera.cpp index ab9230c6..68dd5753 100644 --- a/src/Game/Component/Render/PickingCamera.cpp +++ b/src/Game/Component/Render/PickingCamera.cpp @@ -48,8 +48,11 @@ namespace sh::game Super::BeginUpdate(); if (!pickingCallback.Empty() && !bRequestRead) { - bufferFuture = world.renderer.GetScriptableRenderer()->ReadRenderTextureAsync(*renderTex, x, y); - bRequestRead = true; + if (world.renderer.GetScriptableRenderer() != nullptr) + { + bufferFuture = world.renderer.GetScriptableRenderer()->ReadRenderTextureAsync(*renderTex, x, y); + bRequestRead = true; + } } if (bufferFuture.valid()) diff --git a/src/Game/World.cpp b/src/Game/World.cpp index b11eaba3..b97a00e3 100644 --- a/src/Game/World.cpp +++ b/src/Game/World.cpp @@ -261,13 +261,9 @@ namespace sh::game } SH_GAME_API void World::SetupRenderer() { - if (customRenderer == nullptr) - { - customRenderer = std::make_unique(*renderer.GetContext(), GetUiContext(), *this); - customRenderer->Init(); - - renderer.SetScriptableRenderer(*customRenderer); - } + customRenderer = std::make_unique(*renderer.GetContext(), GetUiContext(), *this); + customRenderer->Init(); + renderer.SetScriptableRenderer(*customRenderer); if (shadowMapManager != nullptr) shadowMapManager->Init(*renderer.GetContext()); } From 0ae67dacc258f62c2c7cd54ea4d37c06cb3338a6 Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Mon, 4 May 2026 12:44:25 +0900 Subject: [PATCH 35/60] fix: Fix issue where viewport did not update when Play was pressed --- include/Game/GameRenderer.h | 2 ++ src/Editor/EditorRenderer.cpp | 4 ++++ src/Editor/EditorWorld.cpp | 2 ++ 3 files changed, 8 insertions(+) diff --git a/include/Game/GameRenderer.h b/include/Game/GameRenderer.h index 53e83a7a..ede90591 100644 --- a/include/Game/GameRenderer.h +++ b/include/Game/GameRenderer.h @@ -22,6 +22,8 @@ namespace sh::game SH_GAME_API void Init() override; SH_GAME_API void Setup(const render::RenderData& data) override; + + auto GetGUIPass() const -> game::GUIPass* { return guiPass; } protected: render::IRenderContext& renderCtx; World& world; diff --git a/src/Editor/EditorRenderer.cpp b/src/Editor/EditorRenderer.cpp index 5a0ff6a9..1982d765 100644 --- a/src/Editor/EditorRenderer.cpp +++ b/src/Editor/EditorRenderer.cpp @@ -56,5 +56,9 @@ namespace sh::editor EnqueRenderPass(*shadowMapPass); return; } + // 그 외 카메라 + EnqueRenderPass(*opaquePass); + EnqueRenderPass(*transparentPass); + EnqueRenderPass(*uiPass); } }//namespace \ No newline at end of file diff --git a/src/Editor/EditorWorld.cpp b/src/Editor/EditorWorld.cpp index 0037ea3f..c3b5916a 100644 --- a/src/Editor/EditorWorld.cpp +++ b/src/Editor/EditorWorld.cpp @@ -23,6 +23,7 @@ #include "Game/Component/Render/PickingRenderer.h" #include "Game/Component/Render/LineRenderer.h" #include "Game/WorldEvents.hpp" +#include "Game/GUIPass.h" namespace sh::editor { @@ -115,6 +116,7 @@ namespace sh::editor } else viewportTexture = viewportPtr; + editorRenderer->GetGUIPass()->viewportTexture = viewportTexture; game::GameObject* const camObj = AddGameObject("EditorCamera"); camObj->transform->SetPosition({ 2.f, 2.f, 2.f }); From 5efa6aeec83b13f0b37fa0a517353873a84ddb96 Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Mon, 4 May 2026 12:52:43 +0900 Subject: [PATCH 36/60] fix(TransparentPass): Set viewport and scissor --- src/Render/RenderPass/TransparentPass.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Render/RenderPass/TransparentPass.cpp b/src/Render/RenderPass/TransparentPass.cpp index ffb56247..d1d581aa 100644 --- a/src/Render/RenderPass/TransparentPass.cpp +++ b/src/Render/RenderPass/TransparentPass.cpp @@ -37,6 +37,8 @@ namespace sh::render std::size_t idx = 0; for (const RenderViewer& viewer : renderTarget.renderViewers) { + cmd.SetViewport(viewer.viewportRect.x, viewer.viewportRect.y, viewer.viewportRect.z, viewer.viewportRect.w); + cmd.SetScissor(viewer.viewportScissor.x, viewer.viewportScissor.y, viewer.viewportScissor.z, viewer.viewportScissor.w); const glm::vec3& camPos = viewer.pos; const glm::vec3& camTo = viewer.to; const glm::vec3 to = glm::normalize(camTo - camPos); From ac6038835ef058a8c7776f64396eccad4f9f2e88 Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Mon, 4 May 2026 17:54:31 +0900 Subject: [PATCH 37/60] feat: Move cached render texture from Material to MaterialData for image usage tracking --- include/Render/Material.h | 3 --- include/Render/MaterialData.h | 20 ++++++++++++++++++++ src/Editor/EditorPostOutlinePass.cpp | 7 +++++-- src/Render/Material.cpp | 3 --- src/Render/MaterialData.cpp | 15 +++++++++++++++ src/Render/ScriptableRenderPass.cpp | 24 +++++++++--------------- 6 files changed, 49 insertions(+), 23 deletions(-) diff --git a/include/Render/Material.h b/include/Render/Material.h index 4da6483f..5f66fca3 100644 --- a/include/Render/Material.h +++ b/include/Render/Material.h @@ -70,8 +70,6 @@ namespace sh::render SH_RENDER_API auto Serialize() const -> core::Json override; SH_RENDER_API void Deserialize(const core::Json& json) override; - - SH_RENDER_API auto GetCachedRenderTextures() const -> const core::SHashMap& { return cachedRts; } private: void Clear(); void SetDefaultProperties(); @@ -100,7 +98,6 @@ namespace sh::render std::vector> dirtyProps; std::vector> cachedConstantData; - core::SHashMap cachedRts; core::Observer::Listener onBufferUpdateListener; diff --git a/include/Render/MaterialData.h b/include/Render/MaterialData.h index 32e93380..5142cf34 100644 --- a/include/Render/MaterialData.h +++ b/include/Render/MaterialData.h @@ -3,6 +3,7 @@ #include "IBuffer.h" #include "IShaderBinding.h" #include "ShaderEnum.h" +#include "IRenderThrMethod.h" #include "Core/ISyncable.h" #include "Core/SContainer.hpp" @@ -18,6 +19,7 @@ namespace sh::render class Shader; class ShaderPass; class IRenderContext; + class RenderTexture; /// @brief 셰이더에 전달 할 데이터를 가지고 있는 클래스 /// @brief 모든 변경 사항은 게임 스레드에서만 작성 시 스레드 안전하다. @@ -25,6 +27,7 @@ namespace sh::render public core::ISyncable, public core::INonCopyable { + friend struct IRenderThrMethod; private: struct PassData { @@ -77,8 +80,17 @@ namespace sh::render SH_RENDER_API auto GetShaderBinding(const ShaderPass& shaderPass, UniformStructLayout::Usage usage) const -> IShaderBinding*; SH_RENDER_API void SyncDirty() override; + public: + struct CachedRT + { + uint32_t set = 0; + uint32_t binding = 0; + core::SObjWeakPtr pass; + core::SObjWeakPtr rt; + }; protected: SH_RENDER_API void Sync() override; + auto GetCachedRTs() const -> const std::vector& { return cachedRTs; } private: void CreateBuffers(const IRenderContext& context, const Shader& shader, bool bPerObject); auto GetMaterialPassData(const ShaderPass& shaderPass) const -> const MaterialData::PassData*; @@ -92,9 +104,17 @@ namespace sh::render std::vector syncDatas; + std::vector cachedRTs; + bool bDirty = false; bool bClearDirty = false; bool bCreateDirty = false; bool bPerObject = false; }; + + template<> + struct IRenderThrMethod + { + static auto GetCachedRTs(const MaterialData& materialData) -> const std::vector& { return materialData.GetCachedRTs(); } + }; }//namespace \ No newline at end of file diff --git a/src/Editor/EditorPostOutlinePass.cpp b/src/Editor/EditorPostOutlinePass.cpp index 83ba9354..5c488f88 100644 --- a/src/Editor/EditorPostOutlinePass.cpp +++ b/src/Editor/EditorPostOutlinePass.cpp @@ -50,8 +50,11 @@ namespace sh::editor renderTextures[renderData.target] = render::ResourceUsage::ColorAttachment; if (mat != nullptr) { - for (auto& [name, rt] : mat->GetCachedRenderTextures()) - renderTextures[rt] = render::ResourceUsage::SampledRead; + for (const render::MaterialData::CachedRT& cachedRT : render::IRenderThrMethod::GetCachedRTs(mat->GetMaterialData())) + { + if (cachedRT.rt.IsValid() && cachedRT.pass->GetLightingPassName() == passName) + renderTextures[cachedRT.rt.Get()] = cachedRT.rt->IsDepthOnly() ? render::ResourceUsage::DepthStencilSampledRead : render::ResourceUsage::SampledRead; + } } } SH_EDITOR_API void EditorPostOutlinePass::Record(render::CommandBuffer& cmd, const render::IRenderContext& ctx, const render::RenderData& renderData) diff --git a/src/Render/Material.cpp b/src/Render/Material.cpp index d6c45440..1426d620 100644 --- a/src/Render/Material.cpp +++ b/src/Render/Material.cpp @@ -269,9 +269,6 @@ namespace sh::render } bPropertyDirty = true; - - if (data->GetType().IsChildOf(RenderTexture::GetStaticType())) - cachedRts[name] = static_cast(data); } SH_RENDER_API void Material::SetProperty(const std::string& name, const glm::vec4& data) { diff --git a/src/Render/MaterialData.cpp b/src/Render/MaterialData.cpp index 21eac9c0..118d4b5d 100644 --- a/src/Render/MaterialData.cpp +++ b/src/Render/MaterialData.cpp @@ -3,6 +3,7 @@ #include "ShaderPass.h" #include "BufferFactory.h" #include "IRenderContext.h" +#include "RenderTexture.h" #include "Core/ThreadSyncManager.h" #include "Core/Logger.h" @@ -310,5 +311,19 @@ namespace sh::render IShaderBinding& shaderBindings = *itUb->second.get(); shaderBindings.Link(binding, *shaderBindingsSyncData.tex); + + if (shaderBindingsSyncData.tex->GetType().IsChildOf(RenderTexture::GetStaticType())) + { + auto it = std::find_if(cachedRTs.begin(), cachedRTs.end(), + [&](const CachedRT& cached) + { + if (cached.set == set && cached.binding == binding && cached.pass == shaderPass) + return true; + return false; + } + ); + if (it == cachedRTs.end()) + cachedRTs.push_back(CachedRT{ set, binding, shaderPass, static_cast(shaderBindingsSyncData.tex) }); + } } }//namespace \ No newline at end of file diff --git a/src/Render/ScriptableRenderPass.cpp b/src/Render/ScriptableRenderPass.cpp index 0d28a5d5..aab82b77 100644 --- a/src/Render/ScriptableRenderPass.cpp +++ b/src/Render/ScriptableRenderPass.cpp @@ -2,6 +2,7 @@ #include "IRenderContext.h" #include "Drawable.h" #include "RenderTexture.h" +#include "MaterialData.h" #include "Core/Logger.h" #include "Core/Util.h" @@ -124,22 +125,15 @@ namespace sh::render continue; const Material* const mat = drawable->GetMaterial(); - for (auto& [name, rt] : mat->GetCachedRenderTextures()) + for (const MaterialData::CachedRT& cachedRT : IRenderThrMethod::GetCachedRTs(mat->GetMaterialData())) { - auto it = renderTextures.find(rt); - if (it != renderTextures.end()) - { - if (it->second == ResourceUsage::ColorAttachment || - it->second == ResourceUsage::DepthStencilAttachment) - { - SH_ERROR_FORMAT("RenderTexture {} is used as Attachment", it->first->GetName().ToString()); - continue; - } - } - else - { - renderTextures[rt] = rt->IsDepthOnly() ? ResourceUsage::DepthStencilSampledRead : ResourceUsage::SampledRead; - } + if (cachedRT.rt.IsValid() && cachedRT.pass->GetLightingPassName() == passName) + renderTextures[cachedRT.rt.Get()] = cachedRT.rt->IsDepthOnly() ? ResourceUsage::DepthStencilSampledRead : ResourceUsage::SampledRead; + } + for (const MaterialData::CachedRT& cachedRT : IRenderThrMethod::GetCachedRTs(drawable->GetMaterialData())) + { + if (cachedRT.rt.IsValid() && cachedRT.pass->GetLightingPassName() == passName) + renderTextures[cachedRT.rt.Get()] = cachedRT.rt->IsDepthOnly() ? ResourceUsage::DepthStencilSampledRead : ResourceUsage::SampledRead; } } } From 82684ad5207e2cbfef1bf7d0ed91f8b8f0427bda Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Mon, 4 May 2026 17:55:06 +0900 Subject: [PATCH 38/60] feat: Support directional light shadow --- .../Game/Component/Render/DirectionalLight.h | 4 +- include/Game/Component/Render/MeshRenderer.h | 1 + include/Game/Component/Render/PointLight.h | 1 + include/Game/ILight.h | 1 + include/Render/ShaderAST.h | 1 + include/Render/ShaderLexer.h | 2 +- include/Render/ShaderParser.h | 1 + include/Render/ShaderPass.h | 2 + include/Render/ShadowMapManager.h | 4 +- .../Component/Render/DirectionalLight.cpp | 9 +- src/Game/Component/Render/MeshRenderer.cpp | 7 +- src/Game/Component/Render/PointLight.cpp | 5 + src/Render/ShaderAST.cpp | 2 + src/Render/ShaderLexer.cpp | 115 +++++------------- src/Render/ShaderParser.cpp | 40 +++++- src/Render/ShaderPass.cpp | 2 + src/Render/ShadowMapManager.cpp | 6 +- 17 files changed, 100 insertions(+), 103 deletions(-) diff --git a/include/Game/Component/Render/DirectionalLight.h b/include/Game/Component/Render/DirectionalLight.h index 86201ccc..51ad1b52 100644 --- a/include/Game/Component/Render/DirectionalLight.h +++ b/include/Game/Component/Render/DirectionalLight.h @@ -33,13 +33,13 @@ namespace sh::game SH_GAME_API void SetCastShadow(bool b) override; SH_GAME_API void SetShadowBias(float bias) override; SH_GAME_API void SetShadowMapResolution(uint32_t res) override; + /// @brief 라이트의 광원 공간 변환 행렬 (proj * view)을 반환한다. + SH_GAME_API auto GetLightSpaceMatrix() const -> glm::mat4 override; /// @brief 그림자 아틀라스 텍스처를 반환한다 (모든 광원이 공유) SH_GAME_API auto GetShadowMap() const -> render::RenderTexture*; /// @brief 아틀라스 내에서 이 광원의 슬롯 정보를 반환한다. SH_GAME_API auto GetShadowSlot() const -> render::ShadowMapManager::Slot; - /// @brief 디렉셔널 라이트의 광원 공간 변환 행렬 (proj * view)을 반환한다. - SH_GAME_API auto GetLightSpaceMatrix() const -> glm::mat4; SH_GAME_API auto GetShadowMapResolution() const -> uint32_t override { return shadowMapResolution; } SH_GAME_API auto GetShadowViewMatrix() const -> glm::mat4 override; diff --git a/include/Game/Component/Render/MeshRenderer.h b/include/Game/Component/Render/MeshRenderer.h index ec361b1b..661c4001 100644 --- a/include/Game/Component/Render/MeshRenderer.h +++ b/include/Game/Component/Render/MeshRenderer.h @@ -82,6 +82,7 @@ namespace sh::game { glm::vec4 pos; glm::vec4 other; + glm::vec4 shadow; // offset, size glm::mat4 lightSpaceMatrix; }; std::vector lightDatas; diff --git a/include/Game/Component/Render/PointLight.h b/include/Game/Component/Render/PointLight.h index f486ed2c..47ae599b 100644 --- a/include/Game/Component/Render/PointLight.h +++ b/include/Game/Component/Render/PointLight.h @@ -40,6 +40,7 @@ namespace sh::game SH_GAME_API auto GetIntensity() const -> float; SH_GAME_API auto GetPos() const -> const Vec3& override; SH_GAME_API auto GetLightType() const -> ILight::Type override; + SH_GAME_API auto GetLightSpaceMatrix() const->glm::mat4 override; SH_GAME_API auto IsCastShadow() const -> bool override; SH_GAME_API void SetCastShadow(bool b) override; diff --git a/include/Game/ILight.h b/include/Game/ILight.h index 419ef6ef..f4706817 100644 --- a/include/Game/ILight.h +++ b/include/Game/ILight.h @@ -22,6 +22,7 @@ namespace sh::game virtual ~ILight() {}; virtual void SetIntensity(float intensity) = 0; virtual auto GetLightType() const -> Type = 0; + virtual auto GetLightSpaceMatrix() const -> glm::mat4 = 0; virtual auto IsCastShadow() const -> bool { return false; } virtual void SetCastShadow(bool b) {} diff --git a/include/Render/ShaderAST.h b/include/Render/ShaderAST.h index 1dac46b9..cff81640 100644 --- a/include/Render/ShaderAST.h +++ b/include/Render/ShaderAST.h @@ -137,6 +137,7 @@ namespace sh::render std::string code; int lightingBinding = -1; int skinBinding = -1; + int shadowMapBinding = -1; SH_RENDER_API auto Serialize() const -> core::Json override; SH_RENDER_API void Deserialize(const core::Json& json) override; diff --git a/include/Render/ShaderLexer.h b/include/Render/ShaderLexer.h index 2f13bbee..2883cf9c 100644 --- a/include/Render/ShaderLexer.h +++ b/include/Render/ShaderLexer.h @@ -17,7 +17,7 @@ namespace sh::render Layout, Uniform, In, Out, Sampler2D, Constexpr, Const, VERTEX, UV, NORMAL, TANGENT, MVP, LIGHT, BONE_WEIGHTS, BONE_INDICES, SKIN, - MATRIX_MODEL, MATRIX_VIEW, MATRIX_PROJ, MATRIX_SKIN, + MATRIX_MODEL, MATRIX_VIEW, MATRIX_PROJ, MATRIX_SKIN, TEXTURE_SHADOW, LBracket, // ( RBracket, // ) LBrace, // { diff --git a/include/Render/ShaderParser.h b/include/Render/ShaderParser.h index b3657cfa..3f4e87d3 100644 --- a/include/Render/ShaderParser.h +++ b/include/Render/ShaderParser.h @@ -68,6 +68,7 @@ namespace sh::render auto SubstitutionFunctionToken(const ShaderLexer::Token& token) const -> std::string; private: const std::vector* tokens; + ShaderAST::ShaderNode curShaderNode; std::size_t pos; // 현재 토큰 위치 diff --git a/include/Render/ShaderPass.h b/include/Render/ShaderPass.h index 277dba90..ecfff94e 100644 --- a/include/Render/ShaderPass.h +++ b/include/Render/ShaderPass.h @@ -79,6 +79,7 @@ namespace sh::render /// @brief 스킨 유니폼의 바인딩 번호를 리턴한다. /// @return 스킨을 안 쓸 시 -1 SH_RENDER_API auto GetSkinBinding() const -> int { return skinBinding; } + SH_RENDER_API auto GetShadowMapBinding() const -> int { return shadowMapBinding; } SH_RENDER_API auto GetConstants() const -> const std::unordered_map& { return constantNameMap; } SH_RENDER_API auto GetConstantsInfo(const std::string& name) const -> const ConstantInfo*; SH_RENDER_API auto GetConstantSize() const -> std::size_t; @@ -120,6 +121,7 @@ namespace sh::render std::size_t constantSize = 0; int lightingBinding = -1; int skinBinding = -1; + int shadowMapBinding = -1; bool bZWrite = true; bool bZTest = true; bool bHasConstant = false; diff --git a/include/Render/ShadowMapManager.h b/include/Render/ShadowMapManager.h index 8fd1d3d4..0f410a8d 100644 --- a/include/Render/ShadowMapManager.h +++ b/include/Render/ShadowMapManager.h @@ -37,8 +37,8 @@ namespace sh::render public: struct Slot { - glm::uvec2 offset{ 0u, 0u }; - glm::uvec2 size{ 0u, 0u }; + glm::vec2 uvOffset{ 0.f, 0.f }; + glm::vec2 uvSize{ 0.f, 0.f }; bool valid = false; }; public: diff --git a/src/Game/Component/Render/DirectionalLight.cpp b/src/Game/Component/Render/DirectionalLight.cpp index fcc6cd8a..a9ad2258 100644 --- a/src/Game/Component/Render/DirectionalLight.cpp +++ b/src/Game/Component/Render/DirectionalLight.cpp @@ -75,7 +75,10 @@ namespace sh::game { shadowMapResolution = res; } - + SH_GAME_API auto DirectionalLight::GetLightSpaceMatrix() const -> glm::mat4 + { + return GetShadowProjMatrix() * GetShadowViewMatrix(); + } SH_GAME_API auto DirectionalLight::GetShadowMap() const -> render::RenderTexture* { if (!bCastShadow) @@ -88,10 +91,6 @@ namespace sh::game return render::ShadowMapManager::Slot{}; return world.GetShadowMapManager().GetSlot(*this); } - SH_GAME_API auto DirectionalLight::GetLightSpaceMatrix() const -> glm::mat4 - { - return GetShadowProjMatrix() * GetShadowViewMatrix(); - } SH_GAME_API auto DirectionalLight::GetShadowViewMatrix() const -> glm::mat4 { diff --git a/src/Game/Component/Render/MeshRenderer.cpp b/src/Game/Component/Render/MeshRenderer.cpp index 5dfa66a5..75944915 100644 --- a/src/Game/Component/Render/MeshRenderer.cpp +++ b/src/Game/Component/Render/MeshRenderer.cpp @@ -458,7 +458,11 @@ namespace sh::game const Vec3& dir = dirLight->GetDirection(); lightStruct.pos = { dir.x, dir.y, dir.z, dirLight->GetIntensity() }; lightStruct.other.w = 0; + render::ShadowMapManager::Slot slot = dirLight->GetShadowSlot(); + lightStruct.shadow = { slot.uvOffset.x,slot.uvOffset.y,slot.uvSize.x, slot.uvSize.y }; } + lightStruct.lightSpaceMatrix = light->GetLightSpaceMatrix(); + std::memcpy(lightDatas.data() + offset, &lightStruct, sizeof(Light)); offset += sizeof(Light); } @@ -468,8 +472,9 @@ namespace sh::game { if (pass.IsPendingKill() || pass.GetLightingBinding() == -1) continue; - drawable.GetMaterialData().SetBindingData(pass, render::UniformStructLayout::Usage::Object, pass.GetLightingBinding(), lightDatas.data(), lightDatas.size()); + if (pass.GetShadowMapBinding() != -1 && world.GetShadowMapManager().GetAtlas() != nullptr) + drawable.GetMaterialData().SetTextureData(pass, render::UniformStructLayout::Usage::Object, pass.GetShadowMapBinding(), *world.GetShadowMapManager().GetAtlas()); } } } diff --git a/src/Game/Component/Render/PointLight.cpp b/src/Game/Component/Render/PointLight.cpp index ae2b3dc0..ace324fa 100644 --- a/src/Game/Component/Render/PointLight.cpp +++ b/src/Game/Component/Render/PointLight.cpp @@ -105,6 +105,11 @@ namespace sh::game return ILight::Type::Point; } + SH_GAME_API auto PointLight::GetLightSpaceMatrix() const -> glm::mat4 + { + return glm::mat4{ 1.f }; + } + SH_GAME_API auto PointLight::IsCastShadow() const -> bool { return bCastShadow; diff --git a/src/Render/ShaderAST.cpp b/src/Render/ShaderAST.cpp index 7ede9d63..10452ec0 100644 --- a/src/Render/ShaderAST.cpp +++ b/src/Render/ShaderAST.cpp @@ -121,6 +121,7 @@ namespace sh::render json["type"] = static_cast(type); json["lightingBinding"] = lightingBinding; json["skinBinding"] = skinBinding; + json["shadowMapBinding"] = shadowMapBinding; json["code"] = code; // in @@ -160,6 +161,7 @@ namespace sh::render type = static_cast(json.at("type").get()); lightingBinding = json.value("lightingBinding", -1); skinBinding = json.value("skinBinding", -1); + shadowMapBinding = json.value("shadowMapBinding", -1); code = json.at("code").get(); // in diff --git a/src/Render/ShaderLexer.cpp b/src/Render/ShaderLexer.cpp index 93a05399..330072f8 100644 --- a/src/Render/ShaderLexer.cpp +++ b/src/Render/ShaderLexer.cpp @@ -127,91 +127,39 @@ namespace sh::render ++column; continue; } - if (c == '=') - { - ++i; - ++column; - if (i + 1 < source.size()) - { - if (source[i] == '=') - { - AddToken(TokenType::Operator, "=="); - ++i; - ++column; - continue; - } - } - AddToken(TokenType::Operator, "="); - continue; - } - if (c == '+') - { - ++i; - ++column; - if (i < source.size()) - { - if (source[i] == '+' || source[i] == '=') - { - AddToken(TokenType::Operator, std::string{ c } + source[i]); - ++i; - ++column; - continue; - } - } - AddToken(TokenType::Operator, std::string{ c }); - continue; - } - if (c == '-') - { - ++i; - ++column; - if (i < source.size()) + // 두 글자 합성 연산자가 가능한 연산자들 + auto LexCompoundOp = [&](char single, std::initializer_list followers) { - if (source[i] == '-' || source[i] == '=') - { - AddToken(TokenType::Operator, std::string{ c } + source[i]); - ++i; - ++column; - continue; - } - } - AddToken(TokenType::Operator, std::string{ c }); - continue; - } - if (c == '*') - { - ++i; - ++column; - if (i < source.size()) - { - if (source[i] == '=') - { - AddToken(TokenType::Operator, std::string{ c } + source[i]); - ++i; - ++column; - continue; - } - } - AddToken(TokenType::Operator, std::string{ c }); - continue; - } - if (c == '/') - { - ++i; - ++column; - if (i < source.size()) - { - if (source[i] == '=') + std::string op{ single }; + ++i; + ++column; + if (i < source.size()) { - AddToken(TokenType::Operator, std::string{ c } + source[i]); - ++i; - ++column; - continue; + for (char f : followers) + { + if (source[i] == f) + { + op.push_back(source[i]); + ++i; + ++column; + break; + } + } } - } - AddToken(TokenType::Operator, std::string{ c }); - continue; - } + AddToken(TokenType::Operator, op); + }; + if (c == '=') { LexCompoundOp('=', { '=' }); continue; } + if (c == '+') { LexCompoundOp('+', { '+', '=' }); continue; } + if (c == '-') { LexCompoundOp('-', { '-', '=' }); continue; } + if (c == '*') { LexCompoundOp('*', { '=' }); continue; } + if (c == '/') { LexCompoundOp('/', { '=' }); continue; } + if (c == '<') { LexCompoundOp('<', { '=', '<' }); continue; } + if (c == '>') { LexCompoundOp('>', { '=', '>' }); continue; } + if (c == '!') { LexCompoundOp('!', { '=' }); continue; } + if (c == '&') { LexCompoundOp('&', { '&', '=' }); continue; } + if (c == '|') { LexCompoundOp('|', { '|', '=' }); continue; } + if (c == '%') { LexCompoundOp('%', { '=' }); continue; } + if (c == '^') { LexCompoundOp('^', { '=' }); continue; } if (c == ';') { @@ -311,7 +259,8 @@ namespace sh::render {"BONE_WEIGHTS", TokenType::BONE_WEIGHTS}, {"BONE_INDICES", TokenType::BONE_INDICES}, {"SKIN", TokenType::SKIN}, - {"MATRIX_SKIN", TokenType::MATRIX_SKIN} + {"MATRIX_SKIN", TokenType::MATRIX_SKIN}, + {"TEXTURE_SHADOW", TokenType::TEXTURE_SHADOW} }; auto it = keywordMap.find(ident); if (it == keywordMap.end()) diff --git a/src/Render/ShaderParser.cpp b/src/Render/ShaderParser.cpp index 5e84fab9..78b48da2 100644 --- a/src/Render/ShaderParser.cpp +++ b/src/Render/ShaderParser.cpp @@ -151,19 +151,19 @@ namespace sh::render auto ShaderParser::ParseShader() -> ShaderAST::ShaderNode { - ShaderAST::ShaderNode shaderNode; + curShaderNode = ShaderAST::ShaderNode{}; while (CheckToken(ShaderLexer::TokenType::Preprocessor)) { NextToken(); - ParsePreprocessor(shaderNode); + ParsePreprocessor(curShaderNode); } ConsumeToken(ShaderLexer::TokenType::Shader); if (auto& token = PeekToken(); token.type == ShaderLexer::TokenType::String) { - shaderNode.shaderName = token.text; + curShaderNode.shaderName = token.text; NextToken(); } else @@ -173,15 +173,15 @@ namespace sh::render // 프로퍼티가 있으면 파싱 (없을 수도 있음) if (CheckToken(ShaderLexer::TokenType::Property)) - ParseProperty(shaderNode); + ParseProperty(curShaderNode); while (!CheckToken(ShaderLexer::TokenType::RBrace) && !CheckToken(ShaderLexer::TokenType::EndOfFile)) { - shaderNode.passes.push_back(ParsePass(shaderNode)); + curShaderNode.passes.push_back(ParsePass(curShaderNode)); } ConsumeToken(ShaderLexer::TokenType::RBrace); - return shaderNode; + return curShaderNode; } void ShaderParser::ParsePreprocessor(ShaderAST::ShaderNode& shaderNode) { @@ -636,6 +636,7 @@ namespace sh::render bool usingLIGHT = false; bool usingSKIN = false; bool usingMATRIX_SKIN = false; + bool usingTEXTURE_SHADOW = false; while (nested != 0 || PeekToken().type != ShaderLexer::TokenType::EndOfFile) { @@ -717,6 +718,7 @@ namespace sh::render structNode.name = "Light"; structNode.vars.push_back(ShaderAST::VariableNode{ ShaderAST::VariableType::Vec4, 1, "pos" }); structNode.vars.push_back(ShaderAST::VariableNode{ ShaderAST::VariableType::Vec4, 1, "other" }); + structNode.vars.push_back(ShaderAST::VariableNode{ ShaderAST::VariableType::Vec4, 1, "shadow" }); structNode.vars.push_back(ShaderAST::VariableNode{ ShaderAST::VariableType::Mat4, 1, "lightSpaceMatrix" }); ShaderAST::BufferNode& ssboNode = stageNode.buffers.emplace_back(); @@ -733,6 +735,32 @@ namespace sh::render usingLIGHT = true; } } + else if (CheckToken(ShaderLexer::TokenType::TEXTURE_SHADOW)) + { + if (!usingTEXTURE_SHADOW) + { + auto it = std::find_if(stageNode.buffers.begin(), stageNode.buffers.end(), + [](const ShaderAST::BufferNode& ubo) { return ubo.name == "TEXTURE_SHADOW"; }); + if (it == stageNode.buffers.end()) + { + ShaderAST::BufferNode& samplerNode = stageNode.buffers.emplace_back(); + samplerNode.bufferType = ShaderAST::BufferType::Sampler; + samplerNode.name = "TEXTURE_SHADOW"; + samplerNode.set = static_cast(UniformStructLayout::Usage::Object); + samplerNode.binding = lastObjectUniformBinding++; + + stageNode.shadowMapBinding = samplerNode.binding; + + ShaderAST::VariableNode& varNode = curShaderNode.properties.emplace_back(); + varNode.name = "TEXTURE_SHADOW"; + varNode.attribute = ShaderAST::VariableAttribute::Local; + varNode.type = ShaderAST::VariableType::Sampler; + + uboit = refreshUboIt(); + } + usingTEXTURE_SHADOW = true; + } + } else if (CheckToken(ShaderLexer::TokenType::SKIN) || CheckToken(ShaderLexer::TokenType::MATRIX_SKIN)) { registerAttributeFn(usingBoneWeights, "BONE_WEIGHTS", SkinnedMesh::BONE_WEIGHT_ID, ShaderAST::VariableType::Vec4); diff --git a/src/Render/ShaderPass.cpp b/src/Render/ShaderPass.cpp index 7b2f2501..577438b9 100644 --- a/src/Render/ShaderPass.cpp +++ b/src/Render/ShaderPass.cpp @@ -17,6 +17,8 @@ namespace sh::render lightingBinding = stage.lightingBinding; if (stage.skinBinding != -1) skinBinding = stage.skinBinding; + if (stage.shadowMapBinding != -1) + shadowMapBinding = stage.shadowMapBinding; } if (!passNode.constants.empty()) { diff --git a/src/Render/ShadowMapManager.cpp b/src/Render/ShadowMapManager.cpp index 8bd13001..099111f2 100644 --- a/src/Render/ShadowMapManager.cpp +++ b/src/Render/ShadowMapManager.cpp @@ -117,8 +117,8 @@ namespace sh::render } Slot slot{}; - slot.offset = glm::uvec2{ static_cast(x), static_cast(y) }; - slot.size = glm::uvec2{ res, res }; + slot.uvOffset = glm::vec2{ static_cast(x) / atlasSize, static_cast(y) / atlasSize }; + slot.uvSize = glm::vec2{ static_cast(res) / atlasSize, static_cast(res) / atlasSize }; slot.valid = true; const glm::mat4 view = caster->GetShadowViewMatrix(); @@ -132,7 +132,7 @@ namespace sh::render viewer.projMatrix = proj; viewer.pos = caster->GetShadowPos(); viewer.to = caster->GetShadowLookAt(); - viewer.viewportRect = glm::uvec4{ slot.offset.x, slot.offset.y, slot.size.x, slot.size.y }; + viewer.viewportRect = glm::uvec4{ atlasSize * slot.uvOffset.x, atlasSize * slot.uvOffset.y, atlasSize * slot.uvSize.x, atlasSize * slot.uvSize.y }; viewer.viewportScissor = viewer.viewportRect; renderData.renderViewers.push_back(viewer); } From d95f373f54012812f734067e4c6dede9cdbf3fdb Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Thu, 7 May 2026 18:12:11 +0900 Subject: [PATCH 39/60] fix: Fix compilation errors on GCC --- src/Render/VulkanImpl/VulkanContext.cpp | 1 + src/Render/VulkanImpl/VulkanRenderer.cpp | 2 +- src/Sound/SoundSystem.cpp | 7 ++++++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Render/VulkanImpl/VulkanContext.cpp b/src/Render/VulkanImpl/VulkanContext.cpp index 0a5e6bfb..1387b34b 100644 --- a/src/Render/VulkanImpl/VulkanContext.cpp +++ b/src/Render/VulkanImpl/VulkanContext.cpp @@ -6,6 +6,7 @@ #include "VulkanCommandBuffer.h" #include "VulkanDescriptorPool.h" #include "VulkanPipelineManager.h" +#include "VulkanComputePipeline.h" #include "VulkanComputePipelineManager.h" #include "Core/Util.h" diff --git a/src/Render/VulkanImpl/VulkanRenderer.cpp b/src/Render/VulkanImpl/VulkanRenderer.cpp index 3a47b4e4..7c93bc33 100644 --- a/src/Render/VulkanImpl/VulkanRenderer.cpp +++ b/src/Render/VulkanImpl/VulkanRenderer.cpp @@ -112,7 +112,7 @@ namespace sh::render::vk return; } assert(result == VkResult::VK_SUCCESS); - core::ArrayView& renderDatas = IRenderThrMethod::GetRenderDatas(context->GetRenderDataManager()); + core::ArrayView renderDatas = IRenderThrMethod::GetRenderDatas(context->GetRenderDataManager()); std::size_t viewIdx = 0; for (RenderData& rd : renderDatas) { diff --git a/src/Sound/SoundSystem.cpp b/src/Sound/SoundSystem.cpp index 890bd188..8e266b3e 100644 --- a/src/Sound/SoundSystem.cpp +++ b/src/Sound/SoundSystem.cpp @@ -137,7 +137,12 @@ namespace sh::sound std::string requestedDevice{ deviceName }; impl->device = alcOpenDevice(requestedDevice.empty() ? nullptr : requestedDevice.c_str()); if (impl->device == nullptr) - throw std::runtime_error{ "Failed to open OpenAL device." }; + { + // 아마도 기본 사운드 시스템이 없는 경우임 + const ALenum err = alcGetError(nullptr); + SH_ERROR_FORMAT("Failed to open OpenAL device: {}", GetAlcErrorString(err)); + return; + } impl->context = alcCreateContext(impl->device, nullptr); if (impl->context == nullptr) From c1844c797e63466bee1fb3897d5c53a99c0eab26 Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Thu, 7 May 2026 18:59:06 +0900 Subject: [PATCH 40/60] refact: Add LightBase class that is base class for lights --- .../Game/Component/Render/DirectionalLight.h | 44 ++-------- include/Game/Component/Render/LightBase.h | 67 ++++++++++++++ include/Game/Component/Render/PointLight.h | 57 +++++------- include/Game/ILight.h | 8 +- include/Render/ShadowMapManager.h | 1 + .../Component/Render/DirectionalLight.cpp | 82 ++--------------- src/Game/Component/Render/LightBase.cpp | 88 +++++++++++++++++++ src/Game/Component/Render/PointLight.cpp | 58 +++--------- src/Render/ShaderParser.cpp | 2 +- 9 files changed, 207 insertions(+), 200 deletions(-) create mode 100644 include/Game/Component/Render/LightBase.h create mode 100644 src/Game/Component/Render/LightBase.cpp diff --git a/include/Game/Component/Render/DirectionalLight.h b/include/Game/Component/Render/DirectionalLight.h index 51ad1b52..781eaaa4 100644 --- a/include/Game/Component/Render/DirectionalLight.h +++ b/include/Game/Component/Render/DirectionalLight.h @@ -1,7 +1,6 @@ #pragma once #include "Game/Export.h" -#include "Game/ILight.h" -#include "Game/Component/Component.h" +#include "Game/Component/Render/LightBase.h" #include "Render/RenderData.h" #include "Render/ShadowMapManager.h" @@ -15,67 +14,34 @@ namespace sh::render namespace sh::game { - class DirectionalLight : public Component, public ILight, public render::IShadowCaster + class DirectionalLight : public LightBase { COMPONENT(DirectionalLight) public: SH_GAME_API DirectionalLight(GameObject& owner); SH_GAME_API ~DirectionalLight(); - SH_GAME_API void Awake() override; - SH_GAME_API void OnDestroy() override; - SH_GAME_API void OnPropertyChanged(const core::reflection::Property& prop) override; - - SH_GAME_API bool Intersect(const render::AABB& aabb) const override; + SH_GAME_API auto Intersect(const render::AABB& aabb) const -> bool override; SH_GAME_API void SetIntensity(float intensity) override; SH_GAME_API void SetDirection(const Vec3& dir); - SH_GAME_API void SetCastShadow(bool b) override; - SH_GAME_API void SetShadowBias(float bias) override; - SH_GAME_API void SetShadowMapResolution(uint32_t res) override; /// @brief 라이트의 광원 공간 변환 행렬 (proj * view)을 반환한다. SH_GAME_API auto GetLightSpaceMatrix() const -> glm::mat4 override; - /// @brief 그림자 아틀라스 텍스처를 반환한다 (모든 광원이 공유) - SH_GAME_API auto GetShadowMap() const -> render::RenderTexture*; - /// @brief 아틀라스 내에서 이 광원의 슬롯 정보를 반환한다. - SH_GAME_API auto GetShadowSlot() const -> render::ShadowMapManager::Slot; - - SH_GAME_API auto GetShadowMapResolution() const -> uint32_t override { return shadowMapResolution; } SH_GAME_API auto GetShadowViewMatrix() const -> glm::mat4 override; SH_GAME_API auto GetShadowProjMatrix() const -> glm::mat4 override; SH_GAME_API auto GetShadowPos() const -> glm::vec3 override; SH_GAME_API auto GetShadowLookAt() const -> glm::vec3 override; SH_GAME_API auto GetPos() const -> const Vec3& override; + auto GetLightType() const -> ILight::Type override { return ILight::Type::Directional; } - auto IsCastShadow() const -> bool override { return bCastShadow; } - auto GetShadowBias() const -> float override { return shadowBias; } + auto GetIntensity() const -> float override { return intensity; } auto GetDirection() const -> const Vec3& { return direction; } - auto GetIntensity() const -> float { return intensity; } - private: - /// @brief bCastShadow 변경 시 월드의 ShadowMapManager에 등록/해제한다. - void RegisterToShadowManager(); - void UnregisterFromShadowManager(); private: PROPERTY(direction) Vec3 direction{ -1.f, -1.f, -1.f }; PROPERTY(intensity) float intensity = 1.f; - - PROPERTY(bCastShadow) - bool bCastShadow = false; - PROPERTY(shadowBias) - float shadowBias = 0.005f; - PROPERTY(shadowMapResolution) - uint32_t shadowMapResolution = 1024; - PROPERTY(shadowOrthoSize) - float shadowOrthoSize = 20.f; - PROPERTY(shadowNearPlane) - float shadowNearPlane = 0.1f; - PROPERTY(shadowFarPlane) - float shadowFarPlane = 100.f; - - bool bRegistered = false; }; }//namespace diff --git a/include/Game/Component/Render/LightBase.h b/include/Game/Component/Render/LightBase.h new file mode 100644 index 00000000..d33e3520 --- /dev/null +++ b/include/Game/Component/Render/LightBase.h @@ -0,0 +1,67 @@ +#pragma once +#include "Game/Export.h" +#include "Game/Component/Component.h" +#include "Game/ILight.h" + +#include "Render/ShadowMapManager.h" + +namespace sh::game +{ + /// @brief 광원 컴포넌트 추상 클래스 + class LightBase : public Component, public ILight, public render::IShadowCaster + { + SCLASS(LightBase) + public: + SH_GAME_API LightBase(GameObject& owner); + + SH_GAME_API void Awake() override; + SH_GAME_API void OnDestroy() override; + SH_GAME_API void OnEnable() override; + SH_GAME_API void OnDisable() override; + SH_GAME_API void OnPropertyChanged(const core::reflection::Property& prop) override; + + SH_GAME_API void SetCastShadow(bool bCastShadow); + + /// @brief 그림자 아틀라스 텍스처를 반환한다 (모든 광원이 공유) + SH_GAME_API auto GetShadowMap() const -> render::RenderTexture*; + /// @brief 아틀라스 내에서 이 광원의 슬롯 정보를 반환한다. + SH_GAME_API auto GetShadowSlot() const -> render::ShadowMapManager::Slot; + + virtual void SetIntensity(float intensity) = 0; + virtual auto GetIntensity() const -> float = 0; + virtual auto GetLightType() const -> Type = 0; + virtual auto GetLightSpaceMatrix() const -> glm::mat4 = 0; + + virtual auto GetShadowBias() const -> float { return shadowBias; } + virtual auto GetShadowMapResolution() const -> uint32_t { return shadowMapResolution; } + virtual auto GetShadowViewMatrix() const -> glm::mat4 = 0; + virtual auto GetShadowProjMatrix() const -> glm::mat4 = 0; + virtual auto GetShadowPos() const -> glm::vec3 = 0; + virtual auto GetShadowLookAt() const -> glm::vec3 = 0; + + void SetShadowBias(float bias) { shadowBias = bias; } + void SetShadowMapResolution(uint32_t res) { shadowMapResolution = res; } + auto IsCastShadow() const -> bool { return bCastShadow; } + auto GetShadowOrthoSize() const -> float { return shadowOrthoSize; } + auto GetShadowNearPlane() const -> float { return shadowNearPlane; } + auto GetShadowFarPlane() const -> float { return shadowFarPlane; } + protected: + SH_GAME_API void UpdateLightOctree(); + SH_GAME_API void RegisterToShadowManager(); + SH_GAME_API void UnregisterFromShadowManager(); + private: + PROPERTY(shadowBias) + float shadowBias = 0.005f; + PROPERTY(shadowMapResolution) + uint32_t shadowMapResolution = 1024; + PROPERTY(shadowOrthoSize) + float shadowOrthoSize = 20.f; + PROPERTY(shadowNearPlane) + float shadowNearPlane = 0.1f; + PROPERTY(shadowFarPlane) + float shadowFarPlane = 100.f; + PROPERTY(bCastShadow) + bool bCastShadow = false; + bool bRegistered = false; + }; +}//namespace \ No newline at end of file diff --git a/include/Game/Component/Render/PointLight.h b/include/Game/Component/Render/PointLight.h index 47ae599b..dbb311df 100644 --- a/include/Game/Component/Render/PointLight.h +++ b/include/Game/Component/Render/PointLight.h @@ -1,54 +1,45 @@ #pragma once #include "Game/Export.h" -#include "Game/ILight.h" -#include "Game/Component/Component.h" +#include "Game/Component/Render/LightBase.h" namespace sh::game { - class PointLight : public Component, public ILight + class PointLight : public LightBase { COMPONENT(PointLight) - private: - PROPERTY(range) - float range = 5.f; - PROPERTY(intensity) - float intensity = 1.f; - - PROPERTY(bCastShadow) - bool bCastShadow = false; - PROPERTY(shadowBias) - float shadowBias = 0.005f; - PROPERTY(shadowMapResolution) - uint32_t shadowMapResolution = 512; - - Vec3 lastPos{ 0.f, 0.f, 0.f }; - bool bUpdateOctree = false; public: SH_GAME_API PointLight(GameObject& owner); SH_GAME_API ~PointLight(); - SH_GAME_API void OnDestroy() override; - SH_GAME_API void BeginUpdate() override; - SH_GAME_API bool Intersect(const render::AABB& aabb) const override; + SH_GAME_API auto Intersect(const render::AABB& aabb) const -> bool override; SH_GAME_API void SetRadius(float radius); - SH_GAME_API void SetIntensity(float intensity); - SH_GAME_API auto GetRadius() const -> float; - SH_GAME_API auto GetIntensity() const -> float; - SH_GAME_API auto GetPos() const -> const Vec3& override; - SH_GAME_API auto GetLightType() const -> ILight::Type override; - SH_GAME_API auto GetLightSpaceMatrix() const->glm::mat4 override; + /// @brief 라이트의 광원 공간 변환 행렬 (proj * view)을 반환한다. + SH_GAME_API auto GetLightSpaceMatrix() const -> glm::mat4 override; - SH_GAME_API auto IsCastShadow() const -> bool override; - SH_GAME_API void SetCastShadow(bool b) override; - SH_GAME_API auto GetShadowBias() const -> float override; - SH_GAME_API void SetShadowBias(float bias) override; - SH_GAME_API auto GetShadowMapResolution() const -> uint32_t override; - SH_GAME_API void SetShadowMapResolution(uint32_t res) override; + SH_GAME_API auto GetShadowViewMatrix() const -> glm::mat4 override; + SH_GAME_API auto GetShadowProjMatrix() const -> glm::mat4 override; + SH_GAME_API auto GetShadowPos() const -> glm::vec3 override; + SH_GAME_API auto GetShadowLookAt() const -> glm::vec3 override; + + SH_GAME_API auto GetPos() const -> const Vec3& override; SH_GAME_API void OnPropertyChanged(const core::reflection::Property& prop) override; + + void SetIntensity(float intensity) override { this->intensity = intensity; } + auto GetIntensity() const -> float override { return intensity; } + auto GetLightType() const -> ILight::Type override { return ILight::Type::Point; } + auto GetRadius() const -> float { return range; } + private: + PROPERTY(range) + float range = 5.f; + PROPERTY(intensity) + float intensity = 1.f; + + Vec3 lastPos{ 0.f, 0.f, 0.f }; + bool bUpdateOctree = false; }; }//namespace \ No newline at end of file diff --git a/include/Game/ILight.h b/include/Game/ILight.h index f4706817..91d39105 100644 --- a/include/Game/ILight.h +++ b/include/Game/ILight.h @@ -21,14 +21,8 @@ namespace sh::game public: virtual ~ILight() {}; virtual void SetIntensity(float intensity) = 0; + virtual auto GetIntensity() const -> float = 0; virtual auto GetLightType() const -> Type = 0; virtual auto GetLightSpaceMatrix() const -> glm::mat4 = 0; - - virtual auto IsCastShadow() const -> bool { return false; } - virtual void SetCastShadow(bool b) {} - virtual auto GetShadowBias() const -> float { return 0.005f; } - virtual void SetShadowBias(float bias) {} - virtual auto GetShadowMapResolution() const -> uint32_t { return 1024; } - virtual void SetShadowMapResolution(uint32_t res) {} }; }//namespace diff --git a/include/Render/ShadowMapManager.h b/include/Render/ShadowMapManager.h index 0f410a8d..6ff10857 100644 --- a/include/Render/ShadowMapManager.h +++ b/include/Render/ShadowMapManager.h @@ -24,6 +24,7 @@ namespace sh::render { public: virtual ~IShadowCaster() = default; + virtual auto GetShadowBias() const -> float = 0; virtual auto GetShadowMapResolution() const -> uint32_t = 0; virtual auto GetShadowViewMatrix() const -> glm::mat4 = 0; virtual auto GetShadowProjMatrix() const -> glm::mat4 = 0; diff --git a/src/Game/Component/Render/DirectionalLight.cpp b/src/Game/Component/Render/DirectionalLight.cpp index a9ad2258..2fded976 100644 --- a/src/Game/Component/Render/DirectionalLight.cpp +++ b/src/Game/Component/Render/DirectionalLight.cpp @@ -13,40 +13,14 @@ namespace sh::game { DirectionalLight::DirectionalLight(GameObject& owner) : - Component(owner) + LightBase(owner) { - world.GetLightOctree().Insert(*this); - canPlayInEditor = true; } DirectionalLight::~DirectionalLight() = default; - SH_GAME_API void DirectionalLight::Awake() - { - Super::Awake(); - if (bCastShadow) - RegisterToShadowManager(); - } - - SH_GAME_API void DirectionalLight::OnDestroy() - { - UnregisterFromShadowManager(); - world.GetLightOctree().Erase(*this); - Super::OnDestroy(); - } - - SH_GAME_API void DirectionalLight::OnPropertyChanged(const core::reflection::Property& prop) - { - if (prop.GetName() == "bCastShadow") - { - if (bCastShadow) - RegisterToShadowManager(); - else - UnregisterFromShadowManager(); - } - } - SH_GAME_API bool DirectionalLight::Intersect(const render::AABB& aabb) const { + // 항상 통과 return true; } SH_GAME_API void DirectionalLight::SetIntensity(float intensity) @@ -57,59 +31,30 @@ namespace sh::game { direction = dir; } - SH_GAME_API void DirectionalLight::SetCastShadow(bool b) - { - if (bCastShadow == b) - return; - bCastShadow = b; - if (bCastShadow) - RegisterToShadowManager(); - else - UnregisterFromShadowManager(); - } - SH_GAME_API void DirectionalLight::SetShadowBias(float bias) - { - shadowBias = bias; - } - SH_GAME_API void DirectionalLight::SetShadowMapResolution(uint32_t res) - { - shadowMapResolution = res; - } SH_GAME_API auto DirectionalLight::GetLightSpaceMatrix() const -> glm::mat4 { return GetShadowProjMatrix() * GetShadowViewMatrix(); } - SH_GAME_API auto DirectionalLight::GetShadowMap() const -> render::RenderTexture* - { - if (!bCastShadow) - return nullptr; - return world.GetShadowMapManager().GetAtlas(); - } - SH_GAME_API auto DirectionalLight::GetShadowSlot() const -> render::ShadowMapManager::Slot - { - if (!bCastShadow) - return render::ShadowMapManager::Slot{}; - return world.GetShadowMapManager().GetSlot(*this); - } SH_GAME_API auto DirectionalLight::GetShadowViewMatrix() const -> glm::mat4 { const glm::vec3 dir = glm::normalize(glm::vec3{ direction.x, direction.y, direction.z }); const glm::vec3 sceneCenter{ 0.f, 0.f, 0.f }; - const float distance = shadowFarPlane * 0.5f; + const float distance = GetShadowFarPlane() * 0.5f; const glm::vec3 lightPos = sceneCenter - dir * distance; const glm::vec3 worldUp = (glm::abs(dir.y) > 0.99f) ? glm::vec3{ 0.f, 0.f, 1.f } : glm::vec3{ 0.f, 1.f, 0.f }; return glm::lookAt(lightPos, lightPos + dir, worldUp); } SH_GAME_API auto DirectionalLight::GetShadowProjMatrix() const -> glm::mat4 { - return glm::orthoRH_ZO(-shadowOrthoSize, shadowOrthoSize, -shadowOrthoSize, shadowOrthoSize, shadowNearPlane, shadowFarPlane); + const float ortho = GetShadowOrthoSize(); + return glm::orthoRH_ZO(-ortho, ortho, -ortho, ortho, GetShadowNearPlane(), GetShadowFarPlane()); } SH_GAME_API auto DirectionalLight::GetShadowPos() const -> glm::vec3 { const glm::vec3 dir = glm::normalize(glm::vec3{ direction.x, direction.y, direction.z }); const glm::vec3 sceneCenter{ 0.f, 0.f, 0.f }; - const float distance = shadowFarPlane * 0.5f; + const float distance = GetShadowFarPlane() * 0.5f; return sceneCenter - dir * distance; } SH_GAME_API auto DirectionalLight::GetShadowLookAt() const -> glm::vec3 @@ -120,19 +65,4 @@ namespace sh::game { return gameObject.transform->GetWorldPosition(); } - - void DirectionalLight::RegisterToShadowManager() - { - if (bRegistered) - return; - world.GetShadowMapManager().Register(*this); - bRegistered = true; - } - void DirectionalLight::UnregisterFromShadowManager() - { - if (!bRegistered) - return; - world.GetShadowMapManager().Unregister(*this); - bRegistered = false; - } }//namespace diff --git a/src/Game/Component/Render/LightBase.cpp b/src/Game/Component/Render/LightBase.cpp new file mode 100644 index 00000000..101f9016 --- /dev/null +++ b/src/Game/Component/Render/LightBase.cpp @@ -0,0 +1,88 @@ +#include "Game/Component/Render/LightBase.h" +#include "Game/World.h" + +namespace sh::game +{ + LightBase::LightBase(GameObject& owner) : + Component(owner) + { + canPlayInEditor = true; + } + SH_GAME_API void LightBase::Awake() + { + Super::Awake(); + if (bCastShadow) + RegisterToShadowManager(); + world.GetLightOctree().Insert(*this); + } + SH_GAME_API void LightBase::OnDestroy() + { + UnregisterFromShadowManager(); + world.GetLightOctree().Erase(*this); + Super::OnDestroy(); + } + SH_GAME_API void LightBase::OnEnable() + { + if (bCastShadow) + RegisterToShadowManager(); + world.GetLightOctree().Insert(*this); + } + SH_GAME_API void LightBase::OnDisable() + { + UnregisterFromShadowManager(); + world.GetLightOctree().Erase(*this); + } + SH_GAME_API void LightBase::OnPropertyChanged(const core::reflection::Property& prop) + { + Super::OnPropertyChanged(prop); + if (prop.GetName() == "bCastShadow") + { + if (bCastShadow) + RegisterToShadowManager(); + else + UnregisterFromShadowManager(); + } + } + SH_GAME_API void LightBase::SetCastShadow(bool _bCastShadow) + { + if (bCastShadow == _bCastShadow) + return; + bCastShadow = _bCastShadow; + if (bCastShadow) + RegisterToShadowManager(); + else + UnregisterFromShadowManager(); + } + SH_GAME_API auto LightBase::GetShadowMap() const -> render::RenderTexture* + { + if (!bCastShadow) + return nullptr; + return world.GetShadowMapManager().GetAtlas(); + } + SH_GAME_API auto LightBase::GetShadowSlot() const -> render::ShadowMapManager::Slot + { + if (!bCastShadow) + return render::ShadowMapManager::Slot{}; + return world.GetShadowMapManager().GetSlot(*this); + } + SH_GAME_API void LightBase::UpdateLightOctree() + { + world.GetLightOctree().Erase(*this); + world.GetLightOctree().Insert(*this); + } + SH_GAME_API void LightBase::RegisterToShadowManager() + { + if (bRegistered) + return; + world.GetShadowMapManager().Register(*this); + bRegistered = true; + } + + SH_GAME_API void LightBase::UnregisterFromShadowManager() + { + if (!bRegistered) + return; + world.GetShadowMapManager().Unregister(*this); + bRegistered = false; + } +}//namespace \ No newline at end of file diff --git a/src/Game/Component/Render/PointLight.cpp b/src/Game/Component/Render/PointLight.cpp index ace324fa..14395a4f 100644 --- a/src/Game/Component/Render/PointLight.cpp +++ b/src/Game/Component/Render/PointLight.cpp @@ -5,21 +5,13 @@ namespace sh::game { SH_GAME_API PointLight::PointLight(GameObject& owner) : - Component(owner) + LightBase(owner) { - world.GetLightOctree().Insert(*this); - canPlayInEditor = true; } SH_GAME_API PointLight::~PointLight() { } - SH_GAME_API void PointLight::OnDestroy() - { - Super::OnDestroy(); - world.GetLightOctree().Erase(*this); - } - SH_GAME_API void PointLight::BeginUpdate() { if (lastPos != gameObject.transform->position) @@ -27,8 +19,7 @@ namespace sh::game if (bUpdateOctree) { - world.GetLightOctree().Erase(*this); - world.GetLightOctree().Insert(*this); + UpdateLightOctree(); } } @@ -83,60 +74,39 @@ namespace sh::game this->range = radius; bUpdateOctree = true; } - SH_GAME_API void PointLight::SetIntensity(float intensity) - { - this->intensity = intensity; - } - SH_GAME_API auto PointLight::GetRadius() const -> float - { - return range; - } - SH_GAME_API auto PointLight::GetIntensity() const -> float - { - return intensity; - } SH_GAME_API auto PointLight::GetPos() const -> const Vec3& { return gameObject.transform->GetWorldPosition(); } - SH_GAME_API auto PointLight::GetLightType() const -> ILight::Type - { - return ILight::Type::Point; - } - SH_GAME_API auto PointLight::GetLightSpaceMatrix() const -> glm::mat4 { return glm::mat4{ 1.f }; } - SH_GAME_API auto PointLight::IsCastShadow() const -> bool - { - return bCastShadow; - } - SH_GAME_API void PointLight::SetCastShadow(bool b) - { - bCastShadow = b; - } - SH_GAME_API auto PointLight::GetShadowBias() const -> float + SH_GAME_API auto PointLight::GetShadowViewMatrix() const -> glm::mat4 { - return shadowBias; + return glm::mat4{ 1.f }; } - SH_GAME_API void PointLight::SetShadowBias(float bias) + + SH_GAME_API auto PointLight::GetShadowProjMatrix() const -> glm::mat4 { - shadowBias = bias; + return glm::mat4{ 1.f }; } - SH_GAME_API auto PointLight::GetShadowMapResolution() const -> uint32_t + + SH_GAME_API auto PointLight::GetShadowPos() const -> glm::vec3 { - return shadowMapResolution; + return glm::vec3{ 0.f }; } - SH_GAME_API void PointLight::SetShadowMapResolution(uint32_t res) + + SH_GAME_API auto PointLight::GetShadowLookAt() const -> glm::vec3 { - shadowMapResolution = res; + return glm::vec3{ 0.f }; } SH_GAME_API void PointLight::OnPropertyChanged(const core::reflection::Property& prop) { + Super::OnPropertyChanged(prop); if (prop.GetName() == "range") { world.GetLightOctree().Erase(*this); diff --git a/src/Render/ShaderParser.cpp b/src/Render/ShaderParser.cpp index 78b48da2..d21a0f86 100644 --- a/src/Render/ShaderParser.cpp +++ b/src/Render/ShaderParser.cpp @@ -718,7 +718,7 @@ namespace sh::render structNode.name = "Light"; structNode.vars.push_back(ShaderAST::VariableNode{ ShaderAST::VariableType::Vec4, 1, "pos" }); structNode.vars.push_back(ShaderAST::VariableNode{ ShaderAST::VariableType::Vec4, 1, "other" }); - structNode.vars.push_back(ShaderAST::VariableNode{ ShaderAST::VariableType::Vec4, 1, "shadow" }); + structNode.vars.push_back(ShaderAST::VariableNode{ ShaderAST::VariableType::Vec4, 1, "shadowRect" }); structNode.vars.push_back(ShaderAST::VariableNode{ ShaderAST::VariableType::Mat4, 1, "lightSpaceMatrix" }); ShaderAST::BufferNode& ssboNode = stageNode.buffers.emplace_back(); From 207b3dfd20b6181092d681320e0f28c7493a7f84 Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Thu, 7 May 2026 19:04:45 +0900 Subject: [PATCH 41/60] build: Change default project template --- .../ProjectTemplate/Assets/SampleWorld.world | 396 ++++++++++++++++++ .../{SampleWorld => }/SampleWorld.world.meta | 2 +- .../{assets => }/DefaultMaterial.mat | 6 +- .../{assets => }/DefaultMaterial.mat.meta | 2 +- .../Assets/SampleWorld/SampleWorld.world | 235 ----------- .../SampleWorld/{assets => }/Text.shader | 0 .../SampleWorld/{assets => }/Text.shader.meta | 0 .../Tiles107_2K-JPG_AmbientOcclusion.jpg | Bin .../Tiles107_2K-JPG_AmbientOcclusion.jpg.meta | 2 +- .../{assets => }/Tiles107_2K-JPG_NormalDX.jpg | Bin .../Tiles107_2K-JPG_NormalDX.jpg.meta | 2 +- .../SampleWorld/{assets => }/Unlit.shader | 0 .../{assets => }/Unlit.shader.meta | 0 .../Assets/SampleWorld/assets/default.shader | 77 ---- .../Assets/SampleWorld/default.shader | 137 ++++++ .../{assets => }/default.shader.meta | 0 .../Assets/SampleWorld/skinned.shader | 71 ++++ .../Assets/SampleWorld/skinned.shader.meta | 6 + 18 files changed, 618 insertions(+), 318 deletions(-) create mode 100644 resource/ProjectTemplate/Assets/SampleWorld.world rename resource/ProjectTemplate/Assets/{SampleWorld => }/SampleWorld.world.meta (79%) rename resource/ProjectTemplate/Assets/SampleWorld/{assets => }/DefaultMaterial.mat (82%) rename resource/ProjectTemplate/Assets/SampleWorld/{assets => }/DefaultMaterial.mat.meta (77%) delete mode 100644 resource/ProjectTemplate/Assets/SampleWorld/SampleWorld.world rename resource/ProjectTemplate/Assets/SampleWorld/{assets => }/Text.shader (100%) rename resource/ProjectTemplate/Assets/SampleWorld/{assets => }/Text.shader.meta (100%) rename resource/ProjectTemplate/Assets/SampleWorld/{assets => }/Tiles107_2K-JPG_AmbientOcclusion.jpg (100%) rename resource/ProjectTemplate/Assets/SampleWorld/{assets => }/Tiles107_2K-JPG_AmbientOcclusion.jpg.meta (92%) rename resource/ProjectTemplate/Assets/SampleWorld/{assets => }/Tiles107_2K-JPG_NormalDX.jpg (100%) rename resource/ProjectTemplate/Assets/SampleWorld/{assets => }/Tiles107_2K-JPG_NormalDX.jpg.meta (92%) rename resource/ProjectTemplate/Assets/SampleWorld/{assets => }/Unlit.shader (100%) rename resource/ProjectTemplate/Assets/SampleWorld/{assets => }/Unlit.shader.meta (100%) delete mode 100644 resource/ProjectTemplate/Assets/SampleWorld/assets/default.shader create mode 100644 resource/ProjectTemplate/Assets/SampleWorld/default.shader rename resource/ProjectTemplate/Assets/SampleWorld/{assets => }/default.shader.meta (100%) create mode 100644 resource/ProjectTemplate/Assets/SampleWorld/skinned.shader create mode 100644 resource/ProjectTemplate/Assets/SampleWorld/skinned.shader.meta diff --git a/resource/ProjectTemplate/Assets/SampleWorld.world b/resource/ProjectTemplate/Assets/SampleWorld.world new file mode 100644 index 00000000..6e8011e1 --- /dev/null +++ b/resource/ProjectTemplate/Assets/SampleWorld.world @@ -0,0 +1,396 @@ +{ + "cam": { + "Camera": { + "farPlane": 10000.0, + "fov": 60.0, + "lookPos": [ + -0.13613072037696838, + -0.5873733162879944, + -0.809805154800415 + ], + "nearPlane": 0.10000000149011612, + "priority": 0, + "projection": 0, + "renderTexture": "180635b4e4d1a98ebb0064ab47dc452a" + }, + "Component": { + "hideInspector": false, + "priority": 0 + }, + "EditorCamera": { + "distance": 9.383123397827148 + }, + "SObject": null, + "fullType": "sh::game::EditorCamera", + "name": "EditorCamera", + "type": "EditorCamera", + "uuid": "849679621b2c3878ef538c9b069b0ffc", + "version": 1 + }, + "camPos": [ + 0.19899359345436096, + 5.197760581970215, + 6.570095062255859 + ], + "editorObjs": null, + "name": "World", + "objs": [ + { + "Components": [ + { + "Component": { + "hideInspector": false, + "priority": 0 + }, + "SObject": null, + "Transform": { + "quat": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "vPosition": [ + 0.0, + 0.0, + 0.0 + ], + "vRotation": [ + 0.0, + 0.0, + 0.0 + ], + "vScale": [ + 5.0, + 5.0, + 5.0 + ] + }, + "fullType": "sh::game::Transform", + "name": "Unknown", + "type": "Transform", + "uuid": "8467e3e80fc4f2ee568a6397bed89eb4", + "version": 1 + }, + { + "Component": { + "hideInspector": false, + "priority": 0 + }, + "MeshRenderer": { + "mats": [ + "ed351337b72281ef5f457457b65b8060" + ], + "mesh": "bbc4ef7ec45dce223297a224f8093f21", + "renderTag": 1 + }, + "SObject": null, + "fullType": "sh::game::MeshRenderer", + "name": "MeshRenderer", + "type": "MeshRenderer", + "uuid": "48b1921fcf12e85469036c4404a65f15", + "version": 1 + } + ], + "GameObject": { + "bEditorOnly": false, + "bEnable": true, + "bStatic": false, + "hideInspector": false + }, + "name": "Quad", + "type": "GameObject", + "uuid": "c84b38fb3db87ac051ac5d0b797443c1", + "version": 1 + }, + { + "Components": [ + { + "Component": { + "hideInspector": false, + "priority": 0 + }, + "SObject": null, + "Transform": { + "quat": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "vPosition": [ + 0.0, + 0.5, + 0.0 + ], + "vRotation": [ + 0.0, + -0.0, + 0.0 + ], + "vScale": [ + 1.0, + 1.0, + 1.0 + ] + }, + "fullType": "sh::game::Transform", + "name": "Unknown", + "type": "Transform", + "uuid": "61353cbcb255ce1e1fa35b7e8aa364ad", + "version": 1 + }, + { + "Component": { + "hideInspector": false, + "priority": 0 + }, + "MeshRenderer": { + "mats": [ + "ed351337b72281ef5f457457b65b8060" + ], + "mesh": "bbc4ef7ec45dce223297a224f8093f20", + "renderTag": 1 + }, + "SObject": null, + "fullType": "sh::game::MeshRenderer", + "name": "MeshRenderer", + "type": "MeshRenderer", + "uuid": "6bf0748332a42bec3a7006ea08c3a7fb", + "version": 1 + } + ], + "GameObject": { + "bEditorOnly": false, + "bEnable": true, + "bStatic": false, + "hideInspector": false + }, + "name": "Cube", + "type": "GameObject", + "uuid": "647942f843f96152cce200a1435da773", + "version": 1 + }, + { + "Components": [ + { + "Component": { + "hideInspector": false, + "priority": 0 + }, + "SObject": null, + "Transform": { + "quat": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "vPosition": [ + 0.0, + 0.0, + 0.0 + ], + "vRotation": [ + 0.0, + 0.0, + 0.0 + ], + "vScale": [ + 1.0, + 1.0, + 1.0 + ] + }, + "fullType": "sh::game::Transform", + "name": "Unknown", + "type": "Transform", + "uuid": "6de53e419d723c382dd4da942d436a1a", + "version": 1 + }, + { + "Component": { + "hideInspector": false, + "priority": 0 + }, + "DirectionalLight": { + "direction": [ + -1.0, + -1.0, + -1.0 + ], + "intensity": 1.0 + }, + "LightBase": { + "bCastShadow": true, + "shadowBias": 0.004999999888241291, + "shadowFarPlane": 100.0, + "shadowMapResolution": 1024, + "shadowNearPlane": 0.10000000149011612, + "shadowOrthoSize": 20.0 + }, + "SObject": null, + "fullType": "sh::game::DirectionalLight", + "name": "DirectionalLight", + "type": "DirectionalLight", + "uuid": "0db6d7a50692bfccdb72c587d78dabc7", + "version": 1 + } + ], + "GameObject": { + "bEditorOnly": false, + "bEnable": true, + "bStatic": false, + "hideInspector": false + }, + "name": "Light", + "type": "GameObject", + "uuid": "6ceb7e7f4dbbc7be3314cda5662544b5", + "version": 1 + }, + { + "Components": [ + { + "Component": { + "hideInspector": false, + "priority": 0 + }, + "SObject": null, + "Transform": { + "quat": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "vPosition": [ + -2.0, + 1.0, + -2.0 + ], + "vRotation": [ + 0.0, + -0.0, + 0.0 + ], + "vScale": [ + 1.0, + 2.0, + 1.0 + ] + }, + "fullType": "sh::game::Transform", + "name": "Unknown", + "type": "Transform", + "uuid": "02ca20a96bfc8bdb056cdeb4708f91fa", + "version": 1 + }, + { + "Component": { + "hideInspector": false, + "priority": 0 + }, + "MeshRenderer": { + "mats": [ + "ed351337b72281ef5f457457b65b8060" + ], + "mesh": "bbc4ef7ec45dce223297a224f8093f20", + "renderTag": 1 + }, + "SObject": null, + "fullType": "sh::game::MeshRenderer", + "name": "MeshRenderer", + "type": "MeshRenderer", + "uuid": "ed3d01dc72d0cf8ac2de51961d967c87", + "version": 1 + } + ], + "GameObject": { + "bEditorOnly": false, + "bEnable": true, + "bStatic": false, + "hideInspector": false + }, + "name": "Cube", + "type": "GameObject", + "uuid": "12ff273a34e3102c74d1f52f0d17d653", + "version": 1 + }, + { + "Components": [ + { + "Component": { + "hideInspector": false, + "priority": 0 + }, + "SObject": null, + "Transform": { + "quat": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "vPosition": [ + 4.0, + 4.0, + 4.0 + ], + "vRotation": [ + 0.0, + 0.0, + 0.0 + ], + "vScale": [ + 1.0, + 1.0, + 1.0 + ] + }, + "fullType": "sh::game::Transform", + "name": "Unknown", + "type": "Transform", + "uuid": "971f215a0d326bdec2b0f32277ccdb3d", + "version": 1 + }, + { + "Camera": { + "farPlane": 1000.0, + "fov": 60.0, + "lookPos": [ + 0.0, + 0.0, + 0.0 + ], + "nearPlane": 0.10000000149011612, + "priority": 0, + "projection": 0 + }, + "Component": { + "hideInspector": false, + "priority": 0 + }, + "SObject": null, + "fullType": "sh::game::Camera", + "name": "Camera", + "type": "Camera", + "uuid": "42dd416bc98175fdc72f01bfecc37973", + "version": 1 + } + ], + "GameObject": { + "bEditorOnly": false, + "bEnable": true, + "bStatic": false, + "hideInspector": false + }, + "name": "Camera", + "type": "GameObject", + "uuid": "30cc3381a4934bedcce6082de63ae3f4", + "version": 1 + } + ], + "type": "EditorWorld", + "uuid": "12c5133bfc598964668e5266f0f74a03", + "version": 1 +} \ No newline at end of file diff --git a/resource/ProjectTemplate/Assets/SampleWorld/SampleWorld.world.meta b/resource/ProjectTemplate/Assets/SampleWorld.world.meta similarity index 79% rename from resource/ProjectTemplate/Assets/SampleWorld/SampleWorld.world.meta rename to resource/ProjectTemplate/Assets/SampleWorld.world.meta index 3f66c60b..88c7fb58 100644 --- a/resource/ProjectTemplate/Assets/SampleWorld/SampleWorld.world.meta +++ b/resource/ProjectTemplate/Assets/SampleWorld.world.meta @@ -1,5 +1,5 @@ { - "name": "World", + "name": "SampleWorld", "typeHash": 3952108875956490026, "uuid": "c3a4ec39f722e6d2704dceb7afbdc3c8", "version": 1 diff --git a/resource/ProjectTemplate/Assets/SampleWorld/assets/DefaultMaterial.mat b/resource/ProjectTemplate/Assets/SampleWorld/DefaultMaterial.mat similarity index 82% rename from resource/ProjectTemplate/Assets/SampleWorld/assets/DefaultMaterial.mat rename to resource/ProjectTemplate/Assets/SampleWorld/DefaultMaterial.mat index 16a38e04..8c69e234 100644 --- a/resource/ProjectTemplate/Assets/SampleWorld/assets/DefaultMaterial.mat +++ b/resource/ProjectTemplate/Assets/SampleWorld/DefaultMaterial.mat @@ -1,13 +1,15 @@ { "Constant": [ + [], [] ], "Material": { "shader": "0131fbb6184d3156cf10504d1df27929" }, "MaterialPropertyBlock": { + "matrix": null, "scalar": { - "ambient": 0.019999999552965164 + "ambient": 0.0 }, "textures": { "normalTex": "05d320be6d0c3a57ec078815a45cd58b", @@ -15,7 +17,7 @@ }, "vector": null }, - "name": "NewMaterial.mat", + "name": "DefaultMaterial", "type": "Material", "uuid": "ed351337b72281ef5f457457b65b8060", "version": 1 diff --git a/resource/ProjectTemplate/Assets/SampleWorld/assets/DefaultMaterial.mat.meta b/resource/ProjectTemplate/Assets/SampleWorld/DefaultMaterial.mat.meta similarity index 77% rename from resource/ProjectTemplate/Assets/SampleWorld/assets/DefaultMaterial.mat.meta rename to resource/ProjectTemplate/Assets/SampleWorld/DefaultMaterial.mat.meta index af7fa432..18bbf877 100644 --- a/resource/ProjectTemplate/Assets/SampleWorld/assets/DefaultMaterial.mat.meta +++ b/resource/ProjectTemplate/Assets/SampleWorld/DefaultMaterial.mat.meta @@ -1,5 +1,5 @@ { - "name": "NewMaterial.mat", + "name": "DefaultMaterial", "typeHash": 14895272822536500559, "uuid": "ed351337b72281ef5f457457b65b8060", "version": 1 diff --git a/resource/ProjectTemplate/Assets/SampleWorld/SampleWorld.world b/resource/ProjectTemplate/Assets/SampleWorld/SampleWorld.world deleted file mode 100644 index 0b4250d8..00000000 --- a/resource/ProjectTemplate/Assets/SampleWorld/SampleWorld.world +++ /dev/null @@ -1,235 +0,0 @@ -{ - "cam": { - "Camera": { - "depth": 0, - "fov": 60.0, - "lookPos": [ - 0.0, - 0.0, - 0.0 - ], - "projection": 0, - "renderTexture": "180635b4e4d1a98ebb0064ab47dc452a" - }, - "Component": { - "hideInspector": false, - "priority": 0 - }, - "EditorCamera": { - "distance": 7.244863510131836 - }, - "SObject": {}, - "name": "EditorCamera", - "type": "EditorCamera", - "uuid": "49b6e267952bf7cbfe326fe9d8017299", - "version": 1 - }, - "camPos": [ - 2.2565512657165527, - 4.759199619293213, - 4.974539279937744 - ], - "editorObjs": [], - "name": "World", - "objs": [ - { - "Components": [ - { - "Component": { - "hideInspector": false, - "priority": 0 - }, - "SObject": {}, - "Transform": { - "quat": [ - 0.0, - 0.0, - 0.0, - 1.0 - ], - "vPosition": [ - 0.0, - 0.0, - 0.0 - ], - "vRotation": [ - 0.0, - 0.0, - 0.0 - ], - "vScale": [ - 5.0, - 1.0, - 5.0 - ] - }, - "name": "Unknown", - "type": "Transform", - "uuid": "b44e6e7898be2c02285711b9fdd04c4a", - "version": 1 - }, - { - "Component": { - "hideInspector": false, - "priority": 0 - }, - "MeshRenderer": { - "mat": "ed351337b72281ef5f457457b65b8060", - "mesh": "bbc4ef7ec45dce223297a224f8093f20", - "renderTag": 1 - }, - "SObject": {}, - "name": "MeshRenderer", - "type": "MeshRenderer", - "uuid": "b2a3ec6fa22673f1d505269011493b71", - "version": 1 - } - ], - "GameObject": { - "bEditorOnly": false, - "bEnable": true, - "bStatic": false, - "hideInspector": false - }, - "name": "Cube", - "type": "GameObject", - "uuid": "01aa951d8dafee78706b05d275f48b08", - "version": 1 - }, - { - "Components": [ - { - "Component": { - "hideInspector": false, - "priority": 0 - }, - "SObject": {}, - "Transform": { - "quat": [ - 0.0, - 0.0, - 0.0, - 1.0 - ], - "vPosition": [ - 0.0, - 2.0, - 0.0 - ], - "vRotation": [ - 0.0, - 0.0, - 0.0 - ], - "vScale": [ - 1.0, - 1.0, - 1.0 - ] - }, - "name": "Unknown", - "type": "Transform", - "uuid": "e606e7a9aaf0006dfcbde7cf9aa5e2b9", - "version": 1 - }, - { - "Component": { - "hideInspector": false, - "priority": 0 - }, - "PointLight": { - "intensity": 1.0, - "range": 4.0 - }, - "SObject": {}, - "name": "PointLight", - "type": "PointLight", - "uuid": "ce042c273824748a5b90874ab3cb2403", - "version": 1 - } - ], - "GameObject": { - "bEditorOnly": false, - "bEnable": true, - "bStatic": false, - "hideInspector": false - }, - "name": "Light", - "type": "GameObject", - "uuid": "034cc967fe0412b031c04155de682b19", - "version": 1 - }, - { - "Components": [ - { - "Component": { - "hideInspector": false, - "priority": 0 - }, - "SObject": {}, - "Transform": { - "quat": [ - 0.0, - 0.0, - 0.0, - 1.0 - ], - "vPosition": [ - 2.0, - 2.0, - 2.0 - ], - "vRotation": [ - 0.0, - 0.0, - 0.0 - ], - "vScale": [ - 1.0, - 1.0, - 1.0 - ] - }, - "name": "Unknown", - "type": "Transform", - "uuid": "8a7c0cadb9a86ca9690bf19cf851f221", - "version": 1 - }, - { - "Camera": { - "depth": 0, - "fov": 60.0, - "lookPos": [ - 0.0, - 0.0, - 0.0 - ], - "projection": 0 - }, - "Component": { - "hideInspector": false, - "priority": 0 - }, - "SObject": {}, - "name": "Camera", - "type": "Camera", - "uuid": "9945d707ba86250259f1d5721ef434fa", - "version": 1 - } - ], - "GameObject": { - "bEditorOnly": false, - "bEnable": true, - "bStatic": false, - "hideInspector": false - }, - "name": "Camera", - "type": "GameObject", - "uuid": "72fff2c07f59927205ef7fe7b9d65ce8", - "version": 1 - } - ], - "type": "EditorWorld", - "uuid": "c3a4ec39f722e6d2704dceb7afbdc3c8", - "version": 1 -} \ No newline at end of file diff --git a/resource/ProjectTemplate/Assets/SampleWorld/assets/Text.shader b/resource/ProjectTemplate/Assets/SampleWorld/Text.shader similarity index 100% rename from resource/ProjectTemplate/Assets/SampleWorld/assets/Text.shader rename to resource/ProjectTemplate/Assets/SampleWorld/Text.shader diff --git a/resource/ProjectTemplate/Assets/SampleWorld/assets/Text.shader.meta b/resource/ProjectTemplate/Assets/SampleWorld/Text.shader.meta similarity index 100% rename from resource/ProjectTemplate/Assets/SampleWorld/assets/Text.shader.meta rename to resource/ProjectTemplate/Assets/SampleWorld/Text.shader.meta diff --git a/resource/ProjectTemplate/Assets/SampleWorld/assets/Tiles107_2K-JPG_AmbientOcclusion.jpg b/resource/ProjectTemplate/Assets/SampleWorld/Tiles107_2K-JPG_AmbientOcclusion.jpg similarity index 100% rename from resource/ProjectTemplate/Assets/SampleWorld/assets/Tiles107_2K-JPG_AmbientOcclusion.jpg rename to resource/ProjectTemplate/Assets/SampleWorld/Tiles107_2K-JPG_AmbientOcclusion.jpg diff --git a/resource/ProjectTemplate/Assets/SampleWorld/assets/Tiles107_2K-JPG_AmbientOcclusion.jpg.meta b/resource/ProjectTemplate/Assets/SampleWorld/Tiles107_2K-JPG_AmbientOcclusion.jpg.meta similarity index 92% rename from resource/ProjectTemplate/Assets/SampleWorld/assets/Tiles107_2K-JPG_AmbientOcclusion.jpg.meta rename to resource/ProjectTemplate/Assets/SampleWorld/Tiles107_2K-JPG_AmbientOcclusion.jpg.meta index 22c5987b..c892dc01 100644 --- a/resource/ProjectTemplate/Assets/SampleWorld/assets/Tiles107_2K-JPG_AmbientOcclusion.jpg.meta +++ b/resource/ProjectTemplate/Assets/SampleWorld/Tiles107_2K-JPG_AmbientOcclusion.jpg.meta @@ -1,5 +1,5 @@ { - "metaHash": 0, + "metaHash": 15575255052746670163, "name": "Tiles107_2K-JPG_AmbientOcclusion", "obj": { "Texture": { diff --git a/resource/ProjectTemplate/Assets/SampleWorld/assets/Tiles107_2K-JPG_NormalDX.jpg b/resource/ProjectTemplate/Assets/SampleWorld/Tiles107_2K-JPG_NormalDX.jpg similarity index 100% rename from resource/ProjectTemplate/Assets/SampleWorld/assets/Tiles107_2K-JPG_NormalDX.jpg rename to resource/ProjectTemplate/Assets/SampleWorld/Tiles107_2K-JPG_NormalDX.jpg diff --git a/resource/ProjectTemplate/Assets/SampleWorld/assets/Tiles107_2K-JPG_NormalDX.jpg.meta b/resource/ProjectTemplate/Assets/SampleWorld/Tiles107_2K-JPG_NormalDX.jpg.meta similarity index 92% rename from resource/ProjectTemplate/Assets/SampleWorld/assets/Tiles107_2K-JPG_NormalDX.jpg.meta rename to resource/ProjectTemplate/Assets/SampleWorld/Tiles107_2K-JPG_NormalDX.jpg.meta index 8659d9ca..fdd2ebf7 100644 --- a/resource/ProjectTemplate/Assets/SampleWorld/assets/Tiles107_2K-JPG_NormalDX.jpg.meta +++ b/resource/ProjectTemplate/Assets/SampleWorld/Tiles107_2K-JPG_NormalDX.jpg.meta @@ -1,5 +1,5 @@ { - "metaHash": 0, + "metaHash": 17930001031590521832, "name": "Tiles107_2K-JPG_NormalDX", "obj": { "Texture": { diff --git a/resource/ProjectTemplate/Assets/SampleWorld/assets/Unlit.shader b/resource/ProjectTemplate/Assets/SampleWorld/Unlit.shader similarity index 100% rename from resource/ProjectTemplate/Assets/SampleWorld/assets/Unlit.shader rename to resource/ProjectTemplate/Assets/SampleWorld/Unlit.shader diff --git a/resource/ProjectTemplate/Assets/SampleWorld/assets/Unlit.shader.meta b/resource/ProjectTemplate/Assets/SampleWorld/Unlit.shader.meta similarity index 100% rename from resource/ProjectTemplate/Assets/SampleWorld/assets/Unlit.shader.meta rename to resource/ProjectTemplate/Assets/SampleWorld/Unlit.shader.meta diff --git a/resource/ProjectTemplate/Assets/SampleWorld/assets/default.shader b/resource/ProjectTemplate/Assets/SampleWorld/assets/default.shader deleted file mode 100644 index 63e49cbf..00000000 --- a/resource/ProjectTemplate/Assets/SampleWorld/assets/default.shader +++ /dev/null @@ -1,77 +0,0 @@ -#version 430 core - -Shader "Default Shader" -{ - Property - { - float ambient; - sampler2D tex; - sampler2D normalTex; - } - - Pass - { - LightingPass "Opaque" - - Stage Vertex - { - layout(location = 0) out vec2 fragUvs; - layout(location = 1) out vec3 fragPos; - layout(location = 2) out mat3 TBN; - void main() - { - gl_Position = MATRIX_PROJ * MATRIX_VIEW * MATRIX_MODEL * vec4(VERTEX, 1.0f); - fragUvs = UV; - fragPos = (MATRIX_MODEL * vec4(VERTEX, 1.0f)).xyz; - - mat3 normalMatrix = transpose(inverse(mat3(MATRIX_MODEL))); - - vec3 t = normalize(normalMatrix * TANGENT); - vec3 n = normalize(normalMatrix * NORMAL); - vec3 b = cross(n, t); - - TBN = mat3(t, b, n); - } - } - Stage Fragment - { - layout(location = 0) out vec4 outColor; - - layout(location = 0) in vec2 uvs; - layout(location = 1) in vec3 fragPos; - layout(location = 2) in mat3 TBN; - uniform float ambient; - uniform sampler2D tex; - uniform sampler2D normalTex; - - void main() - { - vec3 normal = texture(normalTex, uvs).xyz; - normal = normal * 2.0 - 1.0; - normal = normalize(TBN * normal); - - float diffuse = 0.0; - for (int i = 0; i < LIGHT.count; ++i) - { - int type = int(LIGHT.other[i].w); - if (type == 0) - { - vec3 toLightDir = -LIGHT.pos[i].xyz; - float intensity = LIGHT.pos[i].w; - diffuse += max(dot(normal, toLightDir), 0.0) * intensity; - } - else if (type == 1) - { - vec3 toLightVec = LIGHT.pos[i].xyz - fragPos; - vec3 toLightDir = normalize(toLightVec); - float lightDis = length(toLightVec); - float attenuation = clamp(1.0 - (lightDis / LIGHT.pos[i].w), 0.0, 1.0); - diffuse += max(dot(normal, toLightDir), 0.0) * attenuation; - } - } - outColor = texture(tex, uvs); - outColor.xyz *= diffuse + ambient; - } - } - } -} \ No newline at end of file diff --git a/resource/ProjectTemplate/Assets/SampleWorld/default.shader b/resource/ProjectTemplate/Assets/SampleWorld/default.shader new file mode 100644 index 00000000..3468e48a --- /dev/null +++ b/resource/ProjectTemplate/Assets/SampleWorld/default.shader @@ -0,0 +1,137 @@ +#version 430 core + +Shader "Default Shader" +{ + Property + { + float ambient; + sampler2D tex; + sampler2D normalTex; + } + Pass + { + LightingPass "ShadowMapPass" + Cull Off; + + Stage Vertex + { + void main() + { + gl_Position = MATRIX_PROJ * MATRIX_VIEW * MATRIX_MODEL * vec4(VERTEX, 1.0f); + } + } + Stage Fragment + { + void main() + { + } + } + } + Pass + { + LightingPass "Opaque" + + Stage Vertex + { + layout(location = 0) out vec2 fragUvs; + layout(location = 1) out vec3 fragPos; + layout(location = 2) out mat3 TBN; + void main() + { + fragUvs = UV; + fragPos = (MATRIX_MODEL * vec4(VERTEX, 1.0f)).xyz; + gl_Position = MATRIX_PROJ * MATRIX_VIEW * vec4(fragPos, 1.0f); + + mat3 normalMatrix = transpose(inverse(mat3(MATRIX_MODEL))); + + vec3 t = normalize(normalMatrix * TANGENT); + vec3 n = normalize(normalMatrix * NORMAL); + vec3 b = cross(n, t); + + TBN = mat3(t, b, n); + } + } + Stage Fragment + { + layout(location = 0) out vec4 outColor; + + layout(location = 0) in vec2 uvs; + layout(location = 1) in vec3 fragPos; + layout(location = 2) in mat3 TBN; + uniform float ambient; + uniform sampler2D tex; + uniform sampler2D normalTex; + + float ShadowSample(vec3 worldPos, float nDotL, vec4 shadowRect, mat4 lightMatrix) + { + const vec4 lightClip = lightMatrix * vec4(worldPos, 1.0); + const vec3 proj = lightClip.xyz / lightClip.w; + if (proj.z > 1.0 || proj.z < 0.0) + return 1.0; + + vec2 tileUV; + tileUV.x = proj.x * 0.5 + 0.5; + tileUV.y = 1.0 - (proj.y * 0.5 + 0.5); + if (tileUV.x < 0.0 || tileUV.x > 1.0 || tileUV.y < 0.0 || tileUV.y > 1.0) + return 1.0; + const vec2 uv = tileUV * shadowRect.zw + shadowRect.xy; + + const float currentDepth = proj.z; + const vec2 atlasSize = textureSize(TEXTURE_SHADOW, 0); + const vec2 texelSize = 1.0 / (atlasSize); + + const float bias = max(0.005 * (1.0 - nDotL), 0.001); + float result = 0.0; + for (int x = -1; x <= 1; ++x) + { + for (int y = -1; y <= 1; ++y) + { + float pcfDepth = texture(TEXTURE_SHADOW, uv + vec2(x, y) * texelSize).r; + result += (currentDepth - bias) > pcfDepth ? 0.0 : 1.0; + } + } + result /= 9.0; + return result; + } + + void main() + { + vec3 normal = texture(normalTex, uvs).xyz; + normal = normal * 2.0 - 1.0; + normal = normalize(TBN * normal); + + float diffuse = 0.0; + for (int i = 0; i < LIGHT.count; ++i) + { + int type = int(LIGHT.lights[i].other.w); + if (type == 0) + { + vec3 toLightDir = normalize(-LIGHT.lights[i].pos.xyz); + const float intensity = LIGHT.lights[i].pos.w; + const float nDotL = max(dot(normal, toLightDir), 0.0); + if (nDotL > 0.0) + { + const float shadow = ShadowSample(fragPos, nDotL, LIGHT.lights[i].shadowRect, LIGHT.lights[i].lightSpaceMatrix); + diffuse += nDotL * intensity * shadow; + } + } + else if (type == 1) + { + vec3 toLightVec = LIGHT.lights[i].pos.xyz - fragPos; + vec3 toLightDir = normalize(toLightVec); + float lightDis = length(toLightVec); + float attenuation = clamp(1.0 - (lightDis / LIGHT.lights[i].pos.w), 0.0, 1.0); + const float nDotL = max(dot(normal, toLightDir), 0.0); + if (nDotL > 0.0) + { + const float shadow = ShadowSample(fragPos, nDotL, LIGHT.lights[i].shadowRect, LIGHT.lights[i].lightSpaceMatrix); + diffuse += nDotL * attenuation * shadow; + } + } + } + outColor = texture(tex, uvs); + outColor.xyz *= diffuse + ambient; + } + } + } +} \ No newline at end of file diff --git a/resource/ProjectTemplate/Assets/SampleWorld/assets/default.shader.meta b/resource/ProjectTemplate/Assets/SampleWorld/default.shader.meta similarity index 100% rename from resource/ProjectTemplate/Assets/SampleWorld/assets/default.shader.meta rename to resource/ProjectTemplate/Assets/SampleWorld/default.shader.meta diff --git a/resource/ProjectTemplate/Assets/SampleWorld/skinned.shader b/resource/ProjectTemplate/Assets/SampleWorld/skinned.shader new file mode 100644 index 00000000..99c0793e --- /dev/null +++ b/resource/ProjectTemplate/Assets/SampleWorld/skinned.shader @@ -0,0 +1,71 @@ +#version 430 core + +Shader "Skinned Shader2" +{ + Property + { + float ambient; + sampler2D tex; + } + + Pass + { + LightingPass "Opaque" + + Stage Vertex + { + layout(location = 0) out vec2 fragUvs; + layout(location = 1) out vec3 fragPos; + layout(location = 2) out vec3 fragNormal; + + void main() + { + vec4 skinnedPos = MATRIX_SKIN * vec4(VERTEX, 1.0); + vec3 skinnedNormal = mat3(MATRIX_SKIN) * NORMAL; + + gl_Position = MATRIX_PROJ * MATRIX_VIEW * MATRIX_MODEL * skinnedPos; + + fragUvs = UV; + fragPos = (MATRIX_MODEL * skinnedPos).xyz; + fragNormal = normalize(mat3(transpose(inverse(MATRIX_MODEL))) * skinnedNormal); + } + } + Stage Fragment + { + layout(location = 0) out vec4 outColor; + + layout(location = 0) in vec2 uvs; + layout(location = 1) in vec3 fragPos; + layout(location = 2) in vec3 fragNormal; + + uniform float ambient; + uniform sampler2D tex; + + void main() + { + float diffuse = 0.0; + + for (int i = 0; i < LIGHT.count; ++i) + { + int type = int(LIGHT.lights[i].other.w); + if (type == 0) + { + vec3 toLightDir = -LIGHT.lights[i].pos.xyz; + float intensity = LIGHT.lights[i].pos.w; + diffuse += max(dot(fragNormal, toLightDir), 0.0) * intensity; + } + else if (type == 1) + { + vec3 toLightVec = LIGHT.lights[i].pos.xyz - fragPos; + vec3 toLightDir = normalize(toLightVec); + float lightDis = length(toLightVec); + float attenuation = clamp(1.0 - (lightDis / LIGHT.lights[i].pos.w), 0.0, 1.0); + diffuse += max(dot(fragNormal, toLightDir), 0.0) * attenuation; + } + } + outColor = texture(tex, uvs); + outColor.xyz *= diffuse + ambient; + } + } + } +} diff --git a/resource/ProjectTemplate/Assets/SampleWorld/skinned.shader.meta b/resource/ProjectTemplate/Assets/SampleWorld/skinned.shader.meta new file mode 100644 index 00000000..331b7cbf --- /dev/null +++ b/resource/ProjectTemplate/Assets/SampleWorld/skinned.shader.meta @@ -0,0 +1,6 @@ +{ + "name": "skinned", + "typeHash": 11175581975934587683, + "uuid": "fb1961bc1fad585cac9526892b097a86", + "version": 1 +} \ No newline at end of file From f703949052ed0d729b7195946e06a882910d71ed Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Fri, 8 May 2026 16:04:19 +0900 Subject: [PATCH 42/60] feat: Migrate render call counter logic (Using command buffer) --- include/Render/CommandBuffer.h | 11 +++++++++++ include/Render/VulkanImpl/VulkanCommandBuffer.h | 5 +++++ src/Render/VulkanImpl/VulkanCommandBuffer.cpp | 2 ++ src/Render/VulkanImpl/VulkanRenderer.cpp | 4 ++++ 4 files changed, 22 insertions(+) diff --git a/include/Render/CommandBuffer.h b/include/Render/CommandBuffer.h index 1ff81772..24505f24 100644 --- a/include/Render/CommandBuffer.h +++ b/include/Render/CommandBuffer.h @@ -2,6 +2,8 @@ #include "Core/NonCopyable.h" #include "Core/Name.h" +#include "Render/IRenderThrMethod.h" + #include namespace sh::render { @@ -16,6 +18,7 @@ namespace sh::render class CommandBuffer : public core::INonCopyable { + friend struct IRenderThrMethod; public: virtual ~CommandBuffer() = default; @@ -31,5 +34,13 @@ namespace sh::render virtual void DrawMeshBatch(const std::vector& drawables, core::Name passName, std::size_t viewerIdx = 0) = 0; virtual void DrawMesh(const Drawable& drawable, core::Name passName, std::size_t viewerIdx = 0) = 0; virtual void EmitBarrier(const std::vector& barriers) = 0; + protected: + virtual auto GetRenderCall() const -> uint32_t = 0; + }; + + template<> + struct IRenderThrMethod + { + static auto GetRenderCall(const CommandBuffer& cmd) -> uint32_t { return cmd.GetRenderCall(); } }; }//namespace \ No newline at end of file diff --git a/include/Render/VulkanImpl/VulkanCommandBuffer.h b/include/Render/VulkanImpl/VulkanCommandBuffer.h index 0238f6dc..601a81d2 100644 --- a/include/Render/VulkanImpl/VulkanCommandBuffer.h +++ b/include/Render/VulkanImpl/VulkanCommandBuffer.h @@ -77,6 +77,8 @@ namespace sh::render::vk SH_RENDER_API auto GetCommandPool() const -> VkCommandPool { return cmdPool; } SH_RENDER_API void Clear(); + protected: + auto GetRenderCall() const -> uint32_t override { return renderCall; } private: void BindCameraSet(const Material& mat, const ShaderPass& pass, VkPipelineLayout pipelineLayout, uint32_t cameraOffset); void BindMaterialSet(const Material& mat, const ShaderPass& pass, VkPipelineLayout pipelineLayout); @@ -103,6 +105,9 @@ namespace sh::render::vk std::vector signalSemaphores; VkFence fence = VK_NULL_HANDLE; + + uint32_t renderCall = 0; + bool bBeginRender = false; }; }//namespace \ No newline at end of file diff --git a/src/Render/VulkanImpl/VulkanCommandBuffer.cpp b/src/Render/VulkanImpl/VulkanCommandBuffer.cpp index 08f56c29..a20702af 100644 --- a/src/Render/VulkanImpl/VulkanCommandBuffer.cpp +++ b/src/Render/VulkanImpl/VulkanCommandBuffer.cpp @@ -50,6 +50,7 @@ namespace sh::render::vk SH_RENDER_API void VulkanCommandBuffer::Begin(bool bOnetime) { + renderCall = 0; if (buffer == VK_NULL_HANDLE) return; @@ -608,5 +609,6 @@ namespace sh::render::vk } vkCmdDrawIndexed(buffer, indexCount, 1, firstIndex, 0, 0); + ++renderCall; } }//namespace \ No newline at end of file diff --git a/src/Render/VulkanImpl/VulkanRenderer.cpp b/src/Render/VulkanImpl/VulkanRenderer.cpp index 7c93bc33..4672d7a0 100644 --- a/src/Render/VulkanImpl/VulkanRenderer.cpp +++ b/src/Render/VulkanImpl/VulkanRenderer.cpp @@ -125,10 +125,12 @@ namespace sh::render::vk IRenderThrMethod::ExecuteTransfer(*renderer, imgIdx); + uint32_t renderCall = 0; const std::vector& recordedCmds = renderer->GetRecordedCommands(); if (recordedCmds.size() == 1) { VulkanCommandBuffer& cmd = static_cast(recordedCmds.front().cmd); + renderCall += IRenderThrMethod::GetRenderCall(cmd); cmd.AddWaitSemaphore( WaitSemaphore { @@ -149,6 +151,7 @@ namespace sh::render::vk { ScriptableRenderPass& pass = recordedCmd.pass; VulkanCommandBuffer& cmd = static_cast(recordedCmd.cmd); + renderCall += IRenderThrMethod::GetRenderCall(cmd); // 스왑체인을 쓰는 첫 패스는 imageAvailableSemaphore대기 if (!bUsedImageAvailableSemaphore) @@ -173,6 +176,7 @@ namespace sh::render::vk context->GetQueueManager().Submit(VulkanQueueManager::Role::Graphics, cmd, fence); } } + SetDrawCallCount(renderCall); VkPresentInfoKHR presentInfo{}; presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; From 11422a22245fa8314a614fa056f9220fe11cc0cd Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Fri, 8 May 2026 23:43:13 +0900 Subject: [PATCH 43/60] refactor: Move EditorRenderer functionality to GameRenderer --- include/Editor/EditorRenderer.h | 7 ------- include/Game/GameRenderer.h | 13 +++++++++++-- src/Editor/EditorRenderer.cpp | 30 +++--------------------------- src/Editor/EditorWorld.cpp | 2 +- src/Game/GameRenderer.cpp | 25 ++++++++++++++----------- 5 files changed, 29 insertions(+), 48 deletions(-) diff --git a/include/Editor/EditorRenderer.h b/include/Editor/EditorRenderer.h index 4a7d5c0e..a3b697f7 100644 --- a/include/Editor/EditorRenderer.h +++ b/include/Editor/EditorRenderer.h @@ -15,10 +15,6 @@ namespace sh::game class GUIPass; class World; } -namespace sh::render -{ - class TransparentPass; -} namespace sh::editor { class EditorRenderer : public game::GameRenderer @@ -34,9 +30,6 @@ namespace sh::editor SH_EDITOR_API void Setup(const render::RenderData& data) override; private: render::ScriptableRenderPass* pickingPass = nullptr; - render::ScriptableRenderPass* opaquePass = nullptr; - render::TransparentPass* transparentPass = nullptr; - render::TransparentPass* uiPass = nullptr; EditorOutlinePass* outlinePass = nullptr; EditorPostOutlinePass* postOutlinePass = nullptr; }; diff --git a/include/Game/GameRenderer.h b/include/Game/GameRenderer.h index ede90591..6b14bfa8 100644 --- a/include/Game/GameRenderer.h +++ b/include/Game/GameRenderer.h @@ -8,6 +8,8 @@ namespace sh::render { class ShadowMapPass; + class OpaquePass; + class TransparentPass; } namespace sh::game { @@ -23,13 +25,20 @@ namespace sh::game SH_GAME_API void Init() override; SH_GAME_API void Setup(const render::RenderData& data) override; - auto GetGUIPass() const -> game::GUIPass* { return guiPass; } + auto GetShadowMapPass() const -> render::ShadowMapPass& { return *shadowMapPass; } + auto GetOpaquePass() const -> render::ScriptableRenderPass& { return *opaquePass; } + auto GetTransparentPass() const -> render::TransparentPass& { return *transparentPass; } + auto GetUIPass() const -> render::TransparentPass& { return *uiPass; } + auto GetGUIPass() const -> game::GUIPass& { return *guiPass; } protected: render::IRenderContext& renderCtx; World& world; game::ImGUImpl& guiCtx; - game::GUIPass* guiPass = nullptr; render::ShadowMapPass* shadowMapPass = nullptr; + render::ScriptableRenderPass* opaquePass = nullptr; + render::TransparentPass* transparentPass = nullptr; + render::TransparentPass* uiPass = nullptr; + game::GUIPass* guiPass = nullptr; }; }//namespace \ No newline at end of file diff --git a/src/Editor/EditorRenderer.cpp b/src/Editor/EditorRenderer.cpp index 1982d765..90447ff6 100644 --- a/src/Editor/EditorRenderer.cpp +++ b/src/Editor/EditorRenderer.cpp @@ -1,9 +1,8 @@ #include "EditorRenderer.h" #include "Game/GUIPass.h" - -#include "Render/RenderPass/TransparentPass.h" #include "Render/RenderPass/ShadowMapPass.h" +#include "Render/RenderPass/TransparentPass.h" #include namespace sh::editor @@ -14,23 +13,13 @@ namespace sh::editor } SH_EDITOR_API void EditorRenderer::Init() { + GameRenderer::Init(); pickingPass = &AddRenderPass(core::Name{ "EditorPicking" }, render::RenderQueue::Picking); outlinePass = &AddRenderPass(); - shadowMapPass = &AddRenderPass(); - opaquePass = &AddRenderPass(core::Name{ "Opaque" }, render::RenderQueue::Opaque); - transparentPass = &AddRenderPass(); - uiPass = &AddRenderPass("UI", render::RenderQueue::Transparent); postOutlinePass = &AddRenderPass(ctx); - guiPass = &AddRenderPass(); - guiPass->SetImGUIContext(guiCtx); } SH_EDITOR_API void EditorRenderer::Setup(const render::RenderData& data) { - if (data.tag == "ImGUI") - { - EnqueRenderPass(*guiPass); - return; - } if (data.tag == "EditorCamera") { guiPass->viewportTexture = data.target; @@ -46,19 +35,6 @@ namespace sh::editor EnqueRenderPass(*pickingPass); return; } - if (data.tag == "ImGUI") - { - EnqueRenderPass(*guiPass); - return; - } - if (data.tag == "Shadow") - { - EnqueRenderPass(*shadowMapPass); - return; - } - // 그 외 카메라 - EnqueRenderPass(*opaquePass); - EnqueRenderPass(*transparentPass); - EnqueRenderPass(*uiPass); + GameRenderer::Setup(data); } }//namespace \ No newline at end of file diff --git a/src/Editor/EditorWorld.cpp b/src/Editor/EditorWorld.cpp index c3b5916a..2452b8a2 100644 --- a/src/Editor/EditorWorld.cpp +++ b/src/Editor/EditorWorld.cpp @@ -116,7 +116,7 @@ namespace sh::editor } else viewportTexture = viewportPtr; - editorRenderer->GetGUIPass()->viewportTexture = viewportTexture; + editorRenderer->GetGUIPass().viewportTexture = viewportTexture; game::GameObject* const camObj = AddGameObject("EditorCamera"); camObj->transform->SetPosition({ 2.f, 2.f, 2.f }); diff --git a/src/Game/GameRenderer.cpp b/src/Game/GameRenderer.cpp index 6f355040..847ae051 100644 --- a/src/Game/GameRenderer.cpp +++ b/src/Game/GameRenderer.cpp @@ -2,6 +2,7 @@ #include "GUIPass.h" #include "World.h" +#include "Render/RenderPass/ShadowMapPass.h" #include "Render/RenderPass/TransparentPass.h" #include @@ -16,27 +17,29 @@ namespace sh::game } SH_GAME_API void GameRenderer::Init() { - //AddShadowPass(); - AddRenderPass(core::Name{ "Opaque" }, render::RenderQueue::Opaque); - AddRenderPass(); - AddRenderPass("UI", render::RenderQueue::UI); + shadowMapPass = &AddRenderPass(); + opaquePass = &AddRenderPass(core::Name{ "Opaque" }, render::RenderQueue::Opaque); + transparentPass = &AddRenderPass(); + uiPass = &AddRenderPass("UI", render::RenderQueue::Transparent); guiPass = &AddRenderPass(); guiPass->SetImGUIContext(guiCtx); } SH_GAME_API void GameRenderer::Setup(const render::RenderData& data) { + if (data.tag == "Shadow") + { + EnqueRenderPass(*shadowMapPass); + return; + } if (data.tag == "ImGUI") { if (guiPass != nullptr) EnqueRenderPass(*guiPass); return; } - - for (const std::unique_ptr& pass : allPasses) - { - if (pass.get() == guiPass) - continue; - EnqueRenderPass(*pass); - } + // 그 외 카메라 + EnqueRenderPass(*opaquePass); + EnqueRenderPass(*transparentPass); + EnqueRenderPass(*uiPass); } }//namespace From b9ed59fb419850b84411f227023a76038db58bd4 Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Fri, 8 May 2026 23:59:27 +0900 Subject: [PATCH 44/60] refact(ShadowMapPass): Rename to DepthPass --- include/Editor/EditorRenderer.h | 2 -- include/Game/GameRenderer.h | 6 +++--- .../RenderPass/{ShadowMapPass.h => DepthPass.h} | 5 +++-- src/Editor/EditorRenderer.cpp | 1 - src/Game/GameRenderer.cpp | 8 ++++---- .../RenderPass/{ShadowMapPass.cpp => DepthPass.cpp} | 12 ++++++------ src/Render/ShadowMapManager.cpp | 2 +- 7 files changed, 17 insertions(+), 19 deletions(-) rename include/Render/RenderPass/{ShadowMapPass.h => DepthPass.h} (79%) rename src/Render/RenderPass/{ShadowMapPass.cpp => DepthPass.cpp} (60%) diff --git a/include/Editor/EditorRenderer.h b/include/Editor/EditorRenderer.h index a3b697f7..7dc4b83f 100644 --- a/include/Editor/EditorRenderer.h +++ b/include/Editor/EditorRenderer.h @@ -3,8 +3,6 @@ #include "EditorOutlinePass.h" #include "EditorPostOutlinePass.h" -#include "Render/RenderPass/ShadowMapPass.h" - #include "Game/Component/Render/Camera.h" #include "Game/GameRenderer.h" diff --git a/include/Game/GameRenderer.h b/include/Game/GameRenderer.h index 6b14bfa8..46f2b066 100644 --- a/include/Game/GameRenderer.h +++ b/include/Game/GameRenderer.h @@ -7,7 +7,7 @@ #include namespace sh::render { - class ShadowMapPass; + class DepthPass; class OpaquePass; class TransparentPass; } @@ -25,7 +25,7 @@ namespace sh::game SH_GAME_API void Init() override; SH_GAME_API void Setup(const render::RenderData& data) override; - auto GetShadowMapPass() const -> render::ShadowMapPass& { return *shadowMapPass; } + auto GetDepthPass() const -> render::DepthPass& { return *depthPass; } auto GetOpaquePass() const -> render::ScriptableRenderPass& { return *opaquePass; } auto GetTransparentPass() const -> render::TransparentPass& { return *transparentPass; } auto GetUIPass() const -> render::TransparentPass& { return *uiPass; } @@ -35,7 +35,7 @@ namespace sh::game World& world; game::ImGUImpl& guiCtx; - render::ShadowMapPass* shadowMapPass = nullptr; + render::DepthPass* depthPass = nullptr; render::ScriptableRenderPass* opaquePass = nullptr; render::TransparentPass* transparentPass = nullptr; render::TransparentPass* uiPass = nullptr; diff --git a/include/Render/RenderPass/ShadowMapPass.h b/include/Render/RenderPass/DepthPass.h similarity index 79% rename from include/Render/RenderPass/ShadowMapPass.h rename to include/Render/RenderPass/DepthPass.h index dfb5c293..ede4259c 100644 --- a/include/Render/RenderPass/ShadowMapPass.h +++ b/include/Render/RenderPass/DepthPass.h @@ -6,10 +6,11 @@ namespace sh::render { /// @brief 그림자 아틀라스에 광원의 깊이 정보를 기록하는 패스 - class ShadowMapPass : public ScriptableRenderPass + /// @brief RenderData의 태그가 Depth면 작동 + class DepthPass : public ScriptableRenderPass { public: - SH_RENDER_API ShadowMapPass(); + SH_RENDER_API DepthPass(); SH_RENDER_API void Configure(const RenderData& renderData) override; SH_RENDER_API void Record(CommandBuffer& cmd, const IRenderContext& ctx, const RenderData& renderData) override; diff --git a/src/Editor/EditorRenderer.cpp b/src/Editor/EditorRenderer.cpp index 90447ff6..0f1346f5 100644 --- a/src/Editor/EditorRenderer.cpp +++ b/src/Editor/EditorRenderer.cpp @@ -1,7 +1,6 @@ #include "EditorRenderer.h" #include "Game/GUIPass.h" -#include "Render/RenderPass/ShadowMapPass.h" #include "Render/RenderPass/TransparentPass.h" #include diff --git a/src/Game/GameRenderer.cpp b/src/Game/GameRenderer.cpp index 847ae051..df9e6c50 100644 --- a/src/Game/GameRenderer.cpp +++ b/src/Game/GameRenderer.cpp @@ -2,7 +2,7 @@ #include "GUIPass.h" #include "World.h" -#include "Render/RenderPass/ShadowMapPass.h" +#include "Render/RenderPass/DepthPass.h" #include "Render/RenderPass/TransparentPass.h" #include @@ -17,7 +17,7 @@ namespace sh::game } SH_GAME_API void GameRenderer::Init() { - shadowMapPass = &AddRenderPass(); + depthPass = &AddRenderPass(); opaquePass = &AddRenderPass(core::Name{ "Opaque" }, render::RenderQueue::Opaque); transparentPass = &AddRenderPass(); uiPass = &AddRenderPass("UI", render::RenderQueue::Transparent); @@ -26,9 +26,9 @@ namespace sh::game } SH_GAME_API void GameRenderer::Setup(const render::RenderData& data) { - if (data.tag == "Shadow") + if (data.tag == "Depth") { - EnqueRenderPass(*shadowMapPass); + EnqueRenderPass(*depthPass); return; } if (data.tag == "ImGUI") diff --git a/src/Render/RenderPass/ShadowMapPass.cpp b/src/Render/RenderPass/DepthPass.cpp similarity index 60% rename from src/Render/RenderPass/ShadowMapPass.cpp rename to src/Render/RenderPass/DepthPass.cpp index db2ae667..68f1239f 100644 --- a/src/Render/RenderPass/ShadowMapPass.cpp +++ b/src/Render/RenderPass/DepthPass.cpp @@ -1,24 +1,24 @@ -#include "RenderPass/ShadowMapPass.h" +#include "RenderPass/DepthPass.h" #include "CommandBuffer.h" #include "IRenderContext.h" namespace sh::render { - ShadowMapPass::ShadowMapPass() : - ScriptableRenderPass(core::Name{ "ShadowMapPass" }, RenderQueue::BeforeRendering) + DepthPass::DepthPass() : + ScriptableRenderPass(core::Name{ "DepthPass" }, RenderQueue::BeforeRendering) { } - SH_RENDER_API void ShadowMapPass::Configure(const RenderData& renderData) + SH_RENDER_API void DepthPass::Configure(const RenderData& renderData) { ScriptableRenderPass::Configure(renderData); } - SH_RENDER_API void ShadowMapPass::Record(CommandBuffer& cmd, const IRenderContext& ctx, const RenderData& renderData) + SH_RENDER_API void DepthPass::Record(CommandBuffer& cmd, const IRenderContext& ctx, const RenderData& renderData) { if (renderData.target == nullptr) return; - // 한 프레임 내 첫 그림자 호출에서만 아틀라스를 클리어하고, 이후엔 LOAD하여 다른 광원의 슬롯 결과를 보존 + // 한 프레임 내 첫 호출에서만 클리어 if (renderData.frameIndex != lastFrameIndex) { lastFrameIndex = renderData.frameIndex; diff --git a/src/Render/ShadowMapManager.cpp b/src/Render/ShadowMapManager.cpp index 099111f2..447d7f5f 100644 --- a/src/Render/ShadowMapManager.cpp +++ b/src/Render/ShadowMapManager.cpp @@ -27,7 +27,7 @@ namespace sh::render this->atlasSize = atlasSize; packer = std::make_unique(static_cast(atlasSize), static_cast(atlasSize), 0); - renderData.tag = core::Name{ "Shadow" }; + renderData.tag = core::Name{ "Depth" }; renderData.priority = 1000; // 다른 패스보다 먼저 실행되도록 } From 049eb002f185a651f454940dc00c220941f8fab8 Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Sun, 10 May 2026 18:37:42 +0900 Subject: [PATCH 45/60] feat(Render): Support multiple render targets --- include/Game/Component/Render/Camera.h | 1 + include/Game/Component/TestComputeShader.h | 23 +++ include/Game/GameRenderer.h | 3 + include/Render/CommandBuffer.h | 2 +- include/Render/Formats.hpp | 25 +++- include/Render/RenderData.h | 95 ++++++++---- include/Render/RenderPass/CopyPass.h | 2 +- include/Render/RenderTexture.h | 23 +-- include/Render/ScriptableRenderPass.h | 17 ++- include/Render/VulkanImpl/VulkanPipeline.h | 2 +- include/Render/VulkanImpl/VulkanSwapChain.h | 28 ++-- src/Editor/EditorOutlinePass.cpp | 27 ++-- src/Editor/EditorPostOutlinePass.cpp | 17 ++- src/Editor/EditorRenderer.cpp | 4 +- src/Editor/EditorResource.cpp | 9 +- src/Editor/EditorWorld.cpp | 7 +- src/Game/Component/Render/Camera.cpp | 5 +- src/Game/Component/Render/PickingCamera.cpp | 7 +- src/Game/Component/TestComputeShader.cpp | 53 +++++++ src/Game/GUIPass.cpp | 4 +- src/Game/GameRenderer.cpp | 7 + src/Render/RenderPass/DepthPass.cpp | 9 +- src/Render/RenderPass/TransparentPass.cpp | 4 +- src/Render/RenderTexture.cpp | 89 ++++++------ src/Render/ScriptableRenderPass.cpp | 92 +++++++----- src/Render/ScriptableRenderer.cpp | 6 +- src/Render/ShadowMapManager.cpp | 9 +- src/Render/VulkanImpl/VulkanCommandBuffer.cpp | 136 +++++++++++++----- src/Render/VulkanImpl/VulkanPipeline.cpp | 31 ++-- src/Render/VulkanImpl/VulkanRenderer.cpp | 4 +- src/Render/VulkanImpl/VulkanSwapChain.cpp | 2 +- 31 files changed, 483 insertions(+), 260 deletions(-) create mode 100644 include/Game/Component/TestComputeShader.h create mode 100644 src/Game/Component/TestComputeShader.cpp diff --git a/include/Game/Component/Render/Camera.h b/include/Game/Component/Render/Camera.h index 8748984c..bb046178 100644 --- a/include/Game/Component/Render/Camera.h +++ b/include/Game/Component/Render/Camera.h @@ -63,6 +63,7 @@ namespace sh::game auto GetTag() const -> const core::Name& { return renderData.tag; } auto GetProjection() const -> Projection { return projection; } auto GetRenderTexture() const -> render::RenderTexture* { return renderTexture; } + auto GetRenderData() const -> const render::RenderData& { return renderData; } protected: SH_GAME_API virtual void UpdateViewMatrix(); SH_GAME_API virtual void UpdateProjMatrix(); diff --git a/include/Game/Component/TestComputeShader.h b/include/Game/Component/TestComputeShader.h new file mode 100644 index 00000000..6a299106 --- /dev/null +++ b/include/Game/Component/TestComputeShader.h @@ -0,0 +1,23 @@ +#pragma once +#include "Export.h" +#include "Game/Component/Component.h" + +namespace sh::render +{ + class ComputeShader; +} + +namespace sh::game +{ + class TestComputeShader : public Component + { + COMPONENT(TestComputeShader) + public: + SH_GAME_API TestComputeShader(GameObject& owner); + + SH_GAME_API void Update() override; + private: + PROPERTY(shader) + render::ComputeShader* shader = nullptr; + }; +}//namespace diff --git a/include/Game/GameRenderer.h b/include/Game/GameRenderer.h index 46f2b066..ad2008be 100644 --- a/include/Game/GameRenderer.h +++ b/include/Game/GameRenderer.h @@ -9,6 +9,7 @@ namespace sh::render { class DepthPass; class OpaquePass; + class SSAOPass; class TransparentPass; } namespace sh::game @@ -27,6 +28,7 @@ namespace sh::game auto GetDepthPass() const -> render::DepthPass& { return *depthPass; } auto GetOpaquePass() const -> render::ScriptableRenderPass& { return *opaquePass; } + auto GetSSAOPass() const -> render::SSAOPass& { return *ssaoPass; } auto GetTransparentPass() const -> render::TransparentPass& { return *transparentPass; } auto GetUIPass() const -> render::TransparentPass& { return *uiPass; } auto GetGUIPass() const -> game::GUIPass& { return *guiPass; } @@ -37,6 +39,7 @@ namespace sh::game render::DepthPass* depthPass = nullptr; render::ScriptableRenderPass* opaquePass = nullptr; + render::SSAOPass* ssaoPass = nullptr; render::TransparentPass* transparentPass = nullptr; render::TransparentPass* uiPass = nullptr; game::GUIPass* guiPass = nullptr; diff --git a/include/Render/CommandBuffer.h b/include/Render/CommandBuffer.h index 24505f24..3a5d0497 100644 --- a/include/Render/CommandBuffer.h +++ b/include/Render/CommandBuffer.h @@ -13,7 +13,7 @@ namespace sh::render class Material; class Drawable; class ShaderPass; - struct RenderData; + class RenderData; struct BarrierInfo; class CommandBuffer : public core::INonCopyable diff --git a/include/Render/Formats.hpp b/include/Render/Formats.hpp index 04460740..707e38e8 100644 --- a/include/Render/Formats.hpp +++ b/include/Render/Formats.hpp @@ -21,7 +21,7 @@ namespace sh::render D16 }; - static auto GetTextureFormatChannel(TextureFormat format) -> int + inline static auto GetTextureFormatChannel(TextureFormat format) -> int { switch (format) { @@ -31,7 +31,7 @@ namespace sh::render return 4; } - static auto IsDepthTexture(TextureFormat format) -> bool + inline static auto IsDepthTexture(TextureFormat format) -> bool { if (format == TextureFormat::D32S8 || format == TextureFormat::D24S8 || @@ -41,4 +41,25 @@ namespace sh::render return true; return false; } + + inline static auto TextureFormatToString(TextureFormat format) -> const char* + { + switch (format) + { + case TextureFormat::None: return "None"; + case TextureFormat::SRGB24: return "SRGB24"; + case TextureFormat::SRGBA32: return "SRGBA32"; + case TextureFormat::SBGR24: return "SBGR24"; + case TextureFormat::SBGRA32: return "SBGRA32"; + case TextureFormat::RGB24: return "RGB24"; + case TextureFormat::RGBA32: return "RGBA32"; + case TextureFormat::R8: return "R8"; + case TextureFormat::D32S8: return "D32S8"; + case TextureFormat::D24S8: return "D24S8"; + case TextureFormat::D16S8: return "D16S8"; + case TextureFormat::D32: return "D32"; + case TextureFormat::D16: return "D16"; + default: return "None"; + } + } }//namespace \ No newline at end of file diff --git a/include/Render/RenderData.h b/include/Render/RenderData.h index 95c7e1d6..786f6d50 100644 --- a/include/Render/RenderData.h +++ b/include/Render/RenderData.h @@ -1,13 +1,17 @@ #pragma once #include "Mesh.h" #include "Texture.h" +#include "IRenderThrMethod.h" +#include "Formats.hpp" #include "Core/SContainer.hpp" #include "Core/Name.h" #include #include +#include #include +#include namespace sh { @@ -34,46 +38,75 @@ namespace sh::render std::size_t offset = 0; }; - struct RenderData + class RenderData { + friend struct IRenderThrMethod; + public: + void SetRenderTarget(const RenderTexture* renderTarget) { targets.clear(); targets.push_back(renderTarget); } + void SetRenderTargets(std::initializer_list renderTargets) + { + if (renderTargets.size() == 0) + { + targets.clear(); + targets.push_back(nullptr); + } + else + targets.assign(renderTargets.begin(), renderTargets.end()); + } + void SetRenderTargets(std::vector renderTargets) + { + if (renderTargets.size() == 0) + { + targets.clear(); + targets.push_back(nullptr); + } + else + targets = std::move(renderTargets); + } + void ClearRenderTargets() { targets.clear(); targets.push_back(nullptr); } + auto GetRenderTarget(std::size_t idx) const -> const RenderTexture* { return idx < targets.size() ? targets[idx] : nullptr; } + auto GetRenderTargets() const -> const std::vector& { return targets; } + auto GetFrameIdx() const -> uint32_t { return frameIndex; } + auto GetDrawablesPtr() const -> const std::vector* { return drawables; } + public: int priority = 0; - uint32_t frameIndex = 0; - const RenderTexture* target = nullptr; core::Name tag{ "Camera" }; - std::vector renderViewers; + private: + uint32_t frameIndex = 0; + std::vector targets{ nullptr }; const std::vector* drawables = nullptr; }; - struct RenderTargetLayout + template<> + struct IRenderThrMethod { - TextureFormat format; - TextureFormat depthFormat; - bool bUseMSAA = false; + inline static void SetFrameIndex(RenderData& rd, uint32_t frameIdx) { rd.frameIndex = frameIdx; } + inline static void SetDrawablesPtr(RenderData& rd, const std::vector* ptr) { rd.drawables = ptr; } + }; + class RenderTargetLayout + { + public: auto operator==(const RenderTargetLayout& other) const -> bool { - return format == other.format && depthFormat == other.depthFormat && bUseMSAA == other.bUseMSAA; + return colorFormats == other.colorFormats && depthFormat == other.depthFormat && bUseMSAA == other.bUseMSAA; } - }; + auto IsDepthOnly() const -> bool + { + for (TextureFormat format : colorFormats) + { + if (format != TextureFormat::None) + return false; + } + return depthFormat != TextureFormat::None; + } + public: + std::vector colorFormats{ TextureFormat::None }; + TextureFormat depthFormat = TextureFormat::None; - //struct DrawList - //{ - // struct RenderGroup - // { - // const Material* material; - // Mesh::Topology topology; - // std::vector drawables; - // }; - // struct RenderItem - // { - // const Material* material; - // Mesh::Topology topology; - // Drawable* drawable; - // }; - // std::variant, std::vector> renderData; - // uint32_t drawableCount = 0; - //}; + bool bUseMSAA = false; + }; /// @brief 리소스(이미지/버퍼) 사용 의도. 배리어 계산의 입력으로 쓰임 enum class ResourceUsage @@ -103,12 +136,12 @@ namespace std auto operator()(const sh::render::RenderTargetLayout& layout) const -> std::size_t { std::hash intHasher{}; - std::hash boolHasher{}; + std::hash boolHasher{}; - std::size_t hash = intHasher(static_cast(layout.format)); - hash = sh::core::Util::CombineHash(hash, intHasher(static_cast(layout.depthFormat))); + std::size_t hash = intHasher(static_cast(layout.depthFormat)); hash = sh::core::Util::CombineHash(hash, boolHasher(layout.bUseMSAA)); - + for (std::size_t i = 0; i < layout.colorFormats.size(); ++i) + hash = sh::core::Util::CombineHash(hash, intHasher(static_cast(layout.colorFormats[i]))); return hash; } }; diff --git a/include/Render/RenderPass/CopyPass.h b/include/Render/RenderPass/CopyPass.h index abb92e4a..f542177d 100644 --- a/include/Render/RenderPass/CopyPass.h +++ b/include/Render/RenderPass/CopyPass.h @@ -10,7 +10,7 @@ namespace sh::render class CopyPass : public ScriptableRenderPass { - friend class IRenderThrMethod; + friend struct IRenderThrMethod; public: SH_RENDER_API CopyPass(); protected: diff --git a/include/Render/RenderTexture.h b/include/Render/RenderTexture.h index 4f010277..b5589ed1 100644 --- a/include/Render/RenderTexture.h +++ b/include/Render/RenderTexture.h @@ -3,6 +3,7 @@ #include "Texture.h" #include "RenderData.h" #include "IRenderThrMethod.h" +#include "ITextureBuffer.h" #include "glm/vec2.hpp" @@ -22,7 +23,7 @@ namespace sh::render friend IRenderThrMethod; SCLASS(RenderTexture); public: - SH_RENDER_API RenderTexture(const RenderTargetLayout& layout); + SH_RENDER_API RenderTexture(TextureFormat colorFormat, TextureFormat depthFormat = TextureFormat::D24S8, bool bMSAA = false); SH_RENDER_API RenderTexture(RenderTexture&& other) noexcept; SH_RENDER_API ~RenderTexture(); @@ -31,24 +32,25 @@ namespace sh::render SH_RENDER_API void OnPropertyChanged(const core::reflection::Property& property) override; SH_RENDER_API void SetSize(uint32_t width, uint32_t height); - SH_RENDER_API auto GetSize() const -> glm::vec2; - SH_RENDER_API auto GetLayout() const -> const RenderTargetLayout& { return layout; } - SH_RENDER_API auto GetMSAABuffer() const -> ITextureBuffer* { return msaaBuffer.get(); } - SH_RENDER_API auto GetDepthBuffer() const -> ITextureBuffer*; - SH_RENDER_API auto IsDepthOnly() const -> bool { return layout.format == TextureFormat::None; } - SH_RENDER_API auto GetUsage() const -> ResourceUsage { return usage; } + auto GetSize() const -> glm::vec2 { return { width, height }; } + auto GetDepthFormat() const -> TextureFormat { return depthFormat; } + auto GetMSAABuffer() const -> ITextureBuffer* { return msaaBuffer.get(); } + auto GetDepthBuffer() const -> ITextureBuffer* { return IsDepthTexture() ? textureBuffer.get() : depthBuffer.get(); } + auto IsDepthTexture() const -> bool { return GetTextureFormat() == TextureFormat::None && depthFormat != TextureFormat::None; } + auto GetUsage() const -> ResourceUsage { return usage; } protected: - SH_RENDER_API void ChangeUsage(ResourceUsage newUsage); + void ChangeUsage(ResourceUsage newUsage) { usage = newUsage; } private: void CreateBuffers(); + void CreateDepthBuffer(const ITextureBuffer::CreateInfo& ci); private: PROPERTY(width); uint32_t width; PROPERTY(height); uint32_t height; - RenderTargetLayout layout; + TextureFormat depthFormat = TextureFormat::D24S8; std::unique_ptr msaaBuffer; std::unique_ptr depthBuffer; @@ -56,10 +58,11 @@ namespace sh::render ResourceUsage usage = ResourceUsage::Undefined; bool bChangeSize = false; + bool bMSAA = false; }; inline void IRenderThrMethod::ChangeUsage(RenderTexture& rt, ResourceUsage newUsage) { rt.ChangeUsage(newUsage); } -}//namespace \ No newline at end of file +}//namespace diff --git a/include/Render/ScriptableRenderPass.h b/include/Render/ScriptableRenderPass.h index 9e84aa79..d75e02ce 100644 --- a/include/Render/ScriptableRenderPass.h +++ b/include/Render/ScriptableRenderPass.h @@ -52,11 +52,6 @@ namespace sh::render { friend IRenderThrMethod; public: - SH_RENDER_API ScriptableRenderPass(const core::Name& passName, RenderQueue renderQueue); - SH_RENDER_API virtual ~ScriptableRenderPass() = default; - - SH_RENDER_API auto GetRenderTextures() const -> const std::unordered_map& { return renderTextures; } - protected: struct RenderBatch { const Material* material; @@ -64,12 +59,20 @@ namespace sh::render bool bSkinned = false; std::vector drawables; }; + SH_RENDER_API ScriptableRenderPass(const core::Name& passName, RenderQueue renderQueue); + SH_RENDER_API virtual ~ScriptableRenderPass() = default; + + SH_RENDER_API static auto CreateRenderBatch(const core::Name& passName, const std::vector& drawables) -> std::vector; + + SH_RENDER_API auto GetRenderTextures() const -> const std::unordered_map& { return renderTextures; } + protected: SH_RENDER_API virtual void Configure(const RenderData& renderData); SH_RENDER_API virtual void Record(CommandBuffer& cmd, const IRenderContext& ctx, const RenderData& renderData); - SH_RENDER_API auto CreateRenderBatch(const std::vector& drawables) const -> std::vector; + SH_RENDER_API void SetRenderTargetImageUsages(const RenderData& renderData); SH_RENDER_API void SetImageUsages(const RenderData& renderData); SH_RENDER_API void SetImageUsages(const std::vector& drawables); + SH_RENDER_API void SetImageUsages(const Material& mat); SH_RENDER_API void SetViewportScissor(CommandBuffer& cmd, const IRenderContext& ctx, const RenderViewer& renderViewer); /// @brief Configure 이후에 호출해야 정확한 렌더콜 갯수를 알 수 있음 @@ -96,4 +99,4 @@ namespace sh::render { return pass.GetRenderCallCount(); } -}//namespace \ No newline at end of file +}//namespace diff --git a/include/Render/VulkanImpl/VulkanPipeline.h b/include/Render/VulkanImpl/VulkanPipeline.h index 4215b496..327a4fa8 100644 --- a/include/Render/VulkanImpl/VulkanPipeline.h +++ b/include/Render/VulkanImpl/VulkanPipeline.h @@ -96,4 +96,4 @@ namespace sh::render::vk bool bZWrite = true; bool bZTest = true; }; -} \ No newline at end of file +}//namespace \ No newline at end of file diff --git a/include/Render/VulkanImpl/VulkanSwapChain.h b/include/Render/VulkanImpl/VulkanSwapChain.h index 2c4cc602..d2c135e5 100644 --- a/include/Render/VulkanImpl/VulkanSwapChain.h +++ b/include/Render/VulkanImpl/VulkanSwapChain.h @@ -45,19 +45,19 @@ namespace sh::render::vk SH_RENDER_API auto IsSwapChainSupport(VkPhysicalDevice gpu) -> bool; - SH_RENDER_API auto GetSurface() const -> const VkSurfaceKHR { return surface; } - SH_RENDER_API auto GetSwapChain() const -> const VkSwapchainKHR { return swapChain; } - SH_RENDER_API auto GetSwapChainDetail() const -> const SwapChainSupportDetails& { return details; } - SH_RENDER_API auto GetSwapChainSize() const -> VkExtent2D { return swapChainSize; } - SH_RENDER_API auto GetSwapChainImageFormat() const -> VkFormat { return swapChainImageFormat; } - SH_RENDER_API auto GetSwapChainImages() const -> const std::vector& { return swapChainImages; } - SH_RENDER_API auto GetSwapChainImages() -> std::vector& { return swapChainImages; } - SH_RENDER_API auto GetSwapChainMSAAImages() const -> const std::vector& { return swapChainImagesMSAA; } - SH_RENDER_API auto GetSwapChainMSAAImages() -> std::vector& { return swapChainImagesMSAA; } - SH_RENDER_API auto GetSwapChainDepthImages() const -> const std::vector& { return swapChainImagesDepth; } - SH_RENDER_API auto GetSwapChainDepthImages() -> std::vector& { return swapChainImagesDepth; } - SH_RENDER_API auto GetSwapChainImageCount() const -> uint32_t { return swapChainImageCount; } - SH_RENDER_API auto GetRenderTargetLayout() const -> const RenderTargetLayout& { return rtLayout; } + auto GetSurface() const -> const VkSurfaceKHR { return surface; } + auto GetSwapChain() const -> const VkSwapchainKHR { return swapChain; } + auto GetSwapChainDetail() const -> const SwapChainSupportDetails& { return details; } + auto GetSwapChainSize() const -> VkExtent2D { return swapChainSize; } + auto GetSwapChainImageFormat() const -> VkFormat { return swapChainImageFormat; } + auto GetSwapChainImages() const -> const std::vector& { return swapChainImages; } + auto GetSwapChainImages() -> std::vector& { return swapChainImages; } + auto GetSwapChainMSAAImages() const -> const std::vector& { return swapChainImagesMSAA; } + auto GetSwapChainMSAAImages() -> std::vector& { return swapChainImagesMSAA; } + auto GetSwapChainDepthImages() const -> const std::vector& { return swapChainImagesDepth; } + auto GetSwapChainDepthImages() -> std::vector& { return swapChainImagesDepth; } + auto GetSwapChainImageCount() const -> uint32_t { return swapChainImageCount; } + auto GetRenderTargetLayout() const -> const RenderTargetLayout& { return rtLayout; } private: void QuerySwapChainDetails(VkPhysicalDevice gpu); auto SelectSurfaceFormat() -> VkSurfaceFormatKHR; @@ -79,4 +79,4 @@ namespace sh::render::vk RenderTargetLayout rtLayout; }; -} \ No newline at end of file +}//namespace \ No newline at end of file diff --git a/src/Editor/EditorOutlinePass.cpp b/src/Editor/EditorOutlinePass.cpp index 926e9f6f..fdadb815 100644 --- a/src/Editor/EditorOutlinePass.cpp +++ b/src/Editor/EditorOutlinePass.cpp @@ -15,37 +15,34 @@ namespace sh::editor } SH_EDITOR_API void EditorOutlinePass::SetOutTexture(render::RenderTexture& tex) { - rd.target = &tex; + rd.SetRenderTarget(&tex); rd.renderViewers.resize(1); } SH_EDITOR_API void EditorOutlinePass::Configure(const render::RenderData& renderData) { renderTextures.clear(); - renderTextures[rd.target] = render::ResourceUsage::ColorAttachment; - if (renderData.drawables != nullptr) + renderTextures[rd.GetRenderTargets()[0]] = render::ResourceUsage::ColorAttachment; + if (renderData.GetDrawablesPtr() != nullptr) { - SetImageUsages(*renderData.drawables); - renderBatches = CreateRenderBatch(*renderData.drawables); + SetImageUsages(*renderData.GetDrawablesPtr()); + renderBatches = CreateRenderBatch(passName, *renderData.GetDrawablesPtr()); } } SH_EDITOR_API void EditorOutlinePass::Record(render::CommandBuffer& cmd, const render::IRenderContext& ctx, const render::RenderData& renderData) { - if (renderData.renderViewers.empty()) + if (renderData.renderViewers.empty() || renderData.GetDrawablesPtr() == nullptr) return; + rd.renderViewers[0] = renderData.renderViewers.front(); - rd.drawables = renderData.drawables; + render::IRenderThrMethod::SetDrawablesPtr(rd, renderData.GetDrawablesPtr()); + const render::RenderTexture& mainTaret = *rd.GetRenderTargets()[0]; cmd.SetRenderData(rd, true, true, true, true); - cmd.SetViewport(0, 0, rd.target->GetSize().x, rd.target->GetSize().y); - cmd.SetScissor(0, 0, rd.target->GetSize().x, rd.target->GetSize().y); - - if (rd.drawables == nullptr) - return; + cmd.SetViewport(0, 0, mainTaret.GetSize().x, mainTaret.GetSize().y); + cmd.SetScissor(0, 0, mainTaret.GetSize().x, mainTaret.GetSize().y); for (const RenderBatch& batch : renderBatches) - { cmd.DrawMeshBatch(batch.drawables, passName); - } } -}//namespace \ No newline at end of file +}//namespace diff --git a/src/Editor/EditorPostOutlinePass.cpp b/src/Editor/EditorPostOutlinePass.cpp index 5c488f88..df79073f 100644 --- a/src/Editor/EditorPostOutlinePass.cpp +++ b/src/Editor/EditorPostOutlinePass.cpp @@ -33,7 +33,11 @@ namespace sh::editor } SH_EDITOR_API void EditorPostOutlinePass::SetOutlineMaterial(render::Material& mat) { + if (this->mat == &mat || mat.IsPendingKill()) + return; this->mat = &mat; + if (drawable != nullptr) + drawable->SetMaterial(mat); } SH_EDITOR_API void EditorPostOutlinePass::Configure(const render::RenderData& renderData) @@ -47,15 +51,10 @@ namespace sh::editor } renderTextures.clear(); - renderTextures[renderData.target] = render::ResourceUsage::ColorAttachment; + SetRenderTargetImageUsages(renderData); + if (mat != nullptr) - { - for (const render::MaterialData::CachedRT& cachedRT : render::IRenderThrMethod::GetCachedRTs(mat->GetMaterialData())) - { - if (cachedRT.rt.IsValid() && cachedRT.pass->GetLightingPassName() == passName) - renderTextures[cachedRT.rt.Get()] = cachedRT.rt->IsDepthOnly() ? render::ResourceUsage::DepthStencilSampledRead : render::ResourceUsage::SampledRead; - } - } + SetImageUsages(*mat); } SH_EDITOR_API void EditorPostOutlinePass::Record(render::CommandBuffer& cmd, const render::IRenderContext& ctx, const render::RenderData& renderData) { @@ -70,4 +69,4 @@ namespace sh::editor ++viewerIdx; } } -}//namespace \ No newline at end of file +}//namespace diff --git a/src/Editor/EditorRenderer.cpp b/src/Editor/EditorRenderer.cpp index 0f1346f5..907b5abb 100644 --- a/src/Editor/EditorRenderer.cpp +++ b/src/Editor/EditorRenderer.cpp @@ -21,7 +21,7 @@ namespace sh::editor { if (data.tag == "EditorCamera") { - guiPass->viewportTexture = data.target; + guiPass->viewportTexture = data.GetRenderTargets()[0]; EnqueRenderPass(*outlinePass); EnqueRenderPass(*opaquePass); EnqueRenderPass(*transparentPass); @@ -36,4 +36,4 @@ namespace sh::editor } GameRenderer::Setup(data); } -}//namespace \ No newline at end of file +}//namespace diff --git a/src/Editor/EditorResource.cpp b/src/Editor/EditorResource.cpp index d0906ad4..61493634 100644 --- a/src/Editor/EditorResource.cpp +++ b/src/Editor/EditorResource.cpp @@ -20,7 +20,7 @@ namespace sh::editor { EditorResource::~EditorResource() { - for (auto icon : icons) + for (game::GUITexture* icon : icons) icon->Destroy(); } SH_EDITOR_API void EditorResource::LoadAllAssets(Project& project) @@ -115,12 +115,7 @@ namespace sh::editor planeModel->SetUUID(core::UUID{ "bbc4ef7ec45dce223297a224f8093f17" }); planeModel->GetMeshes()[0]->SetUUID(core::UUID{ "bbc4ef7ec45dce223297a224f8093f21" }); - render::RenderTargetLayout rt{}; - rt.format = render::TextureFormat::R8; - rt.depthFormat = render::TextureFormat::D24S8; - rt.bUseMSAA = false; - - render::RenderTexture* outlineTexture = core::SObject::Create(rt); + render::RenderTexture* outlineTexture = core::SObject::Create(render::TextureFormat::R8, render::TextureFormat::D24S8, false); outlineTexture->SetSize(1024, 1024); outlineTexture->Build(*renderer.GetContext()); textures.insert_or_assign("OutlineTexture", outlineTexture); diff --git a/src/Editor/EditorWorld.cpp b/src/Editor/EditorWorld.cpp index 2452b8a2..2f666ff5 100644 --- a/src/Editor/EditorWorld.cpp +++ b/src/Editor/EditorWorld.cpp @@ -105,12 +105,7 @@ namespace sh::editor if (viewportPtr != nullptr && viewportPtr->IsPendingKill()) viewportPtr->SetUUID(core::UUID::Generate()); - render::RenderTargetLayout rt{}; - rt.format = render::TextureFormat::SRGBA32; - rt.depthFormat = render::TextureFormat::D24S8; - rt.bUseMSAA = true; - - viewportTexture = core::SObject::Create(rt); + viewportTexture = core::SObject::Create(render::TextureFormat::SRGBA32, render::TextureFormat::D24S8, true); viewportTexture->SetUUID(core::UUID{ "180635b4e4d1a98ebb0064ab47dc452a" }); viewportTexture->Build(*renderer.GetContext()); } diff --git a/src/Game/Component/Render/Camera.cpp b/src/Game/Component/Render/Camera.cpp index 06a04c63..c44b089a 100644 --- a/src/Game/Component/Render/Camera.cpp +++ b/src/Game/Component/Render/Camera.cpp @@ -33,6 +33,7 @@ namespace sh::game height = size.y; viewer.viewportRect = { 0, 0, size.x, size.y }; viewer.viewportScissor = { 0, 0, size.x, size.y }; + renderData.ClearRenderTargets(); } else { @@ -42,7 +43,7 @@ namespace sh::game viewer.viewportRect = { 0, 0, size.x, size.y }; viewer.viewportScissor = { 0, 0, size.x, size.y }; - renderData.target = renderTexture; + renderData.SetRenderTarget(renderTexture); } gameObject.transform->UpdateMatrix(); viewer.pos = gameObject.transform->GetWorldPosition(); @@ -125,4 +126,4 @@ namespace sh::game renderData.renderViewers.front().projMatrix = glm::orthoRH_ZO(-dis * aspect, dis * aspect, -dis, dis, nearPlane, farPlane); } } -}//namespace \ No newline at end of file +}//namespace diff --git a/src/Game/Component/Render/PickingCamera.cpp b/src/Game/Component/Render/PickingCamera.cpp index 68dd5753..9344dc8f 100644 --- a/src/Game/Component/Render/PickingCamera.cpp +++ b/src/Game/Component/Render/PickingCamera.cpp @@ -19,12 +19,7 @@ namespace sh::game { pixels.resize(4); - render::RenderTargetLayout rt{}; - rt.format = render::TextureFormat::RGBA32; - rt.depthFormat = render::TextureFormat::D24S8; - rt.bUseMSAA = false; - - renderTex = core::SObject::Create(rt); + renderTex = core::SObject::Create(render::TextureFormat::RGBA32, render::TextureFormat::D24S8, false); renderTex->SetSize(1024, 768); renderTex->Build(*world.renderer.GetContext()); renderTex->SetName("PickingFramebuffer"); diff --git a/src/Game/Component/TestComputeShader.cpp b/src/Game/Component/TestComputeShader.cpp new file mode 100644 index 00000000..d58d26d1 --- /dev/null +++ b/src/Game/Component/TestComputeShader.cpp @@ -0,0 +1,53 @@ +#include "Component/TestComputeShader.h" + +#include "Game/World.h" +#include "Game/Input.h" + +#include "Render/Renderer.h" +#include "Render/ScriptableRenderer.h" +#include "Render/ComputeShader.h" + +#include "Core/Logger.h" + +namespace sh::game +{ + TestComputeShader::TestComputeShader(GameObject& owner) : + Component(owner) + { + } + + SH_GAME_API void TestComputeShader::Update() + { + if (!Input::GetKeyPressed(Input::KeyCode::C)) + return; + + if (shader == nullptr) + { + SH_ERROR("TestComputeShader: shader is null"); + return; + } + + render::ScriptableRenderer* const scriptableRenderer = world.renderer.GetScriptableRenderer(); + if (scriptableRenderer == nullptr) + { + SH_ERROR("TestComputeShader: ScriptableRenderer is null"); + return; + } + std::vector a(10'000, 0); + std::vector b(10'000, 0); + std::vector c(10'000, 0); + for (int i = 0; i < 10'000; ++i) + { + a[i] = i; + b[i] = 10'000 - i; + } + shader->SetFloats("bufferA", a.data(), a.size()); + shader->SetFloats("bufferB", b.data(), b.size()); + shader->SetFloats("bufferC", c.data(), c.size()); + const uint32_t workGroup = std::ceil(10'000.f / 128.f); + SH_INFO_FORMAT("Dispatch ComputeShader [{} x {} x {}]", workGroup, 1, 1); + scriptableRenderer->Dispatch(*shader, workGroup, 1, 1); + std::vector buffer = shader->GetBuffer("bufferC")->GetData(); + std::memcpy(c.data(), buffer.data(), buffer.size()); + } +}//namespace diff --git a/src/Game/GUIPass.cpp b/src/Game/GUIPass.cpp index aeefca97..2614fc32 100644 --- a/src/Game/GUIPass.cpp +++ b/src/Game/GUIPass.cpp @@ -19,7 +19,7 @@ namespace sh::game SH_GAME_API void GUIPass::Configure(const render::RenderData& renderData) { renderTextures.clear(); - renderTextures[renderData.target] = render::ResourceUsage::ColorAttachment; + SetRenderTargetImageUsages(renderData); if (viewportTexture != nullptr) renderTextures[viewportTexture] = render::ResourceUsage::SampledRead; } @@ -33,4 +33,4 @@ namespace sh::game ImGui_ImplVulkan_RenderDrawData(&gui->GetDrawData(), static_cast(cmd).GetCommandBuffer()); } } -}//namespace \ No newline at end of file +}//namespace diff --git a/src/Game/GameRenderer.cpp b/src/Game/GameRenderer.cpp index df9e6c50..b72b6521 100644 --- a/src/Game/GameRenderer.cpp +++ b/src/Game/GameRenderer.cpp @@ -4,6 +4,7 @@ #include "Render/RenderPass/DepthPass.h" #include "Render/RenderPass/TransparentPass.h" +#include "Render/RenderPass/SSAOPass.h" #include namespace sh::game @@ -19,6 +20,7 @@ namespace sh::game { depthPass = &AddRenderPass(); opaquePass = &AddRenderPass(core::Name{ "Opaque" }, render::RenderQueue::Opaque); + ssaoPass = &AddRenderPass(renderCtx); transparentPass = &AddRenderPass(); uiPass = &AddRenderPass("UI", render::RenderQueue::Transparent); guiPass = &AddRenderPass(); @@ -31,6 +33,11 @@ namespace sh::game EnqueRenderPass(*depthPass); return; } + if (data.tag == "SSAO") + { + EnqueRenderPass(*ssaoPass); + return; + } if (data.tag == "ImGUI") { if (guiPass != nullptr) diff --git a/src/Render/RenderPass/DepthPass.cpp b/src/Render/RenderPass/DepthPass.cpp index 68f1239f..a66a8ebe 100644 --- a/src/Render/RenderPass/DepthPass.cpp +++ b/src/Render/RenderPass/DepthPass.cpp @@ -15,13 +15,10 @@ namespace sh::render } SH_RENDER_API void DepthPass::Record(CommandBuffer& cmd, const IRenderContext& ctx, const RenderData& renderData) { - if (renderData.target == nullptr) - return; - // 한 프레임 내 첫 호출에서만 클리어 - if (renderData.frameIndex != lastFrameIndex) + if (renderData.GetFrameIdx() != lastFrameIndex) { - lastFrameIndex = renderData.frameIndex; + lastFrameIndex = renderData.GetFrameIdx(); callsThisFrame = 0; } const bool bClearDepth = (callsThisFrame == 0); @@ -29,7 +26,7 @@ namespace sh::render cmd.SetRenderData(renderData, false, bClearDepth, false, true); - if (renderData.drawables == nullptr) + if (renderData.GetDrawablesPtr() == nullptr) return; std::size_t viewerIdx = 0; diff --git a/src/Render/RenderPass/TransparentPass.cpp b/src/Render/RenderPass/TransparentPass.cpp index d1d581aa..4fb05ad7 100644 --- a/src/Render/RenderPass/TransparentPass.cpp +++ b/src/Render/RenderPass/TransparentPass.cpp @@ -21,11 +21,11 @@ namespace sh::render { cmd.SetRenderData(renderTarget, false, false, true, false); - if (renderTarget.drawables == nullptr) + if (renderTarget.GetDrawablesPtr() == nullptr) return; items.clear(); - for (const Drawable* drawable : *renderTarget.drawables) + for (const Drawable* drawable : *renderTarget.GetDrawablesPtr()) { RenderItem item{}; item.material = drawable->GetMaterial(); diff --git a/src/Render/RenderTexture.cpp b/src/Render/RenderTexture.cpp index cc7cbaee..c4fa99d3 100644 --- a/src/Render/RenderTexture.cpp +++ b/src/Render/RenderTexture.cpp @@ -2,13 +2,16 @@ #include "VulkanImageBuffer.h" #include "IRenderContext.h" +#include "Core/Logger.h" + namespace sh::render { - RenderTexture::RenderTexture(const RenderTargetLayout& layout) : - Texture(layout.format, 1024, 1024, false), + RenderTexture::RenderTexture(TextureFormat colorFormat, TextureFormat depthFormat, bool bMSAA) : + Texture(colorFormat, 1024, 1024, false), width(1024), height(1024), - layout(layout) + depthFormat(depthFormat), + bMSAA(bMSAA) { SetAnisoLevel(0); } @@ -16,12 +19,11 @@ namespace sh::render Texture(std::move(other)), width(other.width), height(other.height), - layout(other.layout) - { - } - RenderTexture::~RenderTexture() + depthFormat(other.depthFormat), + bMSAA(other.bMSAA) { } + RenderTexture::~RenderTexture() = default; SH_RENDER_API void RenderTexture::Build(const IRenderContext& context) { @@ -59,43 +61,34 @@ namespace sh::render bChangeSize = true; SyncDirty(); } - SH_RENDER_API auto RenderTexture::GetSize() const -> glm::vec2 - { - return { width, height }; - } - SH_RENDER_API void RenderTexture::ChangeUsage(ResourceUsage newUsage) - { - usage = newUsage; - } - void RenderTexture::CreateBuffers() { assert(context->GetRenderAPIType() == RenderAPI::Vulkan); - const bool bDepthOnly = (layout.format == TextureFormat::None); - - if (textureBuffer == nullptr) - { - if (context->GetRenderAPIType() == RenderAPI::Vulkan) - textureBuffer = std::make_unique(); - } ITextureBuffer::CreateInfo ci{}; ci.width = width; ci.height = height; - ci.format = bDepthOnly ? layout.depthFormat : GetTextureFormat(); + ci.format = IsDepthTexture() ? depthFormat : GetTextureFormat(); ci.aniso = 0; ci.filtering = 1; ci.mipLevel = 1; ci.bMSAAImg = false; ci.bRenderTarget = true; - textureBuffer->Create(*context, ci); usage = ResourceUsage::Undefined; - if (bDepthOnly) + if (IsDepthTexture()) + { + CreateDepthBuffer(ci); return; + } - if (layout.bUseMSAA) + if (textureBuffer == nullptr) + if (context->GetRenderAPIType() == RenderAPI::Vulkan) + textureBuffer = std::make_unique(); + textureBuffer->Create(*context, ci); + + if (bMSAA) { ci.bMSAAImg = true; if (msaaBuffer == nullptr) @@ -105,26 +98,38 @@ namespace sh::render } msaaBuffer->Create(*context, ci); } - - if (layout.depthFormat == TextureFormat::D32S8 || - layout.depthFormat == TextureFormat::D24S8 || - layout.depthFormat == TextureFormat::D16S8) + if (depthFormat != TextureFormat::None) + { + ci.format = depthFormat; + CreateDepthBuffer(ci); + } + } + void RenderTexture::CreateDepthBuffer(const ITextureBuffer::CreateInfo& ci) + { + const bool bValidDepthFormat = + depthFormat == TextureFormat::D32 || + depthFormat == TextureFormat::D16 || + depthFormat == TextureFormat::D32S8 || + depthFormat == TextureFormat::D24S8 || + depthFormat == TextureFormat::D16S8; + if (!bValidDepthFormat) + { + SH_ERROR_FORMAT("Invalid depth foramt!: {}", TextureFormatToString(ci.format)); + return; + } + if (IsDepthTexture()) + { + if (textureBuffer == nullptr) + if (context->GetRenderAPIType() == RenderAPI::Vulkan) + textureBuffer = std::make_unique(); + textureBuffer->Create(*context, ci); + } + else { if (depthBuffer == nullptr) - { if (context->GetRenderAPIType() == RenderAPI::Vulkan) depthBuffer = std::make_unique(); - } - ci.format = layout.depthFormat; depthBuffer->Create(*context, ci); } } - SH_RENDER_API auto RenderTexture::GetDepthBuffer() const -> ITextureBuffer* - { - if (depthBuffer != nullptr) - return depthBuffer.get(); - if (layout.format == TextureFormat::None) - return textureBuffer.get(); // depth only인 경우 textureBuffer 자체가 DepthBuffer - return nullptr; - } }//namespace \ No newline at end of file diff --git a/src/Render/ScriptableRenderPass.cpp b/src/Render/ScriptableRenderPass.cpp index aab82b77..2707a220 100644 --- a/src/Render/ScriptableRenderPass.cpp +++ b/src/Render/ScriptableRenderPass.cpp @@ -14,30 +14,7 @@ namespace sh::render renderQueue(renderQueue) { } - SH_RENDER_API void ScriptableRenderPass::Configure(const RenderData& renderData) - { - if (renderData.drawables != nullptr) - { - renderBatches = CreateRenderBatch(*renderData.drawables); - } - SetImageUsages(renderData); - } - SH_RENDER_API void ScriptableRenderPass::Record(CommandBuffer& cmd, const IRenderContext& ctx, const RenderData& renderData) - { - cmd.SetRenderData(renderData, true, true, true, true); - if (renderData.drawables == nullptr) - return; - std::size_t viewerIdx = 0; - for (const RenderViewer& viewer : renderData.renderViewers) - { - SetViewportScissor(cmd, ctx, viewer); - for (const RenderBatch& batch : renderBatches) - cmd.DrawMeshBatch(batch.drawables, passName, viewerIdx); - ++viewerIdx; - } - //ctx.GetRenderImpl().RecordCommand(cmd, passName, renderTarget, drawList, bStoreImage); - } - SH_RENDER_API auto ScriptableRenderPass::CreateRenderBatch(const std::vector& drawables) const -> std::vector + SH_RENDER_API auto ScriptableRenderPass::CreateRenderBatch(const core::Name& passName, const std::vector& drawables) -> std::vector { std::vector groups; struct GroupKey @@ -105,17 +82,50 @@ namespace sh::render } return groups; } + SH_RENDER_API void ScriptableRenderPass::Configure(const RenderData& renderData) + { + if (renderData.GetDrawablesPtr() != nullptr) + { + renderBatches = CreateRenderBatch(passName, *renderData.GetDrawablesPtr()); + } + SetImageUsages(renderData); + } + SH_RENDER_API void ScriptableRenderPass::Record(CommandBuffer& cmd, const IRenderContext& ctx, const RenderData& renderData) + { + cmd.SetRenderData(renderData, true, true, true, true); + if (renderData.GetDrawablesPtr() == nullptr) + return; + std::size_t viewerIdx = 0; + for (const RenderViewer& viewer : renderData.renderViewers) + { + SetViewportScissor(cmd, ctx, viewer); + for (const RenderBatch& batch : renderBatches) + cmd.DrawMeshBatch(batch.drawables, passName, viewerIdx); + ++viewerIdx; + } + //ctx.GetRenderImpl().RecordCommand(cmd, passName, renderTarget, drawList, bStoreImage); + } SH_RENDER_API void ScriptableRenderPass::SetImageUsages(const RenderData& renderData) { renderTextures.clear(); - - if (renderData.target != nullptr && renderData.target->IsDepthOnly()) - renderTextures[renderData.target] = ResourceUsage::DepthStencilAttachment; - else - renderTextures[renderData.target] = ResourceUsage::ColorAttachment; - if (renderData.drawables != nullptr) - SetImageUsages(*renderData.drawables); + SetRenderTargetImageUsages(renderData); + + if (renderData.GetDrawablesPtr() != nullptr) + SetImageUsages(*renderData.GetDrawablesPtr()); + } + SH_RENDER_API void ScriptableRenderPass::SetRenderTargetImageUsages(const RenderData& renderData) + { + for (const RenderTexture* target : renderData.GetRenderTargets()) + { + if (target == nullptr) + { + renderTextures[nullptr] = ResourceUsage::ColorAttachment; + continue; + } + + renderTextures[target] = target->IsDepthTexture() ? ResourceUsage::DepthStencilAttachment : ResourceUsage::ColorAttachment; + } } SH_RENDER_API void ScriptableRenderPass::SetImageUsages(const std::vector& drawables) { @@ -124,17 +134,21 @@ namespace sh::render if (drawable == nullptr || !drawable->CheckAssetValid()) continue; - const Material* const mat = drawable->GetMaterial(); - for (const MaterialData::CachedRT& cachedRT : IRenderThrMethod::GetCachedRTs(mat->GetMaterialData())) - { - if (cachedRT.rt.IsValid() && cachedRT.pass->GetLightingPassName() == passName) - renderTextures[cachedRT.rt.Get()] = cachedRT.rt->IsDepthOnly() ? ResourceUsage::DepthStencilSampledRead : ResourceUsage::SampledRead; - } for (const MaterialData::CachedRT& cachedRT : IRenderThrMethod::GetCachedRTs(drawable->GetMaterialData())) { if (cachedRT.rt.IsValid() && cachedRT.pass->GetLightingPassName() == passName) - renderTextures[cachedRT.rt.Get()] = cachedRT.rt->IsDepthOnly() ? ResourceUsage::DepthStencilSampledRead : ResourceUsage::SampledRead; + renderTextures[cachedRT.rt.Get()] = cachedRT.rt->IsDepthTexture() ? ResourceUsage::DepthStencilSampledRead : ResourceUsage::SampledRead; } + const Material* const mat = drawable->GetMaterial(); + SetImageUsages(*mat); + } + } + SH_RENDER_API void ScriptableRenderPass::SetImageUsages(const Material& mat) + { + for (const MaterialData::CachedRT& cachedRT : IRenderThrMethod::GetCachedRTs(mat.GetMaterialData())) + { + if (cachedRT.rt.IsValid() && cachedRT.pass->GetLightingPassName() == passName) + renderTextures[cachedRT.rt.Get()] = cachedRT.rt->IsDepthTexture() ? ResourceUsage::DepthStencilSampledRead : ResourceUsage::SampledRead; } } SH_RENDER_API void ScriptableRenderPass::SetViewportScissor(CommandBuffer& cmd, const IRenderContext& ctx, const RenderViewer& renderViewer) @@ -142,4 +156,4 @@ namespace sh::render cmd.SetViewport(renderViewer.viewportRect.x, renderViewer.viewportRect.y, renderViewer.viewportRect.z, renderViewer.viewportRect.w); cmd.SetScissor(renderViewer.viewportScissor.x, renderViewer.viewportScissor.y, renderViewer.viewportScissor.z, renderViewer.viewportScissor.w); } -}//namespace \ No newline at end of file +}//namespace diff --git a/src/Render/ScriptableRenderer.cpp b/src/Render/ScriptableRenderer.cpp index 2d43e652..33e7d247 100644 --- a/src/Render/ScriptableRenderer.cpp +++ b/src/Render/ScriptableRenderer.cpp @@ -111,7 +111,7 @@ namespace sh::render for (int i = 0; i < activePasses.size(); ++i) { - barriers[i] = BuildBarrierInfo(*activePasses[i], data.frameIndex); + barriers[i] = BuildBarrierInfo(*activePasses[i], data.GetFrameIdx()); } std::vector>> futurePasses; @@ -146,8 +146,8 @@ namespace sh::render SH_RENDER_API void ScriptableRenderer::ExecuteTransfer(uint32_t imgIdx) { RenderData data{}; - data.frameIndex = imgIdx; - data.drawables = nullptr; + IRenderThrMethod::SetFrameIndex(data, imgIdx); + IRenderThrMethod::SetDrawablesPtr(data, nullptr); IRenderThrMethod::Configure(*cpyPass, data); diff --git a/src/Render/ShadowMapManager.cpp b/src/Render/ShadowMapManager.cpp index 447d7f5f..b47f7a56 100644 --- a/src/Render/ShadowMapManager.cpp +++ b/src/Render/ShadowMapManager.cpp @@ -55,12 +55,7 @@ namespace sh::render SH_ERROR("ShadowMapManager: context is not initialized"); return; } - RenderTargetLayout layout{}; - layout.format = TextureFormat::None; - layout.depthFormat = TextureFormat::D32; - layout.bUseMSAA = false; - - atlas = core::SObject::Create(layout); + atlas = core::SObject::Create(TextureFormat::None, TextureFormat::D32, false); atlas->SetSize(atlasSize, atlasSize); atlas->Build(*ctx); atlas->SetName("ShadowAtlas"); @@ -96,7 +91,7 @@ namespace sh::render casterLightSpace.clear(); renderData.renderViewers.clear(); - renderData.target = atlas; + renderData.SetRenderTarget(atlas); renderData.renderViewers.reserve(casters.size()); for (const IShadowCaster* caster : casters) diff --git a/src/Render/VulkanImpl/VulkanCommandBuffer.cpp b/src/Render/VulkanImpl/VulkanCommandBuffer.cpp index a20702af..d8d9d268 100644 --- a/src/Render/VulkanImpl/VulkanCommandBuffer.cpp +++ b/src/Render/VulkanImpl/VulkanCommandBuffer.cpp @@ -24,9 +24,54 @@ #include "Core/Logger.h" #include - +#include namespace sh::render::vk { + namespace + { + auto HasStencil(TextureFormat format) -> bool + { + return format == TextureFormat::D32S8 || + format == TextureFormat::D24S8 || + format == TextureFormat::D16S8; + } + + auto BuildRenderTargetLayout(const VulkanContext& context, const std::vector& targets) -> RenderTargetLayout + { + RenderTargetLayout layout{}; + + std::vector colorFormats; + colorFormats.reserve(targets.size()); + + const RenderTexture* main = targets[0]; + if (main == nullptr) + { + layout.depthFormat = context.GetSwapChain().GetRenderTargetLayout().depthFormat; + layout.bUseMSAA = context.GetSampleCount() != VkSampleCountFlagBits::VK_SAMPLE_COUNT_1_BIT; + } + else + { + layout.depthFormat = main->GetDepthFormat(); + layout.bUseMSAA = main->GetMSAABuffer() != nullptr; + } + + for (const RenderTexture* target : targets) + { + if (target == nullptr) + colorFormats.push_back(context.GetSwapChain().GetRenderTargetLayout().colorFormats[0]); + else + { + if (target->IsDepthTexture()) + layout.depthFormat = target->GetDepthFormat(); + else + colorFormats.push_back(target->GetTextureFormat()); + } + } + layout.colorFormats = std::move(colorFormats); + return layout; + } + } + VulkanCommandBuffer::VulkanCommandBuffer(const VulkanContext& context) : context(context) {} @@ -135,45 +180,66 @@ namespace sh::render::vk bBeginRender = false; } - const RenderTexture* const target = renderData.target; - - const bool bDepthOnly = target == nullptr ? false : target->IsDepthOnly(); - const VulkanImageBuffer* colorImg = nullptr; - const VulkanImageBuffer* colorMSAAImg = nullptr; + const VulkanImageBuffer* colorImgs[10]{}; // 힙 할당 최소화 + const VulkanImageBuffer* colorMSAAImgs[10]{}; // 힙 할당 최소화 const VulkanImageBuffer* depthImg = nullptr; - RenderTargetLayout rtLayout{}; - if (target != nullptr) - { - colorImg = bDepthOnly ? nullptr : static_cast(target->GetTextureBuffer()); - colorMSAAImg = static_cast(target->GetMSAABuffer()); - depthImg = static_cast(target->GetDepthBuffer()); - rtLayout = target->GetLayout(); - } + std::size_t colorImgsCount = 0; + std::size_t colorMSAAImgsCount = 0; + + // 메모) 깊이 버퍼는 제일 앞의 렌더 텍스쳐 것을 쓰고 깊이 버퍼 전용 텍스쳐가 있으면 그걸 우선시 함 + const RenderTexture* const main = renderData.GetRenderTargets()[0]; + if (main == nullptr) + depthImg = &context.GetSwapChain().GetSwapChainDepthImages()[renderData.GetFrameIdx()]; else + depthImg = static_cast(main->GetDepthBuffer()); + + for (const RenderTexture* target : renderData.GetRenderTargets()) { - colorImg = &context.GetSwapChain().GetSwapChainImages()[renderData.frameIndex]; - colorMSAAImg = &context.GetSwapChain().GetSwapChainMSAAImages()[renderData.frameIndex]; - depthImg = &context.GetSwapChain().GetSwapChainDepthImages()[renderData.frameIndex]; - rtLayout = context.GetSwapChain().GetRenderTargetLayout(); + if (target == nullptr) + { + colorImgs[colorImgsCount++] = &context.GetSwapChain().GetSwapChainImages()[renderData.GetFrameIdx()]; + const std::vector& swapChainMSAAImages = context.GetSwapChain().GetSwapChainMSAAImages(); + colorMSAAImgs[colorMSAAImgsCount++] = swapChainMSAAImages.empty() ? nullptr : &swapChainMSAAImages[renderData.GetFrameIdx()]; + } + else + { + if (!target->IsDepthTexture()) + { + colorImgs[colorImgsCount++] = static_cast(target->GetTextureBuffer()); + colorMSAAImgs[colorMSAAImgsCount++] = static_cast(target->GetMSAABuffer()); + } + else + depthImg = static_cast(target->GetDepthBuffer()); + } } - const VulkanImageBuffer* const sizeRef = colorImg != nullptr ? colorImg : depthImg; + RenderTargetLayout rtLayout = BuildRenderTargetLayout(context, renderData.GetRenderTargets()); + + const VulkanImageBuffer* const sizeRef = colorImgsCount != 0 ? colorImgs[0] : depthImg; + if (sizeRef == nullptr) + return; const uint32_t width = sizeRef->GetWidth(); const uint32_t height = sizeRef->GetHeight(); const bool bHasDepth = (depthImg != nullptr); + const bool bDepthOnly = colorImgsCount == 0; - VkRenderingAttachmentInfoKHR colorAttachment{}; - if (!bDepthOnly) + VkRenderingAttachmentInfoKHR colorAttachments[10]{}; // 힙 할당 최소화 + const std::size_t colorAttachmentsCount = colorImgsCount; + for (std::size_t i = 0; i < colorAttachmentsCount; ++i) { + assert(colorImgs[i] != nullptr); + assert(colorImgs[i]->GetWidth() == width && colorImgs[i]->GetHeight() == height); + + VkRenderingAttachmentInfoKHR& colorAttachment = colorAttachments[i]; colorAttachment.sType = VkStructureType::VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; - const bool bMSAA = (colorMSAAImg != nullptr); - colorAttachment.imageView = bMSAA ? colorMSAAImg->GetImageView() : colorImg->GetImageView(); + const bool bMSAA = (i < colorMSAAImgsCount && colorMSAAImgs[i] != nullptr); + colorAttachment.imageView = bMSAA ? colorMSAAImgs[i]->GetImageView() : colorImgs[i]->GetImageView(); colorAttachment.imageLayout = VkImageLayout::VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; colorAttachment.loadOp = bClearColor ? VkAttachmentLoadOp::VK_ATTACHMENT_LOAD_OP_CLEAR : VkAttachmentLoadOp::VK_ATTACHMENT_LOAD_OP_LOAD; colorAttachment.storeOp = bStoreColor ? VkAttachmentStoreOp::VK_ATTACHMENT_STORE_OP_STORE : VkAttachmentStoreOp::VK_ATTACHMENT_STORE_OP_DONT_CARE; colorAttachment.clearValue.color = { { 0.f, 0.f, 0.f, 1.f } }; if (bMSAA) { - colorAttachment.resolveImageView = colorImg->GetImageView(); + colorAttachment.resolveImageView = colorImgs[i]->GetImageView(); colorAttachment.resolveImageLayout = VkImageLayout::VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; colorAttachment.resolveMode = VkResolveModeFlagBitsKHR::VK_RESOLVE_MODE_AVERAGE_BIT_KHR; } @@ -191,11 +257,11 @@ namespace sh::render::vk VkRenderingInfoKHR renderingInfo{}; renderingInfo.sType = VkStructureType::VK_STRUCTURE_TYPE_RENDERING_INFO_KHR; - renderingInfo.colorAttachmentCount = bDepthOnly ? 0 : 1; - renderingInfo.pColorAttachments = bDepthOnly ? nullptr : &colorAttachment; + renderingInfo.colorAttachmentCount = static_cast(colorAttachmentsCount); + renderingInfo.pColorAttachments = colorAttachmentsCount == 0 ? nullptr : colorAttachments; renderingInfo.pDepthAttachment = bHasDepth ? &depthAttachment : nullptr; // depth only는 스텐실 지원x - renderingInfo.pStencilAttachment = (bHasDepth && !bDepthOnly) ? &depthAttachment : nullptr; + renderingInfo.pStencilAttachment = (bHasDepth && HasStencil(rtLayout.depthFormat) && !bDepthOnly) ? &depthAttachment : nullptr; renderingInfo.renderArea = { { 0, 0 }, { width, height } }; renderingInfo.layerCount = 1; @@ -366,16 +432,17 @@ namespace sh::render::vk VulkanImageBuffer* imgBuffer = nullptr; VulkanImageBuffer* msaaBuffer = nullptr; - if (barrier.target.index() == 0) + if (std::holds_alternative(barrier.target)) { - imgBuffer = (static_cast(std::get<0>(barrier.target)->GetTextureBuffer())); - msaaBuffer = (static_cast(std::get<0>(barrier.target)->GetMSAABuffer())); + imgBuffer = static_cast(std::get(barrier.target)->GetTextureBuffer()); + msaaBuffer = static_cast(std::get(barrier.target)->GetMSAABuffer()); } else { - const uint32_t imgIdx = std::get<1>(barrier.target); + const uint32_t imgIdx = std::get(barrier.target); imgBuffer = &context.GetSwapChain().GetSwapChainImages()[imgIdx]; - msaaBuffer = &context.GetSwapChain().GetSwapChainMSAAImages()[imgIdx]; + const auto& swapChainMSAAImages = context.GetSwapChain().GetSwapChainMSAAImages(); + msaaBuffer = swapChainMSAAImages.empty() ? nullptr : &context.GetSwapChain().GetSwapChainMSAAImages()[imgIdx]; } VkImageLayout srcLayout, dstLayout; @@ -434,7 +501,8 @@ namespace sh::render::vk mapUsageFn(barrier.lastUsage, srcLayout, srcStage, srcAccess); mapUsageFn(barrier.curUsage, dstLayout, dstStage, dstAccess); - VulkanImageBuffer::BarrierCommand(buffer, *imgBuffer, srcLayout, dstLayout, srcStage, dstStage, srcAccess, dstAccess); + if (imgBuffer != nullptr) + VulkanImageBuffer::BarrierCommand(buffer, *imgBuffer, srcLayout, dstLayout, srcStage, dstStage, srcAccess, dstAccess); if (msaaBuffer != nullptr) { @@ -611,4 +679,4 @@ namespace sh::render::vk vkCmdDrawIndexed(buffer, indexCount, 1, firstIndex, 0, 0); ++renderCall; } -}//namespace \ No newline at end of file +}//namespace diff --git a/src/Render/VulkanImpl/VulkanPipeline.cpp b/src/Render/VulkanImpl/VulkanPipeline.cpp index 4175889d..6cf8db32 100644 --- a/src/Render/VulkanImpl/VulkanPipeline.cpp +++ b/src/Render/VulkanImpl/VulkanPipeline.cpp @@ -6,6 +6,16 @@ #include namespace sh::render::vk { + namespace + { + auto HasStencil(TextureFormat format) -> bool + { + return format == TextureFormat::D32S8 || + format == TextureFormat::D24S8 || + format == TextureFormat::D16S8; + } + } + SH_RENDER_API VulkanPipeline::VulkanPipeline(VkDevice device, const RenderTargetLayout& rt) : device(device), pipeline(nullptr), shader(nullptr), @@ -223,14 +233,15 @@ namespace sh::render::vk colorBlendAttachment.alphaBlendOp = VkBlendOp::VK_BLEND_OP_ADD; colorBlendAttachment.dstAlphaBlendFactor = VkBlendFactor::VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;; - const bool bDepthOnly = (rtLayout.format == TextureFormat::None); + const uint32_t colorAttachmentCount = static_cast(rtLayout.colorFormats.size()); VkPipelineColorBlendStateCreateInfo colorBlending{}; + std::vector colorBlendAttachments(colorAttachmentCount, colorBlendAttachment); colorBlending.sType = VkStructureType::VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; colorBlending.logicOpEnable = VK_FALSE; colorBlending.logicOp = VkLogicOp::VK_LOGIC_OP_COPY; - colorBlending.attachmentCount = bDepthOnly ? 0 : 1; - colorBlending.pAttachments = bDepthOnly ? nullptr : &colorBlendAttachment; + colorBlending.attachmentCount = colorAttachmentCount; + colorBlending.pAttachments = colorBlendAttachments.empty() ? nullptr : colorBlendAttachments.data(); colorBlending.blendConstants[0] = 0.0f; colorBlending.blendConstants[1] = 0.0f; colorBlending.blendConstants[2] = 0.0f; @@ -259,16 +270,20 @@ namespace sh::render::vk stageCreateInfo.pSpecializationInfo = &specializationInfo; } - VkFormat colorFormat = VulkanImageBuffer::ConvertTextureFormat(rtLayout.format); + std::vector colorFormats; + colorFormats.reserve(colorAttachmentCount); + for (TextureFormat format : rtLayout.colorFormats) + colorFormats.push_back(VulkanImageBuffer::ConvertTextureFormat(format)); + VkFormat depthFormat = VulkanImageBuffer::ConvertTextureFormat(rtLayout.depthFormat); VkPipelineRenderingCreateInfoKHR pipelineRenderingCI{}; pipelineRenderingCI.sType = VkStructureType::VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR; - pipelineRenderingCI.colorAttachmentCount = bDepthOnly ? 0 : 1; - pipelineRenderingCI.pColorAttachmentFormats = bDepthOnly ? nullptr : &colorFormat; + pipelineRenderingCI.colorAttachmentCount = colorAttachmentCount; + pipelineRenderingCI.pColorAttachmentFormats = colorFormats.empty() ? nullptr : colorFormats.data(); pipelineRenderingCI.depthAttachmentFormat = depthFormat; // depth-only RT는 view aspect=DEPTH-only로 만들었으므로 stencil 포맷도 비활성화. - pipelineRenderingCI.stencilAttachmentFormat = bDepthOnly ? VkFormat::VK_FORMAT_UNDEFINED : depthFormat; + pipelineRenderingCI.stencilAttachmentFormat = (!rtLayout.IsDepthOnly() && HasStencil(rtLayout.depthFormat)) ? depthFormat : VkFormat::VK_FORMAT_UNDEFINED; VkGraphicsPipelineCreateInfo pipelineInfo{}; pipelineInfo.sType = VkStructureType::VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; @@ -365,4 +380,4 @@ namespace sh::render::vk { specializationData = dataPtr; } -} \ No newline at end of file +} diff --git a/src/Render/VulkanImpl/VulkanRenderer.cpp b/src/Render/VulkanImpl/VulkanRenderer.cpp index 4672d7a0..2062443f 100644 --- a/src/Render/VulkanImpl/VulkanRenderer.cpp +++ b/src/Render/VulkanImpl/VulkanRenderer.cpp @@ -116,8 +116,8 @@ namespace sh::render::vk std::size_t viewIdx = 0; for (RenderData& rd : renderDatas) { - rd.frameIndex = imgIdx; - rd.drawables = &drawables; + IRenderThrMethod::SetFrameIndex(rd, imgIdx); + IRenderThrMethod::SetDrawablesPtr(rd, &drawables); IRenderThrMethod::Setup(*renderer, rd); IRenderThrMethod::Execute(*renderer, rd); // ScriptableRenderer에 등록된 패스들을 렌더큐 순서대로, 병렬적으로 커맨드에 기록 diff --git a/src/Render/VulkanImpl/VulkanSwapChain.cpp b/src/Render/VulkanImpl/VulkanSwapChain.cpp index 4a6280cf..d78eec3a 100644 --- a/src/Render/VulkanImpl/VulkanSwapChain.cpp +++ b/src/Render/VulkanImpl/VulkanSwapChain.cpp @@ -163,7 +163,7 @@ namespace sh::render::vk if (swapChainImageFormat == VkFormat::VK_FORMAT_B8G8R8A8_SRGB) colorFormat = TextureFormat::SBGRA32; - rtLayout.format = colorFormat; + rtLayout.colorFormats[0] = colorFormat; rtLayout.depthFormat = TextureFormat::None; rtLayout.bUseMSAA = bMSAA; for (VkImage img : images) From bb5bdce6fa70d189bfb71e145da23c1ba46386d0 Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Sun, 10 May 2026 18:39:18 +0900 Subject: [PATCH 46/60] fix: Change a pass name ShadowMapPass to DepthPass on ProjectTemplate --- resource/ProjectTemplate/Assets/SampleWorld/default.shader | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resource/ProjectTemplate/Assets/SampleWorld/default.shader b/resource/ProjectTemplate/Assets/SampleWorld/default.shader index 3468e48a..15be92e2 100644 --- a/resource/ProjectTemplate/Assets/SampleWorld/default.shader +++ b/resource/ProjectTemplate/Assets/SampleWorld/default.shader @@ -10,7 +10,7 @@ Shader "Default Shader" } Pass { - LightingPass "ShadowMapPass" + LightingPass "DepthPass" Cull Off; Stage Vertex From fa6dbe4a795c30c6bc17a2b921b9f9d404ac1e72 Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Sun, 10 May 2026 20:21:09 +0900 Subject: [PATCH 47/60] feat(Material): Support uniform array properties --- include/Render/Material.h | 50 ++++- include/Render/MaterialPropertyBlock.h | 84 +++++++- src/Render/Material.cpp | 272 ++++++++++++++++++++++--- src/Render/MaterialPropertyBlock.cpp | 136 ++++++++++++- 4 files changed, 506 insertions(+), 36 deletions(-) diff --git a/include/Render/Material.h b/include/Render/Material.h index 5f66fca3..eba90402 100644 --- a/include/Render/Material.h +++ b/include/Render/Material.h @@ -25,6 +25,8 @@ #include #include #include +#include +#include namespace sh::render { @@ -53,6 +55,12 @@ namespace sh::render template void SetProperty(const std::string& name, const T& data); + template + void SetProperty(const std::string& name, const std::vector& data); + template + void SetProperty(const std::string& name, const std::array& data); + template + void SetPropertyArray(const std::string& name, const T* data, std::size_t count); SH_RENDER_API void SetProperty(const std::string& name, const glm::vec4& data); SH_RENDER_API void SetProperty(const std::string& name, const Texture* data); SH_RENDER_API void SetProperty(const std::string& name, Texture* data); @@ -131,6 +139,46 @@ namespace sh::render bPropertyDirty = true; } template + inline void Material::SetProperty(const std::string& name, const std::vector& data) + { + SetPropertyArray(name, data.data(), data.size()); + } + template + inline void Material::SetProperty(const std::string& name, const std::array& data) + { + SetPropertyArray(name, data.data(), data.size()); + } + template + inline void Material::SetPropertyArray(const std::string& name, const T* data, std::size_t count) + { + if (!core::IsValid(shader)) + return; + + const Shader::PropertyInfo* propInfo = shader->GetProperty(name); + assert(propInfo != nullptr); + if (propInfo == nullptr) + return; + + assert(propInfo->type == core::reflection::GetType()); + if (propInfo->type != core::reflection::GetType()) + return; + + assert(propInfo->locations.empty() || propInfo->locations.front().memberPtr->IsArray()); + if (!propInfo->locations.empty() && !propInfo->locations.front().memberPtr->IsArray()) + return; + + propertyBlock.SetArrayProperty(name, data, count); + + for (auto& location : propInfo->locations) + { + auto it = std::find(dirtyProps.begin(), dirtyProps.end(), std::pair{ location.passPtr.Get(), location.layoutPtr }); + if (it == dirtyProps.end()) + dirtyProps.push_back({ location.passPtr.Get(), location.layoutPtr }); + } + + bPropertyDirty = true; + } + template inline void Material::SetConstant(const std::string& name, const T& value) { if (shader == nullptr) @@ -224,4 +272,4 @@ namespace sh::render static_assert(true); return {}; } -} \ No newline at end of file +} diff --git a/include/Render/MaterialPropertyBlock.h b/include/Render/MaterialPropertyBlock.h index 4d23b179..eadc1e3f 100644 --- a/include/Render/MaterialPropertyBlock.h +++ b/include/Render/MaterialPropertyBlock.h @@ -6,10 +6,17 @@ #include "Core/SContainer.hpp" #include "glm/mat4x4.hpp" +#include "glm/vec2.hpp" +#include "glm/vec3.hpp" +#include "glm/vec4.hpp" #include #include #include +#include +#include +#include +#include namespace sh::render { @@ -21,12 +28,18 @@ namespace sh::render template>> void SetProperty(const std::string& name, const T& data); + template + void SetArrayProperty(const std::string& name, const T* data, std::size_t count); SH_RENDER_API void SetProperty(const std::string& name, const Texture* data); SH_RENDER_API auto GetScalarProperty(const std::string& name) const -> std::optional; SH_RENDER_API auto GetVectorProperty(const std::string& name) const -> const glm::vec4*; SH_RENDER_API auto GetMatrixProperty(const std::string& name) const -> const glm::mat4*; SH_RENDER_API auto GetTextureProperty(const std::string& name) const -> const Texture*; + SH_RENDER_API auto GetIntArrayProperty(const std::string& name) const -> const std::vector*; + SH_RENDER_API auto GetScalarArrayProperty(const std::string& name) const -> const std::vector*; + SH_RENDER_API auto GetVectorArrayProperty(const std::string& name) const -> const std::vector*; + SH_RENDER_API auto GetMatrixArrayProperty(const std::string& name) const -> const std::vector*; SH_RENDER_API auto GetTextureProperties() const -> const std::unordered_map&; SH_RENDER_API auto Serialize() const -> core::Json override; @@ -35,6 +48,10 @@ namespace sh::render std::unordered_map scalarProperties; std::unordered_map vecProperties; std::unordered_map matProperties; + std::unordered_map> intArrayProperties; + std::unordered_map> scalarArrayProperties; + std::unordered_map> vecArrayProperties; + std::unordered_map> matArrayProperties; core::SHashMap textureProperties; }; @@ -58,4 +75,69 @@ namespace sh::render else static_assert(true); } -}//namespace \ No newline at end of file + template + inline void MaterialPropertyBlock::SetArrayProperty(const std::string& name, const T* data, std::size_t count) + { + if constexpr (std::is_arithmetic_v) + { + if constexpr (std::is_integral_v) + { + std::vector values(count); + for (std::size_t i = 0; i < count; ++i) + values[i] = static_cast(data[i]); + intArrayProperties.insert_or_assign(name, std::move(values)); + } + else + { + std::vector values(count); + for (std::size_t i = 0; i < count; ++i) + values[i] = static_cast(data[i]); + scalarArrayProperties.insert_or_assign(name, std::move(values)); + } + } + else if constexpr (std::is_same_v) + { + std::vector values(count); + for (std::size_t i = 0; i < count; ++i) + values[i] = glm::vec4{ data[i], 0.0f, 0.0f }; + vecArrayProperties.insert_or_assign(name, std::move(values)); + } + else if constexpr (std::is_same_v) + { + std::vector values(count); + for (std::size_t i = 0; i < count; ++i) + values[i] = glm::vec4{ data[i], 0.0f }; + vecArrayProperties.insert_or_assign(name, std::move(values)); + } + else if constexpr (std::is_same_v) + { + if (count == 0) + vecArrayProperties.insert_or_assign(name, std::vector{}); + else + vecArrayProperties.insert_or_assign(name, std::vector{ data, data + count }); + } + else if constexpr (std::is_same_v) + { + std::vector values(count); + for (std::size_t i = 0; i < count; ++i) + values[i] = core::Util::ConvertMat2ToMat4(data[i]); + matArrayProperties.insert_or_assign(name, std::move(values)); + } + else if constexpr (std::is_same_v) + { + std::vector values(count); + for (std::size_t i = 0; i < count; ++i) + values[i] = core::Util::ConvertMat3ToMat4(data[i]); + matArrayProperties.insert_or_assign(name, std::move(values)); + } + else if constexpr (std::is_same_v) + { + if (count == 0) + matArrayProperties.insert_or_assign(name, std::vector{}); + else + matArrayProperties.insert_or_assign(name, std::vector{ data, data + count }); + } + else + static_assert(true); + } +}//namespace diff --git a/src/Render/Material.cpp b/src/Render/Material.cpp index 1426d620..079592eb 100644 --- a/src/Render/Material.cpp +++ b/src/Render/Material.cpp @@ -6,6 +6,8 @@ #include "Core/SContainer.hpp" #include "Core/Logger.h" +#include + namespace sh::render { SH_RENDER_API Material::Material() : @@ -161,69 +163,147 @@ namespace sh::render bool isSampler = false; for (const UniformStructLayout::UniformMember& member : uniformLayout->GetMembers()) { + const auto writeArrayFn = [&](const auto* values, auto convertFn) + { + if (values == nullptr || member.count == 0) + return; + + const uint32_t stride = member.layoutSize / member.count; + const uint32_t count = std::min(member.count, static_cast(values->size())); + for (uint32_t i = 0; i < count; ++i) + { + auto converted = convertFn((*values)[i]); + SetData(converted, data, member.offset + stride * i); + } + }; + if (member.typeHash == core::reflection::GetType().hash) { - auto var = propertyBlock.GetScalarProperty(member.name); - if (var.has_value()) - SetData(static_cast(var.value()), data, member.offset); + if (member.IsArray()) + { + writeArrayFn(propertyBlock.GetIntArrayProperty(member.name), + [](int value) { return value; }); + } else - SetData(0, data, member.offset); + { + auto var = propertyBlock.GetScalarProperty(member.name); + if (var.has_value()) + SetData(static_cast(var.value()), data, member.offset); + else + SetData(0, data, member.offset); + } } else if (member.typeHash == core::reflection::GetType().hash) { - auto var = propertyBlock.GetScalarProperty(member.name); - if (var.has_value()) - SetData(var.value(), data, member.offset); + if (member.IsArray()) + { + writeArrayFn(propertyBlock.GetScalarArrayProperty(member.name), + [](float value) { return value; }); + } else - SetData(0.0f, data, member.offset); + { + auto var = propertyBlock.GetScalarProperty(member.name); + if (var.has_value()) + SetData(var.value(), data, member.offset); + else + SetData(0.0f, data, member.offset); + } } else if (member.typeHash == core::reflection::GetType().hash) { - auto var = propertyBlock.GetVectorProperty(member.name); - if (var) - SetData(glm::vec2{ var->x, var->y }, data, member.offset); + if (member.IsArray()) + { + writeArrayFn(propertyBlock.GetVectorArrayProperty(member.name), + [](const glm::vec4& value) { return glm::vec2{ value.x, value.y }; }); + } else - SetData(glm::vec2{ 0.f }, data, member.offset); + { + auto var = propertyBlock.GetVectorProperty(member.name); + if (var) + SetData(glm::vec2{ var->x, var->y }, data, member.offset); + else + SetData(glm::vec2{ 0.f }, data, member.offset); + } } else if (member.typeHash == core::reflection::GetType().hash) { - auto var = propertyBlock.GetVectorProperty(member.name); - if (var) - SetData(glm::vec3{ var->x, var->y, var->z }, data, member.offset); + if (member.IsArray()) + { + writeArrayFn(propertyBlock.GetVectorArrayProperty(member.name), + [](const glm::vec4& value) { return glm::vec3{ value.x, value.y, value.z }; }); + } else - SetData(glm::vec3{ 0.f }, data, member.offset); + { + auto var = propertyBlock.GetVectorProperty(member.name); + if (var) + SetData(glm::vec3{ var->x, var->y, var->z }, data, member.offset); + else + SetData(glm::vec3{ 0.f }, data, member.offset); + } } else if (member.typeHash == core::reflection::GetType().hash) { - auto var = propertyBlock.GetVectorProperty(member.name); - if (var) - SetData(*var, data, member.offset); + if (member.IsArray()) + { + writeArrayFn(propertyBlock.GetVectorArrayProperty(member.name), + [](const glm::vec4& value) { return value; }); + } else - SetData(glm::vec4{ 0.f }, data, member.offset); + { + auto var = propertyBlock.GetVectorProperty(member.name); + if (var) + SetData(*var, data, member.offset); + else + SetData(glm::vec4{ 0.f }, data, member.offset); + } } else if (member.typeHash == core::reflection::GetType().hash) { - auto var = propertyBlock.GetMatrixProperty(member.name); - if (var) - SetData(core::Util::ConvertMat4ToMat2(*var), data, member.offset); + if (member.IsArray()) + { + writeArrayFn(propertyBlock.GetMatrixArrayProperty(member.name), + [](const glm::mat4& value) { return core::Util::ConvertMat4ToMat2(value); }); + } else - SetData(glm::mat2{ 1.f }, data, member.offset); + { + auto var = propertyBlock.GetMatrixProperty(member.name); + if (var) + SetData(core::Util::ConvertMat4ToMat2(*var), data, member.offset); + else + SetData(glm::mat2{ 1.f }, data, member.offset); + } } else if (member.typeHash == core::reflection::GetType().hash) { - auto var = propertyBlock.GetMatrixProperty(member.name); - if (var) - SetData(core::Util::ConvertMat4ToMat3(*var), data, member.offset); + if (member.IsArray()) + { + writeArrayFn(propertyBlock.GetMatrixArrayProperty(member.name), + [](const glm::mat4& value) { return core::Util::ConvertMat4ToMat3(value); }); + } else - SetData(glm::mat3{ 1.f }, data, member.offset); + { + auto var = propertyBlock.GetMatrixProperty(member.name); + if (var) + SetData(core::Util::ConvertMat4ToMat3(*var), data, member.offset); + else + SetData(glm::mat3{ 1.f }, data, member.offset); + } } else if (member.typeHash == core::reflection::GetType().hash) { - auto var = propertyBlock.GetMatrixProperty(member.name); - if (var) - SetData(*var, data, member.offset); + if (member.IsArray()) + { + writeArrayFn(propertyBlock.GetMatrixArrayProperty(member.name), + [](const glm::mat4& value) { return value; }); + } else - SetData(glm::mat2{ 1.f }, data, member.offset); + { + auto var = propertyBlock.GetMatrixProperty(member.name); + if (var) + SetData(*var, data, member.offset); + else + SetData(glm::mat4{ 1.f }, data, member.offset); + } } else if (member.typeHash == core::reflection::GetType().hash) { @@ -380,6 +460,132 @@ namespace sh::render } } } + if (propertyJson.contains("matrix")) + { + const auto& matJson = propertyJson["matrix"]; + for (const auto& [name, value] : matJson.items()) + { + if (!value.is_array() || value.size() != 4) + continue; + + glm::mat4 mat{ 0.f }; + for (int i = 0; i < 4; ++i) + { + const core::Json& col = value[i]; + if (!col.is_array() || col.size() != 4) + continue; + mat[i] = { col[0], col[1], col[2], col[3] }; + } + SetProperty(name, mat); + } + } + if (propertyJson.contains("scalarArrays")) + { + const auto& scalarArrayJson = propertyJson["scalarArrays"]; + for (const auto& [name, value] : scalarArrayJson.items()) + { + if (!value.is_array()) + continue; + + std::vector values = value.get>(); + SetProperty(name, values); + } + } + if (propertyJson.contains("intArrays")) + { + const auto& intArrayJson = propertyJson["intArrays"]; + for (const auto& [name, value] : intArrayJson.items()) + { + if (!value.is_array()) + continue; + + std::vector values = value.get>(); + SetProperty(name, values); + } + } + if (propertyJson.contains("vectorArrays")) + { + const auto& vectorArrayJson = propertyJson["vectorArrays"]; + for (const auto& [name, value] : vectorArrayJson.items()) + { + if (!value.is_array()) + continue; + + std::vector values{}; + for (const core::Json& vecJson : value) + { + if (!vecJson.is_array() || vecJson.size() != 4) + continue; + values.push_back(glm::vec4{ + vecJson[0].get(), + vecJson[1].get(), + vecJson[2].get(), + vecJson[3].get() + }); + } + + const Shader::PropertyInfo* propInfo = core::IsValid(shader) ? shader->GetProperty(name) : nullptr; + if (propInfo != nullptr && propInfo->type == core::reflection::GetType()) + { + std::vector vec2Values(values.size()); + for (std::size_t i = 0; i < values.size(); ++i) + vec2Values[i] = glm::vec2{ values[i].x, values[i].y }; + SetProperty(name, vec2Values); + } + else if (propInfo != nullptr && propInfo->type == core::reflection::GetType()) + { + std::vector vec3Values(values.size()); + for (std::size_t i = 0; i < values.size(); ++i) + vec3Values[i] = glm::vec3{ values[i].x, values[i].y, values[i].z }; + SetProperty(name, vec3Values); + } + else + SetProperty(name, values); + } + } + if (propertyJson.contains("matrixArrays")) + { + const auto& matrixArrayJson = propertyJson["matrixArrays"]; + for (const auto& [name, value] : matrixArrayJson.items()) + { + if (!value.is_array()) + continue; + + std::vector values{}; + for (const core::Json& matJson : value) + { + if (!matJson.is_array() || matJson.size() != 4) + continue; + glm::mat4 mat{ 0.f }; + for (int i = 0; i < 4; ++i) + { + const core::Json& col = matJson[i]; + if (!col.is_array() || col.size() != 4) + continue; + mat[i] = { col[0], col[1], col[2], col[3] }; + } + values.push_back(mat); + } + + const Shader::PropertyInfo* propInfo = core::IsValid(shader) ? shader->GetProperty(name) : nullptr; + if (propInfo != nullptr && propInfo->type == core::reflection::GetType()) + { + std::vector mat2Values(values.size()); + for (std::size_t i = 0; i < values.size(); ++i) + mat2Values[i] = core::Util::ConvertMat4ToMat2(values[i]); + SetProperty(name, mat2Values); + } + else if (propInfo != nullptr && propInfo->type == core::reflection::GetType()) + { + std::vector mat3Values(values.size()); + for (std::size_t i = 0; i < values.size(); ++i) + mat3Values[i] = core::Util::ConvertMat4ToMat3(values[i]); + SetProperty(name, mat3Values); + } + else + SetProperty(name, values); + } + } if (propertyJson.contains("textures")) { const auto& texJson = propertyJson["textures"]; @@ -409,4 +615,4 @@ namespace sh::render } } } -}//namespace \ No newline at end of file +}//namespace diff --git a/src/Render/MaterialPropertyBlock.cpp b/src/Render/MaterialPropertyBlock.cpp index f73025b6..deb3574a 100644 --- a/src/Render/MaterialPropertyBlock.cpp +++ b/src/Render/MaterialPropertyBlock.cpp @@ -8,6 +8,10 @@ namespace sh::render scalarProperties.clear(); vecProperties.clear(); matProperties.clear(); + intArrayProperties.clear(); + scalarArrayProperties.clear(); + vecArrayProperties.clear(); + matArrayProperties.clear(); textureProperties.clear(); } SH_RENDER_API void MaterialPropertyBlock::SetProperty(const std::string& name, const Texture* data) @@ -42,6 +46,34 @@ namespace sh::render return nullptr; return it->second; } + SH_RENDER_API auto MaterialPropertyBlock::GetIntArrayProperty(const std::string& name) const -> const std::vector* + { + auto it = intArrayProperties.find(name); + if (it == intArrayProperties.end()) + return nullptr; + return &it->second; + } + SH_RENDER_API auto MaterialPropertyBlock::GetScalarArrayProperty(const std::string& name) const -> const std::vector* + { + auto it = scalarArrayProperties.find(name); + if (it == scalarArrayProperties.end()) + return nullptr; + return &it->second; + } + SH_RENDER_API auto MaterialPropertyBlock::GetVectorArrayProperty(const std::string& name) const -> const std::vector* + { + auto it = vecArrayProperties.find(name); + if (it == vecArrayProperties.end()) + return nullptr; + return &it->second; + } + SH_RENDER_API auto MaterialPropertyBlock::GetMatrixArrayProperty(const std::string& name) const -> const std::vector* + { + auto it = matArrayProperties.find(name); + if (it == matArrayProperties.end()) + return nullptr; + return &it->second; + } SH_RENDER_API auto MaterialPropertyBlock::GetTextureProperties() const -> const std::unordered_map& { return textureProperties; @@ -68,6 +100,37 @@ namespace sh::render } propertiesJson["matrix"] = std::move(matrixJson); + core::Json intArrayJson{}; + for (auto& [name, values] : intArrayProperties) + intArrayJson[name] = values; + propertiesJson["intArrays"] = std::move(intArrayJson); + + core::Json scalarArrayJson{}; + for (auto& [name, values] : scalarArrayProperties) + scalarArrayJson[name] = values; + propertiesJson["scalarArrays"] = std::move(scalarArrayJson); + + core::Json vectorArrayJson{}; + for (auto& [name, values] : vecArrayProperties) + { + for (const glm::vec4& value : values) + vectorArrayJson[name].push_back({ value.x, value.y, value.z, value.w }); + } + propertiesJson["vectorArrays"] = std::move(vectorArrayJson); + + core::Json matrixArrayJson{}; + for (auto& [name, values] : matArrayProperties) + { + for (const glm::mat4& value : values) + { + core::Json matJson{}; + for (int i = 0; i < 4; ++i) + matJson.push_back({ value[i].x, value[i].y, value[i].z, value[i].w }); + matrixArrayJson[name].push_back(std::move(matJson)); + } + } + propertiesJson["matrixArrays"] = std::move(matrixArrayJson); + core::Json texJson{}; for (auto& [name, value] : textureProperties) texJson[name] = value->GetUUID().ToString(); @@ -120,6 +183,77 @@ namespace sh::render SetProperty(name, mat); } } + if (json.contains("scalarArrays")) + { + const auto& scalarArrayJson = json["scalarArrays"]; + for (const auto& [name, value] : scalarArrayJson.items()) + { + if (value.is_array()) + { + std::vector values = value.get>(); + SetArrayProperty(name, values.data(), values.size()); + } + } + } + if (json.contains("intArrays")) + { + const auto& intArrayJson = json["intArrays"]; + for (const auto& [name, value] : intArrayJson.items()) + { + if (value.is_array()) + { + std::vector values = value.get>(); + SetArrayProperty(name, values.data(), values.size()); + } + } + } + if (json.contains("vectorArrays")) + { + const auto& vectorArrayJson = json["vectorArrays"]; + for (const auto& [name, value] : vectorArrayJson.items()) + { + if (!value.is_array()) + continue; + std::vector values{}; + for (const core::Json& vecJson : value) + { + if (!vecJson.is_array() || vecJson.size() != 4) + continue; + values.push_back(glm::vec4{ + vecJson[0].get(), + vecJson[1].get(), + vecJson[2].get(), + vecJson[3].get() + }); + } + SetArrayProperty(name, values.data(), values.size()); + } + } + if (json.contains("matrixArrays")) + { + const auto& matrixArrayJson = json["matrixArrays"]; + for (const auto& [name, value] : matrixArrayJson.items()) + { + if (!value.is_array()) + continue; + std::vector values{}; + for (const core::Json& matJson : value) + { + if (!matJson.is_array() || matJson.size() != 4) + continue; + glm::mat4 mat{ 0.f }; + for (int i = 0; i < 4; ++i) + { + const core::Json& col = matJson[i]; + if (!col.is_array() || col.size() != 4) + continue; + mat[i] = { col[0], col[1], col[2], col[3] }; + } + values.push_back(mat); + } + SetArrayProperty(name, values.data(), values.size()); + } + } if (json.contains("textures")) { const auto& texJson = json["textures"]; @@ -134,4 +268,4 @@ namespace sh::render } } } -}//namespace \ No newline at end of file +}//namespace From 01f14719e43f9601fd44279048fc0d2f9f436f86 Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Sun, 10 May 2026 23:38:22 +0900 Subject: [PATCH 48/60] feat: Implement SSAOPass --- include/Game/Component/Render/SSAOComponent.h | 73 +++++++ include/Render/Formats.hpp | 22 ++- include/Render/Material.h | 4 +- include/Render/RenderPass/DepthPass.h | 3 - include/Render/RenderPass/SSAOPass.h | 45 +++++ include/Render/Texture.h | 37 ++-- include/Render/VulkanImpl/VulkanImageBuffer.h | 6 +- resource/shaders/SSAO.shader | 104 ++++++++++ src/Editor/EditorResource.cpp | 52 ++--- src/Editor/EditorWorld.cpp | 1 + src/Game/Component/Render/SSAOComponent.cpp | 178 ++++++++++++++++++ src/Render/RenderPass/DepthPass.cpp | 11 +- src/Render/RenderPass/SSAOPass.cpp | 97 ++++++++++ src/Render/Texture.cpp | 111 ++++------- src/Render/VulkanImpl/VulkanCommandBuffer.cpp | 1 - src/Render/VulkanImpl/VulkanImageBuffer.cpp | 20 +- 16 files changed, 616 insertions(+), 149 deletions(-) create mode 100644 include/Game/Component/Render/SSAOComponent.h create mode 100644 include/Render/RenderPass/SSAOPass.h create mode 100644 resource/shaders/SSAO.shader create mode 100644 src/Game/Component/Render/SSAOComponent.cpp create mode 100644 src/Render/RenderPass/SSAOPass.cpp diff --git a/include/Game/Component/Render/SSAOComponent.h b/include/Game/Component/Render/SSAOComponent.h new file mode 100644 index 00000000..e75a46e1 --- /dev/null +++ b/include/Game/Component/Render/SSAOComponent.h @@ -0,0 +1,73 @@ +#pragma once +#include "Game/Export.h" +#include "Game/Component/Component.h" + +#include "Render/RenderData.h" + +#include +namespace sh::render +{ + class Material; + class RenderTexture; + class Texture; +} + +namespace sh::game +{ + class Camera; + + /// @brief 카메라에 부착해 SSAO 를 활성화한다. + class SSAOComponent : public Component + { + COMPONENT(SSAOComponent) + public: + SH_GAME_API SSAOComponent(GameObject& owner); + SH_GAME_API ~SSAOComponent(); + + SH_GAME_API void Awake() override; + SH_GAME_API void OnDestroy() override; + SH_GAME_API void BeginUpdate() override; + + SH_GAME_API void SetMaterial(render::Material* mat); + + auto GetDepthRT() const -> render::RenderTexture* { return depthRT; } + auto GetAORT() const -> render::RenderTexture* { return aoRT; } + auto GetRadius() const -> float { return radius; } + auto GetBias() const -> float { return bias; } + auto GetPower() const -> float { return power; } + private: + void EnsureRenderTextures(); + void UpdateMaterialUniforms(); + static void CreateKernel(); + private: + PROPERTY(mat, core::PropertyOption::sobjPtr) + render::Material* mat = nullptr; + + PROPERTY(radius) + float radius = 0.5f; + PROPERTY(bias) + float bias = 0.025f; + PROPERTY(power) + float power = 1.5f; + + PROPERTY(depthRT, core::PropertyOption::sobjPtr, core::PropertyOption::invisible) + render::RenderTexture* depthRT = nullptr; + PROPERTY(normalRT, core::PropertyOption::sobjPtr, core::PropertyOption::invisible) + render::RenderTexture* normalRT = nullptr; + PROPERTY(aoRT, core::PropertyOption::sobjPtr, core::PropertyOption::invisible) + render::RenderTexture* aoRT = nullptr; + PROPERTY(camera, core::PropertyOption::sobjPtr, core::PropertyOption::invisible); + Camera* camera = nullptr; + PROPERTY(noiseTex, core::PropertyOption::sobjPtr, core::PropertyOption::invisible); + render::Texture* noiseTex = nullptr; + + render::RenderData depthRenderData; + render::RenderData ssaoRenderData; + + uint32_t cachedWidth = 0; + uint32_t cachedHeight = 0; + + static std::array kernel; + static bool bKernelInit; + }; +}//namespace diff --git a/include/Render/Formats.hpp b/include/Render/Formats.hpp index 707e38e8..ea1b82c8 100644 --- a/include/Render/Formats.hpp +++ b/include/Render/Formats.hpp @@ -1,5 +1,8 @@ #pragma once +#include +#include + namespace sh::render { enum class TextureFormat @@ -10,6 +13,7 @@ namespace sh::render SBGR24, SBGRA32, RGB24, + RG32F, RGBA32, BGR24, BGRA32, @@ -25,12 +29,27 @@ namespace sh::render { switch (format) { + case TextureFormat::RG32F: + return 2; case TextureFormat::R8: return 1; } return 4; } + inline static auto GetTextureFormatPixelSize(TextureFormat format) -> std::size_t + { + switch (format) + { + case TextureFormat::RG32F: + return sizeof(uint16_t) * 2; + case TextureFormat::R8: + return 1; + default: + return 4; + } + } + inline static auto IsDepthTexture(TextureFormat format) -> bool { if (format == TextureFormat::D32S8 || @@ -52,6 +71,7 @@ namespace sh::render case TextureFormat::SBGR24: return "SBGR24"; case TextureFormat::SBGRA32: return "SBGRA32"; case TextureFormat::RGB24: return "RGB24"; + case TextureFormat::RG32F: return "RG32F"; case TextureFormat::RGBA32: return "RGBA32"; case TextureFormat::R8: return "R8"; case TextureFormat::D32S8: return "D32S8"; @@ -62,4 +82,4 @@ namespace sh::render default: return "None"; } } -}//namespace \ No newline at end of file +}//namespace diff --git a/include/Render/Material.h b/include/Render/Material.h index eba90402..7b3bfc05 100644 --- a/include/Render/Material.h +++ b/include/Render/Material.h @@ -129,7 +129,7 @@ namespace sh::render propertyBlock.SetProperty(name, data); - for (auto& location : propInfo->locations) + for (const Shader::PropertyInfo::Location& location : propInfo->locations) { auto it = std::find(dirtyProps.begin(), dirtyProps.end(), std::pair{ location.passPtr.Get(), location.layoutPtr}); if (it == dirtyProps.end()) @@ -169,7 +169,7 @@ namespace sh::render propertyBlock.SetArrayProperty(name, data, count); - for (auto& location : propInfo->locations) + for (const Shader::PropertyInfo::Location& location : propInfo->locations) { auto it = std::find(dirtyProps.begin(), dirtyProps.end(), std::pair{ location.passPtr.Get(), location.layoutPtr }); if (it == dirtyProps.end()) diff --git a/include/Render/RenderPass/DepthPass.h b/include/Render/RenderPass/DepthPass.h index ede4259c..5b2f2d25 100644 --- a/include/Render/RenderPass/DepthPass.h +++ b/include/Render/RenderPass/DepthPass.h @@ -14,8 +14,5 @@ namespace sh::render SH_RENDER_API void Configure(const RenderData& renderData) override; SH_RENDER_API void Record(CommandBuffer& cmd, const IRenderContext& ctx, const RenderData& renderData) override; - private: - uint32_t lastFrameIndex = ~0u; - uint32_t callsThisFrame = 0; }; }//namespace diff --git a/include/Render/RenderPass/SSAOPass.h b/include/Render/RenderPass/SSAOPass.h new file mode 100644 index 00000000..1dc7dced --- /dev/null +++ b/include/Render/RenderPass/SSAOPass.h @@ -0,0 +1,45 @@ +#pragma once +#include "Render/Export.h" +#include "Render/ScriptableRenderPass.h" +#include "Render/RenderData.h" + +#include "Core/Observer.hpp" +#include "Core/GCObject.h" + +namespace sh::render +{ + class RenderTexture; + class Mesh; + class Drawable; + class Material; + + /// @brief depthRT 를 샘플링하여 aoRT(R8) 에 AO 값을 쓰는 풀스크린 패스. + /// "SSAO" 태그가 RenderData 에 있을 때만 동작. + class SSAOPass : public ScriptableRenderPass + { + public: + SH_RENDER_API SSAOPass(const IRenderContext& ctx); + SH_RENDER_API ~SSAOPass(); + + SH_RENDER_API void SetMaterial(Material& mat); + protected: + SH_RENDER_API void Configure(const RenderData& renderData) override; + SH_RENDER_API void Record(CommandBuffer& cmd, const IRenderContext& ctx, const RenderData& renderData) override; + private: + void EnsureDrawable(); + private: + const IRenderContext& ctx; + + struct Resource : core::GCObject + { + Mesh* plane = nullptr; + Drawable* drawable = nullptr; + Material* mat = nullptr; + + SH_RENDER_API void PushReferenceObjects(core::GarbageCollection& gc) override; + } resource; + RenderData localRenderData; + + core::Observer::Listener onMatDestroy; + }; +}//namespace diff --git a/include/Render/Texture.h b/include/Render/Texture.h index d47b3d3c..1e471248 100644 --- a/include/Render/Texture.h +++ b/include/Render/Texture.h @@ -34,19 +34,21 @@ namespace sh::render SH_RENDER_API Texture(Texture&& other) noexcept; SH_RENDER_API virtual ~Texture(); + SH_RENDER_API void SyncDirty() override; + SH_RENDER_API void Sync() override; + SH_RENDER_API void OnPropertyChanged(const core::reflection::Property& prop) override; /// @brief 픽셀 데이터를 지정한다. /// @brief [주의] 동기화 타이밍에 텍스쳐 버퍼가 재설정됨. /// @param data 데이터 포인터 /// @param mipLevel 밉 레벨 - SH_RENDER_API void SetPixelData(const std::vector& pixels, uint32_t mipLevel = 0); - SH_RENDER_API void SetPixelData(std::vector&& pixels, uint32_t mipLevel = 0); + SH_RENDER_API void SetPixelData(std::vector pixels, uint32_t mipLevel = 0); /// @brief 픽셀 데이터를 지정한다. /// @brief [주의] 동기화 타이밍에 텍스쳐 버퍼가 재설정됨. /// @param pixels 데이터 포인터 /// @param size 데이터 사이즈 /// @param mipLevel 밉 레벨 SH_RENDER_API void SetPixelData(const uint8_t* pixels, std::size_t size, uint32_t mipLevel = 0); - SH_RENDER_API virtual auto GetPixelData() const -> const std::vector>&; + virtual auto GetPixelData() const -> const std::vector>& { return pixels; } SH_RENDER_API virtual auto GetPixelData(uint32_t mipLevel) const -> const std::vector&; SH_RENDER_API virtual void Build(const IRenderContext& context); @@ -54,36 +56,29 @@ namespace sh::render /// @brief 네이티브 텍스쳐 버퍼를 가져온다. /// @return 텍스쳐 버퍼 포인터 SH_RENDER_API auto GetTextureBuffer() const -> ITextureBuffer*; - SH_RENDER_API auto GetTextureFormat() const -> TextureFormat; - - SH_RENDER_API auto GetWidth() const -> uint32_t; - SH_RENDER_API auto GetHeight() const -> uint32_t; /// @brief 텍스쳐 포멧을 변경한다. /// @brief [주의] 동기화 타이밍에 텍스쳐 버퍼가 재설정됨. /// @param target 텍스쳐 포멧 SH_RENDER_API void ChangeTextureFormat(TextureFormat target); - SH_RENDER_API auto IsSRGB() const -> bool; - SH_RENDER_API auto GetMipLevel() const -> uint32_t; - SH_RENDER_API void SetAnisoLevel(uint32_t aniso); - SH_RENDER_API auto GetAnisoLevel() const -> uint32_t; - + SH_RENDER_API void SetGenerateMipmap(bool bGenerate); - SH_RENDER_API auto IsGenerateMipmap() const -> bool; - - SH_RENDER_API void SyncDirty() override; - SH_RENDER_API void Sync() override; SH_RENDER_API void SetSRGB(bool bSRGB); SH_RENDER_API void SetFiltering(Filtering filter); - SH_RENDER_API auto GetFiltering() const-> Filtering; - - SH_RENDER_API void OnPropertyChanged(const core::reflection::Property& prop) override; SH_RENDER_API void ExportToPNG(const std::filesystem::path& path); - SH_RENDER_API auto GetContext() const -> const IRenderContext* { return context; } + SH_RENDER_API auto GetMipLevel() const -> uint32_t; + auto GetContext() const -> const IRenderContext* { return context; } + auto GetTextureFormat() const -> TextureFormat { return format; } + auto GetWidth() const -> uint32_t { return width; } + auto GetHeight() const -> uint32_t { return height; } + auto GetFiltering() const -> Filtering { return filtering; } + auto IsSRGB() const -> bool { return bSRGB; } + auto GetAnisoLevel() const -> uint32_t { return aniso; } + auto IsGenerateMipmap() const -> bool { return bGenerateMipmap; } private: void CreateTextureBuffer(); auto CheckSRGB() const -> bool; @@ -114,4 +109,4 @@ namespace sh::render bool bGenerateMipmap = true; bool bSetDataDirty = false; }; -} \ No newline at end of file +}//namespace \ No newline at end of file diff --git a/include/Render/VulkanImpl/VulkanImageBuffer.h b/include/Render/VulkanImpl/VulkanImageBuffer.h index 3757a3b0..a23a2597 100644 --- a/include/Render/VulkanImpl/VulkanImageBuffer.h +++ b/include/Render/VulkanImpl/VulkanImageBuffer.h @@ -55,7 +55,7 @@ namespace sh::render::vk VkAccessFlags dstAccess); SH_RENDER_API static auto ConvertTextureFormat(TextureFormat format) -> VkFormat; private: - static auto GetChannelCount(VkFormat format) -> uint32_t; + static auto GetFormatPixelSize(VkFormat format) -> uint32_t; private: const VulkanContext* ctx = nullptr; @@ -70,11 +70,11 @@ namespace sh::render::vk VkSampleCountFlagBits sample = VkSampleCountFlagBits::VK_SAMPLE_COUNT_1_BIT; uint32_t width = 0; uint32_t height = 0; - uint32_t channel = 4; + uint32_t pixelSize = 4; uint32_t aniso = 0; uint32_t mipLevel = 1; bool bSwapChainImg = false; bool bRenderTarget = false; }; -} \ No newline at end of file +} diff --git a/resource/shaders/SSAO.shader b/resource/shaders/SSAO.shader new file mode 100644 index 00000000..a7eb5bee --- /dev/null +++ b/resource/shaders/SSAO.shader @@ -0,0 +1,104 @@ +#version 430 core + +Shader "SSAO Shader" +{ + Property + { + vec3 kernel[64]; + sampler2D normalTex; + sampler2D depthTex; + sampler2D noiseTex; + } + + Pass + { + LightingPass "SSAOPass" + Cull Back; + + Stage Vertex + { + layout(location = 0) out vec2 fragUV; + void main() + { + fragUV = UV; + gl_Position = vec4(VERTEX, 1.0f); + } + } + Stage Fragment + { + layout(location = 0) in vec2 uvs; + + layout(location = 0) out vec4 outColor; + + uniform vec3 kernel[64]; + uniform sampler2D depthTex; + uniform sampler2D normalTex; + uniform sampler2D noiseTex; + + vec3 ReconstructViewPos(vec2 uv, float depth) + { + vec4 clipPos = vec4(uv.x * 2.0 - 1.0, (1.0 - uv.y) * 2.0 - 1.0, depth, 1.0); + vec4 viewPos = inverse(MATRIX_PROJ) * clipPos; + viewPos /= viewPos.w; + return viewPos.xyz; + } + + void main() + { + float depth = texture(depthTex, uvs).r; + + if (depth >= 1.0) + { + outColor = vec4(1.0); + return; + } + vec3 fragPos = ReconstructViewPos(uvs, depth); + vec3 normal = texture(normalTex, uvs).xyz; + normal = normalize(normal * 2.0 - 1.0); + + const vec2 noiseScale = vec2(1024.0/4.0, 768.0/4.0); + vec3 randomVec = normalize(vec3(texture(noiseTex, uvs * noiseScale).rg, 0.0)); + + vec3 tangent = normalize(randomVec - normal * dot(randomVec, normal)); + vec3 bitangent = cross(normal, tangent); + mat3 TBN = mat3(tangent, bitangent, normal); + + float occlusion = 0.0; + + for (int i = 0; i < 64; ++i) + { + const float radius = 0.5f; + vec3 samplePos = fragPos + TBN * kernel[i] * radius; + + vec4 offset = MATRIX_PROJ * vec4(samplePos, 1.0); + offset.xyz /= offset.w; + offset.xy = vec2(offset.x * 0.5 + 0.5, 0.5 - offset.y * 0.5); + + if (offset.x < 0.0 || offset.x > 1.0 || + offset.y < 0.0 || offset.y > 1.0) + { + continue; + } + + float sampleDepth = texture(depthTex, offset.xy).r; + vec3 sampleViewPos = ReconstructViewPos(offset.xy, sampleDepth); + + float rangeCheck = smoothstep( + 0.0, + 1.0, + radius / abs(fragPos.z - sampleViewPos.z) + ); + + const float bias = 0.025f; + occlusion += (sampleViewPos.z >= samplePos.z + bias ? 1.0 : 0.0) * rangeCheck; + } + + const float strength = 1.5f; + occlusion = 1.0 - (occlusion / 64.0); + occlusion = pow(occlusion, strength); + + outColor = vec4(vec3(occlusion), 1.0); + } + } + } +} \ No newline at end of file diff --git a/src/Editor/EditorResource.cpp b/src/Editor/EditorResource.cpp index 61493634..9686fe24 100644 --- a/src/Editor/EditorResource.cpp +++ b/src/Editor/EditorResource.cpp @@ -82,36 +82,38 @@ namespace sh::editor for (auto icon : icons) icon->SetName("icon"); - auto lineShader = shaders.insert_or_assign("Line", static_cast(shaderLoader.Load("shaders/line.shader"))).first->second; - auto errorShader = shaders.insert_or_assign("ErrorShader", static_cast(shaderLoader.Load("shaders/error.shader"))).first->second; - auto gridShader = shaders.insert_or_assign("GridShader", static_cast(shaderLoader.Load("shaders/grid.shader"))).first->second; - auto pickingShader = shaders.insert_or_assign("EditorPickingShader", static_cast(shaderLoader.Load("shaders/EditorPicking.shader"))).first->second; - auto outlineShader = shaders.insert_or_assign("OutlineShader", static_cast(shaderLoader.Load("shaders/outline.shader"))).first->second; - auto triangleShader = shaders.insert_or_assign("TriangleShader", static_cast(shaderLoader.Load("shaders/triangle.shader"))).first->second; - auto outlinePreShader = shaders.insert_or_assign("OutlinePreShader", static_cast(shaderLoader.Load("shaders/EditorOutlinePre.shader"))).first->second; - auto outlinePostShader = shaders.insert_or_assign("OutlinePostShader", static_cast(shaderLoader.Load("shaders/EditorOutlinePost.shader"))).first->second; - auto uiTextShader = shaders.insert_or_assign("UITextShader", static_cast(shaderLoader.Load("shaders/UIText.shader"))).first->second; - - auto blackTex = textures.insert_or_assign("BlackTexture", static_cast(texLoader.Load("textures/black.png"))).first->second; + render::Shader* const lineShader = shaders.insert_or_assign("Line", static_cast(shaderLoader.Load("shaders/line.shader"))).first->second; + render::Shader* const errorShader = shaders.insert_or_assign("ErrorShader", static_cast(shaderLoader.Load("shaders/error.shader"))).first->second; + render::Shader* const gridShader = shaders.insert_or_assign("GridShader", static_cast(shaderLoader.Load("shaders/grid.shader"))).first->second; + render::Shader* const pickingShader = shaders.insert_or_assign("EditorPickingShader", static_cast(shaderLoader.Load("shaders/EditorPicking.shader"))).first->second; + render::Shader* const outlineShader = shaders.insert_or_assign("OutlineShader", static_cast(shaderLoader.Load("shaders/outline.shader"))).first->second; + render::Shader* const triangleShader = shaders.insert_or_assign("TriangleShader", static_cast(shaderLoader.Load("shaders/triangle.shader"))).first->second; + render::Shader* const outlinePreShader = shaders.insert_or_assign("OutlinePreShader", static_cast(shaderLoader.Load("shaders/EditorOutlinePre.shader"))).first->second; + render::Shader* const outlinePostShader = shaders.insert_or_assign("OutlinePostShader", static_cast(shaderLoader.Load("shaders/EditorOutlinePost.shader"))).first->second; + render::Shader* const uiTextShader = shaders.insert_or_assign("UITextShader", static_cast(shaderLoader.Load("shaders/UIText.shader"))).first->second; + render::Shader* const ssaoShader = shaders.insert_or_assign("SSAOShader", static_cast(shaderLoader.Load("shaders/SSAO.shader"))).first->second; + + render::Texture* const blackTex = textures.insert_or_assign("BlackTexture", static_cast(texLoader.Load("textures/black.png"))).first->second; blackTex->Build(*renderer.GetContext()); blackTex->SetUUID(core::UUID{ "bbc4ef7ec45dce223297a224f8093f18" }); - auto errorMat = materials.insert_or_assign("ErrorMaterial", core::SObject::Create(errorShader)).first->second; - auto lineMat = materials.insert_or_assign("LineMaterial", core::SObject::Create(lineShader)).first->second; - auto gridMat = materials.insert_or_assign("GridMaterial", core::SObject::Create(gridShader)).first->second; - auto pickingMat = materials.insert_or_assign("PickingMaterial", core::SObject::Create(pickingShader)).first->second; - auto triMat = materials.insert_or_assign("TriangleMaterial", core::SObject::Create(triangleShader)).first->second; - auto outlinePreMat = materials.insert_or_assign("OutlinePreMaterial", core::SObject::Create(outlinePreShader)).first->second; - auto outlinePostMat = materials.insert_or_assign("OutlinePostMaterial", core::SObject::Create(outlinePostShader)).first->second; - auto uiTextMat = materials.insert_or_assign("UITextMaterial", core::SObject::Create(uiTextShader)).first->second; - - auto sphereModel = models.insert_or_assign("SphereModel", static_cast(modelLoader.Load("model/Sphere.obj"))).first->second; + render::Material* const errorMat = materials.insert_or_assign("ErrorMaterial", core::SObject::Create(errorShader)).first->second; + render::Material* const lineMat = materials.insert_or_assign("LineMaterial", core::SObject::Create(lineShader)).first->second; + render::Material* const gridMat = materials.insert_or_assign("GridMaterial", core::SObject::Create(gridShader)).first->second; + render::Material* const pickingMat = materials.insert_or_assign("PickingMaterial", core::SObject::Create(pickingShader)).first->second; + render::Material* const triMat = materials.insert_or_assign("TriangleMaterial", core::SObject::Create(triangleShader)).first->second; + render::Material* const outlinePreMat = materials.insert_or_assign("OutlinePreMaterial", core::SObject::Create(outlinePreShader)).first->second; + render::Material* const outlinePostMat = materials.insert_or_assign("OutlinePostMaterial", core::SObject::Create(outlinePostShader)).first->second; + render::Material* const uiTextMat = materials.insert_or_assign("UITextMaterial", core::SObject::Create(uiTextShader)).first->second; + render::Material* const ssaoMat = materials.insert_or_assign("SSAOMaterial", core::SObject::Create(ssaoShader)).first->second; + + render::Model* const sphereModel = models.insert_or_assign("SphereModel", static_cast(modelLoader.Load("model/Sphere.obj"))).first->second; sphereModel->SetUUID(core::UUID{ "bbc4ef7ec45dce223297a224f8093f15" }); sphereModel->GetMeshes()[0]->SetUUID(core::UUID{ "bbc4ef7ec45dce223297a224f8093f19" }); - auto cubeModel = models.insert_or_assign("CubeModel", static_cast(modelLoader.Load("model/cube.obj"))).first->second; + render::Model* const cubeModel = models.insert_or_assign("CubeModel", static_cast(modelLoader.Load("model/cube.obj"))).first->second; cubeModel->SetUUID(core::UUID{ "bbc4ef7ec45dce223297a224f8093f16" }); cubeModel->GetMeshes()[0]->SetUUID(core::UUID{ "bbc4ef7ec45dce223297a224f8093f20" }); - auto planeModel = models.insert_or_assign("PlaneModel", static_cast(modelLoader.Load("model/Plane.glb"))).first->second; + render::Model* const planeModel = models.insert_or_assign("PlaneModel", static_cast(modelLoader.Load("model/Plane.glb"))).first->second; planeModel->SetUUID(core::UUID{ "bbc4ef7ec45dce223297a224f8093f17" }); planeModel->GetMeshes()[0]->SetUUID(core::UUID{ "bbc4ef7ec45dce223297a224f8093f21" }); @@ -151,6 +153,10 @@ namespace sh::editor uiTextMat->SetUUID(core::UUID{ "bbc4ef7ec45dce223297a224f8093f23" }); uiTextMat->Build(*renderer.GetContext()); uiTextMat->SetProperty("color", glm::vec3{ 1.f, 1.f, 1.f }); + + ssaoMat->SetName("SSAOMaterial"); + ssaoMat->SetUUID(core::UUID{ "bbc4ef7ec45dce223297a224f8093f24" }); + ssaoMat->Build(*renderer.GetContext()); } SH_EDITOR_API auto EditorResource::GetIcon(Icon icon) -> game::GUITexture* diff --git a/src/Editor/EditorWorld.cpp b/src/Editor/EditorWorld.cpp index 2f666ff5..4e40dc94 100644 --- a/src/Editor/EditorWorld.cpp +++ b/src/Editor/EditorWorld.cpp @@ -13,6 +13,7 @@ #include "Render/Mesh/Grid.h" #include "Render/Model.h" #include "Render/ShadowMapManager.h" +#include "Render/RenderPass/SSAOPass.h" #include "Editor/Component/EditorControl.h" diff --git a/src/Game/Component/Render/SSAOComponent.cpp b/src/Game/Component/Render/SSAOComponent.cpp new file mode 100644 index 00000000..bdac72d8 --- /dev/null +++ b/src/Game/Component/Render/SSAOComponent.cpp @@ -0,0 +1,178 @@ +#include "Component/Render/SSAOComponent.h" +#include "Component/Render/Camera.h" +#include "World.h" +#include "GameObject.h" +#include "GameRenderer.h" + +#include "Core/SObject.h" +#include "Core/GarbageCollection.h" +#include "Core/Util.h" + +#include "Render/Renderer.h" +#include "Render/IRenderContext.h" +#include "Render/Material.h" +#include "Render/RenderTexture.h" +#include "Render/RenderPass/SSAOPass.h" + +#include +#include + +#include +#include +namespace sh::game +{ + std::array SSAOComponent::kernel{}; + bool SSAOComponent::bKernelInit = false; + + SSAOComponent::SSAOComponent(GameObject& owner) : + Component(owner) + { + canPlayInEditor = true; + + depthRenderData.tag = core::Name{ "Depth" }; + ssaoRenderData.tag = core::Name{ "SSAO" }; + } + SSAOComponent::~SSAOComponent() = default; + + SH_GAME_API void SSAOComponent::Awake() + { + camera = gameObject.GetComponent(); + if (camera == nullptr) + SH_ERROR("SSAOComponent requires a Camera component on the same GameObject"); + CreateKernel(); + SetMaterial(static_cast(core::SObject::GetSObjectUsingResolver(core::UUID{ "bbc4ef7ec45dce223297a224f8093f24" }))); + } + SH_GAME_API void SSAOComponent::OnDestroy() + { + if (depthRT != nullptr) + depthRT->Destroy(); + if (aoRT != nullptr) + aoRT->Destroy(); + + Super::OnDestroy(); + } + SH_GAME_API void SSAOComponent::BeginUpdate() + { + if (camera == nullptr) + return; + + depthRenderData.renderViewers = ssaoRenderData.renderViewers = camera->GetRenderData().renderViewers; + EnsureRenderTextures(); + UpdateMaterialUniforms(); + + world.renderer.PushRenderData(depthRenderData); + world.renderer.PushRenderData(ssaoRenderData); + } + SH_GAME_API void SSAOComponent::SetMaterial(render::Material* _mat) + { + mat = _mat; + if (mat == nullptr) + return; + + GameRenderer* const renderer = dynamic_cast(world.renderer.GetScriptableRenderer()); + if (renderer == nullptr) + return; + + render::SSAOPass& ssaoPass = renderer->GetSSAOPass(); + ssaoPass.SetMaterial(*mat); + } + + void SSAOComponent::EnsureRenderTextures() + { + uint32_t w = static_cast(camera->GetWidth()); + uint32_t h = static_cast(camera->GetHeight()); + if (w == 0 || h == 0) + return; + + render::IRenderContext* ctx = world.renderer.GetContext(); + if (ctx == nullptr) + return; + + core::GarbageCollection& gc = *core::GarbageCollection::GetInstance(); + + if (depthRT == nullptr) + { + depthRT = core::SObject::Create(render::TextureFormat::None, render::TextureFormat::D32, false); + depthRT->SetSize(w, h); + depthRT->Build(*ctx); + } + if (normalRT == nullptr) + { + normalRT = core::SObject::Create(render::TextureFormat::RGBA32, render::TextureFormat::None, false); + normalRT->SetSize(w, h); + normalRT->Build(*ctx); + } + depthRenderData.SetRenderTargets({ depthRT, normalRT }); + + if (aoRT == nullptr) + { + aoRT = core::SObject::Create(render::TextureFormat::R8, render::TextureFormat::None, false); + aoRT->SetSize(w, h); + aoRT->Build(*ctx); + + ssaoRenderData.SetRenderTarget(aoRT); + } + + if (noiseTex == nullptr) + { + noiseTex = core::SObject::Create(render::TextureFormat::RG32F, 4, 4, false); + std::array noise{}; // 4x4x2 channels, R16G16_SFLOAT + for (int i = 0; i < 32; ++i) + noise[i] = glm::packHalf1x16(core::Util::RandomRange(-1.f, 1.f)); + noiseTex->SetPixelData(reinterpret_cast(noise.data()), sizeof(uint16_t) * noise.size()); + noiseTex->SetAnisoLevel(0); + noiseTex->SetFiltering(render::Texture::Filtering::Box); + noiseTex->Build(*ctx); + } + + if (w != cachedWidth || h != cachedHeight) + { + depthRT->SetSize(w, h); + normalRT->SetSize(w, h); + aoRT->SetSize(w, h); + cachedWidth = w; + cachedHeight = h; + } + } + void SSAOComponent::UpdateMaterialUniforms() + { + if (!core::IsValid(mat)) + return; + + const glm::mat4& proj = camera->GetProjMatrix(); + const glm::mat4 invProj = glm::inverse(proj); + + mat->SetProperty("kernel", kernel); + mat->SetProperty("depthTex", depthRT); + mat->SetProperty("normalTex", normalRT); + mat->SetProperty("noiseTex", noiseTex); + mat->UpdateUniformBuffers(); + //material->SetProperty("invProj", invProj); + //material->SetProperty("proj", proj); + //material->SetProperty("radius", radius); + //material->SetProperty("bias", bias); + //material->SetProperty("power", power); + //material->SetProperty("depthTex", static_cast(depthRT)); + } + void SSAOComponent::CreateKernel() + { + if (bKernelInit) + return; + bKernelInit = true; + + for (int i = 0; i < 64; ++i) + { + glm::vec3 sample( + core::Util::RandomRange(-1.f, 1.f), + core::Util::RandomRange(-1.f, 1.f), + core::Util::RandomRange(0.f, 1.f) + ); + sample = glm::normalize(sample); + sample *= core::Util::RandomRange(0.f, 1.f); + float scale = i / 64.f; + scale = glm::mix(0.1f, 1.0f, scale * scale); + sample *= scale; + kernel[i] = sample; + } + } +}//namespace diff --git a/src/Render/RenderPass/DepthPass.cpp b/src/Render/RenderPass/DepthPass.cpp index a66a8ebe..e0b2e1ef 100644 --- a/src/Render/RenderPass/DepthPass.cpp +++ b/src/Render/RenderPass/DepthPass.cpp @@ -15,16 +15,7 @@ namespace sh::render } SH_RENDER_API void DepthPass::Record(CommandBuffer& cmd, const IRenderContext& ctx, const RenderData& renderData) { - // 한 프레임 내 첫 호출에서만 클리어 - if (renderData.GetFrameIdx() != lastFrameIndex) - { - lastFrameIndex = renderData.GetFrameIdx(); - callsThisFrame = 0; - } - const bool bClearDepth = (callsThisFrame == 0); - ++callsThisFrame; - - cmd.SetRenderData(renderData, false, bClearDepth, false, true); + cmd.SetRenderData(renderData, true, true, true, true); if (renderData.GetDrawablesPtr() == nullptr) return; diff --git a/src/Render/RenderPass/SSAOPass.cpp b/src/Render/RenderPass/SSAOPass.cpp new file mode 100644 index 00000000..b33df359 --- /dev/null +++ b/src/Render/RenderPass/SSAOPass.cpp @@ -0,0 +1,97 @@ +#include "RenderPass/SSAOPass.h" +#include "RenderTexture.h" +#include "CommandBuffer.h" +#include "Mesh.h" +#include "Material.h" +#include "Drawable.h" +#include "IRenderContext.h" + +#include "Core/SObject.h" +#include "Core/GarbageCollection.h" + +namespace sh::render +{ + SSAOPass::SSAOPass(const IRenderContext& ctx) : + ScriptableRenderPass(core::Name{ "SSAOPass" }, RenderQueue::AfterOpaque), + ctx(ctx) + { + resource.plane = core::SObject::Create(); + + resource.plane->SetVertex({ + {{-1.0f, -1.0f, 0.0f}, {0.0f, 1.0f}, {0.f, 0.f, 0.f}}, + {{ 1.0f, -1.0f, 0.0f}, {1.0f, 1.0f}, {0.f, 0.f, 0.f}}, + {{ 1.0f, 1.0f, 0.0f}, {1.0f, 0.0f}, {0.f, 0.f, 0.f}}, + {{-1.0f, 1.0f, 0.0f}, {0.0f, 0.0f}, {0.f, 0.f, 0.f}}, + }); + resource.plane->SetIndices({ 0, 1, 2, 2, 3, 0 }); + resource.plane->Build(ctx); + + onMatDestroy.SetCallback( + [this](const core::SObject* objPtr) + { + resource.mat = nullptr; + if (resource.drawable != nullptr) + resource.drawable->Destroy(); + resource.drawable = nullptr; + } + ); + } + SSAOPass::~SSAOPass() = default; + + SH_RENDER_API void SSAOPass::SetMaterial(Material& mat) + { + if (resource.mat == &mat || mat.IsPendingKill()) + return; + resource.mat = &mat; + resource.mat->onDestroy.Register(onMatDestroy); + if (resource.drawable != nullptr) + resource.drawable->SetMaterial(*resource.mat); + } + + void SSAOPass::EnsureDrawable() + { + if (resource.drawable != nullptr || resource.mat == nullptr) + return; + resource.drawable = core::SObject::Create(*resource.mat, *resource.plane); + resource.drawable->Build(ctx); + } + + SH_RENDER_API void SSAOPass::Configure(const RenderData& renderData) + { + renderTextures.clear(); + + if (!core::IsValid(resource.mat)) + return; + + EnsureDrawable(); + if (resource.drawable == nullptr || !core::IsValid(resource.drawable->GetMesh()) || !core::IsValid(resource.drawable->GetMaterial())) + return; + + SetRenderTargetImageUsages(renderData); + SetImageUsages(*resource.mat); + } + SH_RENDER_API void SSAOPass::Record(CommandBuffer& cmd, const IRenderContext& ctx, const RenderData& renderData) + { + if (!core::IsValid(resource.mat) || !core::IsValid(resource.drawable)) + return; + localRenderData.SetRenderTargets(renderData.GetRenderTargets()); + localRenderData.renderViewers = renderData.renderViewers; + + cmd.SetRenderData(renderData, true, false, true, false); + + std::size_t viewerIdx = 0; + for (const RenderViewer& viewer : localRenderData.renderViewers) + { + SetViewportScissor(cmd, ctx, viewer); + cmd.DrawMesh(*resource.drawable, passName, viewerIdx); + ++viewerIdx; + } + } + + SH_RENDER_API void SSAOPass::Resource::PushReferenceObjects(core::GarbageCollection& gc) + { + gc.PushReferenceObject(plane); + gc.PushReferenceObject(drawable); + gc.PushReferenceObject(mat); + } +}//namespace diff --git a/src/Render/Texture.cpp b/src/Render/Texture.cpp index 60d90f3e..0d005ef6 100644 --- a/src/Render/Texture.cpp +++ b/src/Render/Texture.cpp @@ -25,7 +25,7 @@ namespace sh::render int mipHeight = height; for (int i = 0; i < mipLevels; ++i) { - pixels[i].resize(mipWidth * mipHeight * GetTextureFormatChannel(format)); + pixels[i].resize(mipWidth * mipHeight * GetTextureFormatPixelSize(format)); mipWidth = std::max(1, mipWidth / 2); mipHeight = std::max(1, mipHeight / 2); } @@ -41,21 +41,44 @@ namespace sh::render if (other.bDirty.test_and_set(std::memory_order::memory_order_acquire)) bDirty.test_and_set(std::memory_order::memory_order_relaxed); } - Texture::~Texture() + Texture::~Texture() = default; + + SH_RENDER_API void Texture::SyncDirty() { + if (!bDirty.test_and_set(std::memory_order::memory_order_acquire)) + core::ThreadSyncManager::PushSyncable(*this); } + SH_RENDER_API void Texture::Sync() + { + if (bSetDataDirty) + { + CreateTextureBuffer(); + bSetDataDirty = false; + } - SH_RENDER_API void Texture::SetPixelData(const std::vector& pixels, uint32_t mipLevel) + bDirty.clear(std::memory_order::memory_order_relaxed); + } + SH_RENDER_API void Texture::OnPropertyChanged(const core::reflection::Property& prop) { - assert(this->pixels[mipLevel].size() == pixels.size()); - this->pixels[mipLevel] = pixels; - if (textureBuffer != nullptr) + if (prop.GetName() == core::Util::ConstexprHash("aniso")) { - bSetDataDirty = true; - SyncDirty(); + SetAnisoLevel(aniso); + } + else if (prop.GetName() == core::Util::ConstexprHash("bSRGB")) + { + SetSRGB(bSRGB); + } + else if (prop.GetName() == core::Util::ConstexprHash("bGenerateMipmap")) + { + SetGenerateMipmap(bGenerateMipmap); + } + else if (prop.GetName() == core::Util::ConstexprHash("filtering")) + { + SetFiltering(filtering); } } - SH_RENDER_API void Texture::SetPixelData(std::vector&& pixels, uint32_t mipLevel) + + SH_RENDER_API void Texture::SetPixelData(std::vector pixels, uint32_t mipLevel) { assert(this->pixels[mipLevel].size() == pixels.size()); this->pixels[mipLevel] = std::move(pixels); @@ -68,6 +91,7 @@ namespace sh::render SH_RENDER_API void Texture::SetPixelData(const uint8_t* pixels, std::size_t size, uint32_t mipLevel) { assert(this->pixels[mipLevel].size() == size); + this->pixels[mipLevel].resize(size); std::memcpy(this->pixels[mipLevel].data(), pixels, size); if (textureBuffer != nullptr) { @@ -75,11 +99,6 @@ namespace sh::render SyncDirty(); } } - - SH_RENDER_API auto Texture::GetPixelData() const -> const std::vector>& - { - return pixels; - } SH_RENDER_API auto Texture::GetPixelData(uint32_t mipLevel) const -> const std::vector& { assert(pixels.size() > mipLevel); @@ -99,18 +118,6 @@ namespace sh::render { return textureBuffer.get(); } - SH_RENDER_API auto Texture::GetTextureFormat() const -> TextureFormat - { - return format; - } - SH_RENDER_API auto Texture::GetWidth() const -> uint32_t - { - return width; - } - SH_RENDER_API auto Texture::GetHeight() const -> uint32_t - { - return height; - } SH_RENDER_API void Texture::ChangeTextureFormat(TextureFormat target) { @@ -126,10 +133,6 @@ namespace sh::render SyncDirty(); } } - SH_RENDER_API auto Texture::IsSRGB() const -> bool - { - return bSRGB; - } SH_RENDER_API auto Texture::GetMipLevel() const -> uint32_t { if (!bGenerateMipmap) @@ -145,35 +148,12 @@ namespace sh::render SyncDirty(); } } - SH_RENDER_API auto Texture::GetAnisoLevel() const -> uint32_t - { - return aniso; - } SH_RENDER_API void Texture::SetGenerateMipmap(bool bGenerate) { bGenerateMipmap = bGenerate; bSetDataDirty = true; SyncDirty(); } - SH_RENDER_API auto Texture::IsGenerateMipmap() const -> bool - { - return bGenerateMipmap; - } - SH_RENDER_API void Texture::SyncDirty() - { - if (!bDirty.test_and_set(std::memory_order::memory_order_acquire)) - core::ThreadSyncManager::PushSyncable(*this); - } - SH_RENDER_API void Texture::Sync() - { - if (bSetDataDirty) - { - CreateTextureBuffer(); - bSetDataDirty = false; - } - - bDirty.clear(std::memory_order::memory_order_relaxed); - } SH_RENDER_API void Texture::SetSRGB(bool bSRGB) { if (bSRGB) @@ -200,29 +180,6 @@ namespace sh::render SyncDirty(); } } - SH_RENDER_API auto Texture::GetFiltering() const -> Filtering - { - return filtering; - } - SH_RENDER_API void Texture::OnPropertyChanged(const core::reflection::Property& prop) - { - if (prop.GetName() == core::Util::ConstexprHash("aniso")) - { - SetAnisoLevel(aniso); - } - else if (prop.GetName() == core::Util::ConstexprHash("bSRGB")) - { - SetSRGB(bSRGB); - } - else if (prop.GetName() == core::Util::ConstexprHash("bGenerateMipmap")) - { - SetGenerateMipmap(bGenerateMipmap); - } - else if (prop.GetName() == core::Util::ConstexprHash("filtering")) - { - SetFiltering(filtering); - } - } SH_RENDER_API void Texture::ExportToPNG(const std::filesystem::path& path) { stbi_write_png(path.u8string().c_str(), width, height, GetTextureFormatChannel(format), pixels[0].data(), width * GetTextureFormatChannel(format)); @@ -260,4 +217,4 @@ namespace sh::render return true; return false; } -} \ No newline at end of file +}//namespace diff --git a/src/Render/VulkanImpl/VulkanCommandBuffer.cpp b/src/Render/VulkanImpl/VulkanCommandBuffer.cpp index d8d9d268..94a86057 100644 --- a/src/Render/VulkanImpl/VulkanCommandBuffer.cpp +++ b/src/Render/VulkanImpl/VulkanCommandBuffer.cpp @@ -185,7 +185,6 @@ namespace sh::render::vk const VulkanImageBuffer* depthImg = nullptr; std::size_t colorImgsCount = 0; std::size_t colorMSAAImgsCount = 0; - // 메모) 깊이 버퍼는 제일 앞의 렌더 텍스쳐 것을 쓰고 깊이 버퍼 전용 텍스쳐가 있으면 그걸 우선시 함 const RenderTexture* const main = renderData.GetRenderTargets()[0]; if (main == nullptr) diff --git a/src/Render/VulkanImpl/VulkanImageBuffer.cpp b/src/Render/VulkanImpl/VulkanImageBuffer.cpp index d6ab000b..a646553e 100644 --- a/src/Render/VulkanImpl/VulkanImageBuffer.cpp +++ b/src/Render/VulkanImpl/VulkanImageBuffer.cpp @@ -25,7 +25,7 @@ namespace sh::render::vk sample(other.sample), width(other.width), height(other.height), - channel(other.channel), + pixelSize(other.pixelSize), aniso(other.aniso), mipLevel(other.mipLevel), @@ -51,7 +51,7 @@ namespace sh::render::vk sample = other.sample; width = other.width; height = other.height; - channel = other.channel; + pixelSize = other.pixelSize; aniso = other.aniso; mipLevel = other.mipLevel; @@ -94,7 +94,7 @@ namespace sh::render::vk width = info.width == 0 ? 1 : info.width; height = info.height == 0 ? 1 : info.height; - channel = GetChannelCount(format); + pixelSize = GetFormatPixelSize(format); aniso = info.aniso; mipLevel = actualMip; @@ -208,7 +208,7 @@ namespace sh::render::vk filter = VkFilter::VK_FILTER_LINEAR; format = VkFormat::VK_FORMAT_UNDEFINED; width = height = 0; - channel = 4; + pixelSize = 4; aniso = 0; mipLevel = 1; @@ -250,7 +250,7 @@ namespace sh::render::vk mipWidth = std::max(1u, mipWidth / 2); mipHeight = std::max(1u, mipHeight / 2); } - std::size_t bufferSize = mipWidth * mipHeight * channel; + std::size_t bufferSize = mipWidth * mipHeight * pixelSize; stagingBuffer.Create(bufferSize, VkBufferUsageFlagBits::VK_BUFFER_USAGE_TRANSFER_SRC_BIT, @@ -326,7 +326,7 @@ namespace sh::render::vk sample = VkSampleCountFlagBits::VK_SAMPLE_COUNT_1_BIT; width = swapChain.GetSwapChainSize().width; height = swapChain.GetSwapChainSize().height; - channel = GetChannelCount(format); + pixelSize = GetFormatPixelSize(format); aniso = 0; mipLevel = 1; @@ -415,6 +415,8 @@ namespace sh::render::vk case TextureFormat::RGB24: [[fallthrough]]; // 대부분 지원 안 함 case TextureFormat::RGBA32: return VkFormat::VK_FORMAT_R8G8B8A8_UNORM; + case TextureFormat::RG32F: + return VkFormat::VK_FORMAT_R16G16_SFLOAT; case TextureFormat::BGR24: [[fallthrough]]; // 대부분 지원 안 함 case TextureFormat::BGRA32: return VkFormat::VK_FORMAT_B8G8R8A8_UNORM; @@ -434,7 +436,7 @@ namespace sh::render::vk return VkFormat::VK_FORMAT_UNDEFINED; } } - auto VulkanImageBuffer::GetChannelCount(VkFormat format) -> uint32_t + auto VulkanImageBuffer::GetFormatPixelSize(VkFormat format) -> uint32_t { switch (format) { @@ -443,6 +445,8 @@ namespace sh::render::vk case VkFormat::VK_FORMAT_B8G8R8A8_UNORM: [[fallthrough]]; case VkFormat::VK_FORMAT_B8G8R8A8_SRGB: [[fallthrough]]; return 4; + case VkFormat::VK_FORMAT_R16G16_SFLOAT: + return sizeof(uint16_t) * 2; case VkFormat::VK_FORMAT_D32_SFLOAT_S8_UINT: [[fallthrough]]; case VkFormat::VK_FORMAT_D24_UNORM_S8_UINT: [[fallthrough]]; case VkFormat::VK_FORMAT_D16_UNORM_S8_UINT: [[fallthrough]]; @@ -454,4 +458,4 @@ namespace sh::render::vk return 0; } } -}//namespace \ No newline at end of file +}//namespace From 597f7538e64c6f9792dc835ebff3388d9157779a Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Mon, 11 May 2026 00:09:08 +0900 Subject: [PATCH 49/60] feat: Implement CombinePass --- include/Game/Component/Render/SSAOComponent.h | 1 + include/Game/GameRenderer.h | 5 +- include/Render/RenderPass/CombinePass.h | 43 ++++++++ include/Render/RenderPass/SSAOPass.h | 2 +- resource/shaders/SSAO.shader | 37 ++++++- src/Game/Component/Render/SSAOComponent.cpp | 18 ++- src/Game/GameRenderer.cpp | 7 ++ src/Render/RenderPass/CombinePass.cpp | 103 ++++++++++++++++++ src/Render/RenderPass/SSAOPass.cpp | 10 +- 9 files changed, 221 insertions(+), 5 deletions(-) create mode 100644 include/Render/RenderPass/CombinePass.h create mode 100644 src/Render/RenderPass/CombinePass.cpp diff --git a/include/Game/Component/Render/SSAOComponent.h b/include/Game/Component/Render/SSAOComponent.h index e75a46e1..b9a9b318 100644 --- a/include/Game/Component/Render/SSAOComponent.h +++ b/include/Game/Component/Render/SSAOComponent.h @@ -63,6 +63,7 @@ namespace sh::game render::RenderData depthRenderData; render::RenderData ssaoRenderData; + render::RenderData combineRenderData; uint32_t cachedWidth = 0; uint32_t cachedHeight = 0; diff --git a/include/Game/GameRenderer.h b/include/Game/GameRenderer.h index ad2008be..dd6a7ca8 100644 --- a/include/Game/GameRenderer.h +++ b/include/Game/GameRenderer.h @@ -10,6 +10,7 @@ namespace sh::render class DepthPass; class OpaquePass; class SSAOPass; + class CombinePass; class TransparentPass; } namespace sh::game @@ -29,6 +30,7 @@ namespace sh::game auto GetDepthPass() const -> render::DepthPass& { return *depthPass; } auto GetOpaquePass() const -> render::ScriptableRenderPass& { return *opaquePass; } auto GetSSAOPass() const -> render::SSAOPass& { return *ssaoPass; } + auto GetCombinePass() const -> render::CombinePass& { return *combinePass; } auto GetTransparentPass() const -> render::TransparentPass& { return *transparentPass; } auto GetUIPass() const -> render::TransparentPass& { return *uiPass; } auto GetGUIPass() const -> game::GUIPass& { return *guiPass; } @@ -40,8 +42,9 @@ namespace sh::game render::DepthPass* depthPass = nullptr; render::ScriptableRenderPass* opaquePass = nullptr; render::SSAOPass* ssaoPass = nullptr; + render::CombinePass* combinePass = nullptr; render::TransparentPass* transparentPass = nullptr; render::TransparentPass* uiPass = nullptr; game::GUIPass* guiPass = nullptr; }; -}//namespace \ No newline at end of file +}//namespace diff --git a/include/Render/RenderPass/CombinePass.h b/include/Render/RenderPass/CombinePass.h new file mode 100644 index 00000000..ab6877dd --- /dev/null +++ b/include/Render/RenderPass/CombinePass.h @@ -0,0 +1,43 @@ +#pragma once +#include "Render/Export.h" +#include "Render/ScriptableRenderPass.h" +#include "Render/RenderData.h" + +#include "Core/Observer.hpp" +#include "Core/GCObject.h" + +namespace sh::render +{ + class Mesh; + class Drawable; + class Material; + + /// @brief aoTex 를 현재 렌더 타겟 위에 덮어쓰는 풀스크린 패스. + /// @brief "Combine" 태그가 RenderData 에 있을 때만 동작. + class CombinePass : public ScriptableRenderPass + { + public: + SH_RENDER_API CombinePass(const IRenderContext& ctx); + SH_RENDER_API ~CombinePass(); + + SH_RENDER_API void SetMaterial(Material& mat); + protected: + SH_RENDER_API void Configure(const RenderData& renderData) override; + SH_RENDER_API void Record(CommandBuffer& cmd, const IRenderContext& ctx, const RenderData& renderData) override; + private: + void EnsureDrawable(); + private: + const IRenderContext& ctx; + + struct Resource : core::GCObject + { + Mesh* plane = nullptr; + Drawable* drawable = nullptr; + Material* mat = nullptr; + + SH_RENDER_API void PushReferenceObjects(core::GarbageCollection& gc) override; + } resource; + + core::Observer::Listener onMatDestroy; + }; +}//namespace diff --git a/include/Render/RenderPass/SSAOPass.h b/include/Render/RenderPass/SSAOPass.h index 1dc7dced..ecc98879 100644 --- a/include/Render/RenderPass/SSAOPass.h +++ b/include/Render/RenderPass/SSAOPass.h @@ -14,7 +14,7 @@ namespace sh::render class Material; /// @brief depthRT 를 샘플링하여 aoRT(R8) 에 AO 값을 쓰는 풀스크린 패스. - /// "SSAO" 태그가 RenderData 에 있을 때만 동작. + /// @brief "SSAO" 태그가 RenderData 에 있을 때만 동작. class SSAOPass : public ScriptableRenderPass { public: diff --git a/resource/shaders/SSAO.shader b/resource/shaders/SSAO.shader index a7eb5bee..eed826b3 100644 --- a/resource/shaders/SSAO.shader +++ b/resource/shaders/SSAO.shader @@ -8,6 +8,7 @@ Shader "SSAO Shader" sampler2D normalTex; sampler2D depthTex; sampler2D noiseTex; + sampler2D aoTex; } Pass @@ -101,4 +102,38 @@ Shader "SSAO Shader" } } } -} \ No newline at end of file + Pass + { + LightingPass "CombinePass" + Cull Off; + ZWrite Off; + ZTest Off; + + Stage Vertex + { + layout(location = 0) out vec2 fragUV; + void main() + { + fragUV = UV; + gl_Position = vec4(VERTEX, 1.0); + } + } + Stage Fragment + { + layout(location = 0) in vec2 uvs; + + layout(location = 0) out vec4 outColor; + + uniform sampler2D aoTex; + + void main() + { + float ao = texture(aoTex, uvs).r; + float shadow = 1.0 - ao; + if (shadow <= 0.001) + discard; + outColor = vec4(0.0, 0.0, 0.0, shadow); + } + } + } +} diff --git a/src/Game/Component/Render/SSAOComponent.cpp b/src/Game/Component/Render/SSAOComponent.cpp index bdac72d8..8f9d3944 100644 --- a/src/Game/Component/Render/SSAOComponent.cpp +++ b/src/Game/Component/Render/SSAOComponent.cpp @@ -12,6 +12,7 @@ #include "Render/IRenderContext.h" #include "Render/Material.h" #include "Render/RenderTexture.h" +#include "Render/RenderPass/CombinePass.h" #include "Render/RenderPass/SSAOPass.h" #include @@ -31,6 +32,11 @@ namespace sh::game depthRenderData.tag = core::Name{ "Depth" }; ssaoRenderData.tag = core::Name{ "SSAO" }; + combineRenderData.tag = core::Name{ "Combine" }; + depthRenderData.priority = 2; + ssaoRenderData.priority = 1; + combineRenderData.priority = -1; + combineRenderData.ClearRenderTargets(); } SSAOComponent::~SSAOComponent() = default; @@ -46,8 +52,12 @@ namespace sh::game { if (depthRT != nullptr) depthRT->Destroy(); + if (normalRT != nullptr) + normalRT->Destroy(); if (aoRT != nullptr) aoRT->Destroy(); + if (noiseTex != nullptr) + noiseTex->Destroy(); Super::OnDestroy(); } @@ -56,12 +66,14 @@ namespace sh::game if (camera == nullptr) return; - depthRenderData.renderViewers = ssaoRenderData.renderViewers = camera->GetRenderData().renderViewers; + depthRenderData.renderViewers = ssaoRenderData.renderViewers = combineRenderData.renderViewers = camera->GetRenderData().renderViewers; + combineRenderData.SetRenderTargets(camera->GetRenderData().GetRenderTargets()); EnsureRenderTextures(); UpdateMaterialUniforms(); world.renderer.PushRenderData(depthRenderData); world.renderer.PushRenderData(ssaoRenderData); + world.renderer.PushRenderData(combineRenderData); } SH_GAME_API void SSAOComponent::SetMaterial(render::Material* _mat) { @@ -75,6 +87,9 @@ namespace sh::game render::SSAOPass& ssaoPass = renderer->GetSSAOPass(); ssaoPass.SetMaterial(*mat); + + render::CombinePass& combinePass = renderer->GetCombinePass(); + combinePass.SetMaterial(*mat); } void SSAOComponent::EnsureRenderTextures() @@ -146,6 +161,7 @@ namespace sh::game mat->SetProperty("depthTex", depthRT); mat->SetProperty("normalTex", normalRT); mat->SetProperty("noiseTex", noiseTex); + mat->SetProperty("aoTex", aoRT); mat->UpdateUniformBuffers(); //material->SetProperty("invProj", invProj); //material->SetProperty("proj", proj); diff --git a/src/Game/GameRenderer.cpp b/src/Game/GameRenderer.cpp index b72b6521..0ea21804 100644 --- a/src/Game/GameRenderer.cpp +++ b/src/Game/GameRenderer.cpp @@ -5,6 +5,7 @@ #include "Render/RenderPass/DepthPass.h" #include "Render/RenderPass/TransparentPass.h" #include "Render/RenderPass/SSAOPass.h" +#include "Render/RenderPass/CombinePass.h" #include namespace sh::game @@ -21,6 +22,7 @@ namespace sh::game depthPass = &AddRenderPass(); opaquePass = &AddRenderPass(core::Name{ "Opaque" }, render::RenderQueue::Opaque); ssaoPass = &AddRenderPass(renderCtx); + combinePass = &AddRenderPass(renderCtx); transparentPass = &AddRenderPass(); uiPass = &AddRenderPass("UI", render::RenderQueue::Transparent); guiPass = &AddRenderPass(); @@ -38,6 +40,11 @@ namespace sh::game EnqueRenderPass(*ssaoPass); return; } + if (data.tag == "Combine") + { + EnqueRenderPass(*combinePass); + return; + } if (data.tag == "ImGUI") { if (guiPass != nullptr) diff --git a/src/Render/RenderPass/CombinePass.cpp b/src/Render/RenderPass/CombinePass.cpp new file mode 100644 index 00000000..1a77af01 --- /dev/null +++ b/src/Render/RenderPass/CombinePass.cpp @@ -0,0 +1,103 @@ +#include "RenderPass/CombinePass.h" +#include "CommandBuffer.h" +#include "Drawable.h" +#include "IRenderContext.h" +#include "Material.h" +#include "Mesh.h" + +#include "Core/GarbageCollection.h" +#include "Core/SObject.h" + +namespace sh::render +{ + CombinePass::CombinePass(const IRenderContext& ctx) : + ScriptableRenderPass(core::Name{ "CombinePass" }, RenderQueue::AfterRendering), + ctx(ctx) + { + resource.plane = core::SObject::Create(); + + resource.plane->SetVertex({ + {{-1.0f, -1.0f, 0.0f}, {0.0f, 1.0f}, {0.f, 0.f, 0.f}}, + {{ 1.0f, -1.0f, 0.0f}, {1.0f, 1.0f}, {0.f, 0.f, 0.f}}, + {{ 1.0f, 1.0f, 0.0f}, {1.0f, 0.0f}, {0.f, 0.f, 0.f}}, + {{-1.0f, 1.0f, 0.0f}, {0.0f, 0.0f}, {0.f, 0.f, 0.f}}, + }); + resource.plane->SetIndices({ 0, 1, 2, 2, 3, 0 }); + resource.plane->Build(ctx); + + onMatDestroy.SetCallback( + [this](const core::SObject* objPtr) + { + resource.mat = nullptr; + if (resource.drawable != nullptr) + resource.drawable->Destroy(); + resource.drawable = nullptr; + } + ); + } + CombinePass::~CombinePass() + { + core::GarbageCollection& gc = *core::GarbageCollection::GetInstance(); + gc.ForceDelete(resource.plane); + gc.ForceDelete(resource.drawable); + resource.plane = nullptr; + resource.drawable = nullptr; + resource.mat = nullptr; + } + + SH_RENDER_API void CombinePass::SetMaterial(Material& mat) + { + if (resource.mat == &mat || mat.IsPendingKill()) + return; + resource.mat = &mat; + resource.mat->onDestroy.Register(onMatDestroy); + if (resource.drawable != nullptr) + resource.drawable->SetMaterial(*resource.mat); + } + + void CombinePass::EnsureDrawable() + { + if (resource.drawable != nullptr || resource.mat == nullptr) + return; + resource.drawable = core::SObject::Create(*resource.mat, *resource.plane); + resource.drawable->Build(ctx); + } + + SH_RENDER_API void CombinePass::Configure(const RenderData& renderData) + { + renderTextures.clear(); + + if (!core::IsValid(resource.mat)) + return; + + EnsureDrawable(); + if (resource.drawable == nullptr || !core::IsValid(resource.drawable->GetMesh()) || !core::IsValid(resource.drawable->GetMaterial())) + return; + + SetRenderTargetImageUsages(renderData); + SetImageUsages(*resource.mat); + } + + SH_RENDER_API void CombinePass::Record(CommandBuffer& cmd, const IRenderContext& ctx, const RenderData& renderData) + { + if (!core::IsValid(resource.mat) || !core::IsValid(resource.drawable)) + return; + + cmd.SetRenderData(renderData, false, false, false, false); + + std::size_t viewerIdx = 0; + for (const RenderViewer& viewer : renderData.renderViewers) + { + SetViewportScissor(cmd, ctx, viewer); + cmd.DrawMesh(*resource.drawable, passName, viewerIdx); + ++viewerIdx; + } + } + + SH_RENDER_API void CombinePass::Resource::PushReferenceObjects(core::GarbageCollection& gc) + { + gc.PushReferenceObject(plane); + gc.PushReferenceObject(drawable); + gc.PushReferenceObject(mat); + } +}//namespace diff --git a/src/Render/RenderPass/SSAOPass.cpp b/src/Render/RenderPass/SSAOPass.cpp index b33df359..b8d6d2e1 100644 --- a/src/Render/RenderPass/SSAOPass.cpp +++ b/src/Render/RenderPass/SSAOPass.cpp @@ -36,7 +36,15 @@ namespace sh::render } ); } - SSAOPass::~SSAOPass() = default; + SSAOPass::~SSAOPass() + { + core::GarbageCollection& gc = *core::GarbageCollection::GetInstance(); + gc.ForceDelete(resource.plane); + gc.ForceDelete(resource.drawable); + resource.plane = nullptr; + resource.drawable = nullptr; + resource.mat = nullptr; + } SH_RENDER_API void SSAOPass::SetMaterial(Material& mat) { From 8861c487505a6a00623ed05c43fa9ca3f7638185 Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Mon, 11 May 2026 00:48:18 +0900 Subject: [PATCH 50/60] fix: Fix crash when pressing Stop in viewport during play mode --- src/Editor/EditorResource.cpp | 6 +----- src/Game/Component/Render/SSAOComponent.cpp | 7 ++++++- src/Game/World.cpp | 1 + src/Render/MaterialData.cpp | 21 ++++++++++++--------- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/Editor/EditorResource.cpp b/src/Editor/EditorResource.cpp index 9686fe24..040d5ab8 100644 --- a/src/Editor/EditorResource.cpp +++ b/src/Editor/EditorResource.cpp @@ -92,6 +92,7 @@ namespace sh::editor render::Shader* const outlinePostShader = shaders.insert_or_assign("OutlinePostShader", static_cast(shaderLoader.Load("shaders/EditorOutlinePost.shader"))).first->second; render::Shader* const uiTextShader = shaders.insert_or_assign("UITextShader", static_cast(shaderLoader.Load("shaders/UIText.shader"))).first->second; render::Shader* const ssaoShader = shaders.insert_or_assign("SSAOShader", static_cast(shaderLoader.Load("shaders/SSAO.shader"))).first->second; + ssaoShader->SetUUID(core::UUID{ "bbc4ef7ec45dce223297a224f8093f24" }); render::Texture* const blackTex = textures.insert_or_assign("BlackTexture", static_cast(texLoader.Load("textures/black.png"))).first->second; blackTex->Build(*renderer.GetContext()); @@ -105,7 +106,6 @@ namespace sh::editor render::Material* const outlinePreMat = materials.insert_or_assign("OutlinePreMaterial", core::SObject::Create(outlinePreShader)).first->second; render::Material* const outlinePostMat = materials.insert_or_assign("OutlinePostMaterial", core::SObject::Create(outlinePostShader)).first->second; render::Material* const uiTextMat = materials.insert_or_assign("UITextMaterial", core::SObject::Create(uiTextShader)).first->second; - render::Material* const ssaoMat = materials.insert_or_assign("SSAOMaterial", core::SObject::Create(ssaoShader)).first->second; render::Model* const sphereModel = models.insert_or_assign("SphereModel", static_cast(modelLoader.Load("model/Sphere.obj"))).first->second; sphereModel->SetUUID(core::UUID{ "bbc4ef7ec45dce223297a224f8093f15" }); @@ -153,10 +153,6 @@ namespace sh::editor uiTextMat->SetUUID(core::UUID{ "bbc4ef7ec45dce223297a224f8093f23" }); uiTextMat->Build(*renderer.GetContext()); uiTextMat->SetProperty("color", glm::vec3{ 1.f, 1.f, 1.f }); - - ssaoMat->SetName("SSAOMaterial"); - ssaoMat->SetUUID(core::UUID{ "bbc4ef7ec45dce223297a224f8093f24" }); - ssaoMat->Build(*renderer.GetContext()); } SH_EDITOR_API auto EditorResource::GetIcon(Icon icon) -> game::GUITexture* diff --git a/src/Game/Component/Render/SSAOComponent.cpp b/src/Game/Component/Render/SSAOComponent.cpp index 8f9d3944..c9202d24 100644 --- a/src/Game/Component/Render/SSAOComponent.cpp +++ b/src/Game/Component/Render/SSAOComponent.cpp @@ -46,7 +46,10 @@ namespace sh::game if (camera == nullptr) SH_ERROR("SSAOComponent requires a Camera component on the same GameObject"); CreateKernel(); - SetMaterial(static_cast(core::SObject::GetSObjectUsingResolver(core::UUID{ "bbc4ef7ec45dce223297a224f8093f24" }))); + + mat = core::SObject::Create(static_cast(core::SObject::GetSObjectUsingResolver(core::UUID{ "bbc4ef7ec45dce223297a224f8093f24" }))); + mat->Build(*world.renderer.GetContext()); + SetMaterial(mat); } SH_GAME_API void SSAOComponent::OnDestroy() { @@ -58,6 +61,8 @@ namespace sh::game aoRT->Destroy(); if (noiseTex != nullptr) noiseTex->Destroy(); + if (mat != nullptr) + mat->Destroy(); Super::OnDestroy(); } diff --git a/src/Game/World.cpp b/src/Game/World.cpp index b97a00e3..bfabdb74 100644 --- a/src/Game/World.cpp +++ b/src/Game/World.cpp @@ -530,6 +530,7 @@ namespace sh::game AddAfterSyncTask( [this, savePoint = it->second]() { + renderer.Reset(); Deserialize(savePoint); } ); diff --git a/src/Render/MaterialData.cpp b/src/Render/MaterialData.cpp index 118d4b5d..aef336d0 100644 --- a/src/Render/MaterialData.cpp +++ b/src/Render/MaterialData.cpp @@ -312,18 +312,21 @@ namespace sh::render IShaderBinding& shaderBindings = *itUb->second.get(); shaderBindings.Link(binding, *shaderBindingsSyncData.tex); + auto it = std::find_if(cachedRTs.begin(), cachedRTs.end(), + [&](const CachedRT& cached) + { + return cached.set == set && cached.binding == binding && cached.pass == shaderPass; + }); + if (shaderBindingsSyncData.tex->GetType().IsChildOf(RenderTexture::GetStaticType())) { - auto it = std::find_if(cachedRTs.begin(), cachedRTs.end(), - [&](const CachedRT& cached) - { - if (cached.set == set && cached.binding == binding && cached.pass == shaderPass) - return true; - return false; - } - ); + const RenderTexture* const rt = static_cast(shaderBindingsSyncData.tex); if (it == cachedRTs.end()) - cachedRTs.push_back(CachedRT{ set, binding, shaderPass, static_cast(shaderBindingsSyncData.tex) }); + cachedRTs.push_back(CachedRT{ set, binding, shaderPass, rt }); + else + it->rt = rt; } + else if (it != cachedRTs.end()) + cachedRTs.erase(it); } }//namespace \ No newline at end of file From 243b00b13e1d6aa367d47a2b1c36e243cb420ed3 Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Mon, 11 May 2026 00:55:35 +0900 Subject: [PATCH 51/60] feat(SSAO): Add blur SSAO shader --- resource/shaders/SSAO.shader | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/resource/shaders/SSAO.shader b/resource/shaders/SSAO.shader index eed826b3..c07167f8 100644 --- a/resource/shaders/SSAO.shader +++ b/resource/shaders/SSAO.shader @@ -126,9 +126,26 @@ Shader "SSAO Shader" uniform sampler2D aoTex; + float BlurAO(vec2 uv) + { + vec2 texelSize = 1.0 / vec2(textureSize(aoTex, 0)); + float result = 0.0; + + for (int x = -2; x < 2; ++x) + { + for (int y = -2; y < 2; ++y) + { + vec2 offset = vec2(float(x), float(y)) * texelSize; + result += texture(aoTex, uv + offset).r; + } + } + + return result / 16.0; + } + void main() { - float ao = texture(aoTex, uvs).r; + float ao = BlurAO(uvs); float shadow = 1.0 - ao; if (shadow <= 0.001) discard; From 143b7b233f1b63c03ae9a3587ba4e6ca24c468f8 Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Mon, 11 May 2026 01:24:21 +0900 Subject: [PATCH 52/60] feat(ShaderLexer): Add CAMERA token --- include/Render/RenderDataManager.h | 6 ++- include/Render/ShaderLexer.h | 2 +- src/Render/RenderDataManager.cpp | 4 +- src/Render/ShaderLexer.cpp | 3 +- src/Render/ShaderParser.cpp | 63 ++++++++++++++++++++---------- 5 files changed, 51 insertions(+), 27 deletions(-) diff --git a/include/Render/RenderDataManager.h b/include/Render/RenderDataManager.h index ead773b3..26e1514a 100644 --- a/include/Render/RenderDataManager.h +++ b/include/Render/RenderDataManager.h @@ -7,6 +7,8 @@ #include "Core/LockFreeMPSCQueue.h" #include "Core/ArrayView.hpp" +#include "glm/vec4.hpp" + #include #include #include @@ -37,7 +39,7 @@ namespace sh::render { glm::mat4 view; glm::mat4 proj; - glm::vec3 pos; + glm::vec4 pos; }; private: friend struct IRenderThrMethod; @@ -59,4 +61,4 @@ namespace sh::render static void UploadToGPU(RenderDataManager& manager) { manager.UploadToGPU(); } static auto GetRenderDatas(RenderDataManager& manager) -> core::ArrayView { return manager.GetRenderDatas(); } }; -}//namespace \ No newline at end of file +}//namespace diff --git a/include/Render/ShaderLexer.h b/include/Render/ShaderLexer.h index 2883cf9c..3e814d2c 100644 --- a/include/Render/ShaderLexer.h +++ b/include/Render/ShaderLexer.h @@ -17,7 +17,7 @@ namespace sh::render Layout, Uniform, In, Out, Sampler2D, Constexpr, Const, VERTEX, UV, NORMAL, TANGENT, MVP, LIGHT, BONE_WEIGHTS, BONE_INDICES, SKIN, - MATRIX_MODEL, MATRIX_VIEW, MATRIX_PROJ, MATRIX_SKIN, TEXTURE_SHADOW, + MATRIX_MODEL, MATRIX_VIEW, MATRIX_PROJ, CAMERA, MATRIX_SKIN, TEXTURE_SHADOW, LBracket, // ( RBracket, // ) LBrace, // { diff --git a/src/Render/RenderDataManager.cpp b/src/Render/RenderDataManager.cpp index baa89593..63063255 100644 --- a/src/Render/RenderDataManager.cpp +++ b/src/Render/RenderDataManager.cpp @@ -58,7 +58,7 @@ namespace sh::render BufferData data; data.view = viewer.viewMatrix; data.proj = viewer.projMatrix; - data.pos = viewer.pos; + data.pos = glm::vec4{ viewer.pos, 1.0f }; buffer->SetData(&data, offset, sizeof(BufferData)); @@ -81,4 +81,4 @@ namespace sh::render { return core::ArrayView{renderDatas.data(), renderDatasSize}; } -}//namespace \ No newline at end of file +}//namespace diff --git a/src/Render/ShaderLexer.cpp b/src/Render/ShaderLexer.cpp index 330072f8..a165f0d2 100644 --- a/src/Render/ShaderLexer.cpp +++ b/src/Render/ShaderLexer.cpp @@ -256,6 +256,7 @@ namespace sh::render {"MATRIX_MODEL", TokenType::MATRIX_MODEL}, {"MATRIX_VIEW", TokenType::MATRIX_VIEW}, {"MATRIX_PROJ", TokenType::MATRIX_PROJ}, + {"CAMERA", TokenType::CAMERA}, {"BONE_WEIGHTS", TokenType::BONE_WEIGHTS}, {"BONE_INDICES", TokenType::BONE_INDICES}, {"SKIN", TokenType::SKIN}, @@ -291,4 +292,4 @@ namespace sh::render { return std::isalnum(static_cast(c)) || c == '_'; } -}//namespace \ No newline at end of file +}//namespace diff --git a/src/Render/ShaderParser.cpp b/src/Render/ShaderParser.cpp index d21a0f86..1ec8679b 100644 --- a/src/Render/ShaderParser.cpp +++ b/src/Render/ShaderParser.cpp @@ -9,6 +9,7 @@ #include #include #include +#include namespace sh::render { @@ -622,7 +623,6 @@ namespace sh::render }; auto uboit = refreshUboIt(); - std::string code{}; int nested = 1; bool usingVertex = false; @@ -637,6 +637,42 @@ namespace sh::render bool usingSKIN = false; bool usingMATRIX_SKIN = false; bool usingTEXTURE_SHADOW = false; + auto registerCameraFn = [&]() + { + if (usingCamera) + return; + + auto it = std::find_if(stageNode.buffers.begin(), stageNode.buffers.end(), + [](const ShaderAST::BufferNode& ubo) { return ubo.name == "CAMERA"; }); + if (it == stageNode.buffers.end()) + { + ShaderAST::BufferNode uboNode{}; + uboNode.bufferType = ShaderAST::BufferType::Uniform; + uboNode.name = "CAMERA"; + uboNode.set = static_cast(UniformStructLayout::Usage::Camera); + uboNode.binding = 0; + uboNode.vars.push_back(ShaderAST::VariableNode{ ShaderAST::VariableType::Mat4, 1, "view" }); + uboNode.vars.push_back(ShaderAST::VariableNode{ ShaderAST::VariableType::Mat4, 1, "proj" }); + uboNode.vars.push_back(ShaderAST::VariableNode{ ShaderAST::VariableType::Vec3, 1, "pos" }); + stageNode.buffers.push_back(std::move(uboNode)); + } + else + { + auto hasVar = [&](std::string_view name) + { + return std::find_if(it->vars.begin(), it->vars.end(), + [&](const ShaderAST::VariableNode& var) { return var.name == name; }) != it->vars.end(); + }; + if (!hasVar("view")) + it->vars.push_back(ShaderAST::VariableNode{ ShaderAST::VariableType::Mat4, 1, "view" }); + if (!hasVar("proj")) + it->vars.push_back(ShaderAST::VariableNode{ ShaderAST::VariableType::Mat4, 1, "proj" }); + if (!hasVar("pos")) + it->vars.push_back(ShaderAST::VariableNode{ ShaderAST::VariableType::Vec3, 1, "pos" }); + } + usingCamera = true; + uboit = refreshUboIt(); + }; while (nested != 0 || PeekToken().type != ShaderLexer::TokenType::EndOfFile) { @@ -665,26 +701,11 @@ namespace sh::render registerAttributeFn(usingBoneWeights, "BONE_WEIGHTS", SkinnedMesh::BONE_WEIGHT_ID, ShaderAST::VariableType::Vec4); else if (CheckToken(ShaderLexer::TokenType::BONE_INDICES)) registerAttributeFn(usingBoneIndices, "BONE_INDICES", SkinnedMesh::BONE_INDEX_ID, ShaderAST::VariableType::IVec4); - else if (CheckToken(ShaderLexer::TokenType::MATRIX_VIEW) || CheckToken(ShaderLexer::TokenType::MATRIX_PROJ)) + else if (CheckToken(ShaderLexer::TokenType::MATRIX_VIEW) || + CheckToken(ShaderLexer::TokenType::MATRIX_PROJ) || + CheckToken(ShaderLexer::TokenType::CAMERA)) { - if (!usingCamera) - { - auto it = std::find_if(stageNode.buffers.begin(), stageNode.buffers.end(), - [](const ShaderAST::BufferNode& ubo) { return ubo.name == "CAMERA"; }); - if (it == stageNode.buffers.end()) - { - ShaderAST::BufferNode uboNode{}; - uboNode.bufferType = ShaderAST::BufferType::Uniform; - uboNode.name = "CAMERA"; - uboNode.set = static_cast(UniformStructLayout::Usage::Camera); - uboNode.binding = 0; - uboNode.vars.push_back(ShaderAST::VariableNode{ ShaderAST::VariableType::Mat4, 1, "view" }); - uboNode.vars.push_back(ShaderAST::VariableNode{ ShaderAST::VariableType::Mat4, 1, "proj" }); - stageNode.buffers.push_back(std::move(uboNode)); - uboit = refreshUboIt(); - } - usingCamera = true; - } + registerCameraFn(); } else if (CheckToken(ShaderLexer::TokenType::MATRIX_MODEL)) { @@ -1169,4 +1190,4 @@ namespace sh::render } return shaderNode; } -}//namespace \ No newline at end of file +}//namespace From 7e465fe7cc4e5da860e8bbf1915124f03800ea06 Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Mon, 11 May 2026 01:25:35 +0900 Subject: [PATCH 53/60] fix(VulkanShaderPass): Allow uniforms with same set and binding across shader stages --- src/Render/VulkanImpl/VulkanShaderPass.cpp | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/Render/VulkanImpl/VulkanShaderPass.cpp b/src/Render/VulkanImpl/VulkanShaderPass.cpp index 604ccef5..bc95a194 100644 --- a/src/Render/VulkanImpl/VulkanShaderPass.cpp +++ b/src/Render/VulkanImpl/VulkanShaderPass.cpp @@ -165,6 +165,20 @@ namespace sh::render::vk void VulkanShaderPass::AddDescriptorBinding(uint32_t set, uint32_t binding, VkDescriptorType type, VkShaderStageFlagBits stage) { + if (setlayouts.size() <= set) + { + setlayouts.resize(set + 1); + } + setlayouts[set].set = set; + + auto it = setlayouts[set].descriptorSetLayoutBindings.find(binding); + if (it != setlayouts[set].descriptorSetLayoutBindings.end()) + { + assert(it->second.descriptorType == type); + it->second.stageFlags |= stage; + return; + } + VkDescriptorSetLayoutBinding layoutBinding{}; layoutBinding.binding = binding; layoutBinding.descriptorCount = 1; @@ -172,11 +186,6 @@ namespace sh::render::vk layoutBinding.pImmutableSamplers = nullptr; layoutBinding.stageFlags = stage; - if (setlayouts.size() <= set) - { - setlayouts.resize(set + 1); - } - setlayouts[set].set = set; setlayouts[set].descriptorSetLayoutBindings.insert_or_assign(binding, layoutBinding); } auto VulkanShaderPass::CreateDescriptorLayout() -> VkResult @@ -245,4 +254,4 @@ namespace sh::render::vk } setlayouts.clear(); } -}//namespace \ No newline at end of file +}//namespace From e0ba5023318773f4fc692c51d4a12db80d094149 Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Mon, 11 May 2026 01:43:03 +0900 Subject: [PATCH 54/60] fix: Fix some bugs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 빌드 에셋 누락 (SSAO 셰이더) - 스왑체인 이미지를 저장하게 수정 --- include/Game/Component/Render/SSAOComponent.h | 12 ++++++------ src/Editor/BuildSystem.cpp | 2 ++ src/Editor/EditorPostOutlinePass.cpp | 2 +- src/Game/GUIPass.cpp | 2 +- src/Render/RenderPass/CombinePass.cpp | 2 +- 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/include/Game/Component/Render/SSAOComponent.h b/include/Game/Component/Render/SSAOComponent.h index b9a9b318..26a97036 100644 --- a/include/Game/Component/Render/SSAOComponent.h +++ b/include/Game/Component/Render/SSAOComponent.h @@ -40,7 +40,7 @@ namespace sh::game void UpdateMaterialUniforms(); static void CreateKernel(); private: - PROPERTY(mat, core::PropertyOption::sobjPtr) + PROPERTY(mat, core::PropertyOption::sobjPtr, core::PropertyOption::invisible, core::PropertyOption::noSave) render::Material* mat = nullptr; PROPERTY(radius) @@ -50,15 +50,15 @@ namespace sh::game PROPERTY(power) float power = 1.5f; - PROPERTY(depthRT, core::PropertyOption::sobjPtr, core::PropertyOption::invisible) + PROPERTY(depthRT, core::PropertyOption::sobjPtr, core::PropertyOption::invisible, core::PropertyOption::noSave) render::RenderTexture* depthRT = nullptr; - PROPERTY(normalRT, core::PropertyOption::sobjPtr, core::PropertyOption::invisible) + PROPERTY(normalRT, core::PropertyOption::sobjPtr, core::PropertyOption::invisible, core::PropertyOption::noSave) render::RenderTexture* normalRT = nullptr; - PROPERTY(aoRT, core::PropertyOption::sobjPtr, core::PropertyOption::invisible) + PROPERTY(aoRT, core::PropertyOption::sobjPtr, core::PropertyOption::invisible, core::PropertyOption::noSave) render::RenderTexture* aoRT = nullptr; - PROPERTY(camera, core::PropertyOption::sobjPtr, core::PropertyOption::invisible); + PROPERTY(camera, core::PropertyOption::sobjPtr, core::PropertyOption::invisible, core::PropertyOption::noSave); Camera* camera = nullptr; - PROPERTY(noiseTex, core::PropertyOption::sobjPtr, core::PropertyOption::invisible); + PROPERTY(noiseTex, core::PropertyOption::sobjPtr, core::PropertyOption::invisible, core::PropertyOption::noSave); render::Texture* noiseTex = nullptr; render::RenderData depthRenderData; diff --git a/src/Editor/BuildSystem.cpp b/src/Editor/BuildSystem.cpp index 8b81104b..fc2d6c7b 100644 --- a/src/Editor/BuildSystem.cpp +++ b/src/Editor/BuildSystem.cpp @@ -219,6 +219,8 @@ namespace sh::editor bundle.AddAsset(lineShaderAsset, true); game::ShaderAsset uiTextShaderAsset{ *editorResource->GetShader("UITextShader") }; bundle.AddAsset(uiTextShaderAsset, true); + game::ShaderAsset ssaoShaderAsset{ *editorResource->GetShader("SSAOShader") }; + bundle.AddAsset(ssaoShaderAsset, true); game::MaterialAsset errorMatAsset{ *editorResource->GetMaterial("ErrorMaterial") }; bundle.AddAsset(errorMatAsset, true); diff --git a/src/Editor/EditorPostOutlinePass.cpp b/src/Editor/EditorPostOutlinePass.cpp index df79073f..0ba7113c 100644 --- a/src/Editor/EditorPostOutlinePass.cpp +++ b/src/Editor/EditorPostOutlinePass.cpp @@ -60,7 +60,7 @@ namespace sh::editor { if (drawable == nullptr || !core::IsValid(drawable->GetMesh()) || !core::IsValid(drawable->GetMaterial())) return; - cmd.SetRenderData(renderData, false, false, false, false); + cmd.SetRenderData(renderData, false, false, true, false); std::size_t viewerIdx = 0; for (const render::RenderViewer& viewer : renderData.renderViewers) { diff --git a/src/Game/GUIPass.cpp b/src/Game/GUIPass.cpp index 2614fc32..2bfe2bac 100644 --- a/src/Game/GUIPass.cpp +++ b/src/Game/GUIPass.cpp @@ -26,7 +26,7 @@ namespace sh::game SH_GAME_API void GUIPass::Record(render::CommandBuffer& cmd, const render::IRenderContext& ctx, const render::RenderData& renderData) { - cmd.SetRenderData(renderData, false, true, false, false); + cmd.SetRenderData(renderData, false, true, true, false); for (const render::RenderViewer& viewer : renderData.renderViewers) { SetViewportScissor(cmd, ctx, viewer); diff --git a/src/Render/RenderPass/CombinePass.cpp b/src/Render/RenderPass/CombinePass.cpp index 1a77af01..c47c883c 100644 --- a/src/Render/RenderPass/CombinePass.cpp +++ b/src/Render/RenderPass/CombinePass.cpp @@ -83,7 +83,7 @@ namespace sh::render if (!core::IsValid(resource.mat) || !core::IsValid(resource.drawable)) return; - cmd.SetRenderData(renderData, false, false, false, false); + cmd.SetRenderData(renderData, false, false, true, false); std::size_t viewerIdx = 0; for (const RenderViewer& viewer : renderData.renderViewers) From 025152de6cc3496c78f987f3b4cb2539483efe0e Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Mon, 11 May 2026 01:53:31 +0900 Subject: [PATCH 55/60] docs: Update RenderArchitecture.md --- RenderArchitecture.md | 335 ++++++++++++++++++++++++++++-------------- 1 file changed, 226 insertions(+), 109 deletions(-) diff --git a/RenderArchitecture.md b/RenderArchitecture.md index 21ad01f1..4ad0afbd 100644 --- a/RenderArchitecture.md +++ b/RenderArchitecture.md @@ -1,161 +1,278 @@ -# ShellEngine 패스 기반 렌더링 구조 +# ShellEngine 패스 기반 렌더링 구조 ## 개요 +ShellEngine의 렌더링은 `RenderData`가 정의한 뷰/타겟 단위 작업을 `ScriptableRenderer`가 패스 목록으로 풀고, 각 `ScriptableRenderPass`가 자신의 리소스 사용 의도를 선언한 뒤, 백엔드 `CommandBuffer`가 실제 Vulkan 커맨드로 기록하는 구조입니다. + | 계층 | 책임 | |---|---| -| `ScriptableRenderPass` | 렌더링 의도 선언 (타겟, 샘플링 텍스처, 드로우 목록) | -| `ScriptableRenderer` | 패스 간 리소스 상태 추적 및 전이 정보 계산 | -| Vulkan 구현 계층 | 전이 정보를 실제 Vulkan API 호출로 번역 | - -ShellEngine의 Vulkan 렌더링 시스템은 드로우 호출을 단일 흐름으로 나열하는 방식 대신, **패스 단위로 렌더링 작업을 분리**하고 각 패스가 사용하는 렌더 텍스처와 일반 텍스쳐의 읽기/쓰기 관계를 추적하여, 리소스 전이와 실행 흐름을 자동으로 조립하는 구조로 설계되어 있습니다. - -해당 구조로 인해 프로젝트 목적에 맞게 코드를 크게 수정하지 않고 패스를 재배치 할 수 있으며 멀티스레딩과도 자연스럽게 연결됩니다. - -## 멀티스레딩 -`ScriptableRenderer`에서 렌더큐에 맞는 패스 순서를 조정하고 리소스 의존 상태를 계산 후, 스레드 풀에 넣어 각 패스의 렌더링을 병렬적으로 수행합니다. - -1. **직렬 단계** - 중앙에서 리소스 상태 정리 및 의존 상태 계산 -2. **병렬 단계** - 각 패스의 커맨드 버퍼 기록을 스레드 풀에 task로 분배하여 병렬 실행 - -리소스 전이 판단이 중앙에서 선행되므로, 이후 커맨드 기록 단계는 패스 간 의존성 없이 병렬화할 수 있습니다. - -## 패스 추가 방법 - -새 패스를 추가할 때 필요한 작업은 다음과 같습니다. +| `Renderer` | 렌더 스레드 진입점, drawable/render data 큐 반영, 커맨드 제출 조율 | +| `RenderDataManager` | 카메라/뷰 데이터를 GPU 버퍼에 업로드하고 `RenderData`를 priority 순으로 정렬 | +| `ScriptableRenderer` | 활성 패스 선택, render queue 정렬, 리소스 전이 계산, 패스별 커맨드 버퍼 기록 | +| `ScriptableRenderPass` | 패스 이름, render queue, 렌더 타겟/샘플링 텍스처 사용 의도, 드로우 기록 | +| `CommandBuffer` | 배리어, 렌더 타겟 설정, draw/dispatch/blit 같은 백엔드 독립 커맨드 인터페이스 | +| Vulkan 구현 계층 | `CommandBuffer` 호출을 dynamic rendering, pipeline bind, descriptor bind, barrier 호출로 변환 | + +핵심은 패스가 “무엇을 렌더링하고 어떤 이미지를 읽고 쓰는지”만 표현하고, 이미지 레이아웃 전이와 Vulkan 동기화 코드는 `ScriptableRenderer`와 `VulkanCommandBuffer`가 처리한다는 점입니다. + +## 프레임 흐름 + +1. 게임/에디터 쪽에서 `Renderer::PushDrawAble()`과 `Renderer::PushRenderData()`로 렌더링할 객체와 뷰 정보를 큐에 넣습니다. +2. 렌더 스레드의 `Renderer::Render()`가 큐를 비우고 `RenderDataManager::UploadToGPU()`를 호출합니다. +3. `RenderDataManager`는 각 `RenderViewer`의 view/proj/camera position을 GPU 버퍼에 업로드하고 `RenderData`를 `priority` 기준으로 정렬합니다. +4. `VulkanRenderer::Render()`가 스왑체인 이미지를 획득한 뒤 각 `RenderData`에 frame index와 drawable 목록을 연결합니다. +5. `ScriptableRenderer::Setup()`이 해당 `RenderData`에서 사용할 패스를 활성화합니다. +6. `ScriptableRenderer::Execute()`가 활성 패스를 `RenderQueue` 순으로 정렬하고, 각 패스의 `Configure()`를 호출해 리소스 사용 정보를 확정합니다. +7. 렌더러가 `BarrierInfo`를 계산한 뒤, 각 패스의 커맨드 버퍼 기록을 스레드 풀 task로 분배합니다. +8. 모든 패스 커맨드 기록이 끝나면 `VulkanRenderer`가 기록된 커맨드들을 순서대로 제출하고 마지막 커맨드가 present semaphore를 signal합니다. +9. 마지막에 `ExecuteTransfer()`가 readback/copy 작업을 위한 `CopyPass`를 기록하고, 스왑체인을 `Present` 상태로 전이합니다. + +```mermaid +flowchart TD + A["Game/Editor Thread"] --> B["Renderer::PushDrawAble()\nRenderer::PushRenderData()"] + B --> C["Render thread\nRenderer::Render()"] + C --> D["DrainRenderCommands()\ndrawable/render data queue 반영"] + D --> E["RenderDataManager::UploadToGPU()"] + E --> F["RenderViewer camera buffer 업로드\nRenderData priority 정렬"] + F --> G["VulkanRenderer::Render()"] + G --> H["vkAcquireNextImageKHR()\nframe index 획득"] + H --> I["RenderData에 frame index와\ndrawable list 연결"] + I --> J["ScriptableRenderer::Setup()"] + J --> K["RenderData::tag 기준\n활성 패스 enqueue"] + K --> L["ScriptableRenderer::Execute()"] + L --> M["RenderQueue 정렬"] + M --> N["각 패스 Configure()\nRenderTexture 사용 의도 확정"] + N --> O["BuildBarrierInfo()\nBarrierInfo 계산"] + O --> P{"ThreadPool\n패스별 병렬 기록"} + P --> Q["CommandBuffer::Begin()"] + Q --> R["CommandBuffer::EmitBarrier()"] + R --> S["ScriptableRenderPass::Record()"] + S --> T["CommandBuffer::End()"] + T --> U["recordedCmds에 순서대로 수집"] + U --> V["VulkanQueueManager::Submit()\n커맨드 순차 제출"] + V --> W["ExecuteTransfer()\nCopyPass/readback 기록"] + W --> X["swapchain -> Present 전이"] + X --> Y["vkQueuePresentKHR()"] + Y --> Z["CallReadbacks()\nResetSubmittedCommands()"] +``` -1. `ScriptableRenderPass` 구현체 작성 -2. `renderQueue` 설정 -3. 필요 시 ```BuildDrawList``` 오버라이딩 하여 드로우 할 물체 필터링 -4. `Configure()`에서 드로우 목록 및 리소스 등록 -5. `Record()`에서 커맨드 기록 +## RenderData와 RenderViewer -기존 패스나 배리어 코드를 사용자가 명시적으로 수정할 필요가 없습니다. `CopyPass`처럼 드로우와 성격이 다른 패스도 동일한 방법으로 이루어집니다. +`RenderData`는 한 번의 렌더링 작업이 사용할 뷰어, 렌더 타겟, 태그, 우선순위를 담습니다. -예시 코드: ```cpp -/// 단순히 우선 순위 정렬기능만 추가된 패스 -class TransparentPass : public ScriptableRenderPass +class RenderData { public: - SH_RENDER_API TransparentPass(std::string_view name = "Transparent", RenderQueue renderQueue = RenderQueue::Transparent); + void SetRenderTarget(const RenderTexture* renderTarget); + void SetRenderTargets(std::initializer_list renderTargets); + void ClearRenderTargets(); - SH_RENDER_API auto BuildDrawList(const RenderTarget& renderData) -> DrawList override - { - // renderData로 들어온 카메라 정보와 드로우 할 물체들의 정보를 가지고 DrawList 구조체를 만들어서 반환 - } + int priority = 0; + core::Name tag{ "Camera" }; + std::vector renderViewers; }; ``` -## 상세 -### 설계 배경 +`RenderViewer`는 view/proj 행렬, 카메라 위치/방향, viewport/scissor, GPU camera buffer offset을 가집니다. `RenderDataManager`는 각 viewer마다 `RenderDataManager::BufferData`를 업로드하고, 그 offset을 `RenderViewer::offset`에 기록합니다. Vulkan draw 단계에서는 이 offset이 카메라 descriptor set의 dynamic offset으로 전달됩니다. -Vulkan 렌더링 시스템을 구현할 때 드로우 호출 자체보다 **리소스 상태 관리와 실행 순서**가 더 큰 문제가 됩니다. +최근 구조에서는 `RenderData`가 여러 렌더 타겟을 가질 수 있습니다. -구체적으로 아래 사항들을 매 패스마다 직접 추적해야 합니다. +- `SetRenderTarget()`은 단일 타겟용 편의 함수입니다. +- `SetRenderTargets()`는 MRT를 구성합니다. +- 타겟이 `nullptr`이면 현재 스왑체인 이미지를 의미합니다. +- depth 전용 `RenderTexture`는 color attachment가 아니라 depth attachment로 취급됩니다. -- 패스 간 실행 순서 -- 각 패스의 렌더 타겟 및 샘플링 텍스처 -- 이미지 레이아웃 및 접근 권한 변환 -- `vkCmdPipelineBarrier` 삽입 위치 +## 기본 패스 구성 -렌더링 코드를 단일 흐름으로 구성할 경우, 패스가 늘어날수록 다음과 같은 문제가 발생합니다. +`GameRenderer::Init()`은 기본 패스를 생성하고, `GameRenderer::Setup()`은 `RenderData::tag`에 따라 필요한 패스만 enqueue합니다. -- 패스 추가 시 앞뒤 리소스 상태 전이를 전체적으로 재검토해야 함 -- 배리어 누락 또는 잘못된 레이아웃 전이로 인한 런타임 오류 발생 -- 렌더링 의도 코드와 Vulkan 동기화 코드가 강하게 결합됨 +| 태그 | 실행 패스 | +|---|---| +| `"Depth"` | `DepthPass` | +| `"SSAO"` | `SSAOPass` | +| `"Combine"` | `CombinePass` | +| `"ImGUI"` | `GUIPass` | +| 그 외 카메라 | `Opaque`, `Transparent`, `UI` | + +현재 기본 패스의 역할은 다음과 같습니다. + +| 패스 | Queue | 역할 | +|---|---|---| +| `DepthPass` | `BeforeRendering` | depth/shadow 계열 렌더링. depth 전용 타겟도 지원 | +| `Opaque` | `Opaque` | 기본 불투명 렌더링 | +| `SSAOPass` | `AfterOpaque` | 풀스크린 plane으로 depth 등을 샘플링해 AO 타겟에 기록 | +| `CombinePass` | `AfterRendering` | 풀스크린 plane으로 AO 등 후처리 결과를 최종 타겟에 합성 | +| `Transparent` | `Transparent` | 카메라 방향 기준 back-to-front 정렬 후 개별 draw | +| `UI` | `Transparent` | UI shader pass용 transparent pass | +| `GUIPass` | `UI` | ImGui draw data 기록 | +| `CopyPass` | `AfterRendering` | readback/copy transfer 전용 내부 패스 | -ShellEngine은 이 문제를 해결하기 위해서, 그리고 유연한 확장을 위해 패스의 렌더링 의도와 API 레벨의 전이 처리를 분리하는 구조를 채택했습니다. +`SSAOPass`와 `CombinePass`는 패스 내부에서 fullscreen plane `Mesh`와 `Drawable`을 보유하고, 외부에서 `SetMaterial()`로 지정한 material을 사용합니다. 두 패스 모두 material이 참조하는 `RenderTexture`를 `Configure()`에서 샘플링 리소스로 등록합니다. ---- +## ScriptableRenderPass -### Configure 단계 +패스는 `passName`과 `renderQueue`를 갖고, 크게 두 단계로 동작합니다. -`ScriptableRenderPass::Configure()`는 렌더러가 전이를 계산하는 데 필요한 데이터를 구성하는 단계입니다. +### Configure -1. `BuildDrawList()` - 이번 패스의 드로우 목록 생성 -2. `CollectRenderImages()` - 렌더 텍스처 수집 및 용도 분류 - - 현재 렌더 타겟 -> `ColorAttachment` - - DrawList로 전달 된 메테리얼이 참조하는 `RenderTexture` -> `SampledRead` -3. 동일 패스 내에서 같은 텍스처를 출력과 입력으로 동시에 사용하는 경우 검증 +`Configure(const RenderData&)`는 커맨드 기록 전에 리소스 사용 의도를 확정합니다. -이 단계를 통해 패스의 리소스 사용 관계가 확정됩니다. +- 기본 구현은 현재 `passName`을 가진 shader pass가 있는 drawable만 `RenderBatch`로 묶습니다. +- `SetRenderTargetImageUsages()`가 `RenderData::GetRenderTargets()`를 순회해 color/depth attachment 사용을 등록합니다. +- `SetImageUsages()`가 drawable/material의 `MaterialData::CachedRT`를 확인해 샘플링하는 `RenderTexture`를 등록합니다. +- depth texture를 샘플링하는 경우 `DepthStencilSampledRead`, 일반 texture는 `SampledRead`로 등록합니다. -> [!Note] -> ```BuildDrawList```에는 RenderTarget구조체가 전달 됩니다. -> ```cpp -> struct RenderTarget -> { -> uint32_t frameIndex; -> const Camera* camera; -> const RenderTexture* target; -> const std::vector* drawables; -> }; -> ``` ---- +예전 문서의 `BuildDrawList()` 단계는 현재 `CreateRenderBatch()`와 `Configure()` 흐름으로 대체되었습니다. -### ScriptableRenderer +### Record -`ScriptableRenderer::Execute()`는 패스 간 리소스 연결 관계를 계산하고 커맨드 버퍼 기록을 조율합니다. +`Record(CommandBuffer&, const IRenderContext&, const RenderData&)`는 실제 커맨드를 기록합니다. -1. 활성 패스 수집 -2. renderQueue 기준 정렬 -3. 각 패스 Configure() 호출 -> 리소스 사용 정보 확정 -4. 각 패스 BuildBarrierInfo() 호출 -> 전이 정보 계산 -5. 커맨드 버퍼 기록 +- 기본 구현은 `cmd.SetRenderData(renderData, true, true, true, true)`로 렌더링을 시작합니다. +- viewer마다 viewport/scissor를 설정합니다. +- `RenderBatch` 단위로 `cmd.DrawMeshBatch()`를 호출합니다. +- `TransparentPass`처럼 정렬이 필요한 패스는 batch 대신 개별 `DrawMesh()`를 호출할 수 있습니다. +- 풀스크린 후처리 패스는 내부 drawable 하나를 직접 그립니다. -패스는 직접 배리어를 생성하지 않으며, 리소스 사용 의도만 제공합니다. 실제 전이 계산은 렌더러가 전담합니다. +## 리소스 전이 ---- +패스는 직접 Vulkan barrier를 만들지 않습니다. 대신 `renderTextures`에 “이번 패스에서 이 이미지를 어떤 용도로 쓸 것인지”를 기록합니다. -### BarrierInfo +```cpp +enum class ResourceUsage +{ + Undefined, + ColorAttachment, + SampledRead, + Present, + TransferSrc, + DepthStencilAttachment, + DepthStencilSampledRead +}; +``` -`BarrierInfo`는 패스 간 리소스 의존성을 실행 가능한 형태로 담는 구조체입니다. +`ScriptableRenderer::BuildBarrierInfo()`는 각 패스의 `renderTextures`를 보고 이전 사용 상태와 현재 사용 상태를 `BarrierInfo`로 만듭니다. -| 필드 | 설명 | +| 대상 | 이전 상태 출처 | |---|---| -| 대상 이미지 | 전이 대상 리소스 | -| `lastUsage` | 이전 패스의 사용 상태 | -| `curUsage` | 현재 패스의 사용 상태 | +| `RenderTexture*` | `RenderTexture::GetUsage()` | +| `nullptr` 스왑체인 | `ScriptableRenderer::swapChainStates[frameIndex]` | -렌더러는 각 텍스처의 사용 이력을 추적하여 패스 사이의 상태 전이를 `BarrierInfo`로 구체화합니다. +계산된 `BarrierInfo`는 해당 패스 커맨드 버퍼 시작 직후 `cmd.EmitBarrier()`로 기록됩니다. Vulkan에서는 `VulkanCommandBuffer::EmitBarrier()`가 `ResourceUsage`를 image layout, pipeline stage, access mask로 매핑하고 `VulkanImageBuffer::BarrierCommand()`를 호출합니다. -**예시 - 3패스 렌더링 흐름:** +예시 흐름: -``` -패스 1: 씬 렌더링 -> Undefined -> ColorAttachment -패스 2: 후처리 샘플링 -> ColorAttachment -> SampledRead -패스 3: 스왑체인 출력 -> SampledRead -> Present +```text +depth texture Undefined -> DepthStencilAttachment +depth texture DepthStencilAttachment -> DepthStencilSampledRead +ao texture Undefined -> ColorAttachment +ao texture ColorAttachment -> SampledRead +swapchain Undefined -> ColorAttachment +readback src ColorAttachment -> TransferSrc +swapchain ColorAttachment -> Present ``` ---- +## 멀티스레딩 -### Vulkan 구현 계층 +`ScriptableRenderer::Execute()`는 리소스 전이를 먼저 직렬로 계산한 뒤, 각 패스의 커맨드 버퍼 기록을 스레드 풀에 task로 분배합니다. -`VulkanRenderImpl::EmitBarrier()`는 렌더러가 계산한 `BarrierInfo`를 Vulkan API 호출로 번역합니다. +직렬 단계: -- `ResourceUsage`를 image layout, pipeline stage, access mask로 매핑 -- `VulkanImageBuffer::BarrierCommand()`를 통해 `vkCmdPipelineBarrier` 기록 +- 활성 패스 정렬 +- 모든 패스 `Configure()` +- 모든 패스 `BarrierInfo` 계산 -이 계층은 전이가 필요한지 판단을 내리지 않으며, 상위에서 계산된 전이 정보를 API 호출로 변환하는 역할만 수행합니다. +병렬 단계: -## 한계 +- 패스별 `CommandBuffer` 할당 +- `Begin()` +- `EmitBarrier()` +- `Record()` +- `End()` -현재 구조는 아래 기능은 포함되어 있지 않습니다. +커맨드 버퍼 기록은 병렬이지만, 제출은 `recordedCmds` 순서대로 수행됩니다. 따라서 패스 간 이미지 상태 전이는 정렬된 패스 순서를 기준으로 안정적으로 계산되고, 실제 제출 순서도 그 순서를 유지합니다. -- 의존성 기반 패스 자동 재배치 -- 버퍼 리소스 상태 추적 (현재 `ResourceUsage`는 이미지 상태값만 정의됨) +## Vulkan 구현 -## 구조 요약 +`VulkanCommandBuffer`는 현재 Vulkan dynamic rendering을 사용합니다. -ScriptableRenderPass (N개) -- renderTextures: 리소스 사용 의도 선언 +- `SetRenderData()`가 `RenderData::GetRenderTargets()`로 `VkRenderingAttachmentInfoKHR` 배열을 구성합니다. +- 여러 color attachment를 지원하며, 첫 번째 타겟 또는 depth 전용 타겟에서 depth attachment를 가져옵니다. +- 스왑체인 렌더링에서는 `nullptr` 타겟을 swapchain image로 해석합니다. +- MSAA 타겟이 있으면 color attachment에 MSAA image를 사용하고 resolve image를 원본 color image로 지정합니다. +- `RenderTargetLayout`은 color format 목록, depth format, MSAA 여부를 담고 pipeline cache key로 사용됩니다. -ScriptableRenderer -- 패스 순서 정렬 -- BuildBarrierInfo(): 패스 간 상태 전이 계산 -- 커맨드 버퍼 병렬 기록 +draw 단계에서는 shader pass 이름으로 material의 `ShaderPass` 목록을 찾고, topology/skinned 여부/render target layout에 맞는 pipeline을 가져옵니다. descriptor set은 usage 기준으로 다음처럼 바인딩됩니다. -VulkanRenderImpl -- EmitBarrier(): ResourceUsage -> Vulkan barrier 변환 -- vkCmdPipelineBarrier 기록 +| Set usage | 데이터 | +|---|---| +| `Camera` | `RenderDataManager` GPU buffer, dynamic offset 사용 | +| `Object` | drawable별 material data | +| `Material` | material별 uniform/texture data | + +Shader DSL에서 `MATRIX_VIEW`, `MATRIX_PROJ`, `CAMERA`를 사용하면 parser가 `CAMERA` uniform buffer를 자동으로 구성합니다. 현재 camera buffer는 `view`, `proj`, `pos`를 담고, `MATRIX_VIEW`와 `MATRIX_PROJ`는 각각 `CAMERA.view`, `CAMERA.proj`로 치환됩니다. + +## MaterialData와 텍스처 추적 + +렌더 텍스처 샘플링 추적은 `MaterialData`의 `CachedRT`에 저장됩니다. + +```cpp +struct CachedRT +{ + uint32_t set = 0; + uint32_t binding = 0; + core::SObjWeakPtr pass; + core::SObjWeakPtr rt; +}; +``` + +패스의 `Configure()`는 material/drawable이 가진 cached render texture 중 현재 `passName`과 lighting pass 이름이 같은 항목만 읽기 리소스로 등록합니다. 이 구조 덕분에 후처리 material이 depth, SSAO, color texture를 참조하더라도 패스 쪽에서 별도 barrier 코드를 직접 작성하지 않아도 됩니다. + +## 패스 추가 방법 + +새 패스를 추가할 때는 다음 순서로 작업합니다. + +1. `ScriptableRenderPass`를 상속한 클래스를 만든다. +2. 생성자에서 `passName`과 `RenderQueue`를 정한다. +3. 기본 batch 드로우면 기본 `Configure()`를 쓰고, 풀스크린/특수 패스면 `Configure()`에서 `SetRenderTargetImageUsages()`와 `SetImageUsages()`를 직접 호출한다. +4. `Record()`에서 `CommandBuffer` API로 렌더 타겟 설정, viewport/scissor, draw/dispatch/blit을 기록한다. +5. `ScriptableRenderer` 파생 클래스의 `Init()`에서 `AddRenderPass()`로 생성한다. +6. `Setup()`에서 `RenderData::tag`나 상황에 맞춰 `EnqueRenderPass()` 한다. + +예시: + +```cpp +class MyPostPass : public ScriptableRenderPass +{ +public: + MyPostPass(const IRenderContext& ctx) + : ScriptableRenderPass(core::Name{ "MyPostPass" }, RenderQueue::AfterRendering), + ctx(ctx) + { + } + +protected: + void Configure(const RenderData& renderData) override + { + renderTextures.clear(); + SetRenderTargetImageUsages(renderData); + SetImageUsages(*material); + } + + void Record(CommandBuffer& cmd, const IRenderContext& ctx, const RenderData& renderData) override + { + cmd.SetRenderData(renderData, false, false, true, false); + for (std::size_t i = 0; i < renderData.renderViewers.size(); ++i) + { + SetViewportScissor(cmd, ctx, renderData.renderViewers[i]); + cmd.DrawMesh(*fullscreenDrawable, passName, i); + } + } + +private: + const IRenderContext& ctx; + Material* material = nullptr; + Drawable* fullscreenDrawable = nullptr; +}; +``` From b4f33cde340a7198846096d9a32489ef6b92afbc Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Mon, 11 May 2026 01:56:44 +0900 Subject: [PATCH 56/60] docs: Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 0a66cca9..b7409b6f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ # ShellEngine image +스크린샷 2026-05-11 012031 + + [![Video Label](http://img.youtube.com/vi/SEiktv0WtOM/0.jpg)](https://youtu.be/SEiktv0WtOM)
[간단한 에디터 데모 영상(Youtube)](https://youtu.be/SEiktv0WtOM) From ec04b4992ebc700e864f8c5e5073e27f4b400667 Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Mon, 11 May 2026 01:59:56 +0900 Subject: [PATCH 57/60] docs: Update RenderArchitecture.md --- .gitignore | 2 +- RenderArchitecture.md | 31 +-------- docs/RenderFrameFlow.svg | 134 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 137 insertions(+), 30 deletions(-) create mode 100644 docs/RenderFrameFlow.svg diff --git a/.gitignore b/.gitignore index 2985a8c5..b478653e 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,4 @@ CTestTestfile.cmake _deps .vs out -docs \ No newline at end of file +.codex_temp \ No newline at end of file diff --git a/RenderArchitecture.md b/RenderArchitecture.md index 4ad0afbd..9aa66dbf 100644 --- a/RenderArchitecture.md +++ b/RenderArchitecture.md @@ -17,6 +17,8 @@ ShellEngine의 렌더링은 `RenderData`가 정의한 뷰/타겟 단위 작업 ## 프레임 흐름 +![프레임 흐름](/docs/RenderFrameFlow.svg) + 1. 게임/에디터 쪽에서 `Renderer::PushDrawAble()`과 `Renderer::PushRenderData()`로 렌더링할 객체와 뷰 정보를 큐에 넣습니다. 2. 렌더 스레드의 `Renderer::Render()`가 큐를 비우고 `RenderDataManager::UploadToGPU()`를 호출합니다. 3. `RenderDataManager`는 각 `RenderViewer`의 view/proj/camera position을 GPU 버퍼에 업로드하고 `RenderData`를 `priority` 기준으로 정렬합니다. @@ -27,35 +29,6 @@ ShellEngine의 렌더링은 `RenderData`가 정의한 뷰/타겟 단위 작업 8. 모든 패스 커맨드 기록이 끝나면 `VulkanRenderer`가 기록된 커맨드들을 순서대로 제출하고 마지막 커맨드가 present semaphore를 signal합니다. 9. 마지막에 `ExecuteTransfer()`가 readback/copy 작업을 위한 `CopyPass`를 기록하고, 스왑체인을 `Present` 상태로 전이합니다. -```mermaid -flowchart TD - A["Game/Editor Thread"] --> B["Renderer::PushDrawAble()\nRenderer::PushRenderData()"] - B --> C["Render thread\nRenderer::Render()"] - C --> D["DrainRenderCommands()\ndrawable/render data queue 반영"] - D --> E["RenderDataManager::UploadToGPU()"] - E --> F["RenderViewer camera buffer 업로드\nRenderData priority 정렬"] - F --> G["VulkanRenderer::Render()"] - G --> H["vkAcquireNextImageKHR()\nframe index 획득"] - H --> I["RenderData에 frame index와\ndrawable list 연결"] - I --> J["ScriptableRenderer::Setup()"] - J --> K["RenderData::tag 기준\n활성 패스 enqueue"] - K --> L["ScriptableRenderer::Execute()"] - L --> M["RenderQueue 정렬"] - M --> N["각 패스 Configure()\nRenderTexture 사용 의도 확정"] - N --> O["BuildBarrierInfo()\nBarrierInfo 계산"] - O --> P{"ThreadPool\n패스별 병렬 기록"} - P --> Q["CommandBuffer::Begin()"] - Q --> R["CommandBuffer::EmitBarrier()"] - R --> S["ScriptableRenderPass::Record()"] - S --> T["CommandBuffer::End()"] - T --> U["recordedCmds에 순서대로 수집"] - U --> V["VulkanQueueManager::Submit()\n커맨드 순차 제출"] - V --> W["ExecuteTransfer()\nCopyPass/readback 기록"] - W --> X["swapchain -> Present 전이"] - X --> Y["vkQueuePresentKHR()"] - Y --> Z["CallReadbacks()\nResetSubmittedCommands()"] -``` - ## RenderData와 RenderViewer `RenderData`는 한 번의 렌더링 작업이 사용할 뷰어, 렌더 타겟, 태그, 우선순위를 담습니다. diff --git a/docs/RenderFrameFlow.svg b/docs/RenderFrameFlow.svg new file mode 100644 index 00000000..b1b46b02 --- /dev/null +++ b/docs/RenderFrameFlow.svg @@ -0,0 +1,134 @@ + + ShellEngine render frame flow + Horizontal frame flow diagram from game/editor queues through render data upload, pass setup, parallel command recording, Vulkan submission, present, and cleanup. + + + + + + + + + + + 1. 입력 수집 + + 2. RenderData 준비와 패스 선택 + + 3. 커맨드 기록, 제출, 정리 + + + + 1 + Game / Editor + drawable, render data 생성 + + + + 2 + Renderer queue + PushDrawAble / PushRenderData + + + + 3 + Renderer::Render + 렌더 스레드에서 큐 drain + + + + 4 + UploadToGPU + camera buffer 업로드 + + + + + + + + + 5 + Acquire image + swapchain frame index + RenderData에 연결 + + + + 6 + Setup + RenderData::tag 기준 + 활성 패스 enqueue + + + + 7 + Execute + RenderQueue 정렬 + Configure 호출 + + + + 8 + BarrierInfo + RenderTexture 사용 이력 + 스왑체인 상태 계산 + + + + + + + + + + 9 + ThreadPool + 패스별 병렬 기록 + Begin → Barrier → Record → End + + + + 10 + recordedCmds + 패스 순서대로 수집 + 제출 순서 유지 + + + + 11 + Submit + VulkanQueueManager + semaphore 연결 + + + + 12 + Present / cleanup + ExecuteTransfer, Present + readback, command 반환 + + + + + + + + + 프레임 종료 후 다음 프레임에서 큐 수집부터 반복 + From e22efc98603e9d70b122f39f1b27cc7c4e18ecf6 Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Mon, 11 May 2026 14:54:45 +0900 Subject: [PATCH 58/60] docs: Update Shader.md --- Shader.md | 534 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 435 insertions(+), 99 deletions(-) diff --git a/Shader.md b/Shader.md index 73a1055a..2f0e31a0 100644 --- a/Shader.md +++ b/Shader.md @@ -1,95 +1,459 @@ -# 개요 -코드를 통해 렌더링 파이프라인을 설정 할 필요 없이, 셰이더를 통해 처리 할 수 있습니다. +# Shader 문서 -Unity엔진의 ShaderLab + GLSL의 문법을 합친 것이 특징 입니다. +ShellEngine의 `.shader` 파일은 Unity ShaderLab처럼 렌더 상태와 패스를 선언하고, 각 `Stage` 안에는 GLSL 코드를 직접 작성하는 형식입니다. 엔진은 이 파일을 파싱해 Vulkan용 GLSL을 생성하고, `glslc`로 SPIR-V를 컴파일한 뒤 `Shader`, `ShaderPass`, 머티리얼 유니폼 레이아웃으로 연결합니다. -# 상세 원리 -1. [ShaderLexer](https://github.com/Shell4026/ShellEngine/blob/main/src/Render/ShaderLexer.cpp)클래스로 코드를 토큰 단위로 나눕니다 -2. [ShaderParser](https://github.com/Shell4026/ShellEngine/blob/main/src/Render/ShaderParser.cpp)클래스로 토큰을 하나하나 읽어 문법을 검사하고 파싱 트리 구조를 완성해나갑니다. -3. ShaderParse내 Generate함수로 파싱 트리를 기반으로 GLSL문법으로 변환한 후 셰이더를 컴파일 합니다. +관련 구현은 주로 다음 파일에 있습니다. -## 예시 코드 +- `src/Render/ShaderLexer.cpp`: 소스 문자열을 토큰으로 분해 +- `src/Render/ShaderParser.cpp`: 토큰을 AST로 파싱하고 GLSL 코드 생성 +- `src/Render/ShaderGenerator.cpp`: 패스별 `.vert`, `.frag` 임시 파일 생성 +- `src/Game/Asset/ShaderLoader.cpp`: `.shader` 로드, `glslc` 실행, SPIR-V 로드 +- `src/Render/ShaderPass.cpp`: 파싱 결과에서 어트리뷰트/유니폼 레이아웃 구성 +- `src/Render/VulkanImpl/VulkanShaderPass.cpp`: Vulkan 디스크립터 세트와 파이프라인 레이아웃 생성 + +## 처리 흐름 + +1. `ShaderLoader::Load(path)`가 `.shader` 파일을 텍스트로 읽습니다. +2. `ShaderLexer::Lex()`가 키워드, 식별자, 숫자, 문자열, 연산자, 중괄호 등을 토큰으로 나눕니다. +3. `ShaderParser::Parse()`가 토큰을 `ShaderAST::ShaderNode`로 변환합니다. +4. 파서는 `Stage` 본문을 분석하며 `VERTEX`, `MATRIX_PROJ`, `LIGHT` 같은 엔진 예약 심볼을 발견하면 필요한 입력 어트리뷰트와 버퍼를 자동으로 추가합니다. +5. `GenerateStageCode()`가 각 `Stage`의 최종 GLSL 코드를 만듭니다. +6. `ShaderGenerator::GenerateShaderFile()`이 패스별 `.vert`, `.frag` 파일을 캐시 경로에 저장합니다. +7. `ShaderLoader`가 `glslc`로 각 스테이지를 `.spv`로 컴파일합니다. +8. `ShaderPassBuilder`가 SPIR-V를 `ShaderPass`로 만들고, Vulkan 구현에서는 `VkShaderModule`, descriptor set layout, pipeline layout을 구성합니다. +9. 최종 `Shader` 객체는 프로퍼티 목록과 패스 목록을 보관하고, `Material`은 이를 기준으로 유니폼/샘플러 데이터를 업로드합니다. + +## 기본 구조 + +```glsl +#version 430 core + +Shader "Shader Name" +{ + Property + { + float ambient; + vec4 color; + sampler2D albedo; + } + + Pass + { + LightingPass "Opaque" + Cull Back; + ZWrite On; + ZTest On; + + Stage Vertex + { + layout(location = 0) out vec2 fragUV; + + void main() + { + fragUV = UV; + gl_Position = MATRIX_PROJ * MATRIX_VIEW * MATRIX_MODEL * vec4(VERTEX, 1.0); + } + } + + Stage Fragment + { + layout(location = 0) in vec2 fragUV; + layout(location = 0) out vec4 outColor; + + uniform float ambient; + uniform vec4 color; + uniform sampler2D albedo; + + void main() + { + outColor = texture(albedo, fragUV) * color; + outColor.rgb += ambient; + } + } + } +} +``` + +최상단 `#version`은 현재 파서에서 `#version 430 core` 형태를 기대합니다. `Shader "..."`는 생성될 `Shader` 객체의 이름입니다. `Property`는 머티리얼에서 조작할 수 있는 값 목록이며 생략할 수 있습니다. `Pass`는 하나 이상 선언할 수 있고, 각 패스는 렌더 패스 이름과 렌더 상태, `Vertex`/`Fragment` 스테이지를 가집니다. + +## Property + +`Property`에 선언한 변수는 `Material::SetProperty()`로 값을 넣을 수 있는 머티리얼 프로퍼티가 됩니다. 스테이지 안에서 `uniform`으로 사용하려면 같은 타입과 이름이 `Property`에 먼저 선언되어 있어야 합니다. + +지원되는 주요 타입은 다음과 같습니다. + +| Shader 타입 | C++ 저장 타입 | +| --- | --- | +| `int` | `int` | +| `float` | `float` | +| `vec2` | `glm::vec2` | +| `vec3` | `glm::vec3` | +| `vec4` | `glm::vec4` | +| `mat3` | `glm::mat3` | +| `mat4` | `glm::mat4` | +| `sampler2D` | `Texture` | + +배열도 선언할 수 있습니다. + +```glsl +Property +{ + vec3 kernel[64]; + sampler2D normalTex; +} +``` + +### Local 프로퍼티 + +`[Local]`을 붙인 프로퍼티는 머티리얼 공통 값이 아니라 오브젝트별 값으로 취급됩니다. 예를 들어 같은 머티리얼을 공유하되 오브젝트마다 텍스처를 다르게 넣고 싶을 때 사용합니다. + +```glsl +Property +{ + vec4 color; + [Local] sampler2D tex; +} +``` + +일반 프로퍼티는 `UniformStructLayout::Usage::Material` 세트로, `[Local]` 프로퍼티는 `UniformStructLayout::Usage::Object` 세트로 배치됩니다. 런타임에서는 `MaterialData`와 `MaterialPropertyBlock`이 이 구분을 기준으로 버퍼와 샘플러를 갱신합니다. + +## Pass + +`Pass`는 실제 draw에 사용되는 렌더링 단위입니다. 같은 `LightingPass` 이름을 가진 패스들은 `Shader::GetShaderPasses(name)`로 조회되어 해당 렌더 단계에서 순서대로 실행됩니다. + +```glsl +Pass "Optional Pass Name" +{ + LightingPass "Opaque" + Cull Back; + ZWrite On; + ZTest On; + ColorMask RGBA; + + Stage Vertex { ... } + Stage Fragment { ... } +} +``` + +패스 이름은 생략할 수 있습니다. 생략하면 파서가 `Pass1`, `Pass2`처럼 자동 이름을 붙입니다. + +### 렌더 상태 + +| 구문 | 값 | 기본값 | 설명 | +| --- | --- | --- | --- | +| `LightingPass "name"` | 문자열 | 빈 이름 | 렌더러가 패스를 찾을 때 쓰는 분류 이름 | +| `Cull` | `Off`, `Front`, `Back` | `Back` | 컬링 모드 | +| `ZWrite` | `On`, `Off` | `On` | 깊이 버퍼 쓰기 여부 | +| `ZTest` | `On`, `Off` | `On` | 깊이 테스트 여부 | +| `ColorMask` | `RGBA` 조합 또는 숫자 | `15` | 컬러 채널 쓰기 마스크 | + +`ColorMask`는 비트마스크로 저장됩니다. `R=1`, `G=2`, `B=4`, `A=8`이며 `ColorMask 0;`은 컬러 출력을 막을 때 사용합니다. + +### Stencil + +```glsl +Stencil +{ + Ref 1; + ReadMask 255; + WriteMask 255; + Comp Always; + Pass Replace; + Fail Keep; + ZFail Keep; +} +``` + +`Comp` 값은 `Never`, `Less`, `Equal`, `LessEqual`, `Greater`, `NotEqual`, `GreaterEqual`, `Always`를 사용할 수 있습니다. `Pass`, `Fail`, `ZFail` 값은 `Keep`, `Zero`, `Replace`, `IncrementClamp`, `DecrementClamp`, `Invert`, `IncrementWrap`, `DecrementWrap`를 사용할 수 있습니다. + +## Stage + +현재 그래픽 셰이더는 `Stage Vertex`와 `Stage Fragment`를 사용합니다. 각 `Stage` 내부는 GLSL과 거의 같은 문법을 쓰지만, 파서가 일부 선언과 예약 심볼을 인식해 최종 GLSL 앞부분을 자동 생성합니다. + +```glsl +Stage Vertex +{ + layout(location = 0) out vec2 fragUV; + + const float scale = 1.0; + + void main() + { + fragUV = UV; + gl_Position = MATRIX_PROJ * MATRIX_VIEW * MATRIX_MODEL * vec4(VERTEX * scale, 1.0); + } +} +``` + +### layout 입출력 + +`layout(location = N) in/out ...;` 구문은 스테이지 입출력으로 파싱됩니다. + +```glsl +layout(location = 0) out vec2 fragUV; +layout(location = 1) out vec3 fragNormal; +``` + +버텍스 스테이지에서 엔진 예약 입력(`VERTEX`, `UV`, `NORMAL` 등)을 사용하면 직접 `in`을 선언하지 않아도 파서가 자동으로 입력 어트리뷰트를 추가합니다. + +## 엔진 예약 심볼 + +예약 심볼은 셰이더 코드 안에서 일반 GLSL 변수처럼 사용할 수 있습니다. 파서는 사용 여부를 감지해 필요한 layout, uniform buffer, storage buffer, push constant를 자동으로 생성합니다. + +| 심볼 | 타입/의미 | 자동 생성 | +| --- | --- | --- | +| `VERTEX` | `vec3`, 정점 위치 | vertex input location 0 | +| `UV` | `vec2`, UV | vertex input location 1 | +| `NORMAL` | `vec3`, 노멀 | vertex input location 2 | +| `TANGENT` | `vec3`, 탄젠트 | vertex input location 3 | +| `BONE_INDICES` | `ivec4`, 본 인덱스 | skinned input location 4 | +| `BONE_WEIGHTS` | `vec4`, 본 가중치 | skinned input location 5 | +| `MATRIX_MODEL` | 모델 행렬 | push constant `CONSTANTS.model` | +| `MATRIX_VIEW` | 카메라 view 행렬 | camera UBO `CAMERA.view` | +| `MATRIX_PROJ` | 카메라 projection 행렬 | camera UBO `CAMERA.proj` | +| `CAMERA` | 카메라 UBO 인스턴스 | `view`, `proj`, `pos` 멤버 | +| `LIGHT` | 라이트 SSBO | object set의 storage buffer | +| `SKIN` | 스키닝 SSBO | object set의 storage buffer | +| `MATRIX_SKIN` | 스키닝 행렬 | `BONE_*`와 `SKIN.ibm[]` 기반 계산식 삽입 | +| `TEXTURE_SHADOW` | 그림자 텍스처 | object set의 local sampler | + +`MATRIX_MODEL`, `MATRIX_VIEW`, `MATRIX_PROJ`는 최종 GLSL에서 각각 `CONSTANTS.model`, `CAMERA.view`, `CAMERA.proj`로 치환됩니다. + +### LIGHT 버퍼 형태 + +현재 파서는 `LIGHT` 사용 시 다음과 같은 구조를 생성합니다. + +```glsl +struct Light +{ + vec4 pos; + vec4 other; + vec4 shadowRect; + mat4 lightSpaceMatrix; +}; + +layout(std430, set = 1, binding = N) readonly buffer UNIFORM_LIGHT +{ + int count; + Light lights[]; +} LIGHT; +``` + +따라서 코드에서는 `LIGHT.count`, `LIGHT.lights[i].pos`, `LIGHT.lights[i].other`처럼 접근합니다. + +```glsl +for (int i = 0; i < LIGHT.count; ++i) +{ + int type = int(LIGHT.lights[i].other.w); + vec3 lightPos = LIGHT.lights[i].pos.xyz; +} +``` + +### MATRIX_SKIN + +`MATRIX_SKIN`을 사용하면 파서가 `BONE_WEIGHTS`, `BONE_INDICES`, `SKIN` 버퍼를 자동 등록하고 함수 본문 앞에 다음 계산식을 삽입합니다. + +```glsl +mat4 MATRIX_SKIN = + BONE_WEIGHTS.x * SKIN.ibm[BONE_INDICES.x] + + BONE_WEIGHTS.y * SKIN.ibm[BONE_INDICES.y] + + BONE_WEIGHTS.z * SKIN.ibm[BONE_INDICES.z] + + BONE_WEIGHTS.w * SKIN.ibm[BONE_INDICES.w]; +``` + +스키닝 셰이더에서는 보통 다음처럼 사용합니다. + +```glsl +vec4 skinnedPos = MATRIX_SKIN * vec4(VERTEX, 1.0); +vec3 skinnedNormal = mat3(MATRIX_SKIN) * NORMAL; +``` + +## Uniform 바인딩 규칙 + +스테이지 내부의 단일 `uniform` 선언은 반드시 `Property`에 같은 타입과 이름으로 선언되어 있어야 합니다. + +```glsl +Property +{ + float roughness; + sampler2D albedo; +} + +Stage Fragment +{ + uniform float roughness; + uniform sampler2D albedo; +} +``` + +파서는 스칼라/벡터/행렬 프로퍼티를 `UBO`라는 uniform block으로 묶습니다. `sampler2D`는 별도의 combined image sampler binding이 됩니다. + +일반 프로퍼티는 material set에 배치되고, `[Local]` 프로퍼티는 object set에 배치됩니다. 내부 enum 기준은 다음과 같습니다. + +| Usage | set | 용도 | +| --- | --- | --- | +| `Camera` | `0` | 카메라 공통 버퍼 | +| `Object` | `1` | 오브젝트별 값, push/local/lighting/skinning | +| `Material` | `2` | 머티리얼 공유 값 | + +명시적 descriptor layout도 선언할 수 있습니다. 이 경우 `Property` 검사를 거치지 않고 직접 버퍼 또는 샘플러 layout으로 추가됩니다. + +```glsl +layout(set = 2, binding = 0) uniform MyBlock +{ + vec4 color; + float strength; +} customData; + +layout(set = 2, binding = 1) uniform sampler2D customTex; +``` + +UBO는 `std140`, SSBO는 `std430` 기준으로 `UniformStructLayout`이 CPU 측 offset과 size를 계산합니다. `ShaderParser::Optimize()`는 자동 생성된 `UBO` 멤버를 스칼라, `vec2`, 그 외 큰 타입 순서로 정렬해 패딩을 줄입니다. + +## constexpr와 specialization constant + +스테이지 안에서 `constexpr`를 선언하면 Vulkan specialization constant로 변환됩니다. + +```glsl +Stage Fragment +{ + constexpr int SAMPLE_COUNT = 16; + + void main() + { + for (int i = 0; i < SAMPLE_COUNT; ++i) + { + ... + } + } +} +``` + +생성되는 GLSL은 `layout (constant_id = N) const ...` 형태입니다. 런타임에서는 `Material::SetConstant(name, value)`가 패스별 constant 데이터를 갱신하고, `VulkanPipelineManager`는 constant 데이터 해시를 파이프라인 캐시 키에 포함합니다. + +현재 `ShaderPass`가 크기를 계산하는 specialization constant 타입은 `bool`, `int`, `float` 계열입니다. + +## 생성되는 GLSL 예시 + +작성한 코드: + +```glsl +Stage Vertex +{ + layout(location = 0) out vec2 fragUV; + + void main() + { + fragUV = UV; + gl_Position = MATRIX_PROJ * MATRIX_VIEW * MATRIX_MODEL * vec4(VERTEX, 1.0); + } +} +``` + +대략 다음과 같은 GLSL로 확장됩니다. + +```glsl +#version 430 core +layout(location = 0) in vec3 VERTEX; +layout(location = 1) in vec2 UV; +layout(location = 0) out vec2 fragUV; + +layout(set = 0, binding = 0) uniform UNIFORM_CAMERA +{ + mat4 view; + mat4 proj; + vec3 pos; +} CAMERA; + +layout(push_constant) uniform UNIFORM_CONSTANTS +{ + mat4 model; +} CONSTANTS; + +void main() +{ + fragUV = UV; + gl_Position = CAMERA.proj * CAMERA.view * CONSTANTS.model * vec4(VERTEX, 1.0); +} +``` + +## 예시: 조명 텍스처 셰이더 ```glsl #version 430 core -Shader "Default Shader" +Shader "Lit Texture" { Property { float ambient; sampler2D tex; - sampler2D normalTex; } - + Pass { - LightingPass "Forward" - + LightingPass "Opaque" + Stage Vertex { - layout(location = 0) out vec2 fragUvs; + layout(location = 0) out vec2 fragUV; layout(location = 1) out vec3 fragPos; - layout(location = 2) out mat3 TBN; + layout(location = 2) out vec3 fragNormal; + void main() { - gl_Position = MATRIX_PROJ * MATRIX_VIEW * MATRIX_MODEL * vec4(VERTEX, 1.0f); - fragUvs = UV; - fragPos = (MATRIX_MODEL * vec4(VERTEX, 1.0f)).xyz; - - mat3 normalMatrix = transpose(inverse(mat3(MATRIX_MODEL))); - - vec3 t = normalize(normalMatrix * TANGENT); - vec3 n = normalize(normalMatrix * NORMAL); - vec3 b = cross(n, t); - - TBN = mat3(t, b, n); + fragUV = UV; + fragPos = (MATRIX_MODEL * vec4(VERTEX, 1.0)).xyz; + fragNormal = normalize(mat3(transpose(inverse(MATRIX_MODEL))) * NORMAL); + gl_Position = MATRIX_PROJ * MATRIX_VIEW * vec4(fragPos, 1.0); } } + Stage Fragment { + layout(location = 0) in vec2 fragUV; + layout(location = 1) in vec3 fragPos; + layout(location = 2) in vec3 fragNormal; layout(location = 0) out vec4 outColor; - layout(location = 0) in vec2 uvs; - layout(location = 1) in vec3 fragPos; - layout(location = 2) in mat3 TBN; uniform float ambient; uniform sampler2D tex; - uniform sampler2D normalTex; - void main() + void main() { - vec3 normal = texture(normalTex, uvs).xyz; - normal = normal * 2.0 - 1.0; - normal = normalize(TBN * normal); - float diffuse = 0.0; + for (int i = 0; i < LIGHT.count; ++i) { - int type = int(LIGHT.other[i].w); + int type = int(LIGHT.lights[i].other.w); if (type == 0) { - vec3 toLightDir = -LIGHT.pos[i].xyz; - float intensity = LIGHT.pos[i].w; - diffuse += max(dot(normal, toLightDir), 0.0) * intensity; + vec3 toLightDir = -LIGHT.lights[i].pos.xyz; + float intensity = LIGHT.lights[i].pos.w; + diffuse += max(dot(fragNormal, toLightDir), 0.0) * intensity; } else if (type == 1) { - vec3 toLightVec = LIGHT.pos[i].xyz - fragPos; + vec3 toLightVec = LIGHT.lights[i].pos.xyz - fragPos; vec3 toLightDir = normalize(toLightVec); float lightDis = length(toLightVec); - float attenuation = clamp(1.0 - (lightDis / LIGHT.pos[i].w), 0.0, 1.0); - diffuse += max(dot(normal, toLightDir), 0.0) * attenuation; + float attenuation = clamp(1.0 - (lightDis / LIGHT.lights[i].pos.w), 0.0, 1.0); + diffuse += max(dot(fragNormal, toLightDir), 0.0) * attenuation; } } - outColor = texture(tex, uvs); - outColor.xyz *= diffuse + ambient; + + outColor = texture(tex, fragUV); + outColor.rgb *= diffuse + ambient; } } } } ``` -### 스텐실을 이용한 아웃라인 셰이더 예시 + +## 예시: 스텐실 아웃라인 + +첫 번째 패스는 원본 오브젝트를 스텐실에 기록하고 컬러 출력을 막습니다. 두 번째 패스는 노멀 방향으로 정점을 밀어낸 뒤, 스텐실 값이 다른 영역만 칠합니다. + ```glsl #version 430 core @@ -100,11 +464,12 @@ Shader "Outline Shader" float outlineWidth; vec4 color; } + Pass { - LightingPass "Forward" + LightingPass "Opaque" ColorMask 0; - + Stencil { Ref 1; @@ -115,26 +480,31 @@ Shader "Outline Shader" Fail Replace; ZFail Replace; } - + Stage Vertex { void main() { - gl_Position = MATRIX_PROJ * MATRIX_VIEW * MATRIX_MODEL * vec4(VERTEX, 1.0f); + gl_Position = MATRIX_PROJ * MATRIX_VIEW * MATRIX_MODEL * vec4(VERTEX, 1.0); } } + Stage Fragment { layout(location = 0) out vec4 outColor; + void main() { - outColor = vec4(0.0f, 0.0f, 0.0f, 0.0f); + outColor = vec4(0.0); } } } + Pass { - LightingPass "Forward" + LightingPass "Opaque" + Cull Back; + Stencil { Ref 1; @@ -145,25 +515,25 @@ Shader "Outline Shader" Fail Keep; ZFail Keep; } - Cull Back; - + Stage Vertex { uniform float outlineWidth; void main() { - vec4 vert = vec4(VERTEX + NORMAL * outlineWidth, 1.0f); + vec4 vert = vec4(VERTEX + NORMAL * outlineWidth, 1.0); gl_Position = MATRIX_PROJ * MATRIX_VIEW * MATRIX_MODEL * vert; } } + Stage Fragment { layout(location = 0) out vec4 outColor; uniform vec4 color; - void main() + void main() { outColor = color; } @@ -171,49 +541,15 @@ Shader "Outline Shader" } } ``` -### 로컬 키워드를 사용해 오브젝트별 텍스쳐 설정 가능한 반투명 셰이더 예시 -로컬 키워드로 된 유니폼 변수는 MeshRenderer MaterialPropertyBlock을 지정해준 후 값을 넣어주면 됩니다. - -```glsl -#version 430 core -Shader "Unlit Transparent Shader2" -{ - Property - { - vec3 color; - [Local] sampler2D tex; - } - - Pass - { - LightingPass "Transparent" - - Cull Off; - ZWrite Off; - Stage Vertex - { - layout(location = 0) out vec2 fragUvs; - - void main() - { - gl_Position = MATRIX_PROJ * MATRIX_VIEW * MATRIX_MODEL * vec4(VERTEX, 1.0f); - fragUvs = UV; - } - } - Stage Fragment - { - layout(location = 0) out vec4 outColor; - layout(location = 0) in vec2 uvs; - uniform vec3 color; - uniform sampler2D tex; +## 작성 시 주의할 점 - void main() - { - outColor = texture(tex, uvs); - outColor.xyz *= color.xyz; - } - } - } -} -``` +- `Stage` 안의 일반 `uniform`은 `Property`에 먼저 선언해야 합니다. 선언이 없으면 파서가 오류를 냅니다. +- `LIGHT`는 `LIGHT.pos[i]`가 아니라 `LIGHT.lights[i].pos` 형태로 접근해야 합니다. +- `MATRIX_MODEL`을 사용하면 push constant가 생성됩니다. Vulkan 구현은 현재 모델 행렬 하나(`mat4`)를 push constant로 사용합니다. +- `MATRIX_VIEW`, `MATRIX_PROJ`, `CAMERA` 중 하나를 사용하면 camera UBO가 생성됩니다. +- `[Local]`은 오브젝트별 데이터를 위한 선언입니다. 같은 머티리얼을 여러 오브젝트가 공유할 때 개별 값을 넣어야 하는 프로퍼티에 사용합니다. +- `sampler2D`는 UBO 멤버가 아니라 별도 sampler descriptor로 생성됩니다. +- `mat2`는 파서와 `Shader` 프로퍼티 타입에는 존재하지만, 현재 `ShaderPass::FillAttributes()`의 UBO 멤버 구성에는 반영되어 있지 않습니다. 머티리얼 프로퍼티로 안정적으로 쓰려면 `mat3` 또는 `mat4`를 사용하거나 해당 경로를 보강해야 합니다. +- 자동 생성되는 UBO 멤버는 최종 레이아웃 최적화를 위해 선언 순서와 다르게 정렬될 수 있습니다. CPU 측 업로드는 `UniformStructLayout`의 offset을 기준으로 하므로 이름으로 접근하는 경로를 사용해야 합니다. +- 패스의 `LightingPass` 이름은 렌더러 쪽에서 조회하는 키입니다. 새 이름을 만들 때는 해당 렌더 단계가 실제로 그 이름을 요청하는지 함께 확인해야 합니다. From f69dea08a282ec05fcf41c0b837043f4be4c9547 Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Mon, 11 May 2026 14:55:51 +0900 Subject: [PATCH 59/60] fix(VulkanImageBuffer): Fix Move Constructor correctly --- include/Render/VulkanImpl/VulkanImageBuffer.h | 2 +- src/Render/VulkanImpl/VulkanImageBuffer.cpp | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/include/Render/VulkanImpl/VulkanImageBuffer.h b/include/Render/VulkanImpl/VulkanImageBuffer.h index a23a2597..73a1d723 100644 --- a/include/Render/VulkanImpl/VulkanImageBuffer.h +++ b/include/Render/VulkanImpl/VulkanImageBuffer.h @@ -77,4 +77,4 @@ namespace sh::render::vk bool bSwapChainImg = false; bool bRenderTarget = false; }; -} +}//namespace \ No newline at end of file diff --git a/src/Render/VulkanImpl/VulkanImageBuffer.cpp b/src/Render/VulkanImpl/VulkanImageBuffer.cpp index a646553e..174a7dc8 100644 --- a/src/Render/VulkanImpl/VulkanImageBuffer.cpp +++ b/src/Render/VulkanImpl/VulkanImageBuffer.cpp @@ -32,10 +32,15 @@ namespace sh::render::vk bSwapChainImg(other.bSwapChainImg), bRenderTarget(other.bRenderTarget) { - other.Clear(); + other.img = nullptr; + other.imgMem = nullptr; + other.imgView = nullptr; + other.sampler = nullptr; } SH_RENDER_API auto VulkanImageBuffer::operator=(VulkanImageBuffer&& other) noexcept -> VulkanImageBuffer& { + if (this == &other) + return *this; Clear(); ctx = other.ctx; From 66c669a201a869bb5d5e77c9883dd99ea88cd662 Mon Sep 17 00:00:00 2001 From: Shell <104874910+Shell4026@users.noreply.github.com> Date: Mon, 11 May 2026 14:56:23 +0900 Subject: [PATCH 60/60] fix(VulkanContext): Reset ComputePipelineManager when calling Clear() --- src/Render/VulkanImpl/VulkanContext.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Render/VulkanImpl/VulkanContext.cpp b/src/Render/VulkanImpl/VulkanContext.cpp index 1387b34b..c62992fc 100644 --- a/src/Render/VulkanImpl/VulkanContext.cpp +++ b/src/Render/VulkanImpl/VulkanContext.cpp @@ -57,6 +57,8 @@ namespace sh::render::vk } VulkanContext::~VulkanContext() { + if (device != nullptr) + Clear(); } SH_RENDER_API void VulkanContext::Init() @@ -119,7 +121,9 @@ namespace sh::render::vk vkDeviceWaitIdle(device); + IRenderThrMethod::ClearBuffer(renderDataManager); pipelineManager.reset(); + computePipelineManager.reset(); if (emptyDescLayout) { vkDestroyDescriptorSetLayout(device, emptyDescLayout, nullptr); @@ -136,6 +140,8 @@ namespace sh::render::vk gpu = VK_NULL_HANDLE; gpus.clear(); DestroyInstance(); + + device = nullptr; } SH_RENDER_API auto VulkanContext::AllocateCommandBuffer(bool bCompute) -> CommandBuffer* {