From 743a32601cc6845d4dd4e338f55bed21bfa2587c Mon Sep 17 00:00:00 2001 From: Hoin Lee Date: Sat, 30 May 2026 06:02:15 +0900 Subject: [PATCH 01/17] feat: Support material binding for static meshes. --- Subsystems/Asset/AssetImportFuncs.cc | 13 ++++++++++--- Subsystems/Asset/AssetImportFuncs.h | 8 +++++--- Subsystems/Asset/AssetSystem.cc | 25 +++++++++++++++++++++++++ Subsystems/Asset/AssetSystem.h | 1 + Subsystems/Asset/StaticMeshAsset.h | 1 + 5 files changed, 42 insertions(+), 6 deletions(-) diff --git a/Subsystems/Asset/AssetImportFuncs.cc b/Subsystems/Asset/AssetImportFuncs.cc index 5a15586..04bf4db 100644 --- a/Subsystems/Asset/AssetImportFuncs.cc +++ b/Subsystems/Asset/AssetImportFuncs.cc @@ -70,9 +70,11 @@ static std::vector compileFileGLSL(const shaderc::Compiler& compiler, // Public Function Definitions // =========================================================================== -std::unique_ptr importStaticMesh(const parser::MeshIR& srcMeshIR, - const parser::SkeletonIR& srcSkeletonIR, - const parser::SkinIR& srcSkinIR) +std::unique_ptr importStaticMesh( + const parser::MeshIR& srcMeshIR, + const std::vector>& pMaterialIRs, + const parser::SkeletonIR& srcSkeletonIR, + const parser::SkinIR& srcSkinIR) { std::unique_ptr pNewMesh = std::make_unique(); const std::string nameStr = @@ -151,6 +153,11 @@ std::unique_ptr importStaticMesh(const parser::MeshIR& srcMeshI subMeshes[smi].IndexOffset = indexOffset; subMeshes[smi].IndexCount = static_cast(subMeshIR.Indices.size()); + const parser::MaterialIR& matIR = *pMaterialIRs[srcMeshIR.SubMeshes[smi].RenderMaterialIndex]; + const std::string matNameStr = + matIR.ResourcePath.RemovedExtension().ToString() + "::Material::" + matIR.NameStr; + subMeshes[smi].hRenderMaterialName = StringHandle(matNameStr); + subMeshes[smi].Aabb = subMeshIR.Aabb; subMeshes[smi].Sphere = subMeshIR.Sphere; diff --git a/Subsystems/Asset/AssetImportFuncs.h b/Subsystems/Asset/AssetImportFuncs.h index b25f570..d1b8d06 100644 --- a/Subsystems/Asset/AssetImportFuncs.h +++ b/Subsystems/Asset/AssetImportFuncs.h @@ -26,9 +26,11 @@ struct ShaderAsset; namespace importer { -[[nodiscard]] std::unique_ptr importStaticMesh(const parser::MeshIR& srcMeshIR, - const parser::SkeletonIR& srcSkeletonIR, - const parser::SkinIR& srcSkinIR); +[[nodiscard]] std::unique_ptr importStaticMesh( + const parser::MeshIR& srcMeshIR, + const std::vector>& pMaterialIRs, + const parser::SkeletonIR& srcSkeletonIR, + const parser::SkinIR& srcSkinIR); [[nodiscard]] std::unique_ptr importMaterial(const parser::MaterialIR& srcMaterialIR, eMaterialAssetType type); [[nodiscard]] std::unique_ptr importTexturedMaterial( diff --git a/Subsystems/Asset/AssetSystem.cc b/Subsystems/Asset/AssetSystem.cc index f15c553..1b4cfe3 100644 --- a/Subsystems/Asset/AssetSystem.cc +++ b/Subsystems/Asset/AssetSystem.cc @@ -62,6 +62,31 @@ ShaderHandle AssetSystem::AddShader(ShaderAsset&& shader) return hNewShader; } +bool AssetSystem::ResolveStaticMeshMaterials(StaticMeshHandle hStaticMesh) +{ + HO_ASSERT(hStaticMesh.IsValid(), "Static mesh is invalid."); + StaticMeshAsset* pStaticMesh = hStaticMesh.Get(); + bool bSuccess = true; + for (auto& subMesh : pStaticMesh->SubMeshes) + { + if (subMesh.hRenderMaterialName.IsNULL()) + { + continue; + } + auto it = mNameToMaterialMap.find(subMesh.hRenderMaterialName); + if (it == mNameToMaterialMap.end()) + { + HO_LOG_ERROR("[AssetSystem] Material '%s' doesn't exist for StaticMesh '%s'. Skipping resolution.", + subMesh.hRenderMaterialName.Get()->c_str(), + pStaticMesh->hName.Get()->c_str()); + bSuccess = false; + continue; + } + subMesh.hRenderMaterial = it->second; + } + return bSuccess; +} + bool AssetSystem::ResolveMaterialTextures(MaterialHandle hMaterial) { HO_ASSERT(hMaterial.IsValid(), "Material is invalid."); diff --git a/Subsystems/Asset/AssetSystem.h b/Subsystems/Asset/AssetSystem.h index 26c59d3..fdfeb3a 100644 --- a/Subsystems/Asset/AssetSystem.h +++ b/Subsystems/Asset/AssetSystem.h @@ -25,6 +25,7 @@ class AssetSystem final TextureHandle AddTexture(TextureAsset&& texture); ShaderHandle AddShader(ShaderAsset&& shader); + bool ResolveStaticMeshMaterials(StaticMeshHandle hStaticMesh); bool ResolveMaterialTextures(MaterialHandle hMaterial); bool DeleteStaticMesh(StaticMeshHandle hStaticMesh); diff --git a/Subsystems/Asset/StaticMeshAsset.h b/Subsystems/Asset/StaticMeshAsset.h index 0872f7a..b4a8c3b 100644 --- a/Subsystems/Asset/StaticMeshAsset.h +++ b/Subsystems/Asset/StaticMeshAsset.h @@ -38,6 +38,7 @@ struct StaticMeshAsset int32_t IndexOffset = 0; int32_t IndexCount = 0; + StringHandle hRenderMaterialName = StringHandle::sNULL; MaterialHandle hRenderMaterial = MaterialHandle::sNULL; AABB Aabb; From 758bbcad43aae1274488da4befd89fa370f8cfda Mon Sep 17 00:00:00 2001 From: Hoin Lee Date: Sat, 30 May 2026 19:08:22 +0900 Subject: [PATCH 02/17] fix: Correct alpha mode selection logic. --- Core/IO/ResourceParseFuncs.cc | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/Core/IO/ResourceParseFuncs.cc b/Core/IO/ResourceParseFuncs.cc index 795b3b5..da6a58d 100644 --- a/Core/IO/ResourceParseFuncs.cc +++ b/Core/IO/ResourceParseFuncs.cc @@ -1,7 +1,7 @@ #include "ResourceParseFuncs.h" +#include #include -#include #include #include #include @@ -551,22 +551,28 @@ std::unique_ptr parseMaterial(const Path& path, pMaterialIR->Opacity = fVal; } - pMaterialIR->AlphaMode = - pMaterialIR->Opacity < 1.f ? MaterialIR::eAlphaMode::Blend : MaterialIR::eAlphaMode::Opaque; - pMaterialIR->AlphaThreshold = 0.5f; - - if (AI_SUCCESS == assimpMaterial.Get(AI_MATKEY_GLTF_ALPHAMODE, aiStr)) + aiString alphaModeStr; + if (AI_SUCCESS == assimpMaterial.Get(AI_MATKEY_GLTF_ALPHAMODE, alphaModeStr)) { - const std::string mode(aiStr.C_Str()); - if (mode == "MASK") + if (alphaModeStr == aiString("MASK")) { pMaterialIR->AlphaMode = MaterialIR::eAlphaMode::Mask; } - else if (mode == "BLEND") + else if (alphaModeStr == aiString("BLEND")) { pMaterialIR->AlphaMode = MaterialIR::eAlphaMode::Blend; } + else + { + pMaterialIR->AlphaMode = MaterialIR::eAlphaMode::Opaque; + } } + else + { + pMaterialIR->AlphaMode = + pMaterialIR->Opacity < 1.f ? MaterialIR::eAlphaMode::Blend : MaterialIR::eAlphaMode::Opaque; + } + if (AI_SUCCESS == assimpMaterial.Get(AI_MATKEY_GLTF_ALPHACUTOFF, fVal)) { pMaterialIR->AlphaThreshold = fVal; From 12cfaf3cad59a7af6e1409f4ec28b9f12e661e01 Mon Sep 17 00:00:00 2001 From: Hoin Lee Date: Sat, 30 May 2026 20:16:53 +0900 Subject: [PATCH 03/17] refactor: Modify material's alpha threshold member. --- Subsystems/Asset/AssetImportFuncs.cc | 2 +- Subsystems/Asset/MaterialAsset.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Subsystems/Asset/AssetImportFuncs.cc b/Subsystems/Asset/AssetImportFuncs.cc index 04bf4db..4a758e5 100644 --- a/Subsystems/Asset/AssetImportFuncs.cc +++ b/Subsystems/Asset/AssetImportFuncs.cc @@ -217,7 +217,6 @@ std::unique_ptr importMaterial(const parser::MaterialIR& srcMater pNewMaterial->PipelineState.AlphaMode = toAssetAlphaMode(srcMaterialIR.AlphaMode); pNewMaterial->PipelineState.AlphaBlendMode = toAssetAlphaBlendMode(srcMaterialIR.BlendMode); - pNewMaterial->PipelineState.AlphaThreshold = srcMaterialIR.AlphaThreshold; pNewMaterial->PipelineState.bWireframe = srcMaterialIR.bWireframe; pNewMaterial->PipelineState.bBackfaceCulling = srcMaterialIR.bBackfaceCulling; @@ -232,6 +231,7 @@ std::unique_ptr importMaterial(const parser::MaterialIR& srcMater pNewMaterial->IndexOfRefraction = srcMaterialIR.IndexOfRefraction; pNewMaterial->Opacity = srcMaterialIR.Opacity; + pNewMaterial->AlphaThreshold = srcMaterialIR.AlphaThreshold; pNewMaterial->Emissive = srcMaterialIR.Emissive; pNewMaterial->EmissiveIntensity = srcMaterialIR.EmissiveIntensity; pNewMaterial->NormalScale = srcMaterialIR.NormalScale; diff --git a/Subsystems/Asset/MaterialAsset.h b/Subsystems/Asset/MaterialAsset.h index a12bfb1..d49a941 100644 --- a/Subsystems/Asset/MaterialAsset.h +++ b/Subsystems/Asset/MaterialAsset.h @@ -19,7 +19,6 @@ struct MaterialAsset { eMaterialAlphaMode AlphaMode = eMaterialAlphaMode::None; eMaterialAlphaBlendMode AlphaBlendMode = eMaterialAlphaBlendMode::None; - float AlphaThreshold = 0.5f; bool bWireframe = false; bool bBackfaceCulling = true; }; @@ -56,6 +55,7 @@ struct MaterialAsset // Global / Transparency Attributes float Opacity = 1.0f; + float AlphaThreshold = 0.5f; Color128 Emissive{0.f, 0.f, 0.f}; float EmissiveIntensity = 1.0f; float NormalScale = 1.0f; From 8e179c1761219827d13e1cfab34979ccc135eb45 Mon Sep 17 00:00:00 2001 From: Hoin Lee Date: Wed, 3 Jun 2026 21:46:23 +0900 Subject: [PATCH 04/17] fix: Correct default ambient value in MaterialIR. --- Core/IO/MaterialIR.h | 2 +- Core/IO/ResourceParseFuncs.cc | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Core/IO/MaterialIR.h b/Core/IO/MaterialIR.h index ac0acbe..4e7b1d3 100644 --- a/Core/IO/MaterialIR.h +++ b/Core/IO/MaterialIR.h @@ -83,7 +83,7 @@ struct MaterialIR std::string NameStr; // Legacy / Phong Attributes - Color128 Ambient{0.f, 0.f, 0.f, 1.f}; + Color128 Ambient{0.001f, 0.001f, 0.001f, 1.f}; Color128 Diffuse{1.f, 1.f, 1.f, 1.f}; Color128 Specular{1.f, 1.f, 1.f, 1.f}; float Shininess = 32.0f; diff --git a/Core/IO/ResourceParseFuncs.cc b/Core/IO/ResourceParseFuncs.cc index da6a58d..0c0f3eb 100644 --- a/Core/IO/ResourceParseFuncs.cc +++ b/Core/IO/ResourceParseFuncs.cc @@ -513,6 +513,10 @@ std::unique_ptr parseMaterial(const Path& path, if (AI_SUCCESS == assimpMaterial.Get(AI_MATKEY_COLOR_AMBIENT, color3D)) { pMaterialIR->Ambient = Color128(color3D.r, color3D.g, color3D.b); + if (pMaterialIR->Ambient == Color128(0.0f, 0.0f, 0.0f)) + { + pMaterialIR->Ambient = Color128(0.001f, 0.001f, 0.001f); + } } if (AI_SUCCESS == assimpMaterial.Get(AI_MATKEY_COLOR_DIFFUSE, color3D)) { @@ -525,6 +529,10 @@ std::unique_ptr parseMaterial(const Path& path, if (AI_SUCCESS == assimpMaterial.Get(AI_MATKEY_SHININESS, fVal)) { pMaterialIR->Shininess = fVal; + if (pMaterialIR->Shininess <= 0.01f) + { + pMaterialIR->Shininess = 32.0f; + } } // PBR Core Attributes From 47379fe193282fa4d481c36032b143d2f04d0736 Mon Sep 17 00:00:00 2001 From: Hoin Lee Date: Wed, 3 Jun 2026 21:47:29 +0900 Subject: [PATCH 05/17] fix: Add 'noexcept' keyword in move functions. --- Core/Templates/FixedArray.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/Templates/FixedArray.h b/Core/Templates/FixedArray.h index 8bc20d6..9cab24e 100644 --- a/Core/Templates/FixedArray.h +++ b/Core/Templates/FixedArray.h @@ -29,13 +29,13 @@ class FixedArray FixedArray(const FixedArray&) = delete; FixedArray& operator=(const FixedArray&) = delete; - FixedArray(FixedArray&& other) + FixedArray(FixedArray&& other) noexcept : mpArray(std::move(other.mpArray)) , mSize(other.mSize) { } - FixedArray& operator=(FixedArray&& rhs) + FixedArray& operator=(FixedArray&& rhs) noexcept { if (this == &rhs) { From 0a28ca8f2dd095114fd47071a278fcb95793791f Mon Sep 17 00:00:00 2001 From: Hoin Lee Date: Wed, 3 Jun 2026 21:51:10 +0900 Subject: [PATCH 06/17] refactor: Convert 'CPU_PAUSE' function to macro. --- Core/Thread/SpinLock.h | 57 -------------------------------------- Macros.h | 63 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 57 deletions(-) diff --git a/Core/Thread/SpinLock.h b/Core/Thread/SpinLock.h index 9baecbd..d60928a 100644 --- a/Core/Thread/SpinLock.h +++ b/Core/Thread/SpinLock.h @@ -2,10 +2,6 @@ #include -#ifdef _MSC_VER -#include -#endif - #include "Config.h" #include "Macros.h" #include "Thread.h" @@ -13,59 +9,6 @@ namespace ho { -/* - * This function is adapted from Godot Engine's spin_lock.h. - * - * The original work is licensed under the MIT License: - * - * Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. - * Copyright (c) 2014-present Godot Engine contributors. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - * Local modifications: function name normalized to CPU_PAUSE and minor - * formatting changes. No functional changes to the original logic. - */ - -FORCE_INLINE static void CPU_PAUSE() -{ -#if defined(_MSC_VER) -// ----- MSVC. -#if defined(_M_ARM) || defined(_M_ARM64) // ARM. - __yield(); -#elif defined(_M_IX86) || defined(_M_X64) // x86. - _mm_pause(); -#endif -#elif defined(__GNUC__) || defined(__clang__) -// ----- GCC/Clang. -#if defined(__i386__) || defined(__x86_64__) // x86. - __builtin_ia32_pause(); -#elif defined(__arm__) || defined(__aarch64__) // ARM. - asm volatile("yield"); -#elif defined(__powerpc__) // PowerPC. - asm volatile("or 27,27,27"); -#elif defined(__riscv) // RISC-V. - asm volatile(".insn i 0x0F, 0, x0, x0, 0x010"); -#endif -#endif -} - #if defined(THREAD_ENABLED) static_assert(std::atomic_bool::is_always_lock_free, "std::atomic must be lock-free for this SpinLock."); diff --git a/Macros.h b/Macros.h index 664e557..782203b 100644 --- a/Macros.h +++ b/Macros.h @@ -57,3 +57,66 @@ #define HO_DISABLE_COMPILER_WARNING_FAILING_TO_HOLD_LOCK() #endif + +/* + * This function is adapted from Godot Engine's spin_lock.h. + * + * The original work is licensed under the MIT License: + * + * Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. + * Copyright (c) 2014-present Godot Engine contributors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Local modifications: function name normalized to CPU_PAUSE and minor + * formatting changes. No functional changes to the original logic. + */ + +#ifdef _MSC_VER +#include +#endif + +#if defined(_MSC_VER) +// ----- MSVC. +#if defined(_M_ARM) || defined(_M_ARM64) +#define CPU_PAUSE() __yield() +#elif defined(_M_IX86) || defined(_M_X64) +#define CPU_PAUSE() _mm_pause() +#else +#define CPU_PAUSE() ((void)0) // Fallback +#endif + +#elif defined(__GNUC__) || defined(__clang__) +// ----- GCC/Clang. +#if defined(__i386__) || defined(__x86_64__) +#define CPU_PAUSE() __builtin_ia32_pause() +#elif defined(__arm__) || defined(__aarch64__) +#define CPU_PAUSE() __asm__ __volatile__("yield") +#elif defined(__powerpc__) +#define CPU_PAUSE() __asm__ __volatile__("or 27,27,27") +#elif defined(__riscv) +#define CPU_PAUSE() __asm__ __volatile__(".insn i 0x0F, 0, x0, x0, 0x010") +#else +#define CPU_PAUSE() ((void)0) // Fallback +#endif + +#else +// ----- Unknown Compiler. +#define CPU_PAUSE() ((void)0) +#endif \ No newline at end of file From 82d73a942f2a673a5071e140e93d74fb0c5ba13e Mon Sep 17 00:00:00 2001 From: Hoin Lee Date: Wed, 3 Jun 2026 22:12:58 +0900 Subject: [PATCH 07/17] refactor: Modify platform layer to separate main and render threads. --- Platforms/IPlatformApplication.cc | 16 +- Platforms/IPlatformApplication.h | 13 +- Platforms/IPlatformWindow.h | 47 +++++ Platforms/Windows/GL/Win32ApplicationGL.cc | 223 +++++++++++++++------ Platforms/Windows/GL/Win32ApplicationGL.h | 14 +- Platforms/Windows/GL/Win32WindowGL.cc | 123 ------------ Platforms/Windows/GL/Win32WindowGL.h | 58 +++--- Platforms/Windows/IWin32Application.cc | 29 ++- Platforms/Windows/IWin32Application.h | 7 +- Platforms/Windows/IWin32Window.h | 71 +++++++ 10 files changed, 334 insertions(+), 267 deletions(-) create mode 100644 Platforms/IPlatformWindow.h delete mode 100644 Platforms/Windows/GL/Win32WindowGL.cc create mode 100644 Platforms/Windows/IWin32Window.h diff --git a/Platforms/IPlatformApplication.cc b/Platforms/IPlatformApplication.cc index 6523464..28e3b7a 100644 --- a/Platforms/IPlatformApplication.cc +++ b/Platforms/IPlatformApplication.cc @@ -6,18 +6,9 @@ namespace ho { -int32_t IPlatformApplication::sMainWindowWidth = 0; -int32_t IPlatformApplication::sMainWindowHeight = 0; -IPlatformApplication* IPlatformApplication::spInstance = nullptr; - -int32_t IPlatformApplication::GetMainWindowWidth() const +const IPlatformWindow* IPlatformApplication::GetMainWindow() const { - return sMainWindowWidth; -} - -int32_t IPlatformApplication::GetMainWindowHeight() const -{ - return sMainWindowHeight; + return spMainWindow.get(); } void IPlatformApplication::initImGuiFonts() @@ -60,4 +51,7 @@ void IPlatformApplication::initImGuiFonts() HO_ASSERT(false, "Failed to build font."); } } + +std::unique_ptr IPlatformApplication::spMainWindow = nullptr; +IPlatformApplication* IPlatformApplication::spInstance = nullptr; } // namespace ho \ No newline at end of file diff --git a/Platforms/IPlatformApplication.h b/Platforms/IPlatformApplication.h index b591dce..f4d3ebe 100644 --- a/Platforms/IPlatformApplication.h +++ b/Platforms/IPlatformApplication.h @@ -1,11 +1,14 @@ #pragma once +#include #include #include "Macros.h" +#include "IPlatformWindow.h" namespace ho { + class IPlatformApplication { public: @@ -33,22 +36,16 @@ class IPlatformApplication virtual bool ProcessPlatformMessages() = 0; - virtual void EndFrame() = 0; - virtual void Shutdown() = 0; - int32_t GetMainWindowWidth() const; - int32_t GetMainWindowHeight() const; - - virtual void SetWindowTitle(std::wstring& titleStr) = 0; + const IPlatformWindow* GetMainWindow() const; protected: IPlatformApplication() = default; void initImGuiFonts(); - static int32_t sMainWindowWidth; - static int32_t sMainWindowHeight; + static std::unique_ptr spMainWindow; static IPlatformApplication* spInstance; }; diff --git a/Platforms/IPlatformWindow.h b/Platforms/IPlatformWindow.h new file mode 100644 index 0000000..d288e0f --- /dev/null +++ b/Platforms/IPlatformWindow.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include + +namespace ho +{ +class IPlatformWindow +{ + public: + IPlatformWindow(int32_t clientWidth, int32_t clientHeight) + : mClientWidth(clientWidth) + , mClientHeight(clientHeight) + { + } + + virtual ~IPlatformWindow() = default; + + int32_t GetClientWidth() const + { + return mClientWidth; + } + + int32_t GetClientHeight() const + { + return mClientHeight; + } + + void Resize(int32_t clientWidth, int32_t clientHeight) + { + mClientWidth = clientWidth; + mClientHeight = clientHeight; + } + + virtual void ActivateContext() const = 0; + + virtual void DeactivateContext() const = 0; + + virtual void Present() const = 0; + + virtual void SetTitle(const std::wstring& titleStr) const = 0; + + protected: + int32_t mClientWidth = 0; + int32_t mClientHeight = 0; +}; +} // namespace ho \ No newline at end of file diff --git a/Platforms/Windows/GL/Win32ApplicationGL.cc b/Platforms/Windows/GL/Win32ApplicationGL.cc index 7661b18..14ac80a 100644 --- a/Platforms/Windows/GL/Win32ApplicationGL.cc +++ b/Platforms/Windows/GL/Win32ApplicationGL.cc @@ -4,6 +4,14 @@ #include #include +#include "Win32WindowGL.h" + +typedef HGLRC(WINAPI* PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC hDC, HGLRC hShareContext, const int* pAttribList); +#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 +#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 +#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126 +#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 + namespace ho { void Win32ApplicationGL::CreateInstance(HINSTANCE hApp) @@ -12,19 +20,100 @@ void Win32ApplicationGL::CreateInstance(HINSTANCE hApp) spInstance = new Win32ApplicationGL(hApp); } -bool Win32ApplicationGL::Init(const std::wstring& mainWindowNameStr, - int32_t mainwWindowWidth, - int32_t mainwWindowHeight) +bool Win32ApplicationGL::Init(const std::wstring& mainWindowNameStr, int32_t mainWindowWidth, int32_t mainWindowHeight) { - mMainWindowNameStr = mainWindowNameStr; - sMainWindowWidth = mainwWindowWidth; - sMainWindowHeight = mainwWindowHeight; - ImGui_ImplWin32_EnableDpiAwareness(); const float mainScale = ImGui_ImplWin32_GetDpiScaleForMonitor(::MonitorFromPoint(POINT{0, 0}, MONITOR_DEFAULTTOPRIMARY)); - // Register window + // Calc main window size + RECT stWindowRect = {0, 0, static_cast(mainWindowWidth), static_cast(mainWindowHeight)}; + const DWORD dwStyle = WS_OVERLAPPEDWINDOW; + + AdjustWindowRect(&stWindowRect, dwStyle, FALSE); + + const int32_t finalWidth = stWindowRect.right - stWindowRect.left; + const int32_t finalHeight = stWindowRect.bottom - stWindowRect.top; + + const int32_t scaledWidth = static_cast(static_cast(finalWidth) * mainScale); + const int32_t scaledHeight = static_cast(static_cast(finalHeight) * mainScale); + + // Create GL context + HWND hDummyWnd = CreateWindowW(L"STATIC", + L"Dummy", + dwStyle, + CW_USEDEFAULT, + CW_USEDEFAULT, + scaledWidth, + scaledHeight, + nullptr, + nullptr, + mhApp, + this); + + if (!hDummyWnd) + { + HO_ASSERT(false, "Failed to create main window."); + return false; + } + + HDC hDummyDC = ::GetDC(hDummyWnd); + PIXELFORMATDESCRIPTOR pfd{}; + pfd.nSize = sizeof(pfd); + pfd.nVersion = 1; + pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER | PFD_SWAP_EXCHANGE; + pfd.iPixelType = PFD_TYPE_RGBA; + pfd.cColorBits = 32; + + const int pixelFormat = ::ChoosePixelFormat(hDummyDC, &pfd); + HO_ASSERT(pixelFormat != 0, "Current system not supports such pixel format."); + const BOOL bSuccess = ::SetPixelFormat(hDummyDC, pixelFormat, &pfd); + HO_ASSERT(bSuccess != FALSE, "Failed to setting pixel format."); + (void)bSuccess; + + HGLRC hDummyGLRC = wglCreateContext(hDummyDC); + wglMakeCurrent(hDummyDC, hDummyGLRC); + + PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB = + (PFNWGLCREATECONTEXTATTRIBSARBPROC)wglGetProcAddress("wglCreateContextAttribsARB"); + + wglMakeCurrent(hDummyDC, nullptr); + wglDeleteContext(hDummyGLRC); + + HO_ASSERT(wglCreateContextAttribsARB != nullptr, "Failed to get address of context creation function."); + + int attribs[] = {WGL_CONTEXT_MAJOR_VERSION_ARB, + 4, + WGL_CONTEXT_MINOR_VERSION_ARB, + 6, + WGL_CONTEXT_PROFILE_MASK_ARB, + WGL_CONTEXT_CORE_PROFILE_BIT_ARB, + 0}; + + shGlContext = wglCreateContextAttribsARB(hDummyDC, nullptr, attribs); + + HO_ASSERT(shGlContext != nullptr, "Failed to create GL context."); + + wglMakeCurrent(hDummyDC, shGlContext); + + if (!gladLoadGL()) + { + HO_ASSERT(false, "Failed to load GL functions."); + return false; + } + + wglMakeCurrent(hDummyDC, nullptr); + ::ReleaseDC(hDummyWnd, hDummyDC); + + if (!::DestroyWindow(hDummyWnd)) + { + HO_ASSERT(false, "Failed to destroy dummy window."); + return false; + } + + // Create main window + mMainWndNameStr = mainWindowNameStr; + WNDCLASSEXW stWndClass{}; stWndClass.cbSize = sizeof(WNDCLASSEXW); stWndClass.style = CS_OWNDC; @@ -33,7 +122,7 @@ bool Win32ApplicationGL::Init(const std::wstring& mainWindowNameStr, stWndClass.hIcon = LoadIcon(nullptr, IDI_APPLICATION); stWndClass.hCursor = LoadCursor(nullptr, IDC_ARROW); stWndClass.hbrBackground = static_cast(GetStockObject(WHITE_BRUSH)); - stWndClass.lpszClassName = mMainWindowNameStr.c_str(); + stWndClass.lpszClassName = mMainWndNameStr.c_str(); stWndClass.hIconSm = static_cast(LoadImage(mhApp, MAKEINTRESOURCE(5), IMAGE_ICON, @@ -47,21 +136,8 @@ bool Win32ApplicationGL::Init(const std::wstring& mainWindowNameStr, return false; } - // Calc window size - RECT stWindowRect = {0, 0, static_cast(mainwWindowWidth), static_cast(mainwWindowHeight)}; - const DWORD dwStyle = WS_OVERLAPPEDWINDOW; - - AdjustWindowRect(&stWindowRect, dwStyle, FALSE); - - const int32_t finalWidth = stWindowRect.right - stWindowRect.left; - const int32_t finalHeight = stWindowRect.bottom - stWindowRect.top; - - const int32_t scaledWidth = static_cast(static_cast(finalWidth) * mainScale); - const int32_t scaledHeight = static_cast(static_cast(finalHeight) * mainScale); - - // Create window mhMainWnd = CreateWindowW(stWndClass.lpszClassName, - mMainWindowNameStr.c_str(), + mMainWndNameStr.c_str(), dwStyle, CW_USEDEFAULT, CW_USEDEFAULT, @@ -72,21 +148,8 @@ bool Win32ApplicationGL::Init(const std::wstring& mainWindowNameStr, mhApp, this); - if (!mhMainWnd) - { - HO_ASSERT(false, "Failed to create main window."); - return false; - } - - pMainWindow = std::make_unique(mhMainWnd, sMainWindowWidth, sMainWindowHeight); - - // Create GL context - pMainWindow->MakeCurrent(); - if (!gladLoadGL()) - { - HO_ASSERT(false, "Failed to load GL functions."); - return false; - } + spMainWindow = std::make_unique(finalWidth, finalHeight, mhMainWnd, shGlContext); + spMainWindow->ActivateContext(); // Init imgui // Setup Dear ImGui context @@ -139,10 +202,10 @@ bool Win32ApplicationGL::Init(const std::wstring& mainWindowNameStr, HO_ASSERT(platform_io.Renderer_DestroyWindow == nullptr, ""); HO_ASSERT(platform_io.Renderer_SwapBuffers == nullptr, ""); HO_ASSERT(platform_io.Platform_RenderWindow == nullptr, ""); - platform_io.Renderer_CreateWindow = Win32WindowGL::Hook_CreateWindow; - platform_io.Renderer_DestroyWindow = Win32WindowGL::Hook_DestroyWindow; - platform_io.Renderer_SwapBuffers = Win32WindowGL::Hook_SwapBuffers; - platform_io.Platform_RenderWindow = Win32WindowGL::Hook_RenderWindow; + platform_io.Renderer_CreateWindow = Hook_CreateWindow; + platform_io.Renderer_DestroyWindow = Hook_DestroyWindow; + platform_io.Renderer_SwapBuffers = Hook_SwapBuffers; + platform_io.Platform_RenderWindow = Hook_RenderWindow; } // Load and Merge fonts @@ -152,6 +215,11 @@ bool Win32ApplicationGL::Init(const std::wstring& mainWindowNameStr, ShowWindow(mhMainWnd, SW_SHOWDEFAULT); UpdateWindow(mhMainWnd); + // It's to prevent imgui initialize GL in 'ImGui_ImplOpenGL3_NewFrame()'. + ImGui_ImplOpenGL3_CreateDeviceObjects(); + + spMainWindow->DeactivateContext(); + return true; } @@ -162,47 +230,76 @@ void Win32ApplicationGL::BeginFrame() ImGui::NewFrame(); } -void Win32ApplicationGL::EndFrame() +void Win32ApplicationGL::Shutdown() { - const ImGuiIO& io = ImGui::GetIO(); - // Update and Render additional Platform Windows - if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) + // 'ImGui_ImplOpenGL3_Shutdown()' calls GL APIs internally. So GL context must be brought back from render thread to + // main thread. + IPlatformApplication::GetInstance().GetMainWindow()->ActivateContext(); + + ImGui_ImplOpenGL3_Shutdown(); + ImGui_ImplWin32_Shutdown(); + ImGui::DestroyContext(); + + spMainWindow.reset(); + if (!::DestroyWindow(mhMainWnd)) + { + HO_ASSERT(false, "Failed to destroy main window."); + } + if (!::UnregisterClassW(mMainWndNameStr.c_str(), mhApp)) { - ImGui::UpdatePlatformWindows(); - ImGui::RenderPlatformWindowsDefault(); + HO_ASSERT(false, "Failed to unregister main window class."); + } - // Restore the OpenGL rendering context to the main window DC, since platform windows might have changed it. - pMainWindow->MakeCurrent(); + if (!wglDeleteContext(shGlContext)) + { + HO_ASSERT(false, "Failed to delete GL context."); } +} - // Present - pMainWindow->Present(); +void Win32ApplicationGL::Hook_CreateWindow(ImGuiViewport* viewport) +{ + HO_ASSERT(viewport->RendererUserData == nullptr, ""); + + Win32WindowGL* pWindow = new Win32WindowGL(static_cast(viewport->Size.x), + static_cast(viewport->Size.y), + static_cast(viewport->PlatformHandle), + shGlContext); + viewport->RendererUserData = static_cast(pWindow); } -void Win32ApplicationGL::Shutdown() +void Win32ApplicationGL::Hook_DestroyWindow(ImGuiViewport* viewport) { - ImGui_ImplOpenGL3_Shutdown(); - ImGui_ImplWin32_Shutdown(); - ImGui::DestroyContext(); - pMainWindow.reset(); - if (!Win32WindowGL::DeleteGLContext()) + if (viewport->RendererUserData != nullptr) { - HO_ASSERT(false, "Failed to delete GL context."); + const Win32WindowGL* window = static_cast(viewport->RendererUserData); + delete window; + viewport->RendererUserData = nullptr; } +} - if (!::DestroyWindow(mhMainWnd)) +void Win32ApplicationGL::Hook_RenderWindow(ImGuiViewport* viewport, void* unused) +{ + // Activate the platform window DC in the OpenGL rendering context + if (const Win32WindowGL* pWindow = static_cast(viewport->RendererUserData)) { - HO_ASSERT(false, "Failed to destroy main window."); + pWindow->ActivateContext(); } + (void)unused; +} - if (!::UnregisterClassW(mMainWindowNameStr.c_str(), mhApp)) +void Win32ApplicationGL::Hook_SwapBuffers(ImGuiViewport* viewport, void* unused) +{ + if (const Win32WindowGL* pWindow = static_cast(viewport->RendererUserData)) { - HO_ASSERT(false, "Failed to unregister main window class."); + pWindow->Present(); } + (void)unused; } Win32ApplicationGL::Win32ApplicationGL(HINSTANCE hApp) : IWin32Application(hApp) { } + +HGLRC Win32ApplicationGL::shGlContext = nullptr; } // namespace ho \ No newline at end of file diff --git a/Platforms/Windows/GL/Win32ApplicationGL.h b/Platforms/Windows/GL/Win32ApplicationGL.h index ad2afb2..d384111 100644 --- a/Platforms/Windows/GL/Win32ApplicationGL.h +++ b/Platforms/Windows/GL/Win32ApplicationGL.h @@ -3,7 +3,8 @@ #include #include "../IWin32Application.h" -#include "Win32WindowGL.h" + +class ImGuiViewport; namespace ho { @@ -16,17 +17,20 @@ class Win32ApplicationGL final : public IWin32Application Win32ApplicationGL(const Win32ApplicationGL&) = delete; Win32ApplicationGL& operator=(const Win32ApplicationGL&) = delete; - bool Init(const std::wstring& mainWindowNameStr, int32_t mainwWindowWidth, int32_t mainwWindowHeight) override; + bool Init(const std::wstring& mainWindowNameStr, int32_t mainWindowWidth, int32_t mainWindowHeight) override; void BeginFrame() override; - void EndFrame() override; - void Shutdown() override; + static void Hook_CreateWindow(ImGuiViewport* pViewport); + static void Hook_DestroyWindow(ImGuiViewport* pViewport); + static void Hook_RenderWindow(ImGuiViewport* pViewport, void* pUnused); + static void Hook_SwapBuffers(ImGuiViewport* pViewport, void* pUnused); + private: Win32ApplicationGL(HINSTANCE hApp); - std::unique_ptr pMainWindow; + static HGLRC shGlContext; }; } // namespace ho \ No newline at end of file diff --git a/Platforms/Windows/GL/Win32WindowGL.cc b/Platforms/Windows/GL/Win32WindowGL.cc deleted file mode 100644 index 6dd6eea..0000000 --- a/Platforms/Windows/GL/Win32WindowGL.cc +++ /dev/null @@ -1,123 +0,0 @@ -#include "Win32WindowGL.h" - -#include -#include - -#include "Macros.h" - -namespace ho -{ -HGLRC Win32WindowGL::shGLRC = nullptr; - -Win32WindowGL::Win32WindowGL(HWND hWnd, int32_t clientWidth, int32_t clientHeight) - : mhWnd(hWnd) - , mClientWidth(clientWidth) - , mClientHeight(clientHeight) -{ - HO_ASSERT(mhWnd != nullptr, "Invalid Window Handle."); - - mhDC = ::GetDC(mhWnd); - HO_ASSERT(mhWnd, "Failed to Get DC"); - - PIXELFORMATDESCRIPTOR pfd{}; - pfd.nSize = sizeof(pfd); - pfd.nVersion = 1; - pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER | PFD_SWAP_EXCHANGE; - pfd.iPixelType = PFD_TYPE_RGBA; - pfd.cColorBits = 32; - - const int pixelFormat = ::ChoosePixelFormat(mhDC, &pfd); - HO_ASSERT(pixelFormat != 0, "Current system not supports such pixel format."); - const BOOL bSuccess = ::SetPixelFormat(mhDC, pixelFormat, &pfd); - HO_ASSERT(bSuccess != FALSE, "Failed to setting pixel format."); - (void)bSuccess; - - if (!shGLRC) - { - shGLRC = wglCreateContext(mhDC); - HO_ASSERT(shGLRC != nullptr, ""); - } -} - -Win32WindowGL::~Win32WindowGL() -{ - wglMakeCurrent(nullptr, nullptr); - ::ReleaseDC(mhWnd, mhDC); -} - -bool Win32WindowGL::DeleteGLContext() -{ - return wglDeleteContext(shGLRC); -} - -int32_t Win32WindowGL::GetClientWidth() const -{ - return mClientWidth; -} - -int32_t Win32WindowGL::GetClientHeight() const -{ - return mClientHeight; -} - -void Win32WindowGL::Resize(int32_t clientWidth, int32_t clientHeight) -{ - mClientWidth = clientWidth; - mClientHeight = clientHeight; -} - -void Win32WindowGL::Present() const -{ - if (!::SwapBuffers(mhDC)) - { - HO_ASSERT(false, "Failed to swap buffers."); - } -} - -void Win32WindowGL::MakeCurrent() const -{ - if (!wglMakeCurrent(mhDC, shGLRC)) - { - HO_ASSERT(false, "Failed to bind GL context."); - } -} - -void Win32WindowGL::Hook_CreateWindow(ImGuiViewport* viewport) -{ - HO_ASSERT(viewport->RendererUserData == nullptr, ""); - - Win32WindowGL* window = new Win32WindowGL(static_cast(viewport->PlatformHandle), - static_cast(viewport->Size.x), - static_cast(viewport->Size.y)); - viewport->RendererUserData = static_cast(window); -} - -void Win32WindowGL::Hook_DestroyWindow(ImGuiViewport* viewport) -{ - if (viewport->RendererUserData != nullptr) - { - const Win32WindowGL* window = static_cast(viewport->RendererUserData); - delete window; - viewport->RendererUserData = nullptr; - } -} - -void Win32WindowGL::Hook_RenderWindow(ImGuiViewport* viewport, void* unused) -{ - // Activate the platform window DC in the OpenGL rendering context - if (const Win32WindowGL* window = static_cast(viewport->RendererUserData)) - { - window->MakeCurrent(); - } - (void)unused; -} - -void Win32WindowGL::Hook_SwapBuffers(ImGuiViewport* viewport, void* unused) -{ - if (const Win32WindowGL* window = static_cast(viewport->RendererUserData)) - { - window->Present(); - } - (void)unused; -} -} // namespace ho \ No newline at end of file diff --git a/Platforms/Windows/GL/Win32WindowGL.h b/Platforms/Windows/GL/Win32WindowGL.h index fff7a1b..c96983d 100644 --- a/Platforms/Windows/GL/Win32WindowGL.h +++ b/Platforms/Windows/GL/Win32WindowGL.h @@ -1,46 +1,34 @@ #pragma once -#ifndef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN -#endif - -#define UNICODE -#define _UNICODE - -#define NOMINMAX - -#include -#include - -class ImGuiViewport; +#include "../IWin32Window.h" namespace ho { -class Win32WindowGL final +class Win32WindowGL final : public IWin32Window { public: - Win32WindowGL(HWND hWnd, int32_t clientWidth, int32_t clientHeight); - ~Win32WindowGL(); - - static bool DeleteGLContext(); - - int32_t GetClientWidth() const; - int32_t GetClientHeight() const; - void Resize(int32_t clientWidth, int32_t clientHeight); - void Present() const; - void MakeCurrent() const; - - static void Hook_CreateWindow(ImGuiViewport* viewport); - static void Hook_DestroyWindow(ImGuiViewport* viewport); - static void Hook_RenderWindow(ImGuiViewport* viewport, void* unused); - static void Hook_SwapBuffers(ImGuiViewport* viewport, void* unused); + Win32WindowGL(int32_t clientWidth, int32_t clientHeight, HWND hWnd, HGLRC hGlContext) + : IWin32Window(clientWidth, clientHeight, hWnd) + , mhGlContext(hGlContext) + { + } + + ~Win32WindowGL() override + { + ::wglMakeCurrent(nullptr, nullptr); + } + + void ActivateContext() const + { + ::wglMakeCurrent(mhDC, mhGlContext); + } + + void DeactivateContext() const + { + ::wglMakeCurrent(mhDC, nullptr); + } private: - HWND mhWnd; - HDC mhDC; - int32_t mClientWidth; - int32_t mClientHeight; - - static HGLRC shGLRC; + HGLRC mhGlContext; }; } // namespace ho \ No newline at end of file diff --git a/Platforms/Windows/IWin32Application.cc b/Platforms/Windows/IWin32Application.cc index 768483c..209bc1f 100644 --- a/Platforms/Windows/IWin32Application.cc +++ b/Platforms/Windows/IWin32Application.cc @@ -2,18 +2,12 @@ #include +#include "IWin32Window.h" + extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); namespace ho { - -IWin32Application::IWin32Application(HINSTANCE hApp) - : IPlatformApplication() - , mhApp(hApp) - , mhMainWnd(nullptr) -{ -} - bool IWin32Application::ProcessPlatformMessages() { static MSG msg; @@ -29,12 +23,10 @@ bool IWin32Application::ProcessPlatformMessages() return true; } -void IWin32Application::SetWindowTitle(std::wstring& titleStr) +IWin32Application::IWin32Application(HINSTANCE hApp) + : IPlatformApplication() + , mhApp(hApp) { - if (!SetWindowTextW(mhMainWnd, titleStr.c_str())) - { - HO_ASSERT(false, "Failed to set title text."); - } } LRESULT CALLBACK IWin32Application::wndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) @@ -49,8 +41,10 @@ LRESULT CALLBACK IWin32Application::wndProc(HWND hWnd, UINT iMessage, WPARAM wPa case WM_SIZE: if (wParam != SIZE_MINIMIZED) { - sMainWindowWidth = LOWORD(lParam); - sMainWindowHeight = HIWORD(lParam); + if (spMainWindow) + { + spMainWindow->Resize(LOWORD(lParam), HIWORD(lParam)); + } } return 0; case WM_SYSCOMMAND: @@ -60,8 +54,11 @@ LRESULT CALLBACK IWin32Application::wndProc(HWND hWnd, UINT iMessage, WPARAM wPa } break; + case WM_CLOSE: + PostQuitMessage(0); + return 0; + case WM_DESTROY: - PostQuitMessage(0); return 0; default: break; diff --git a/Platforms/Windows/IWin32Application.h b/Platforms/Windows/IWin32Application.h index 9c49190..80f7ed0 100644 --- a/Platforms/Windows/IWin32Application.h +++ b/Platforms/Windows/IWin32Application.h @@ -28,20 +28,15 @@ class IWin32Application : public IPlatformApplication bool ProcessPlatformMessages() override; - void EndFrame() override = 0; - void Shutdown() override = 0; - void SetWindowTitle(std::wstring& titleStr) override; - protected: IWin32Application(HINSTANCE hApp); static LRESULT CALLBACK wndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam); HINSTANCE mhApp; - - std::wstring mMainWindowNameStr; + std::wstring mMainWndNameStr; HWND mhMainWnd; }; } // namespace ho \ No newline at end of file diff --git a/Platforms/Windows/IWin32Window.h b/Platforms/Windows/IWin32Window.h new file mode 100644 index 0000000..f067a86 --- /dev/null +++ b/Platforms/Windows/IWin32Window.h @@ -0,0 +1,71 @@ +#pragma once + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +#define UNICODE +#define _UNICODE + +#define NOMINMAX + +#include + +#include "../IPlatformWindow.h" +#include "Macros.h" + +namespace ho +{ +class IWin32Window : public IPlatformWindow +{ + public: + IWin32Window(int32_t clientWidth, int32_t clientHeight, HWND hWnd) + : IPlatformWindow(clientWidth, clientHeight) + , mhWnd(hWnd) + { + HO_ASSERT(mhWnd != nullptr, "Invalid Window Handle."); + + mhDC = ::GetDC(mhWnd); + HO_ASSERT(mhWnd, "Failed to Get DC"); + + PIXELFORMATDESCRIPTOR pfd{}; + pfd.nSize = sizeof(pfd); + pfd.nVersion = 1; + pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER | PFD_SWAP_EXCHANGE; + pfd.iPixelType = PFD_TYPE_RGBA; + pfd.cColorBits = 32; + + const int pixelFormat = ::ChoosePixelFormat(mhDC, &pfd); + HO_ASSERT(pixelFormat != 0, "Current system not supports such pixel format."); + const BOOL bSuccess = ::SetPixelFormat(mhDC, pixelFormat, &pfd); + HO_ASSERT(bSuccess != FALSE, "Failed to setting pixel format."); + (void)bSuccess; + } + + ~IWin32Window() override + { + ::ReleaseDC(mhWnd, mhDC); + } + + virtual void ActivateContext() const = 0; + + virtual void DeactivateContext() const = 0; + + void Present() const + { + ::SwapBuffers(mhDC); + } + + void SetTitle(const std::wstring& titleStr) const + { + if (!SetWindowTextW(mhWnd, titleStr.c_str())) + { + HO_ASSERT(false, "Failed to set title text."); + } + } + + protected: + HWND mhWnd = nullptr; + HDC mhDC = nullptr; +}; +} // namespace ho \ No newline at end of file From fdaa9784d00e3eb5f2a355733fac6b522d6cd339 Mon Sep 17 00:00:00 2001 From: Hoin Lee Date: Wed, 3 Jun 2026 22:13:53 +0900 Subject: [PATCH 08/17] feat: Add GraphicsAPI enum. --- Config.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Config.h b/Config.h index 6346a42..04fdffa 100644 --- a/Config.h +++ b/Config.h @@ -6,4 +6,7 @@ #define THREAD_ENABLED #endif -#define GRAPHICS_API_GL \ No newline at end of file +enum eGraphicsAPI +{ + GL = 0, +}; \ No newline at end of file From 4031bbb67764186ade047aa84553343713f1ef36 Mon Sep 17 00:00:00 2001 From: Hoin Lee Date: Thu, 4 Jun 2026 02:08:15 +0900 Subject: [PATCH 09/17] refactor: Modify default constructor. --- Core/Templates/FixedArray.h | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/Core/Templates/FixedArray.h b/Core/Templates/FixedArray.h index 9cab24e..f738394 100644 --- a/Core/Templates/FixedArray.h +++ b/Core/Templates/FixedArray.h @@ -10,11 +10,7 @@ template class FixedArray { public: - FixedArray() - : mpArray(nullptr) - , mSize(0) - { - } + FixedArray() = default; FixedArray(int32_t size) : mSize(size) @@ -122,7 +118,7 @@ class FixedArray } private: - std::unique_ptr mpArray; - int32_t mSize; + std::unique_ptr mpArray = nullptr; + int32_t mSize = 0; }; } // namespace ho \ No newline at end of file From cce39755bf2e049ec5757c7da9d11492f4065eda Mon Sep 17 00:00:00 2001 From: Hoin Lee Date: Thu, 4 Jun 2026 02:08:41 +0900 Subject: [PATCH 10/17] feat: Add FixedQueue class. --- Core/Templates/FixedQueue.h | 143 ++++++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 Core/Templates/FixedQueue.h diff --git a/Core/Templates/FixedQueue.h b/Core/Templates/FixedQueue.h new file mode 100644 index 0000000..6ea9858 --- /dev/null +++ b/Core/Templates/FixedQueue.h @@ -0,0 +1,143 @@ +#pragma once + +#include + +#include "Macros.h" + +namespace ho +{ +template +class FixedQueue +{ + public: + FixedQueue() = default; + + FixedQueue(int32_t size) + : mSize(size) + , mCapacity(size + 1) + { + HO_ASSERT(size >= 0, "Size cannot be negative."); + if (mSize > 0) + { + mpQueue = std::make_unique(mCapacity); + } + } + + FixedQueue(const FixedQueue&) = delete; + FixedQueue& operator=(const FixedQueue&) = delete; + + FixedQueue(FixedQueue&& other) noexcept + : mpQueue(std::move(other.mpQueue)) + , mHeadIdx(other.mHeadIdx) + , mTailIdx(other.mTailIdx) + , mSize(other.mSize) + , mCapacity(other.mCapacity) + { + } + + FixedQueue& operator=(FixedQueue&& rhs) noexcept + { + if (this == &rhs) + { + return *this; + } + + mpQueue = std::move(rhs.mpQueue); + mHeadIdx = rhs.mHeadIdx; + mTailIdx = rhs.mTailIdx; + mSize = rhs.mSize; + mCapacity = rhs.mCapacity; + + return *this; + } + + template + void Push(U&& v) + { + if ((mTailIdx + 1) % mCapacity == mHeadIdx) + { + HO_ASSERT(false, "Queue is full."); + return; + } + + mpQueue[mTailIdx] = std::forward(v); + mTailIdx = (mTailIdx + 1) % mCapacity; + } + + template + void Emplace(Args&&... args) + { + if ((mTailIdx + 1) % mCapacity == mHeadIdx) + { + HO_ASSERT(false, "Queue is full."); + return; + } + + mpQueue[mTailIdx] = T(std::forward(args)...); + mTailIdx = (mTailIdx + 1) % mCapacity; + } + + void Pop() + { + if (IsEmpty()) + { + return; + } + + mHeadIdx = (mHeadIdx + 1) % mCapacity; + } + + T& Front() + { + HO_ASSERT(mHeadIdx != mTailIdx, "Queue is empty."); + return mpQueue[mHeadIdx]; + } + + const T& Front() const + { + HO_ASSERT(mHeadIdx != mTailIdx, "Queue is empty."); + return mpQueue[mHeadIdx]; + } + + int32_t GetCount() const + { + return (mTailIdx + mCapacity - mHeadIdx) % mCapacity; + } + + int32_t GetSize() const + { + return mSize; + } + + bool IsEmpty() const + { + return mHeadIdx == mTailIdx; + } + + bool IsFull() const + { + return (mTailIdx + 1) % mCapacity == mHeadIdx; + } + + void Release() + { + mpQueue.reset(); + mHeadIdx = 0; + mTailIdx = 0; + mSize = 0; + mCapacity = 0; + } + + bool IsReleased() + { + return mpQueue == nullptr; + } + + private: + std::unique_ptr mpQueue = nullptr; + int32_t mHeadIdx = 0; + int32_t mTailIdx = 0; + int32_t mSize = 0; + int32_t mCapacity = 0; +}; +} // namespace ho \ No newline at end of file From 7ec0b21cecbfbc61b690f8ffd7e74f318eae95cb Mon Sep 17 00:00:00 2001 From: Hoin Lee Date: Thu, 4 Jun 2026 09:37:45 +0900 Subject: [PATCH 11/17] chore: Resolve format mismatch. --- Platforms/IPlatformApplication.h | 2 +- Platforms/Windows/IWin32Application.cc | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Platforms/IPlatformApplication.h b/Platforms/IPlatformApplication.h index f4d3ebe..d9576fd 100644 --- a/Platforms/IPlatformApplication.h +++ b/Platforms/IPlatformApplication.h @@ -3,8 +3,8 @@ #include #include -#include "Macros.h" #include "IPlatformWindow.h" +#include "Macros.h" namespace ho { diff --git a/Platforms/Windows/IWin32Application.cc b/Platforms/Windows/IWin32Application.cc index 209bc1f..d4eacb6 100644 --- a/Platforms/Windows/IWin32Application.cc +++ b/Platforms/Windows/IWin32Application.cc @@ -55,8 +55,8 @@ LRESULT CALLBACK IWin32Application::wndProc(HWND hWnd, UINT iMessage, WPARAM wPa break; case WM_CLOSE: - PostQuitMessage(0); - return 0; + PostQuitMessage(0); + return 0; case WM_DESTROY: return 0; From ae13c8facea2b0e070a97a9fd474dd4bb710be75 Mon Sep 17 00:00:00 2001 From: Hoin Lee Date: Thu, 4 Jun 2026 10:18:14 +0900 Subject: [PATCH 12/17] chore: Resolve clang-tidy warnings. --- Core/IO/ResourceParseFuncs.cc | 1 - Platforms/Windows/GL/Win32WindowGL.h | 4 ++-- Platforms/Windows/IWin32Application.cc | 1 + Platforms/Windows/IWin32Window.h | 8 ++++---- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Core/IO/ResourceParseFuncs.cc b/Core/IO/ResourceParseFuncs.cc index 0c0f3eb..1f1859f 100644 --- a/Core/IO/ResourceParseFuncs.cc +++ b/Core/IO/ResourceParseFuncs.cc @@ -507,7 +507,6 @@ std::unique_ptr parseMaterial(const Path& path, aiColor4D color4D; float fVal = 0.0f; int iVal = 0; - aiString aiStr; // Legacy / Phong Attributes if (AI_SUCCESS == assimpMaterial.Get(AI_MATKEY_COLOR_AMBIENT, color3D)) diff --git a/Platforms/Windows/GL/Win32WindowGL.h b/Platforms/Windows/GL/Win32WindowGL.h index c96983d..900967a 100644 --- a/Platforms/Windows/GL/Win32WindowGL.h +++ b/Platforms/Windows/GL/Win32WindowGL.h @@ -18,12 +18,12 @@ class Win32WindowGL final : public IWin32Window ::wglMakeCurrent(nullptr, nullptr); } - void ActivateContext() const + void ActivateContext() const override { ::wglMakeCurrent(mhDC, mhGlContext); } - void DeactivateContext() const + void DeactivateContext() const override { ::wglMakeCurrent(mhDC, nullptr); } diff --git a/Platforms/Windows/IWin32Application.cc b/Platforms/Windows/IWin32Application.cc index d4eacb6..8803aff 100644 --- a/Platforms/Windows/IWin32Application.cc +++ b/Platforms/Windows/IWin32Application.cc @@ -26,6 +26,7 @@ bool IWin32Application::ProcessPlatformMessages() IWin32Application::IWin32Application(HINSTANCE hApp) : IPlatformApplication() , mhApp(hApp) + , mhMainWnd(nullptr) { } diff --git a/Platforms/Windows/IWin32Window.h b/Platforms/Windows/IWin32Window.h index f067a86..daf19c3 100644 --- a/Platforms/Windows/IWin32Window.h +++ b/Platforms/Windows/IWin32Window.h @@ -47,16 +47,16 @@ class IWin32Window : public IPlatformWindow ::ReleaseDC(mhWnd, mhDC); } - virtual void ActivateContext() const = 0; + void ActivateContext() const override = 0; - virtual void DeactivateContext() const = 0; + void DeactivateContext() const override = 0; - void Present() const + void Present() const override { ::SwapBuffers(mhDC); } - void SetTitle(const std::wstring& titleStr) const + void SetTitle(const std::wstring& titleStr) const override { if (!SetWindowTextW(mhWnd, titleStr.c_str())) { From f5a806aef39802b1e5ccfe72b686cc62a28fcbb1 Mon Sep 17 00:00:00 2001 From: Hoin Lee Date: Thu, 4 Jun 2026 11:26:35 +0900 Subject: [PATCH 13/17] test: Update tests to match the modified MaterialIR default values. --- Test/Core/IO/ResourceParsingFuncsTest.cc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Test/Core/IO/ResourceParsingFuncsTest.cc b/Test/Core/IO/ResourceParsingFuncsTest.cc index 7059060..c8052d2 100644 --- a/Test/Core/IO/ResourceParsingFuncsTest.cc +++ b/Test/Core/IO/ResourceParsingFuncsTest.cc @@ -65,10 +65,10 @@ TEST(ResourceParseFuncsTest, parseModelFile_OBJ_VerifiesFullSceneHierarchyAndMat // Check MaterialIR const MaterialIR* pTexMaterialIR = pModelIR->pMaterialIRs[0].get(); - EXPECT_EQ(pTexMaterialIR->Ambient, Color128(0.0f, 0.0f, 0.0f)); + EXPECT_EQ(pTexMaterialIR->Ambient, Color128(0.001f, 0.001f, 0.001f)); EXPECT_TRUE(pTexMaterialIR->Diffuse.IsEqualApprox(Color128(0.6f, 0.6f, 0.6f))); EXPECT_EQ(pTexMaterialIR->Specular, Color128(0.0f, 0.0f, 0.0f)); - EXPECT_EQ(pTexMaterialIR->Shininess, 0.0f); + EXPECT_EQ(pTexMaterialIR->Shininess, 32.0f); EXPECT_EQ(pTexMaterialIR->Albedo, Color128(1.0f, 1.0f, 1.0f)); EXPECT_EQ(pTexMaterialIR->Metallic, 0.0f); EXPECT_EQ(pTexMaterialIR->Roughness, 0.5f); @@ -362,10 +362,10 @@ TEST(ResourceParseFuncsTest, parseModelFile_glTFBox_ValidatesStandardSpecificati } } ASSERT_NE(pMaterialIR, nullptr); - EXPECT_EQ(pMaterialIR->Ambient, Color128(0.0f, 0.0f, 0.0f)); + EXPECT_EQ(pMaterialIR->Ambient, Color128(0.001f, 0.001f, 0.001f)); EXPECT_TRUE(pMaterialIR->Diffuse.IsEqualApprox(Color128(0.800000011920929f, 0.0f, 0.0f))); EXPECT_EQ(pMaterialIR->Specular, Color128(1.0f, 1.0f, 1.0f)); - EXPECT_EQ(pMaterialIR->Shininess, 0.0f); + EXPECT_EQ(pMaterialIR->Shininess, 32.0f); EXPECT_TRUE(pMaterialIR->Albedo.IsEqualApprox(Color128(0.800000011920929f, 0.0f, 0.0f))); EXPECT_EQ(pMaterialIR->Metallic, 0.0f); EXPECT_EQ(pMaterialIR->Roughness, 1.0f); @@ -463,10 +463,10 @@ TEST(ResourceParseFuncsTest, parseModelFile_glTFBoxInterleaved_ValidatesStandard } } ASSERT_NE(pMaterialIR, nullptr); - EXPECT_EQ(pMaterialIR->Ambient, Color128(0.0f, 0.0f, 0.0f)); + EXPECT_EQ(pMaterialIR->Ambient, Color128(0.001f, 0.001f, 0.001f)); EXPECT_TRUE(pMaterialIR->Diffuse.IsEqualApprox(Color128(0.800000011920929f, 0.0f, 0.0f))); EXPECT_EQ(pMaterialIR->Specular, Color128(1.0f, 1.0f, 1.0f)); - EXPECT_EQ(pMaterialIR->Shininess, 0.0f); + EXPECT_EQ(pMaterialIR->Shininess, 32.0f); EXPECT_TRUE(pMaterialIR->Albedo.IsEqualApprox(Color128(0.800000011920929f, 0.0f, 0.0f))); EXPECT_EQ(pMaterialIR->Metallic, 1.0f); EXPECT_EQ(pMaterialIR->Roughness, 1.0f); From 363764edbc4aad6680171d07b54e5617117875ac Mon Sep 17 00:00:00 2001 From: Hoin Lee Date: Thu, 4 Jun 2026 11:28:39 +0900 Subject: [PATCH 14/17] chore: Resolve clang compiler warning. --- Platforms/Windows/GL/Win32ApplicationGL.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Platforms/Windows/GL/Win32ApplicationGL.cc b/Platforms/Windows/GL/Win32ApplicationGL.cc index 14ac80a..cae6673 100644 --- a/Platforms/Windows/GL/Win32ApplicationGL.cc +++ b/Platforms/Windows/GL/Win32ApplicationGL.cc @@ -75,7 +75,7 @@ bool Win32ApplicationGL::Init(const std::wstring& mainWindowNameStr, int32_t mai wglMakeCurrent(hDummyDC, hDummyGLRC); PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB = - (PFNWGLCREATECONTEXTATTRIBSARBPROC)wglGetProcAddress("wglCreateContextAttribsARB"); + reinterpret_cast(wglGetProcAddress("wglCreateContextAttribsARB")); wglMakeCurrent(hDummyDC, nullptr); wglDeleteContext(hDummyGLRC); From 69a846c05b5eb985630e034cd5f0e67f581e8729 Mon Sep 17 00:00:00 2001 From: Hoin Lee Date: Thu, 4 Jun 2026 11:31:05 +0900 Subject: [PATCH 15/17] feat: Add rendering system. --- Subsystems/Rendering/.clang-tidy | 6 + .../Rendering/BuiltInAssets/2x2Black.png | Bin 0 -> 133 bytes Subsystems/Rendering/GL/NativeHandlesGL.h | 29 + Subsystems/Rendering/GL/RenderingSystemGL.cc | 1977 +++++++++++++++++ Subsystems/Rendering/GL/RenderingSystemGL.h | 115 + Subsystems/Rendering/GL/Shaders/Default.vert | 40 + .../Rendering/GL/Shaders/DefaultPBR.frag | 330 +++ .../GL/Shaders/DefaultPBRMasked.frag | 338 +++ .../Rendering/GL/Shaders/DefaultPhong.frag | 254 +++ .../GL/Shaders/DefaultPhongMasked.frag | 266 +++ .../Rendering/GL/Shaders/DefaultSkyMap.frag | 16 + .../Rendering/GL/Shaders/DefaultSkyMap.vert | 48 + .../Rendering/GL/Shaders/WorldGizmo.frag | 47 + .../Rendering/GL/Shaders/WorldGizmo.vert | 44 + Subsystems/Rendering/GpuResources.h | 130 ++ Subsystems/Rendering/IRenderingSystem.cc | 1030 +++++++++ Subsystems/Rendering/IRenderingSystem.h | 464 ++++ Subsystems/Rendering/RenderSync.h | 58 + 18 files changed, 5192 insertions(+) create mode 100644 Subsystems/Rendering/.clang-tidy create mode 100644 Subsystems/Rendering/BuiltInAssets/2x2Black.png create mode 100644 Subsystems/Rendering/GL/NativeHandlesGL.h create mode 100644 Subsystems/Rendering/GL/RenderingSystemGL.cc create mode 100644 Subsystems/Rendering/GL/RenderingSystemGL.h create mode 100644 Subsystems/Rendering/GL/Shaders/Default.vert create mode 100644 Subsystems/Rendering/GL/Shaders/DefaultPBR.frag create mode 100644 Subsystems/Rendering/GL/Shaders/DefaultPBRMasked.frag create mode 100644 Subsystems/Rendering/GL/Shaders/DefaultPhong.frag create mode 100644 Subsystems/Rendering/GL/Shaders/DefaultPhongMasked.frag create mode 100644 Subsystems/Rendering/GL/Shaders/DefaultSkyMap.frag create mode 100644 Subsystems/Rendering/GL/Shaders/DefaultSkyMap.vert create mode 100644 Subsystems/Rendering/GL/Shaders/WorldGizmo.frag create mode 100644 Subsystems/Rendering/GL/Shaders/WorldGizmo.vert create mode 100644 Subsystems/Rendering/GpuResources.h create mode 100644 Subsystems/Rendering/IRenderingSystem.cc create mode 100644 Subsystems/Rendering/IRenderingSystem.h create mode 100644 Subsystems/Rendering/RenderSync.h diff --git a/Subsystems/Rendering/.clang-tidy b/Subsystems/Rendering/.clang-tidy new file mode 100644 index 0000000..ee0b4aa --- /dev/null +++ b/Subsystems/Rendering/.clang-tidy @@ -0,0 +1,6 @@ +InheritParentConfig: true + +Checks: > + -clang-analyzer-optin.performance.Padding, + +HeaderFilterRegex: '.*Subsystems/Rendering/.*' \ No newline at end of file diff --git a/Subsystems/Rendering/BuiltInAssets/2x2Black.png b/Subsystems/Rendering/BuiltInAssets/2x2Black.png new file mode 100644 index 0000000000000000000000000000000000000000..aa710bcc9cccd6ea71a6b252bfcd9d238a02052e GIT binary patch literal 133 zcmeAS@N?(olHy`uVBq!ia0vp^Od!m`1|*BN@u~nR#^NA%Cx&(BWL^R}Ea{HEjtmSN z`?>!lvI6;>1s;*b3=G^tAk28_ZrvZCpp>VJV~9j}a!O+2&+{ivoH%ggz<~oF{{OFM ZV~}8G;h&+Xa0sZ1!PC{xWt~$(69AaQBsTy6 literal 0 HcmV?d00001 diff --git a/Subsystems/Rendering/GL/NativeHandlesGL.h b/Subsystems/Rendering/GL/NativeHandlesGL.h new file mode 100644 index 0000000..b2ccdfe --- /dev/null +++ b/Subsystems/Rendering/GL/NativeHandlesGL.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include + +namespace ho +{ + +struct StaticMeshNativeHandleGL +{ + GLuint GlVbo = 0; + GLuint GlEbo = 0; +}; + +struct MaterialNativeHandleGL +{ + GLuint GlUbo = 0; +}; + +struct TextureNativeHandleGL +{ + GLuint GlTexture = 0; +}; + +struct ShaderNativeHandleGL +{ + GLuint GlProgram = 0; +}; +} // namespace ho \ No newline at end of file diff --git a/Subsystems/Rendering/GL/RenderingSystemGL.cc b/Subsystems/Rendering/GL/RenderingSystemGL.cc new file mode 100644 index 0000000..84d0140 --- /dev/null +++ b/Subsystems/Rendering/GL/RenderingSystemGL.cc @@ -0,0 +1,1977 @@ +#include "RenderingSystemGL.h" + +#include +#include + +#include "Core/IO/Path.h" +#include "Core/IO/ResourceParseFuncs.h" +#include "Core/Log/Logger.h" +#include "Platforms/IPlatformApplication.h" +#include "Subsystems/Asset/AssetImportFuncs.h" +#include "Subsystems/Asset/Assets.h" + +namespace ho +{ +static GLenum toGlRenderTargetFormat(IRenderingSystem::eRenderTargetFormat format); +static GLenum toGlInternalFormat(eTextureFormat format); +static GLenum toGlExternalFormat(eTextureFormat format); +static GLenum toGlExternalType(eTextureFormat format); +static bool isUncompressedFormat(eTextureFormat format); +static GLenum toGlTextureType(eTextureType type); +static GLenum toGlShaderType(eShaderStage stage); +[[maybe_unused]] static GLbitfield toGlShaderBitfield(eShaderStage stage); + +static bool ASSERT_GL(); + +struct Vertex +{ + Vector3 Position; // 16 byte + Vector3 Normal; // 16 byte + Vector4 Tangent; // 16 byte + Vector2 UV0; // 4 byte + Vector2 UV1; // 4 byte + uint8_t Color0[4] = {}; // 4 byte + uint8_t Padding[4] = {}; // 4 byte +}; // 64 byte + +bool RenderingSystemGL::CreateFrameBuffer(const FrameBufferDesc& frameBufferDesc) +{ + if (!validateFrameBufferDesc(frameBufferDesc)) + { + HO_ASSERT(false, "Invalid frame buffer description."); + return false; + } + + GLuint glFBO[2] = {}; + glCreateFramebuffers(2, glFBO); + if (!ASSERT_GL()) + { + return false; + } + GLuint glResolvedFBO[2] = {}; + if (frameBufferDesc.SampleCount > 1) + { + glCreateFramebuffers(2, glResolvedFBO); + if (!ASSERT_GL()) + { + glDeleteFramebuffers(2, glFBO); + return false; + } + } + + FrameBuffer newFrameBuffer; + newFrameBuffer.hName = frameBufferDesc.hName; + newFrameBuffer.Width = frameBufferDesc.Width; + newFrameBuffer.Height = frameBufferDesc.Height; + newFrameBuffer.SampleCount = frameBufferDesc.SampleCount; + + GLint maxSamples = 0; + glGetIntegerv(GL_MAX_SAMPLES, &maxSamples); + HO_ASSERT(frameBufferDesc.SampleCount <= maxSamples, + "Required sample count exceeds current hardware's maximum sample count limits. The hardware maximum " + "sample count will be used instead."); + + GLuint glRenderTargets[2][static_cast(eRenderTargetType::Last)] = {}; + GLuint glResolvedRenderTargets[2][static_cast(eRenderTargetType::Last)] = {}; + for (int32_t targetIdx = 0; targetIdx < static_cast(eRenderTargetType::Last); ++targetIdx) + { + for (int32_t bufferIdx = 0; bufferIdx < 2; ++bufferIdx) + { + if (frameBufferDesc.TargetDesc[targetIdx].Format != eRenderTargetFormat::None) + { + if (frameBufferDesc.SampleCount > 1) + { + glCreateTextures(GL_TEXTURE_2D_MULTISAMPLE, 1, &glRenderTargets[bufferIdx][targetIdx]); + if (targetIdx != static_cast(eRenderTargetType::Depth)) + { + glCreateTextures(GL_TEXTURE_2D, 1, &glResolvedRenderTargets[bufferIdx][targetIdx]); + } + } + else + { + glCreateTextures(GL_TEXTURE_2D, 1, &glRenderTargets[bufferIdx][targetIdx]); + } + if (!ASSERT_GL()) + { + cancelFrameBufferCreation(glFBO, glResolvedFBO, glRenderTargets, glResolvedRenderTargets); + return false; + } + } + } + } + + for (int32_t targetIdx = 0; targetIdx < static_cast(eRenderTargetType::Last); ++targetIdx) + { + for (int32_t bufferIdx = 0; bufferIdx < 2; ++bufferIdx) + { + if (frameBufferDesc.TargetDesc[targetIdx].Format != eRenderTargetFormat::None) + { + if (frameBufferDesc.SampleCount > 1) + { + glTextureStorage2DMultisample(glRenderTargets[bufferIdx][targetIdx], + math::Min(frameBufferDesc.SampleCount, maxSamples), + toGlRenderTargetFormat(frameBufferDesc.TargetDesc[targetIdx].Format), + frameBufferDesc.Width, + frameBufferDesc.Height, + GL_TRUE); + if (targetIdx != static_cast(eRenderTargetType::Depth)) + { + glTextureStorage2D(glResolvedRenderTargets[bufferIdx][targetIdx], + 1, + toGlRenderTargetFormat(frameBufferDesc.TargetDesc[targetIdx].Format), + frameBufferDesc.Width, + frameBufferDesc.Height); + } + } + else + { + glTextureStorage2D(glRenderTargets[bufferIdx][targetIdx], + 1, + toGlRenderTargetFormat(frameBufferDesc.TargetDesc[targetIdx].Format), + frameBufferDesc.Width, + frameBufferDesc.Height); + } + if (!ASSERT_GL()) + { + cancelFrameBufferCreation(glFBO, glResolvedFBO, glRenderTargets, glResolvedRenderTargets); + return false; + } + + if (targetIdx == static_cast(eRenderTargetType::Depth)) + { + switch (frameBufferDesc.TargetDesc[targetIdx].Format) + { + case IRenderingSystem::eRenderTargetFormat::D24_UNORM_S8_UINT: + glNamedFramebufferTexture(glFBO[bufferIdx], + GL_DEPTH_STENCIL_ATTACHMENT, + glRenderTargets[bufferIdx][targetIdx], + 0); + break; + case IRenderingSystem::eRenderTargetFormat::D32_FLOAT: + glNamedFramebufferTexture( + glFBO[bufferIdx], GL_DEPTH_ATTACHMENT, glRenderTargets[bufferIdx][targetIdx], 0); + break; + default: + HO_ASSERT(false, "Invalid depth target format."); + cancelFrameBufferCreation(glFBO, glResolvedFBO, glRenderTargets, glResolvedRenderTargets); + return false; + } + } + else + { + glNamedFramebufferTexture( + glFBO[bufferIdx], GL_COLOR_ATTACHMENT0 + targetIdx, glRenderTargets[bufferIdx][targetIdx], 0); + if (frameBufferDesc.SampleCount > 1) + { + glNamedFramebufferTexture(glResolvedFBO[bufferIdx], + GL_COLOR_ATTACHMENT0 + targetIdx, + glResolvedRenderTargets[bufferIdx][targetIdx], + 0); + } + } + + if (!ASSERT_GL()) + { + cancelFrameBufferCreation(glFBO, glResolvedFBO, glRenderTargets, glResolvedRenderTargets); + return false; + } + + GpuTextureHandle hGpuRenderTarget = prepareUploadTexture(TextureHandle::sNULL, true); + + TextureNativeHandleGL nativeHandle; + nativeHandle.GlTexture = glRenderTargets[bufferIdx][targetIdx]; + hGpuRenderTarget.Get()->NativeHandlePoolIndex = mTextureNativeHandlePool.Add(nativeHandle); + + newFrameBuffer.hRenderTargets[bufferIdx][targetIdx] = hGpuRenderTarget; + + if (frameBufferDesc.SampleCount > 1 && targetIdx != static_cast(eRenderTargetType::Depth)) + { + GpuTextureHandle hGpuResolvedRenderTarget = prepareUploadTexture(TextureHandle::sNULL, true); + + TextureNativeHandleGL nativeHandleResolved; + nativeHandleResolved.GlTexture = glResolvedRenderTargets[bufferIdx][targetIdx]; + hGpuResolvedRenderTarget.Get()->NativeHandlePoolIndex = + mTextureNativeHandlePool.Add(nativeHandleResolved); + + newFrameBuffer.hResolvedRenderTargets[bufferIdx][targetIdx] = hGpuResolvedRenderTarget; + } + } + } + } + + for (int32_t targetIdx = 0; targetIdx < static_cast(eRenderTargetType::Last); ++targetIdx) + { + if (targetIdx == static_cast(eRenderTargetType::Depth)) + { + newFrameBuffer.ClearDepth = frameBufferDesc.TargetDesc[targetIdx].ClearDepth; + newFrameBuffer.ClearStencil = frameBufferDesc.TargetDesc[targetIdx].ClearStencil; + } + else + { + newFrameBuffer.ClearColors[targetIdx] = Vector4(frameBufferDesc.TargetDesc[targetIdx].ClearColor); + } + } + + for (int32_t bufferIdx = 0; bufferIdx < 2; ++bufferIdx) + { + if (glCheckNamedFramebufferStatus(glFBO[bufferIdx], GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) + { + HO_ASSERT(false, "Framebuffer is incomplete."); + cancelFrameBufferCreation(glFBO, glResolvedFBO, glRenderTargets, glResolvedRenderTargets); + for (int32_t targetIdx = 0; targetIdx < static_cast(eRenderTargetType::Last); ++targetIdx) + { + if (newFrameBuffer.hRenderTargets[bufferIdx][targetIdx].IsValid()) + { + mTextureNativeHandlePool.Remove( + newFrameBuffer.hRenderTargets[bufferIdx][targetIdx].Get()->NativeHandlePoolIndex); + mGpuTexturePool.Remove(newFrameBuffer.hRenderTargets[bufferIdx][targetIdx].GetIndex()); + } + } + return false; + } + if (frameBufferDesc.SampleCount > 1) + { + if (glCheckNamedFramebufferStatus(glResolvedFBO[bufferIdx], GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) + { + HO_ASSERT(false, "ResolvedFramebuffer is incomplete."); + cancelFrameBufferCreation(glFBO, glResolvedFBO, glRenderTargets, glResolvedRenderTargets); + for (int32_t targetIdx = 0; targetIdx < static_cast(eRenderTargetType::Last); ++targetIdx) + { + if (newFrameBuffer.hResolvedRenderTargets[bufferIdx][targetIdx].IsValid()) + { + mTextureNativeHandlePool.Remove( + newFrameBuffer.hResolvedRenderTargets[bufferIdx][targetIdx].Get()->NativeHandlePoolIndex); + mGpuTexturePool.Remove(newFrameBuffer.hResolvedRenderTargets[bufferIdx][targetIdx].GetIndex()); + } + } + return false; + } + } + } + + mNameToGlFboMap.insert({frameBufferDesc.hName, GlFBO{{glFBO[0], glFBO[1]}, {glResolvedFBO[0], glResolvedFBO[1]}}}); + mNameToFrameBufferMap.insert({frameBufferDesc.hName, newFrameBuffer}); + return true; +} + +bool RenderingSystemGL::DestroyFrameBuffer(StringHandle hFrameBufferName) +{ + auto fbit = mNameToFrameBufferMap.find(hFrameBufferName); + if (fbit == mNameToFrameBufferMap.end()) + { + return false; + } + FrameBuffer& frameBuffer = fbit->second; + for (int32_t targetIdx = 0; targetIdx < static_cast(eRenderTargetType::Last); ++targetIdx) + { + for (int32_t bufferIdx = 0; bufferIdx < 2; ++bufferIdx) + { + if (frameBuffer.hRenderTargets[bufferIdx][targetIdx].IsValid()) + { + const TextureNativeHandleGL* nativeHandle = mTextureNativeHandlePool.Get( + frameBuffer.hRenderTargets[bufferIdx][targetIdx].Get()->NativeHandlePoolIndex); + HO_ASSERT(nativeHandle, + "Render target's native handle is null. Frame buffer might be created incompletely."); + glDeleteTextures(1, &nativeHandle->GlTexture); + mTextureNativeHandlePool.Remove( + frameBuffer.hRenderTargets[bufferIdx][targetIdx].Get()->NativeHandlePoolIndex); + mGpuTexturePool.Remove(frameBuffer.hRenderTargets[bufferIdx][targetIdx].GetIndex()); + } + if (frameBuffer.hResolvedRenderTargets[bufferIdx][targetIdx].IsValid()) + { + const TextureNativeHandleGL* nativeHandle = mTextureNativeHandlePool.Get( + frameBuffer.hResolvedRenderTargets[bufferIdx][targetIdx].Get()->NativeHandlePoolIndex); + HO_ASSERT(nativeHandle, + "Render target's native handle is null. Frame buffer might be created incompletely."); + glDeleteTextures(1, &nativeHandle->GlTexture); + mTextureNativeHandlePool.Remove( + frameBuffer.hResolvedRenderTargets[bufferIdx][targetIdx].Get()->NativeHandlePoolIndex); + mGpuTexturePool.Remove(frameBuffer.hResolvedRenderTargets[bufferIdx][targetIdx].GetIndex()); + } + } + } + + mNameToFrameBufferMap.erase(hFrameBufferName); + + auto glit = mNameToGlFboMap.find(hFrameBufferName); + HO_ASSERT(glit != mNameToGlFboMap.end(), + "GL id for frame buffer was not inserted. Frame buffer might be created incompletely."); + glDeleteFramebuffers(2, glit->second.FBO); + glDeleteFramebuffers(2, glit->second.ResolvedFBO); + mNameToGlFboMap.erase(hFrameBufferName); + return true; +} + +void* RenderingSystemGL::GetRenderTargetNativeHandle(StringHandle hFrameBufferName, + eRenderTargetType type, + bool bRequireMultisample) const +{ + auto fbit = mNameToFrameBufferMap.find(hFrameBufferName); + + HO_ASSERT(fbit != mNameToFrameBufferMap.end(), "There is no frame buffer."); + + const FrameBuffer& frameBuffer = fbit->second; + HO_ASSERT(frameBuffer.hRenderTargets[mFrontRenderQueueIndex][static_cast(type)].IsValid(), + "Frame buffer doesn't have such render target."); + const TextureNativeHandleGL* nativeHandle = nullptr; + if (bRequireMultisample) + { + HO_ASSERT(frameBuffer.SampleCount > 1, "Frame buffer has single sample render targets."); + nativeHandle = + mTextureNativeHandlePool.Get(frameBuffer.hRenderTargets[mFrontRenderQueueIndex][static_cast(type)] + .Get() + ->NativeHandlePoolIndex); + } + else + { + if (frameBuffer.SampleCount > 1) + { + HO_ASSERT(type != eRenderTargetType::Depth, + "MultiSample Depth Target must be used in rendering system only."); + nativeHandle = mTextureNativeHandlePool.Get( + frameBuffer.hResolvedRenderTargets[mFrontRenderQueueIndex][static_cast(type)] + .Get() + ->NativeHandlePoolIndex); + } + else + { + nativeHandle = mTextureNativeHandlePool.Get( + frameBuffer.hRenderTargets[mFrontRenderQueueIndex][static_cast(type)] + .Get() + ->NativeHandlePoolIndex); + } + } + + HO_ASSERT(nativeHandle, "Texture for render target was not created."); + return (void*)(uintptr_t)nativeHandle->GlTexture; // NOLINT +} + +RenderingSystemGL::RenderingSystemGL() +{ + HO_ASSERT(!spInstance, "Rendering system must be created by 'createInstance' function."); +} + +bool RenderingSystemGL::init() +{ + IPlatformApplication::GetInstance().GetMainWindow()->ActivateContext(); + + if (!spInstance) + { + HO_ASSERT(false, "Rendering system is not created."); + return false; + } + else + { + // Create default GPU resources. + + // VAO + glCreateVertexArrays(1, &mGlVAO); + + glVertexArrayAttribFormat(mGlVAO, mPosAttribIndex, 3, GL_FLOAT, GL_FALSE, 0); + glVertexArrayAttribFormat(mGlVAO, mNormalAttribIndex, 3, GL_FLOAT, GL_FALSE, sizeof(Vector3)); + glVertexArrayAttribFormat(mGlVAO, mTangentsAttribIndex, 4, GL_FLOAT, GL_FALSE, 2 * sizeof(Vector3)); + glVertexArrayAttribFormat( + mGlVAO, mUV0AttribIndex, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(Vector3) + sizeof(Vector4)); + glVertexArrayAttribFormat( + mGlVAO, mUV1AttribIndex, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(Vector3) + sizeof(Vector4) + sizeof(Vector2)); + glVertexArrayAttribFormat(mGlVAO, + mColorAttribIndex, + 4, + GL_UNSIGNED_BYTE, + GL_TRUE, + 2 * sizeof(Vector3) + sizeof(Vector4) + 2 * sizeof(Vector2)); + + glVertexArrayAttribBinding(mGlVAO, mPosAttribIndex, mAttribBindingIndex); + glEnableVertexArrayAttrib(mGlVAO, mPosAttribIndex); + glVertexArrayAttribBinding(mGlVAO, mNormalAttribIndex, mAttribBindingIndex); + glEnableVertexArrayAttrib(mGlVAO, mNormalAttribIndex); + glVertexArrayAttribBinding(mGlVAO, mTangentsAttribIndex, mAttribBindingIndex); + glEnableVertexArrayAttrib(mGlVAO, mTangentsAttribIndex); + glVertexArrayAttribBinding(mGlVAO, mUV0AttribIndex, mAttribBindingIndex); + glEnableVertexArrayAttrib(mGlVAO, mUV0AttribIndex); + glVertexArrayAttribBinding(mGlVAO, mUV1AttribIndex, mAttribBindingIndex); + glEnableVertexArrayAttrib(mGlVAO, mUV1AttribIndex); + glVertexArrayAttribBinding(mGlVAO, mColorAttribIndex, mAttribBindingIndex); + glEnableVertexArrayAttrib(mGlVAO, mColorAttribIndex); + + if (!ASSERT_GL()) + { + glDeleteVertexArrays(1, &mGlVAO); + return false; + } + + // ProgramPipeline + glCreateProgramPipelines(1, &mGlProgramPipeline); + + if (!ASSERT_GL()) + { + cancelInitialization(); + return false; + } + + // Shaders + Path shaderDirPath(std::string("Subsystems/Rendering/GL/Shaders")); + shaderDirPath.ResolveProjectPath(); + + auto loadShader = [&](const std::string& shaderFileName, + GLuint* pGlProgram, + eShaderStage stage, + eMaterialAssetType compatibleMaterialTypes) -> bool + { + const std::unique_ptr pDefaultIR = + parser::parseShaderFile(shaderDirPath / Path(shaderFileName), shaderFileName); + const std::unique_ptr pDefaultAsset = + importer::importShader(*pDefaultIR, stage, static_cast(compatibleMaterialTypes)); + const GLuint glShader = glCreateShader(toGlShaderType(pDefaultAsset->Stage)); + if (!ASSERT_GL()) + { + glDeleteShader(glShader); + return false; + } + glShaderBinary(1, + &glShader, + GL_SHADER_BINARY_FORMAT_SPIR_V, + pDefaultAsset->Binary.Data(), + static_cast(pDefaultAsset->Binary.GetSize() * sizeof(uint32_t))); + if (!ASSERT_GL()) + { + glDeleteShader(glShader); + return false; + } + + glSpecializeShader(glShader, "main", 0, nullptr, nullptr); + if (!ASSERT_GL()) + { + glDeleteShader(glShader); + return false; + } + + GLint isCompiled = 0; + glGetShaderiv(glShader, GL_COMPILE_STATUS, &isCompiled); + if (isCompiled == GL_FALSE) + { + HO_ASSERT(false, "Shader is not compiled."); + glDeleteShader(glShader); + return false; + } + + *pGlProgram = glCreateProgram(); + + glProgramParameteri(*pGlProgram, GL_PROGRAM_SEPARABLE, GL_TRUE); + + glAttachShader(*pGlProgram, glShader); + if (!ASSERT_GL()) + { + glDeleteShader(glShader); + return false; + } + + glLinkProgram(*pGlProgram); + + GLint isLinked = 0; + glGetProgramiv(*pGlProgram, GL_LINK_STATUS, &isLinked); + if (isLinked == GL_FALSE) + { + std::string glLogInfo; + GLint glLogSize = 0; + glGetProgramiv(*pGlProgram, GL_INFO_LOG_LENGTH, &glLogSize); + glLogInfo.resize(glLogSize); + glGetProgramInfoLog(*pGlProgram, glLogSize, nullptr, glLogInfo.data()); + HO_LOG_ERROR((std::string("Shader Link Failed:\n") + glLogInfo).c_str()); + HO_ASSERT(false, "Failed to link shader."); + glDeleteShader(glShader); + *pGlProgram = 0; + return false; + } + + glDetachShader(*pGlProgram, glShader); + glDeleteShader(glShader); + return true; + }; + + if (!loadShader("Default.vert", + &mGlDefaultVS, + eShaderStage::VertexShader, + eMaterialAssetType::Legacy | eMaterialAssetType::StandardLit)) + { + cancelInitialization(); + return false; + } + + if (!loadShader( + "DefaultPhong.frag", &mGlDefaultPhongFS, eShaderStage::FragmentShader, eMaterialAssetType::Legacy)) + { + cancelInitialization(); + return false; + } + + if (!loadShader("DefaultPhongMasked.frag", + &mGlDefaultPhongMaskedFS, + eShaderStage::FragmentShader, + eMaterialAssetType::Legacy)) + { + cancelInitialization(); + return false; + } + + if (!loadShader( + "DefaultPBR.frag", &mGlDefaultPbrFS, eShaderStage::FragmentShader, eMaterialAssetType::StandardLit)) + { + cancelInitialization(); + return false; + } + + if (!loadShader("DefaultPBRMasked.frag", + &mGlDefaultPbrMaskedFS, + eShaderStage::FragmentShader, + eMaterialAssetType::StandardLit)) + { + cancelInitialization(); + return false; + } + + if (!loadShader( + "DefaultSkyMap.vert", &mGlDefaultSkyMapVS, eShaderStage::VertexShader, eMaterialAssetType::None)) + { + cancelInitialization(); + return false; + } + + if (!loadShader( + "DefaultSkyMap.frag", &mGlDefaultSkyMapFS, eShaderStage::FragmentShader, eMaterialAssetType::None)) + { + cancelInitialization(); + return false; + } + + if (!loadShader( + "WorldGizmo.vert", &mGlDefaultWorldGizmoVS, eShaderStage::VertexShader, eMaterialAssetType::None)) + { + cancelInitialization(); + return false; + } + + if (!loadShader( + "WorldGizmo.frag", &mGlDefaultWorldGizmoFS, eShaderStage::FragmentShader, eMaterialAssetType::None)) + { + cancelInitialization(); + return false; + } + + // Cubemap + const std::unique_ptr p2x2Black = parser::parseTextureFile( + Path(std::string("Subsystems/Rendering/BuiltInAssets/2x2Black.png")).ResolvedProjectPath(), + "DefaultCubemap", + true); + glCreateTextures(GL_TEXTURE_CUBE_MAP, 1, &mGlDefaultCubeMap); + glTextureStorage2D(mGlDefaultCubeMap, 1, GL_RGBA8, p2x2Black->Img.GetWidth(), p2x2Black->Img.GetHeight()); + for (int32_t face = 0; face < 6; ++face) + { + glTextureSubImage3D(mGlDefaultCubeMap, + 0, + 0, + 0, + face, + p2x2Black->Img.GetWidth(), + p2x2Black->Img.GetHeight(), + 1, + GL_RGBA, + GL_UNSIGNED_BYTE, + p2x2Black->Img.GetBitmap()); + } + glTextureParameteri(mGlDefaultCubeMap, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTextureParameteri(mGlDefaultCubeMap, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTextureParameteri(mGlDefaultCubeMap, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTextureParameteri(mGlDefaultCubeMap, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTextureParameteri(mGlDefaultCubeMap, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + + // UBOs + glCreateBuffers(1, &mGlMatrixUBO); + glNamedBufferData(mGlMatrixUBO, sizeof(MatrixLayout), nullptr, GL_DYNAMIC_DRAW); + glBindBufferBase(GL_UNIFORM_BUFFER, mMatrixUboIndex, mGlMatrixUBO); + if (!ASSERT_GL()) + { + cancelInitialization(); + return false; + } + + glCreateBuffers(1, &mGlLightUBO); + glNamedBufferData(mGlLightUBO, sizeof(LightLayout), nullptr, GL_DYNAMIC_DRAW); + glBindBufferBase(GL_UNIFORM_BUFFER, mLightUboIndex, mGlLightUBO); + if (!ASSERT_GL()) + { + cancelInitialization(); + return false; + } + + glCreateBuffers(1, &mGlGeneralPurposeUBO); + glNamedBufferData(mGlGeneralPurposeUBO, sizeof(GeneralPurposeLayout), nullptr, GL_DYNAMIC_DRAW); + glBindBufferBase(GL_UNIFORM_BUFFER, mGeneralPurposeUboIndex, mGlGeneralPurposeUBO); + if (!ASSERT_GL()) + { + cancelInitialization(); + return false; + } + + // Set initial GL state + glEnable(GL_MULTISAMPLE); + + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LESS); + + IPlatformApplication::GetInstance().GetMainWindow()->DeactivateContext(); + return true; + } +} + +void RenderingSystemGL::releaseAllResources() +{ + flush(); + // Unload all GPU resources + for (auto& nativeHandle : mStaticMeshNativeHandlePool.GetMutableRawPool()) + { + if (nativeHandle.has_value()) + { + glDeleteBuffers(1, &nativeHandle.value().GlVbo); + glDeleteBuffers(1, &nativeHandle.value().GlEbo); + } + } + for (auto& nativeHandle : mMaterialNativeHandlePool.GetMutableRawPool()) + { + if (nativeHandle.has_value()) + { + glDeleteBuffers(1, &nativeHandle.value().GlUbo); + } + } + for (auto& nativeHandle : mTextureNativeHandlePool.GetMutableRawPool()) + { + if (nativeHandle.has_value()) + { + glDeleteTextures(1, &nativeHandle.value().GlTexture); + } + } + for (auto& nativeHandle : mShaderNativeHandlePool.GetMutableRawPool()) + { + if (nativeHandle.has_value()) + { + glDeleteProgram(nativeHandle.value().GlProgram); + } + } + + for (auto& it : mNameToGlFboMap) + { + glDeleteFramebuffers(2, it.second.FBO); + glDeleteFramebuffers(2, it.second.ResolvedFBO); + } + + mNameToGlFboMap.clear(); + + glDeleteVertexArrays(1, &mGlVAO); + glDeleteProgramPipelines(1, &mGlProgramPipeline); + glDeleteProgram(mGlDefaultVS); + glDeleteProgram(mGlDefaultPhongFS); + glDeleteProgram(mGlDefaultPhongMaskedFS); + glDeleteProgram(mGlDefaultPbrFS); + glDeleteProgram(mGlDefaultPbrMaskedFS); + glDeleteProgram(mGlDefaultSkyMapVS); + glDeleteProgram(mGlDefaultSkyMapFS); + glDeleteProgram(mGlDefaultWorldGizmoVS); + glDeleteProgram(mGlDefaultWorldGizmoFS); + glDeleteTextures(1, &mGlDefaultCubeMap); + glDeleteBuffers(1, &mGlMatrixUBO); + glDeleteBuffers(1, &mGlLightUBO); + glDeleteBuffers(1, &mGlGeneralPurposeUBO); + + mStaticMeshNativeHandlePool.Clear(); + mMaterialNativeHandlePool.Clear(); + mTextureNativeHandlePool.Clear(); + mShaderNativeHandlePool.Clear(); +} + +void RenderingSystemGL::applyMaterial(GpuMaterialHandle hGpuMaterial) +{ + const GpuMaterial* pMaterial = hGpuMaterial.Get(); + + if (pMaterial->PipelineState.AlphaMode == eMaterialAlphaMode::Blend) + { + switch (pMaterial->PipelineState.AlphaBlendMode) + { + case eMaterialAlphaBlendMode::Default: + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + break; + case eMaterialAlphaBlendMode::Additive: + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + break; + default: + break; + } + } + + if (pMaterial->PipelineState.bWireframe) + { + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + } + else + { + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + } + + if (pMaterial->PipelineState.bBackfaceCulling) + { + glEnable(GL_CULL_FACE); + glCullFace(GL_BACK); + } + else + { + glDisable(GL_CULL_FACE); + } + + glBindBufferBase(GL_UNIFORM_BUFFER, + mMaterialUboIndex, + mMaterialNativeHandlePool.Get(hGpuMaterial.Get()->NativeHandlePoolIndex)->GlUbo); + + for (int32_t i = 1; i < static_cast(eMaterialTextureUsage::Last); ++i) + { + if (pMaterial->TextureNativeHandlePoolIndices[i] != 0) + { + glBindTextureUnit(i, mTextureNativeHandlePool.Get(pMaterial->TextureNativeHandlePoolIndices[i])->GlTexture); + } + } + + const GLuint glVS = + pMaterial->ShaderNativeHandlePoolIndices[static_cast(eShaderStage::VertexShader)] != 0 + ? mShaderNativeHandlePool + .Get(pMaterial->ShaderNativeHandlePoolIndices[static_cast(eShaderStage::VertexShader)]) + ->GlProgram + : mGlDefaultVS; + + glUseProgramStages(mGlProgramPipeline, GL_VERTEX_SHADER_BIT, glVS); + + GLuint glFS = 0; + if (pMaterial->ShaderNativeHandlePoolIndices[static_cast(eShaderStage::FragmentShader)] != 0) + { + glFS = mShaderNativeHandlePool + .Get(pMaterial->ShaderNativeHandlePoolIndices[static_cast(eShaderStage::FragmentShader)]) + ->GlProgram; + } + else + { + switch (hGpuMaterial.Get()->Type) + { + case eMaterialAssetType::Legacy: + if (hGpuMaterial.Get()->PipelineState.AlphaMode == eMaterialAlphaMode::Mask) + { + glFS = mGlDefaultPhongMaskedFS; + } + else + { + glFS = mGlDefaultPhongFS; + } + break; + case eMaterialAssetType::StandardLit: + if (hGpuMaterial.Get()->PipelineState.AlphaMode == eMaterialAlphaMode::Mask) + { + glFS = mGlDefaultPbrMaskedFS; + } + else + { + glFS = mGlDefaultPbrFS; + } + break; + default: + HO_ASSERT(false, "Invalid material format."); + } + } + + glUseProgramStages(mGlProgramPipeline, GL_FRAGMENT_SHADER_BIT, glFS); +} + +void RenderingSystemGL::executeDrawCommand(const DrawCommand& command) +{ + glNamedBufferSubData(mGlMatrixUBO, 0, sizeof(Matrix4x4), &command.WorldMat); + Matrix4x4 invWorldMat = command.WorldMat.Inverse(); + glNamedBufferSubData(mGlMatrixUBO, sizeof(Matrix4x4), sizeof(Matrix4x4), &invWorldMat); + + const StaticMeshNativeHandleGL* meshNativehandle = + mStaticMeshNativeHandlePool.Get(command.hGpuStaticMesh.Get()->NativeHandlePoolIndex); + glVertexArrayVertexBuffer(mGlVAO, + mAttribBindingIndex, + meshNativehandle->GlVbo, + static_cast(command.VertexOffset * sizeof(Vertex)), + sizeof(Vertex)); + glVertexArrayElementBuffer(mGlVAO, meshNativehandle->GlEbo); + + glDrawElements( + GL_TRIANGLES, command.IndexCount, GL_UNSIGNED_INT, (void*)(command.IndexOffset * sizeof(uint32_t))); // NOLINT + + ASSERT_GL(); +} + +void RenderingSystemGL::executeSkyMapPass(const GpuTexture* pSkyMap) +{ + HO_ASSERT(pSkyMap, "Sky map is null."); + + glBindTextureUnit(static_cast(eMaterialTextureUsage::Last), + mTextureNativeHandlePool.Get(pSkyMap->NativeHandlePoolIndex)->GlTexture); + + glUseProgramStages(mGlProgramPipeline, GL_VERTEX_SHADER_BIT, mGlDefaultSkyMapVS); + glUseProgramStages(mGlProgramPipeline, GL_FRAGMENT_SHADER_BIT, mGlDefaultSkyMapFS); + + glDepthMask(GL_FALSE); + glDepthFunc(GL_LEQUAL); + + glDrawArrays(GL_TRIANGLES, 0, 36); + + glDepthMask(GL_TRUE); + glDepthFunc(GL_LESS); + + ASSERT_GL(); +} + +void RenderingSystemGL::executeInWorldDebugDrawPass() +{ + glUseProgramStages(mGlProgramPipeline, GL_VERTEX_SHADER_BIT, mGlDefaultWorldGizmoVS); + glUseProgramStages(mGlProgramPipeline, GL_FRAGMENT_SHADER_BIT, mGlDefaultWorldGizmoFS); + + glDrawArrays(GL_TRIANGLES, 0, 6); + + ASSERT_GL(); +} + +void RenderingSystemGL::executeOverlayDebugDrawPass() {} + +void RenderingSystemGL::prepareExecuteRenderQueue() +{ + glBindVertexArray(mGlVAO); + glBindProgramPipeline(mGlProgramPipeline); +} + +void RenderingSystemGL::prepareExecuteRenderPass(const RenderPass& pass) +{ + HO_ASSERT(pass.pFrameBuffer, "Render pass must have target frame buffer."); + auto it = mNameToGlFboMap.find(pass.pFrameBuffer->hName); + HO_ASSERT(it != mNameToGlFboMap.end(), + "GL id for frame buffer was not inserted. Frame buffer might be created incompletely."); + + glBindFramebuffer(GL_FRAMEBUFFER, it->second.FBO[mBackRenderQueueIndex]); + glViewport(pass.Viewport.X, pass.Viewport.Y, pass.Viewport.Width, pass.Viewport.Height); + + ASSERT_GL(); + + for (int32_t i = 0; i < static_cast(eRenderTargetType::Last); ++i) + { + if (pass.bClearTarget[i]) + { + if (i == static_cast(eRenderTargetType::Depth)) + { + glClearBufferfv(GL_DEPTH, 0, &pass.pFrameBuffer->ClearDepth); + } + else + { + glClearBufferfv(GL_COLOR, i, pass.pFrameBuffer->ClearColors[i].Data); + } + } + } + + glNamedBufferSubData(mGlGeneralPurposeUBO, 0, sizeof(float) * 3, &pass.WorldCameraPos); + glNamedBufferSubData(mGlMatrixUBO, sizeof(Matrix4x4) * 2, sizeof(Matrix4x4), &pass.ViewMat); + glNamedBufferSubData(mGlMatrixUBO, sizeof(Matrix4x4) * 3, sizeof(Matrix4x4), &pass.ProjMat); + glNamedBufferSubData(mGlLightUBO, 0, sizeof(LightLayout), &pass.Lights); + + ASSERT_GL(); + + if (pass.pSkyMap) + { + glBindTextureUnit(static_cast(eMaterialTextureUsage::Last), + mTextureNativeHandlePool.Get(pass.pSkyMap->NativeHandlePoolIndex)->GlTexture); + } + else + { + glBindTextureUnit(static_cast(eMaterialTextureUsage::Last), mGlDefaultCubeMap); + } + + if (pass.pIrradianceMap) + { + glBindTextureUnit(static_cast(eMaterialTextureUsage::Last) + 1, + mTextureNativeHandlePool.Get(pass.pIrradianceMap->NativeHandlePoolIndex)->GlTexture); + } + else + { + glBindTextureUnit(static_cast(eMaterialTextureUsage::Last) + 1, mGlDefaultCubeMap); + } + ASSERT_GL(); +} + +void RenderingSystemGL::prepareExecuteOpaqueDrawCommands() +{ + glDepthMask(GL_TRUE); + + glDisable(GL_BLEND); + + ASSERT_GL(); +} + +void RenderingSystemGL::prepareExecuteMaskedDrawCommands() +{ + glDepthMask(GL_TRUE); + + glDisable(GL_BLEND); + + ASSERT_GL(); +} + +void RenderingSystemGL::prepareExecuteBlendedDrawCommands() +{ + glDepthMask(GL_FALSE); + + glEnable(GL_BLEND); + + ASSERT_GL(); +} + +void RenderingSystemGL::finishExecuteRenderPass(const RenderPass& pass) +{ + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + ASSERT_GL(); +} + +void RenderingSystemGL::finishExecuteRenderQueue() {} + +void RenderingSystemGL::resolveFrameBuffer(const FrameBuffer& frameBuffer) +{ + if (frameBuffer.SampleCount == 1) + { + return; + } + + auto glFBO = mNameToGlFboMap.find(frameBuffer.hName); + HO_ASSERT(glFBO != mNameToGlFboMap.end(), + "GL id for frame buffer was not inserted. Frame buffer might be created incompletely."); + + for (int32_t i = 0; i < static_cast(eRenderTargetType::Last); ++i) + { + if (i != static_cast(eRenderTargetType::Depth) && + frameBuffer.hRenderTargets[mBackRenderQueueIndex][i].IsValid()) + { + HO_ASSERT(frameBuffer.hResolvedRenderTargets[mBackRenderQueueIndex][i].IsValid(), + "Render target for resolving was not created. Frame buffer might be created incompletely."); + glNamedFramebufferReadBuffer(glFBO->second.FBO[mBackRenderQueueIndex], GL_COLOR_ATTACHMENT0 + i); + glNamedFramebufferDrawBuffer(glFBO->second.ResolvedFBO[mBackRenderQueueIndex], GL_COLOR_ATTACHMENT0 + i); + glBlitNamedFramebuffer(glFBO->second.FBO[mBackRenderQueueIndex], + glFBO->second.ResolvedFBO[mBackRenderQueueIndex], + 0, + 0, + frameBuffer.Width, + frameBuffer.Height, + 0, + 0, + frameBuffer.Width, + frameBuffer.Height, + GL_COLOR_BUFFER_BIT, + GL_NEAREST); + } + } +} + +void RenderingSystemGL::renderUI() +{ + const RenderQueue& queue = mRenderQueues[mBackRenderQueueIndex]; + + for (int i = 0; i < queue.ActiveViewportCount; ++i) + { + const UIViewportData& viewportData = queue.UIViewports[i]; + + const IPlatformWindow* pWindow = static_cast(viewportData.pTargetWindow); + + pWindow->ActivateContext(); + + ImGui_ImplOpenGL3_RenderDrawData(viewportData.pDrawData.get()); + + glFlush(); + + pWindow->Present(); + } + + IPlatformApplication::GetInstance().GetMainWindow()->ActivateContext(); +} + +void RenderingSystemGL::flush() +{ + glFlush(); +} + +// TODO: Currently, all vertex attributes are always packed and uploaded interleaved +// regardless of their actual presence in the asset. +// Need to optimize vertex buffer generation to pack and upload only the active attributes. +IRenderingSystem::eResourceTransferResult RenderingSystemGL::uploadStaticMesh( + GpuStaticMeshHandle hPreparedGpuStaticMesh, uint64_t currentFrame) +{ + HO_ASSERT(hPreparedGpuStaticMesh.IsValid(), + "Invalid gpu static mesh handle. It might be not created by 'prepareUploadStatucMesh' yet."); + if (hPreparedGpuStaticMesh.Get()->hOrigin.IsNULL()) + { + HO_ASSERT(false, "Invalid static mesh asset. It might be removed from asset system while pending."); + return eResourceTransferResult::Failed; + } + const StaticMeshAsset* pOrigin = hPreparedGpuStaticMesh.Get()->hOrigin.Get(); + + const int32_t vertexCount = pOrigin->Positions.GetSize(); + + FixedArray vertexBuffer(vertexCount); + + for (int32_t i = 0; i < vertexCount; ++i) + { + vertexBuffer[i].Position = pOrigin->Positions[i]; + if (i < pOrigin->Normals.GetSize()) + { + vertexBuffer[i].Normal = pOrigin->Normals[i]; + } + if (i < pOrigin->Tangents.GetSize()) + { + vertexBuffer[i].Tangent = pOrigin->Tangents[i]; + } + if (i < pOrigin->UVs.GetSize()) + { + vertexBuffer[i].UV0 = pOrigin->UVs[i][0]; + vertexBuffer[i].UV1 = pOrigin->UVs[i][1]; + } + if (i < pOrigin->Colors.GetSize()) + { + vertexBuffer[i].Color0[0] = pOrigin->Colors[i][0].R; + vertexBuffer[i].Color0[1] = pOrigin->Colors[i][0].G; + vertexBuffer[i].Color0[2] = pOrigin->Colors[i][0].B; + vertexBuffer[i].Color0[3] = pOrigin->Colors[i][0].A; + } + } + + GLuint glVBO = 0; + glCreateBuffers(1, &glVBO); + glNamedBufferData( + glVBO, static_cast(sizeof(Vertex) * vertexCount), vertexBuffer.Data(), GL_STATIC_DRAW); + if (!ASSERT_GL()) + { + glDeleteBuffers(1, &glVBO); + return eResourceTransferResult::Failed; + } + GLuint glEBO = 0; + glCreateBuffers(1, &glEBO); + glNamedBufferData(glEBO, + static_cast(sizeof(int32_t) * pOrigin->Indices.GetSize()), + pOrigin->Indices.Data(), + GL_STATIC_DRAW); + if (!ASSERT_GL()) + { + glDeleteBuffers(1, &glVBO); + glDeleteBuffers(1, &glEBO); + return eResourceTransferResult::Failed; + } + StaticMeshNativeHandleGL nativeHandle; + nativeHandle.GlVbo = glVBO; + nativeHandle.GlEbo = glEBO; + hPreparedGpuStaticMesh.Get()->NativeHandlePoolIndex = mStaticMeshNativeHandlePool.Add(nativeHandle); + hPreparedGpuStaticMesh.Get()->LastUsedFrame = currentFrame; + hPreparedGpuStaticMesh.Get()->bPendingUpload = false; + return eResourceTransferResult::Success; +} + +IRenderingSystem::eResourceTransferResult RenderingSystemGL::uploadMaterial(GpuMaterialHandle hPreparedGpuMaterial, + uint64_t currentFrame) +{ + HO_ASSERT(hPreparedGpuMaterial.IsValid(), + "Invalid gpu material handle. It might be not created by 'prepareUploadMaterial' yet."); + + if (hPreparedGpuMaterial.Get()->hOrigin.IsNULL()) + { + HO_ASSERT(false, "Invalid material asset. It might be removed from asset system while pending."); + return eResourceTransferResult::Failed; + } + + const MaterialAsset* pOrigin = hPreparedGpuMaterial.Get()->hOrigin.Get(); + + for (int32_t i = 0; i < static_cast(eMaterialTextureUsage::Last); ++i) + { + if (hPreparedGpuMaterial.Get()->hGpuTextures[i].IsNULL()) + { + continue; + } + if (hPreparedGpuMaterial.Get()->hGpuTextures[i].Get()->bPendingUpload) + { + return eResourceTransferResult::NeedsRetry; + } + else + { + hPreparedGpuMaterial.Get()->TextureNativeHandlePoolIndices[i] = + hPreparedGpuMaterial.Get()->hGpuTextures[i].Get()->NativeHandlePoolIndex; + } + } + for (int32_t i = 0; i < static_cast(eShaderStage::Last); ++i) + { + if (hPreparedGpuMaterial.Get()->hGpuShaders[i].IsNULL()) + { + continue; + } + if (hPreparedGpuMaterial.Get()->hGpuShaders[i].Get()->bPendingUpload) + { + return eResourceTransferResult::NeedsRetry; + } + else + { + hPreparedGpuMaterial.Get()->ShaderNativeHandlePoolIndices[i] = + hPreparedGpuMaterial.Get()->hGpuShaders[i].Get()->NativeHandlePoolIndex; + } + } + + GpuMaterial::AttributeLayout attributes; + attributes.Ambient = Vector4(pOrigin->Ambient); + attributes.Diffuse = Vector4(pOrigin->Diffuse); + attributes.Specular = Vector4(pOrigin->Specular); + attributes.Albedo = Vector4(pOrigin->Albedo); + attributes.Emissive = Vector4(pOrigin->Emissive); + attributes.Shininess = pOrigin->Shininess; + attributes.Metallic = pOrigin->Metallic; + attributes.Roughness = pOrigin->Roughness; + attributes.IndexOfRefraction = pOrigin->IndexOfRefraction; + attributes.Opacity = pOrigin->Opacity; + attributes.AlphaThreshold = pOrigin->AlphaThreshold; + attributes.EmissiveIntensity = pOrigin->EmissiveIntensity; + attributes.NormalScale = pOrigin->NormalScale; + attributes.OcclusionStrength = pOrigin->OcclusionStrength; + + for (int32_t i = 0; i < static_cast(eMaterialTextureUsage::Last); ++i) + { + attributes.UVChannels[i].Value = pOrigin->UVChannels[i]; + const Matrix4x4 uvMatrix(Vector4(pOrigin->UVTransforms[i].GetCol0()), + Vector4(pOrigin->UVTransforms[i].GetCol1()), + Vector4(pOrigin->UVTransforms[i].GetCol2()), + Vector4::sUnitW); + attributes.UVMatrices[i] = uvMatrix; + attributes.bHasTexture[i].Value = static_cast(pOrigin->hTextures[i].IsValid()); + } + + GLuint glUBO; + glCreateBuffers(1, &glUBO); + glNamedBufferData(glUBO, sizeof(GpuMaterial::AttributeLayout), &attributes, GL_STATIC_DRAW); + if (!ASSERT_GL()) + { + glDeleteBuffers(1, &glUBO); + return eResourceTransferResult::Failed; + } + MaterialNativeHandleGL nativeHandle; + nativeHandle.GlUbo = glUBO; + hPreparedGpuMaterial.Get()->NativeHandlePoolIndex = mMaterialNativeHandlePool.Add(nativeHandle); + hPreparedGpuMaterial.Get()->LastUsedFrame = currentFrame; + hPreparedGpuMaterial.Get()->bPendingUpload = false; + return eResourceTransferResult::Success; +} + +IRenderingSystem::eResourceTransferResult RenderingSystemGL::uploadTexture(GpuTextureHandle hPreparedGpuTexture, + uint64_t currentFrame) +{ + HO_ASSERT(hPreparedGpuTexture.IsValid(), + "Invalid gpu texture handle. It might be not created by 'prepareUploadTexture' yet."); + + if (hPreparedGpuTexture.Get()->hOrigin.IsNULL()) + { + HO_ASSERT(false, "Invalid texture asset. It might be removed from asset system while pending."); + return eResourceTransferResult::Failed; + } + + const TextureAsset* pOrigin = hPreparedGpuTexture.Get()->hOrigin.Get(); + + GLuint glTexture; + glCreateTextures(toGlTextureType(pOrigin->Type), 1, &glTexture); + + if (!ASSERT_GL()) + { + return eResourceTransferResult::Failed; + } + + switch (pOrigin->Type) + { + case eTextureType::Texture1D: + glTextureStorage1D(glTexture, pOrigin->MipLevels, toGlInternalFormat(pOrigin->Format), pOrigin->Width); + break; + case eTextureType::Texture2D: + case eTextureType::TextureCubeMap: + glTextureStorage2D( + glTexture, pOrigin->MipLevels, toGlInternalFormat(pOrigin->Format), pOrigin->Width, pOrigin->Height); + break; + case eTextureType::Texture3D: + glTextureStorage3D(glTexture, + pOrigin->MipLevels, + toGlInternalFormat(pOrigin->Format), + pOrigin->Width, + pOrigin->Height, + pOrigin->Depth); + break; + case eTextureType::Texture1DArray: + glTextureStorage2D( + glTexture, pOrigin->MipLevels, toGlInternalFormat(pOrigin->Format), pOrigin->Width, pOrigin->ArraySize); + break; + case eTextureType::Texture2DArray: + glTextureStorage3D(glTexture, + pOrigin->MipLevels, + toGlInternalFormat(pOrigin->Format), + pOrigin->Width, + pOrigin->Height, + pOrigin->ArraySize); + break; + case eTextureType::TextureCubeMapArray: + glTextureStorage3D(glTexture, + pOrigin->MipLevels, + toGlInternalFormat(pOrigin->Format), + pOrigin->Width, + pOrigin->Height, + pOrigin->ArraySize * 6); + break; + default: + HO_ASSERT(false, "Invalid texture type."); + glDeleteTextures(1, &glTexture); + return eResourceTransferResult::Failed; + } + + if (!ASSERT_GL()) + { + glDeleteTextures(1, &glTexture); + return eResourceTransferResult::Failed; + } + + for (int32_t i = 0; i < pOrigin->MipLevels; ++i) + { + const int32_t mipWidth = math::Max(1, static_cast(static_cast(pOrigin->Width) >> i)); + const int32_t mipHeight = math::Max(1, static_cast(static_cast(pOrigin->Height) >> i)); + const int32_t mipDepth = math::Max(1, static_cast(pOrigin->Depth >> i)); + + if (isUncompressedFormat(pOrigin->Format)) + { + switch (pOrigin->Type) + { + case eTextureType::Texture1D: + glTextureSubImage1D(glTexture, + i, + 0, + mipWidth, + toGlExternalFormat(pOrigin->Format), + toGlExternalType(pOrigin->Format), + pOrigin->DataBlob.Data() + pOrigin->Layouts[i].Offset); + break; + case eTextureType::Texture2D: + glTextureSubImage2D(glTexture, + i, + 0, + 0, + mipWidth, + mipHeight, + toGlExternalFormat(pOrigin->Format), + toGlExternalType(pOrigin->Format), + pOrigin->DataBlob.Data() + pOrigin->Layouts[i].Offset); + break; + case eTextureType::TextureCubeMap: + glTextureSubImage3D(glTexture, + i, + 0, + 0, + 0, + mipWidth, + mipHeight, + 6, + toGlExternalFormat(pOrigin->Format), + toGlExternalType(pOrigin->Format), + pOrigin->DataBlob.Data() + pOrigin->Layouts[i * 6].Offset); + break; + case eTextureType::Texture3D: + glTextureSubImage3D(glTexture, + i, + 0, + 0, + 0, + mipWidth, + mipHeight, + mipDepth, + toGlExternalFormat(pOrigin->Format), + toGlExternalType(pOrigin->Format), + pOrigin->DataBlob.Data() + pOrigin->Layouts[i].Offset); + break; + case eTextureType::Texture1DArray: + for (int32_t arrIdx = 0; arrIdx < pOrigin->ArraySize; ++arrIdx) + { + const int32_t linearIdx = (arrIdx * pOrigin->MipLevels) + i; + glTextureSubImage2D(glTexture, + i, + 0, + arrIdx, + mipWidth, + 1, + toGlExternalFormat(pOrigin->Format), + toGlExternalType(pOrigin->Format), + pOrigin->DataBlob.Data() + pOrigin->Layouts[linearIdx].Offset); + } + break; + case eTextureType::Texture2DArray: + for (int32_t arrIdx = 0; arrIdx < pOrigin->ArraySize; ++arrIdx) + { + const int32_t linearIdx = (arrIdx * pOrigin->MipLevels) + i; + glTextureSubImage3D(glTexture, + i, + 0, + 0, + arrIdx, + mipWidth, + mipHeight, + 1, + toGlExternalFormat(pOrigin->Format), + toGlExternalType(pOrigin->Format), + pOrigin->DataBlob.Data() + pOrigin->Layouts[linearIdx].Offset); + } + break; + case eTextureType::TextureCubeMapArray: + for (int32_t arrIdx = 0; arrIdx < pOrigin->ArraySize; ++arrIdx) + { + const int32_t linearIdx = (arrIdx * pOrigin->MipLevels * 6) + (i * 6); + + glTextureSubImage3D(glTexture, + i, + 0, + 0, + arrIdx * 6, + mipWidth, + mipHeight, + 6, + toGlExternalFormat(pOrigin->Format), + toGlExternalType(pOrigin->Format), + pOrigin->DataBlob.Data() + pOrigin->Layouts[linearIdx].Offset); + } + break; + default: + HO_ASSERT(false, "Invalid texture type."); + glDeleteTextures(1, &glTexture); + return eResourceTransferResult::Failed; + } + } + else + { + switch (pOrigin->Type) + { + case eTextureType::Texture1D: + glCompressedTextureSubImage1D(glTexture, + i, + 0, + mipWidth, + toGlExternalFormat(pOrigin->Format), + pOrigin->Layouts[i].Size, + pOrigin->DataBlob.Data() + pOrigin->Layouts[i].Offset); + break; + case eTextureType::Texture2D: + glCompressedTextureSubImage2D(glTexture, + i, + 0, + 0, + mipWidth, + mipHeight, + toGlExternalFormat(pOrigin->Format), + pOrigin->Layouts[i].Size, + pOrigin->DataBlob.Data() + pOrigin->Layouts[i].Offset); + break; + case eTextureType::TextureCubeMap: + glCompressedTextureSubImage3D(glTexture, + i, + 0, + 0, + 0, + mipWidth, + mipHeight, + 6, + toGlExternalFormat(pOrigin->Format), + pOrigin->Layouts[i * 6].Size * 6, + pOrigin->DataBlob.Data() + pOrigin->Layouts[i * 6].Offset); + break; + case eTextureType::Texture3D: + glCompressedTextureSubImage3D(glTexture, + i, + 0, + 0, + 0, + mipWidth, + mipHeight, + mipDepth, + toGlExternalFormat(pOrigin->Format), + pOrigin->Layouts[i * 6].Size, + pOrigin->DataBlob.Data() + pOrigin->Layouts[i].Offset); + break; + case eTextureType::Texture1DArray: + for (int32_t arrIdx = 0; arrIdx < pOrigin->ArraySize; ++arrIdx) + { + const int32_t linearIdx = (arrIdx * pOrigin->MipLevels) + i; + glCompressedTextureSubImage2D(glTexture, + i, + 0, + arrIdx, + mipWidth, + 1, + toGlExternalFormat(pOrigin->Format), + pOrigin->Layouts[linearIdx].Size, + pOrigin->DataBlob.Data() + pOrigin->Layouts[linearIdx].Offset); + } + break; + case eTextureType::Texture2DArray: + for (int32_t arrIdx = 0; arrIdx < pOrigin->ArraySize; ++arrIdx) + { + const int32_t linearIdx = (arrIdx * pOrigin->MipLevels) + i; + glCompressedTextureSubImage3D(glTexture, + i, + 0, + 0, + arrIdx, + mipWidth, + mipHeight, + 1, + toGlExternalFormat(pOrigin->Format), + pOrigin->Layouts[linearIdx].Size, + pOrigin->DataBlob.Data() + pOrigin->Layouts[linearIdx].Offset); + } + break; + case eTextureType::TextureCubeMapArray: + for (int32_t arrIdx = 0; arrIdx < pOrigin->ArraySize; ++arrIdx) + { + const int32_t linearIdx = (arrIdx * pOrigin->MipLevels * 6) + (i * 6); + + glCompressedTextureSubImage3D(glTexture, + i, + 0, + 0, + arrIdx * 6, + mipWidth, + mipHeight, + 6, + toGlExternalFormat(pOrigin->Format), + pOrigin->Layouts[linearIdx].Size * 6, + pOrigin->DataBlob.Data() + pOrigin->Layouts[linearIdx].Offset); + } + break; + default: + HO_ASSERT(false, "Invalid texture type."); + glDeleteTextures(1, &glTexture); + return eResourceTransferResult::Failed; + } + } + if (!ASSERT_GL()) + { + glDeleteTextures(1, &glTexture); + return eResourceTransferResult::Failed; + } + } + glTextureParameteri(glTexture, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTextureParameteri(glTexture, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTextureParameteri(glTexture, GL_TEXTURE_MAX_LEVEL, pOrigin->MipLevels - 1); + + if (pOrigin->Type == eTextureType::TextureCubeMap || pOrigin->Type == eTextureType::TextureCubeMapArray) + { + glTextureParameteri(glTexture, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTextureParameteri(glTexture, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTextureParameteri(glTexture, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + } + + if (!ASSERT_GL()) + { + glDeleteTextures(1, &glTexture); + return eResourceTransferResult::Failed; + } + + TextureNativeHandleGL nativeHandle; + nativeHandle.GlTexture = glTexture; + hPreparedGpuTexture.Get()->NativeHandlePoolIndex = mTextureNativeHandlePool.Add(nativeHandle); + hPreparedGpuTexture.Get()->LastUsedFrame = currentFrame; + hPreparedGpuTexture.Get()->bPendingUpload = false; + return eResourceTransferResult::Success; +} + +IRenderingSystem::eResourceTransferResult RenderingSystemGL::uploadShader(GpuShaderHandle hPreparedGpuShader, + uint64_t currentFrame) +{ + HO_ASSERT(hPreparedGpuShader.IsValid(), + "Invalid gpu shader handle. It might be not created by 'prepareUploadShader' yet."); + + if (hPreparedGpuShader.Get()->hOrigin.IsNULL()) + { + HO_ASSERT(false, "Invalid shader asset. It might be removed from asset system while pending."); + return eResourceTransferResult::Failed; + } + + const ShaderAsset* pOrigin = hPreparedGpuShader.Get()->hOrigin.Get(); + + const GLuint glShader = glCreateShader(toGlShaderType(pOrigin->Stage)); + if (!ASSERT_GL()) + { + glDeleteShader(glShader); + return eResourceTransferResult::Failed; + } + glShaderBinary(1, + &glShader, + GL_SHADER_BINARY_FORMAT_SPIR_V, + pOrigin->Binary.Data(), + static_cast(pOrigin->Binary.GetSize() * sizeof(uint32_t))); + if (!ASSERT_GL()) + { + glDeleteShader(glShader); + return eResourceTransferResult::Failed; + } + glSpecializeShader(glShader, "main", 0, nullptr, nullptr); + + GLint isCompiled = 0; + glGetShaderiv(glShader, GL_COMPILE_STATUS, &isCompiled); + if (isCompiled == GL_FALSE) + { + HO_ASSERT(false, "Shader is not compiled."); + glDeleteShader(glShader); + return eResourceTransferResult::Failed; + } + + const GLuint glProgram = glCreateProgram(); + + glProgramParameteri(glProgram, GL_PROGRAM_SEPARABLE, GL_TRUE); + + glAttachShader(glProgram, glShader); + glLinkProgram(glProgram); + + GLint isLinked = 0; + glGetProgramiv(glProgram, GL_LINK_STATUS, &isLinked); + if (isLinked == GL_FALSE) + { + std::string glLogInfo; + GLint glLogSize = 0; + glGetProgramiv(glProgram, GL_INFO_LOG_LENGTH, &glLogSize); + glLogInfo.resize(glLogSize); + glGetProgramInfoLog(glProgram, glLogSize, nullptr, glLogInfo.data()); + HO_LOG_ERROR((std::string("Shader Link Failed:\n") + glLogInfo).c_str()); + HO_ASSERT(false, "Failed to link shader."); + glDeleteShader(glShader); + glDeleteProgram(glProgram); + return eResourceTransferResult::Failed; + } + + glDetachShader(glProgram, glShader); + glDeleteShader(glShader); + + ShaderNativeHandleGL nativeHandle; + nativeHandle.GlProgram = glProgram; + hPreparedGpuShader.Get()->NativeHandlePoolIndex = mShaderNativeHandlePool.Add(nativeHandle); + hPreparedGpuShader.Get()->LastUsedFrame = currentFrame; + hPreparedGpuShader.Get()->bPendingUpload = false; + return eResourceTransferResult::Success; +} + +IRenderingSystem::eResourceTransferResult RenderingSystemGL::unloadStaticMesh(GpuStaticMeshHandle hGpuStaticMesh) +{ + if (hGpuStaticMesh.IsNULL()) + { + return eResourceTransferResult::Failed; + } + const StaticMeshNativeHandleGL* nativeHandle = + mStaticMeshNativeHandlePool.Get(hGpuStaticMesh.Get()->NativeHandlePoolIndex); + if (!nativeHandle) + { + return eResourceTransferResult::Failed; + } + glDeleteBuffers(1, &nativeHandle->GlVbo); + glDeleteBuffers(1, &nativeHandle->GlEbo); + mStaticMeshNativeHandlePool.Remove(hGpuStaticMesh.Get()->NativeHandlePoolIndex); + hGpuStaticMesh.Get()->NativeHandlePoolIndex = 0; + finishUnloadStaticMesh(hGpuStaticMesh); + return eResourceTransferResult::Success; +} + +IRenderingSystem::eResourceTransferResult RenderingSystemGL::unloadMaterial(GpuMaterialHandle hGpuMaterial) +{ + if (hGpuMaterial.IsNULL()) + { + return eResourceTransferResult::Failed; + } + const MaterialNativeHandleGL* nativeHandle = + mMaterialNativeHandlePool.Get(hGpuMaterial.Get()->NativeHandlePoolIndex); + if (!nativeHandle) + { + return eResourceTransferResult::Failed; + } + glDeleteBuffers(1, &nativeHandle->GlUbo); + mMaterialNativeHandlePool.Remove(hGpuMaterial.Get()->NativeHandlePoolIndex); + hGpuMaterial.Get()->NativeHandlePoolIndex = 0; + finishUnloadMaterial(hGpuMaterial); + return eResourceTransferResult::Success; +} + +IRenderingSystem::eResourceTransferResult RenderingSystemGL::unloadTexture(GpuTextureHandle hGpuTexture) +{ + if (hGpuTexture.IsNULL()) + { + return eResourceTransferResult::Failed; + } + const TextureNativeHandleGL* nativeHandle = mTextureNativeHandlePool.Get(hGpuTexture.Get()->NativeHandlePoolIndex); + if (!nativeHandle) + { + return eResourceTransferResult::Failed; + } + glDeleteTextures(1, &nativeHandle->GlTexture); + mTextureNativeHandlePool.Remove(hGpuTexture.Get()->NativeHandlePoolIndex); + hGpuTexture.Get()->NativeHandlePoolIndex = 0; + finishUnloadTexture(hGpuTexture); + return eResourceTransferResult::Success; +} + +IRenderingSystem::eResourceTransferResult RenderingSystemGL::unloadShader(GpuShaderHandle hGpuShader) +{ + if (hGpuShader.IsNULL()) + { + return eResourceTransferResult::Failed; + } + const ShaderNativeHandleGL* nativeHandle = mShaderNativeHandlePool.Get(hGpuShader.Get()->NativeHandlePoolIndex); + if (!nativeHandle) + { + return eResourceTransferResult::Failed; + } + glDeleteProgram(nativeHandle->GlProgram); + mShaderNativeHandlePool.Remove(hGpuShader.Get()->NativeHandlePoolIndex); + hGpuShader.Get()->NativeHandlePoolIndex = 0; + finishUnloadShader(hGpuShader); + return eResourceTransferResult::Success; +} + +void RenderingSystemGL::cancelInitialization() +{ + glDeleteVertexArrays(1, &mGlVAO); + glDeleteProgramPipelines(1, &mGlProgramPipeline); + glDeleteProgram(mGlDefaultVS); + glDeleteProgram(mGlDefaultPhongFS); + glDeleteProgram(mGlDefaultPhongMaskedFS); + glDeleteProgram(mGlDefaultPbrFS); + glDeleteProgram(mGlDefaultPbrMaskedFS); + glDeleteProgram(mGlDefaultSkyMapVS); + glDeleteProgram(mGlDefaultSkyMapFS); + glDeleteProgram(mGlDefaultWorldGizmoVS); + glDeleteProgram(mGlDefaultWorldGizmoFS); + glDeleteTextures(1, &mGlDefaultCubeMap); + glDeleteBuffers(1, &mGlMatrixUBO); + glDeleteBuffers(1, &mGlLightUBO); + glDeleteBuffers(1, &mGlGeneralPurposeUBO); +} + +void RenderingSystemGL::cancelFrameBufferCreation( + GLuint* glFBO, + GLuint* glResolvedFBO, + GLuint (*pGlRenderTargets)[static_cast(eRenderTargetType::Last)], + GLuint (*pGlResolvedRenderTargets)[static_cast(eRenderTargetType::Last)]) +{ + if (pGlRenderTargets) + { + glDeleteTextures(static_cast(eRenderTargetType::Last), pGlRenderTargets[0]); + glDeleteTextures(static_cast(eRenderTargetType::Last), pGlRenderTargets[1]); + } + if (pGlResolvedRenderTargets) + { + glDeleteTextures(static_cast(eRenderTargetType::Last), pGlResolvedRenderTargets[0]); + glDeleteTextures(static_cast(eRenderTargetType::Last), pGlResolvedRenderTargets[1]); + } + glDeleteFramebuffers(2, glFBO); + glDeleteFramebuffers(2, glResolvedFBO); +} + +GLenum toGlRenderTargetFormat(IRenderingSystem::eRenderTargetFormat format) +{ + switch (format) + { + case IRenderingSystem::eRenderTargetFormat::R8G8B8A8_UNORM: + case IRenderingSystem::eRenderTargetFormat::B8G8R8A8_UNORM: + return GL_RGBA8; + case IRenderingSystem::eRenderTargetFormat::R16G16B16A16_FLOAT: + return GL_RGBA16F; + case IRenderingSystem::eRenderTargetFormat::R32_FLOAT: + return GL_R32F; + case IRenderingSystem::eRenderTargetFormat::R16G16_FLOAT: + return GL_RG16F; + case IRenderingSystem::eRenderTargetFormat::R32G32B32A32_FLOAT: + return GL_RGBA32F; + case IRenderingSystem::eRenderTargetFormat::D24_UNORM_S8_UINT: + return GL_DEPTH24_STENCIL8; + case IRenderingSystem::eRenderTargetFormat::D32_FLOAT: + return GL_DEPTH_COMPONENT32F; + case IRenderingSystem::eRenderTargetFormat::None: + default: + HO_ASSERT(false, "Invalid render target format."); + return GL_NONE; + } +} + +GLenum toGlTextureType(eTextureType type) +{ + switch (type) + { + case eTextureType::Texture1D: + return GL_TEXTURE_1D; + case eTextureType::Texture2D: + return GL_TEXTURE_2D; + case eTextureType::Texture3D: + return GL_TEXTURE_3D; + case eTextureType::TextureCubeMap: + return GL_TEXTURE_CUBE_MAP; + case eTextureType::Texture1DArray: + return GL_TEXTURE_1D_ARRAY; + case eTextureType::Texture2DArray: + return GL_TEXTURE_2D_ARRAY; + case eTextureType::TextureCubeMapArray: + return GL_TEXTURE_CUBE_MAP_ARRAY; + default: + HO_ASSERT(false, "Invalid texture type."); + return GL_NONE; + } +} + +GLenum toGlInternalFormat(eTextureFormat format) +{ + switch (format) + { + case eTextureFormat::R8_UNORM: + return GL_R8; + case eTextureFormat::R8G8_UNORM: + return GL_RG8; + case eTextureFormat::R8G8B8A8_UNORM: + return GL_RGBA8; + case eTextureFormat::R8_SRGB: + case eTextureFormat::R8G8_SRGB: + return GL_SRGB8; + case eTextureFormat::R8G8B8A8_SRGB: + return GL_SRGB8_ALPHA8; + case eTextureFormat::R16_FLOAT: + return GL_R16F; + case eTextureFormat::R16G16_FLOAT: + return GL_RG16F; + case eTextureFormat::R16G16B16A16_FLOAT: + return GL_RGBA16F; + case eTextureFormat::R32_FLOAT: + return GL_R32F; + case eTextureFormat::BC1_UNORM: + return GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; + case eTextureFormat::BC1_SRGB: + return GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT; + case eTextureFormat::BC3_UNORM: + return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + case eTextureFormat::BC3_SRGB: + return GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT; + case eTextureFormat::BC4_UNORM: + return GL_COMPRESSED_RED_RGTC1; + case eTextureFormat::BC4_SNORM: + return GL_COMPRESSED_SIGNED_RED_RGTC1; + case eTextureFormat::BC5_UNORM: + return GL_COMPRESSED_RG_RGTC2; + case eTextureFormat::BC5_SNORM: + return GL_COMPRESSED_SIGNED_RG_RGTC2; + case eTextureFormat::BC6H_UF16: + return GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT; + case eTextureFormat::BC6H_SF16: + return GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT; + case eTextureFormat::BC7_UNORM: + return GL_COMPRESSED_RGBA_BPTC_UNORM; + case eTextureFormat::BC7_SRGB: + return GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM; + default: + HO_ASSERT(false, "Invalid texture format."); + return GL_NONE; + } +} + +GLenum toGlExternalFormat(eTextureFormat format) +{ + switch (format) + { + case eTextureFormat::R8_UNORM: + case eTextureFormat::R8_SRGB: + case eTextureFormat::R16_FLOAT: + case eTextureFormat::R32_FLOAT: + return GL_RED; + case eTextureFormat::R8G8_UNORM: + case eTextureFormat::R8G8_SRGB: + case eTextureFormat::R16G16_FLOAT: + return GL_RG; + case eTextureFormat::R8G8B8A8_UNORM: + case eTextureFormat::R8G8B8A8_SRGB: + case eTextureFormat::R16G16B16A16_FLOAT: + return GL_RGBA; + case eTextureFormat::BC1_UNORM: + return GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; + case eTextureFormat::BC1_SRGB: + return GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT; + + case eTextureFormat::BC3_UNORM: + return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + case eTextureFormat::BC3_SRGB: + return GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT; + + case eTextureFormat::BC4_UNORM: + return GL_COMPRESSED_RED_RGTC1; + case eTextureFormat::BC4_SNORM: + return GL_COMPRESSED_SIGNED_RED_RGTC1; + + case eTextureFormat::BC5_UNORM: + return GL_COMPRESSED_RG_RGTC2; + case eTextureFormat::BC5_SNORM: + return GL_COMPRESSED_SIGNED_RG_RGTC2; + + case eTextureFormat::BC6H_UF16: + return GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT; + case eTextureFormat::BC6H_SF16: + return GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT; + + case eTextureFormat::BC7_UNORM: + return GL_COMPRESSED_RGBA_BPTC_UNORM; + case eTextureFormat::BC7_SRGB: + return GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM; + default: + HO_ASSERT(false, "Invalid or unsupported texture format."); + return GL_NONE; + } +} + +GLenum toGlExternalType(eTextureFormat format) +{ + switch (format) + { + case eTextureFormat::R8_UNORM: + case eTextureFormat::R8G8_UNORM: + case eTextureFormat::R8G8B8A8_UNORM: + case eTextureFormat::R8_SRGB: + case eTextureFormat::R8G8_SRGB: + case eTextureFormat::R8G8B8A8_SRGB: + return GL_UNSIGNED_BYTE; + case eTextureFormat::R16_FLOAT: + case eTextureFormat::R16G16_FLOAT: + case eTextureFormat::R16G16B16A16_FLOAT: + return GL_HALF_FLOAT; + case eTextureFormat::R32_FLOAT: + return GL_FLOAT; + default: + HO_ASSERT(false, "Invalid or unsupported texture format."); + return GL_NONE; + } +} + +bool isUncompressedFormat(eTextureFormat format) +{ + switch (format) + { + case eTextureFormat::R8_UNORM: + case eTextureFormat::R8G8_UNORM: + case eTextureFormat::R8G8B8A8_UNORM: + case eTextureFormat::R8_SRGB: + case eTextureFormat::R8G8_SRGB: + case eTextureFormat::R8G8B8A8_SRGB: + case eTextureFormat::R16_FLOAT: + case eTextureFormat::R16G16_FLOAT: + case eTextureFormat::R16G16B16A16_FLOAT: + case eTextureFormat::R32_FLOAT: + return true; + case eTextureFormat::BC1_UNORM: + case eTextureFormat::BC1_SRGB: + case eTextureFormat::BC3_UNORM: + case eTextureFormat::BC3_SRGB: + case eTextureFormat::BC4_UNORM: + case eTextureFormat::BC4_SNORM: + case eTextureFormat::BC5_UNORM: + case eTextureFormat::BC5_SNORM: + case eTextureFormat::BC6H_UF16: + case eTextureFormat::BC6H_SF16: + case eTextureFormat::BC7_UNORM: + case eTextureFormat::BC7_SRGB: + return false; + default: + HO_ASSERT(false, "Invalid texture format."); + return false; + } +} + +GLenum toGlShaderType(eShaderStage stage) +{ + switch (stage) + { + case eShaderStage::VertexShader: + return GL_VERTEX_SHADER; + case eShaderStage::FragmentShader: + return GL_FRAGMENT_SHADER; + default: + HO_ASSERT(false, "Invalid shader stage."); + return GL_NONE; + } +} + +GLbitfield toGlShaderBitfield(eShaderStage stage) +{ + switch (stage) + { + case eShaderStage::VertexShader: + return GL_VERTEX_SHADER_BIT; + case eShaderStage::FragmentShader: + return GL_FRAGMENT_SHADER_BIT; + default: + HO_ASSERT(false, "Invalid shader stage."); + return GL_NONE; + } +} + +// NOLINTBEGIN +// This function no lint for debugging +bool ASSERT_GL() +{ +#if defined(DEBUG) || defined(_DEBUG) + const GLenum glError = glGetError(); + + switch (glError) + { + case GL_INVALID_ENUM: + HO_ASSERT(false, + "An unacceptable value is specified for an enumerated argument. The offending command is ignored " + "and has no other side effect than to set the error flag."); + return false; + case GL_INVALID_VALUE: + HO_ASSERT(false, + "A numeric argument is out of range. The offending command is ignored and has no other side " + "effect than to set the error flag."); + return false; + case GL_INVALID_OPERATION: + HO_ASSERT(false, + "The specified operation is not allowed in the current state. The offending command is ignored " + "and has no " + "other side effect than to set the error flag."); + return false; + + case GL_INVALID_FRAMEBUFFER_OPERATION: + HO_ASSERT(false, + "The framebuffer object is not complete. The offending command is ignored and has no other side " + "effect than to " + "set the error flag."); + return false; + case GL_OUT_OF_MEMORY: + HO_ASSERT(false, + "There is not enough memory left to execute the command. The state of the GL is undefined, " + "except for the " + "state of the error flags, after this error is recorded."); + return false; + + case GL_STACK_UNDERFLOW: + HO_ASSERT( + false, + "An attempt has been made to perform an operation that would cause an internal stack to underflow."); + return false; + + case GL_STACK_OVERFLOW: + HO_ASSERT( + false, + "An attempt has been made to perform an operation that would cause an internal stack to overflow."); + return false; + default: + break; + } +#endif + return true; +} + +// NOLINTEND + +} // namespace ho \ No newline at end of file diff --git a/Subsystems/Rendering/GL/RenderingSystemGL.h b/Subsystems/Rendering/GL/RenderingSystemGL.h new file mode 100644 index 0000000..03d114c --- /dev/null +++ b/Subsystems/Rendering/GL/RenderingSystemGL.h @@ -0,0 +1,115 @@ +#pragma once + +#include + +#include "../IRenderingSystem.h" +#include "NativeHandlesGL.h" + +namespace ho +{ +class IRenderingSystem; + +class RenderingSystemGL final : public IRenderingSystem +{ + friend void IRenderingSystem::createInstance(eGraphicsAPI api); + + public: + bool CreateFrameBuffer(const FrameBufferDesc& frameBufferDesc) override; + + bool DestroyFrameBuffer(StringHandle hFrameBufferName) override; + + void* GetRenderTargetNativeHandle(StringHandle hFrameBufferName, + eRenderTargetType type, + bool bRequireMultisample = false) const override; + + private: + struct GlFBO + { + GLuint FBO[2] = {}; + GLuint ResolvedFBO[2] = {}; + }; + + RenderingSystemGL(); + ~RenderingSystemGL() override = default; + + bool init() override; + void releaseAllResources() override; + + void applyMaterial(GpuMaterialHandle hGpuMaterial) override; + void executeDrawCommand(const DrawCommand& command) override; + + void executeSkyMapPass(const GpuTexture* pSkyMap) override; + void executeInWorldDebugDrawPass() override; + void executeOverlayDebugDrawPass() override; + + void prepareExecuteRenderQueue() override; + void prepareExecuteRenderPass(const RenderPass& pass) override; + void prepareExecuteOpaqueDrawCommands() override; + void prepareExecuteMaskedDrawCommands() override; + void prepareExecuteBlendedDrawCommands() override; + void finishExecuteRenderPass(const RenderPass& pass) override; + void finishExecuteRenderQueue() override; + + void resolveFrameBuffer(const FrameBuffer& frameBuffer) override; + + void renderUI() override; + + void flush() override; + + eResourceTransferResult uploadStaticMesh(GpuStaticMeshHandle hPreparedGpuStaticMesh, + uint64_t currentFrame) override; + eResourceTransferResult uploadMaterial(GpuMaterialHandle hPreparedGpuMaterial, uint64_t currentFrame) override; + eResourceTransferResult uploadTexture(GpuTextureHandle hPreparedGpuTexture, uint64_t currentFrame) override; + eResourceTransferResult uploadShader(GpuShaderHandle hPreparedGpuShader, uint64_t currentFrame) override; + + eResourceTransferResult unloadStaticMesh(GpuStaticMeshHandle hGpuStaticMesh) override; + eResourceTransferResult unloadMaterial(GpuMaterialHandle hGpuMaterial) override; + eResourceTransferResult unloadTexture(GpuTextureHandle hGpuTexture) override; + eResourceTransferResult unloadShader(GpuShaderHandle hGpuShader) override; + + void cancelInitialization(); + + void cancelFrameBufferCreation(GLuint* glFBO, + GLuint* glResolvedFBO, + GLuint (*pGlRenderTargets)[static_cast(eRenderTargetType::Last)], + GLuint (*pGlResolvedRenderTargets)[static_cast(eRenderTargetType::Last)]); + + std::unordered_map mNameToGlFboMap; + + GLuint mGlVAO = 0; + GLuint mAttribBindingIndex = 0; + GLuint mPosAttribIndex = 0; + GLuint mNormalAttribIndex = 1; + GLuint mTangentsAttribIndex = 2; + GLuint mUV0AttribIndex = 3; + GLuint mUV1AttribIndex = 4; + GLuint mColorAttribIndex = 5; + + GLuint mGlProgramPipeline = 0; + GLuint mGlDefaultVS = 0; + GLuint mGlDefaultPhongFS = 0; + GLuint mGlDefaultPhongMaskedFS = 0; + GLuint mGlDefaultPbrFS = 0; + GLuint mGlDefaultPbrMaskedFS = 0; + GLuint mGlDefaultSkyMapVS = 0; + GLuint mGlDefaultSkyMapFS = 0; + GLuint mGlDefaultWorldGizmoVS = 0; + GLuint mGlDefaultWorldGizmoFS = 0; + + GLuint mGlDefaultCubeMap = 0; + + GLuint mGlMatrixUBO = 0; + GLuint mGlLightUBO = 0; + GLuint mGlGeneralPurposeUBO = 0; + + GLuint mMaterialUboIndex = 0; + GLuint mMatrixUboIndex = 1; + GLuint mLightUboIndex = 2; + GLuint mGeneralPurposeUboIndex = 3; + + ObjectPool mStaticMeshNativeHandlePool; + ObjectPool mMaterialNativeHandlePool; + ObjectPool mTextureNativeHandlePool; + ObjectPool mShaderNativeHandlePool; +}; +} // namespace ho \ No newline at end of file diff --git a/Subsystems/Rendering/GL/Shaders/Default.vert b/Subsystems/Rendering/GL/Shaders/Default.vert new file mode 100644 index 0000000..58c359d --- /dev/null +++ b/Subsystems/Rendering/GL/Shaders/Default.vert @@ -0,0 +1,40 @@ +#version 460 core + +const int UNIFORM_MATRICES_BINDING = 1; + +layout (location = 0) in vec3 a_Pos; +layout (location = 1) in vec3 a_Normal; +layout (location = 2) in vec4 a_Tangent; +layout (location = 3) in vec2 a_UV0; +layout (location = 4) in vec2 a_UV1; +layout (location = 5) in vec4 a_Color; + +layout (std140, row_major, binding = UNIFORM_MATRICES_BINDING) uniform Matrices +{ + mat4 World; + mat4 InvWorld; + mat4 View; + mat4 Proj; +} u_Matrices; + +out gl_PerVertex +{ + vec4 gl_Position; +}; + +layout(location = 0) out vec3 v_WorldPos; +layout(location = 1) out vec3 v_Normal; +layout(location = 2) out vec4 v_Tangent; +layout(location = 3) out vec2 v_UV0; +layout(location = 4) out vec2 v_UV1; +layout(location = 5) out vec4 v_Color; + +void main() { + v_WorldPos = (u_Matrices.World * vec4(a_Pos, 1.0f)).xyz; + v_Normal = (transpose(u_Matrices.InvWorld) * vec4(a_Normal, 0.0f)).xyz; + v_Tangent = vec4((u_Matrices.World * vec4(a_Tangent.xyz, 0.0f)).xyz, a_Tangent.w); + v_UV0 = a_UV0; + v_UV1 = a_UV1; + v_Color = a_Color; + gl_Position = u_Matrices.Proj * u_Matrices.View * u_Matrices.World * vec4(a_Pos, 1.0f); +} \ No newline at end of file diff --git a/Subsystems/Rendering/GL/Shaders/DefaultPBR.frag b/Subsystems/Rendering/GL/Shaders/DefaultPBR.frag new file mode 100644 index 0000000..ed0b890 --- /dev/null +++ b/Subsystems/Rendering/GL/Shaders/DefaultPBR.frag @@ -0,0 +1,330 @@ +#version 460 core + +const int UNIFORM_MATERIAL_BINDING = 0; +const int UNIFORM_MATRICES_BINDING = 1; +const int UNIFORM_LIGHTS_BINDING = 2; +const int UNIFORM_GENERAL_BINDING = 3; + +const int TEX_DIFFUSE_BINDING = 1; +const int TEX_SPECULAR_BINDING = 2; +const int TEX_OPACITY_BINDING = 3; +const int TEX_NORMAL_BINDING = 4; +const int TEX_ALBEDO_BINDING = 5; +const int TEX_METALLIC_ROUGHNESS_BINDING = 6; +const int TEX_EMISSIVE_BINDING = 7; +const int TEX_AMBIENT_OCCLUSION_BINDING = 8; + +const int TEX_SKY_MAP_BINDING = 9; +const int TEX_IRRADIANCE_MAP_BINDING = 10; + +const int MAX_DIR_LIGHT_COUNT = 4; +const int MAX_POINT_LIGHT_COUNT = 32; +const int MAX_SPOT_LIGHT_COUNT = 16; + +const float PI = 3.14159265359; + +layout (std140, binding = UNIFORM_MATERIAL_BINDING) uniform Material +{ + vec4 Ambient; + vec4 Diffuse; + vec4 Specular; + vec4 Albedo; + vec4 Emissive; + + float Shininess; + float Metallic; + float Roughness; + float IndexOfRefraction; + + float Opacity; + float AlphaThreshold; + float EmissiveIntensity; + float NormalScale; + float OcclusionStrength; + + int UVChannels[9]; + mat4 UVMatrices[9]; + int HasTextures[9]; +} u_MatAttr; + +struct DirLight { + vec3 Direction; + vec3 Color; + float Intensity; +}; + +struct PointLight { + vec3 Position; + vec3 Color; + float Intensity; + float Constant; + float Linear; + float Quadratic; +}; + +struct SpotLight { + vec3 Position; + vec3 Direction; + vec3 Color; + float Intensity; + float InnerCutOff; + float OuterCutOff; + float Constant; + float Linear; + float Quadratic; +}; + +layout (std140, binding = UNIFORM_LIGHTS_BINDING) uniform Lights +{ + int DirLightCount; + DirLight DirLights[MAX_DIR_LIGHT_COUNT]; + int PointLightCount; + PointLight PointLights[MAX_POINT_LIGHT_COUNT]; + int SpotLightCount; + SpotLight SpotLights[MAX_SPOT_LIGHT_COUNT]; +} u_Lights; + +layout (std140, binding = UNIFORM_GENERAL_BINDING) uniform General +{ + vec3 WorldCameraPos; +} u_General; + +layout(location = 0, binding = TEX_DIFFUSE_BINDING) uniform sampler2D t_Diffuse; +layout(location = 1, binding = TEX_SPECULAR_BINDING) uniform sampler2D t_Specular; +layout(location = 2, binding = TEX_OPACITY_BINDING) uniform sampler2D t_Opacity; +layout(location = 3, binding = TEX_NORMAL_BINDING) uniform sampler2D t_Normal; +layout(location = 4, binding = TEX_ALBEDO_BINDING) uniform sampler2D t_Albedo; +layout(location = 5, binding = TEX_METALLIC_ROUGHNESS_BINDING) uniform sampler2D t_MetallicRoughness; +layout(location = 6, binding = TEX_EMISSIVE_BINDING) uniform sampler2D t_Emissive; +layout(location = 7, binding = TEX_AMBIENT_OCCLUSION_BINDING) uniform sampler2D t_AmbientOcclusion; + +layout(location = 8, binding = TEX_SKY_MAP_BINDING) uniform samplerCube t_SkyMap; +layout(location = 9, binding = TEX_IRRADIANCE_MAP_BINDING) uniform samplerCube t_IrradianceMap; + +layout(location = 0) in vec3 v_WorldPos; +layout(location = 1) in vec3 v_Normal; +layout(location = 2) in vec4 v_Tangent; +layout(location = 3) in vec2 v_UV0; +layout(location = 4) in vec2 v_UV1; +layout(location = 5) in vec4 v_Color; + +layout(location = 0) out vec4 out_FragColor; + +vec2 GetUV(int textureIndex) +{ + int channel = u_MatAttr.UVChannels[textureIndex]; + vec2 baseUV = (channel == 1) ? v_UV1 : v_UV0; + return (u_MatAttr.UVMatrices[textureIndex] * vec4(baseUV, 0.0f, 1.0f)).xy; +} + +vec3 GetNormal() +{ + if (u_MatAttr.HasTextures[TEX_NORMAL_BINDING] == 0) + { + return normalize(v_Normal); + } + + vec3 texNormal = texture(t_Normal, GetUV(TEX_NORMAL_BINDING)).xyz * 2.0f - 1.0f; + + texNormal *= u_MatAttr.NormalScale; + texNormal = normalize(texNormal); + + vec3 N = normalize(v_Normal); + vec3 T = normalize(v_Tangent.xyz - N * dot(v_Tangent.xyz, N)); + vec3 B = normalize(cross(N, T)) * v_Tangent.w; + mat3 TBN = mat3(T, B, N); + + return normalize(TBN * texNormal); +} + +float DistributionGGX(vec3 N, vec3 H, float roughness) +{ + float a = roughness * roughness; + float a2 = a * a; + float NdotH = max(dot(N, H), 0.0f); + float NdotH2 = NdotH * NdotH; + + float nom = a2; + float denom = (NdotH2 * (a2 - 1.0f) + 1.0f); + denom = PI * denom * denom; + + return nom / max(denom, 0.0000001f); +} + +float CalcDirectKforSchlickGGX(float roughness) { + float r = (roughness + 1.0f); + return (r * r) / 8.0f; +} + +float CalcIndirectKforSchlickGGX(float roughness) { + return (roughness * roughness) / 2.0f; +} + +float GeometrySchlickGGX(float NdotV, float k) +{ + float nom = NdotV; + float denom = NdotV * (1.0f - k) + k; + + return nom / denom; +} + +float GeometrySmith(vec3 N, vec3 V, vec3 L, float k) +{ + float NdotV = max(dot(N, V), 0.0f); + float NdotL = max(dot(N, L), 0.0f); + + float ggx1 = GeometrySchlickGGX(NdotL, k); + float ggx2 = GeometrySchlickGGX(NdotV, k); + + return ggx1 * ggx2; +} + +vec3 fresnelSchlickDirect(float cosTheta, vec3 F0) +{ + return F0 + (1.0f - F0) * pow(clamp(1.0f - cosTheta, 0.0f, 1.0f), 5.0f); +} + +vec3 fresnelSchlickIndirect(float cosTheta, vec3 F0, float roughness) +{ + return F0 + (max(vec3(1.0f - roughness), F0) - F0) * pow(clamp(1.0f - cosTheta, 0.0f, 1.0f), 5.0f); +} + +void main() +{ + vec4 texOpacity = texture(t_Opacity, GetUV(TEX_OPACITY_BINDING)); + vec4 texAlbedo = texture(t_Albedo, GetUV(TEX_ALBEDO_BINDING)); + vec4 texMetallicRoughness = texture(t_MetallicRoughness, GetUV(TEX_METALLIC_ROUGHNESS_BINDING)); + vec4 texEmissive = texture(t_Emissive, GetUV(TEX_EMISSIVE_BINDING)); + vec4 texAO = texture(t_AmbientOcclusion, GetUV(TEX_AMBIENT_OCCLUSION_BINDING)); + + float finalOpacity = u_MatAttr.Opacity; + if (u_MatAttr.HasTextures[3] == 1) + { + finalOpacity *= texOpacity.r; + } + + vec3 albedo = u_MatAttr.Albedo.rgb; + if (u_MatAttr.HasTextures[5] == 1) + { + albedo = texAlbedo.rgb; + } + + float metallic = u_MatAttr.Metallic; + float roughness = u_MatAttr.Roughness; + if (u_MatAttr.HasTextures[6] == 1) + { + roughness *= texMetallicRoughness.g; + metallic *= texMetallicRoughness.b; + } + + vec3 F0 = vec3(0.04f); + F0 = mix(F0, albedo, metallic); + + vec3 N = GetNormal(); + vec3 V = normalize(u_General.WorldCameraPos - v_WorldPos); + vec3 R = reflect(-V, N); + vec3 Lo = vec3(0.0f); + + for(int i = 0; i < u_Lights.DirLightCount; ++i) + { + vec3 L = normalize(-u_Lights.DirLights[i].Direction); + vec3 H = normalize(V + L); + vec3 lightRadiance = u_Lights.DirLights[i].Color * u_Lights.DirLights[i].Intensity; + + float NDF = DistributionGGX(N, H, roughness); + float k = CalcDirectKforSchlickGGX(roughness); + float G = GeometrySmith(N, V, L, k); + vec3 F = fresnelSchlickDirect(max(dot(H, V), 0.0f), F0); + + vec3 num = NDF * G * F; + float denom = 4.0f * max(dot(N, V), 0.0f) * max(dot(N, L), 0.0f) + 0.0001f; + vec3 specular = num / denom; + + vec3 kS = F; + vec3 kD = vec3(1.0f) - kS; + kD *= 1.0f - metallic; + + float NdotL = max(dot(N, L), 0.0f); + Lo += (kD * albedo / PI + specular) * lightRadiance * NdotL; + } + + for(int i = 0; i < u_Lights.PointLightCount; ++i) + { + float dist = length(u_Lights.PointLights[i].Position - v_WorldPos); + + vec3 L = (u_Lights.PointLights[i].Position - v_WorldPos) / dist; + vec3 H = normalize(V + L); + + float attenuation = 1.0f / (u_Lights.PointLights[i].Constant + u_Lights.PointLights[i].Linear * dist + u_Lights.PointLights[i].Quadratic * dist * dist); + vec3 lightRadiance = u_Lights.PointLights[i].Color * u_Lights.PointLights[i].Intensity * attenuation; + + float NDF = DistributionGGX(N, H, roughness); + float k = CalcDirectKforSchlickGGX(roughness); + float G = GeometrySmith(N, V, L, k); + vec3 F = fresnelSchlickDirect(max(dot(H, V), 0.0f), F0); + + vec3 num = NDF * G * F; + float denom = 4.0f * max(dot(N, V), 0.0f) * max(dot(N, L), 0.0f) + 0.0001f; + vec3 specular = num / denom; + + vec3 kS = F; + vec3 kD = vec3(1.0f) - kS; + kD *= 1.0f - metallic; + + float NdotL = max(dot(N, L), 0.0f); + Lo += (kD * albedo / PI + specular) * lightRadiance * NdotL; + } + + for(int i = 0; i < u_Lights.SpotLightCount; ++i) + { + float dist = length(u_Lights.SpotLights[i].Position - v_WorldPos); + vec3 L = (u_Lights.SpotLights[i].Position - v_WorldPos) / dist; + vec3 H = normalize(L + V); + + float theta = dot(L, normalize(-u_Lights.SpotLights[i].Direction)); + float epsilon = u_Lights.SpotLights[i].InnerCutOff - u_Lights.SpotLights[i].OuterCutOff; + float angularIntensity = clamp((theta - u_Lights.SpotLights[i].OuterCutOff) / epsilon, 0.0, 1.0); + + float distanceAttenuation = 1.0f / (u_Lights.SpotLights[i].Constant + u_Lights.SpotLights[i].Linear * dist + u_Lights.SpotLights[i].Quadratic * dist * dist); + + vec3 lightRadiance = u_Lights.SpotLights[i].Color * u_Lights.SpotLights[i].Intensity * distanceAttenuation * angularIntensity; + + float NDF = DistributionGGX(N, H, roughness); + float k = CalcDirectKforSchlickGGX(roughness); + float G = GeometrySmith(N, V, L, k); + vec3 F = fresnelSchlickDirect(max(dot(H, V), 0.0f), F0); + + vec3 num = NDF * G * F; + float denom = 4.0f * max(dot(N, V), 0.0f) * max(dot(N, L), 0.0f) + 0.0001f; + vec3 specular = num / denom; + + vec3 kS = F; + vec3 kD = vec3(1.0f) - kS; + kD *= 1.0f - metallic; + + float NdotL = max(dot(N, L), 0.0f); + Lo += (kD * albedo / PI + specular) * lightRadiance * NdotL; + } + + float ao = 1.0f; + if (u_MatAttr.HasTextures[8] == 1) + { + ao *= texture(t_AmbientOcclusion, GetUV(8)).r; + } + ao = mix(1.0f, ao, u_MatAttr.OcclusionStrength); + + vec3 ambient = u_MatAttr.Ambient.rgb * albedo * ao; + + vec3 emissive = u_MatAttr.Emissive.rgb * u_MatAttr.EmissiveIntensity; + if (u_MatAttr.HasTextures[7] == 1) + { + emissive *= texEmissive.rgb; + } + + vec3 finalColor = ambient + Lo + emissive; + + finalColor = finalColor / (finalColor + vec3(1.0f)); + finalColor = pow(finalColor, vec3(1.0f / 2.2f)); + + out_FragColor = vec4(finalColor.rgb, finalOpacity); +} \ No newline at end of file diff --git a/Subsystems/Rendering/GL/Shaders/DefaultPBRMasked.frag b/Subsystems/Rendering/GL/Shaders/DefaultPBRMasked.frag new file mode 100644 index 0000000..bb096e6 --- /dev/null +++ b/Subsystems/Rendering/GL/Shaders/DefaultPBRMasked.frag @@ -0,0 +1,338 @@ +#version 460 core + +const int UNIFORM_MATERIAL_BINDING = 0; +const int UNIFORM_MATRICES_BINDING = 1; +const int UNIFORM_LIGHTS_BINDING = 2; +const int UNIFORM_GENERAL_BINDING = 3; + +const int TEX_DIFFUSE_BINDING = 1; +const int TEX_SPECULAR_BINDING = 2; +const int TEX_OPACITY_BINDING = 3; +const int TEX_NORMAL_BINDING = 4; +const int TEX_ALBEDO_BINDING = 5; +const int TEX_METALLIC_ROUGHNESS_BINDING = 6; +const int TEX_EMISSIVE_BINDING = 7; +const int TEX_AMBIENT_OCCLUSION_BINDING = 8; + +const int TEX_SKY_MAP_BINDING = 9; +const int TEX_IRRADIANCE_MAP_BINDING = 10; + +const int MAX_DIR_LIGHT_COUNT = 4; +const int MAX_POINT_LIGHT_COUNT = 32; +const int MAX_SPOT_LIGHT_COUNT = 16; + +const float PI = 3.14159265359; + +layout (std140, binding = UNIFORM_MATERIAL_BINDING) uniform Material +{ + vec4 Ambient; + vec4 Diffuse; + vec4 Specular; + vec4 Albedo; + vec4 Emissive; + + float Shininess; + float Metallic; + float Roughness; + float IndexOfRefraction; + + float Opacity; + float AlphaThreshold; + float EmissiveIntensity; + float NormalScale; + float OcclusionStrength; + + int UVChannels[9]; + mat4 UVMatrices[9]; + int HasTextures[9]; +} u_MatAttr; + +struct DirLight { + vec3 Direction; + vec3 Color; + float Intensity; +}; + +struct PointLight { + vec3 Position; + vec3 Color; + float Intensity; + float Constant; + float Linear; + float Quadratic; +}; + +struct SpotLight { + vec3 Position; + vec3 Direction; + vec3 Color; + float Intensity; + float InnerCutOff; + float OuterCutOff; + float Constant; + float Linear; + float Quadratic; +}; + +layout (std140, binding = UNIFORM_LIGHTS_BINDING) uniform Lights +{ + int DirLightCount; + DirLight DirLights[MAX_DIR_LIGHT_COUNT]; + int PointLightCount; + PointLight PointLights[MAX_POINT_LIGHT_COUNT]; + int SpotLightCount; + SpotLight SpotLights[MAX_SPOT_LIGHT_COUNT]; +} u_Lights; + +layout (std140, binding = UNIFORM_GENERAL_BINDING) uniform General +{ + vec3 WorldCameraPos; +} u_General; + +layout(location = 0, binding = TEX_DIFFUSE_BINDING) uniform sampler2D t_Diffuse; +layout(location = 1, binding = TEX_SPECULAR_BINDING) uniform sampler2D t_Specular; +layout(location = 2, binding = TEX_OPACITY_BINDING) uniform sampler2D t_Opacity; +layout(location = 3, binding = TEX_NORMAL_BINDING) uniform sampler2D t_Normal; +layout(location = 4, binding = TEX_ALBEDO_BINDING) uniform sampler2D t_Albedo; +layout(location = 5, binding = TEX_METALLIC_ROUGHNESS_BINDING) uniform sampler2D t_MetallicRoughness; +layout(location = 6, binding = TEX_EMISSIVE_BINDING) uniform sampler2D t_Emissive; +layout(location = 7, binding = TEX_AMBIENT_OCCLUSION_BINDING) uniform sampler2D t_AmbientOcclusion; + +layout(location = 8, binding = TEX_SKY_MAP_BINDING) uniform samplerCube t_SkyMap; +layout(location = 9, binding = TEX_IRRADIANCE_MAP_BINDING) uniform samplerCube t_IrradianceMap; + +layout(location = 0) in vec3 v_WorldPos; +layout(location = 1) in vec3 v_Normal; +layout(location = 2) in vec4 v_Tangent; +layout(location = 3) in vec2 v_UV0; +layout(location = 4) in vec2 v_UV1; +layout(location = 5) in vec4 v_Color; + +layout(location = 0) out vec4 out_FragColor; + +vec2 GetUV(int textureIndex) +{ + int channel = u_MatAttr.UVChannels[textureIndex]; + vec2 baseUV = (channel == 1) ? v_UV1 : v_UV0; + return (u_MatAttr.UVMatrices[textureIndex] * vec4(baseUV, 0.0f, 1.0f)).xy; +} + +vec3 GetNormal() +{ + if (u_MatAttr.HasTextures[TEX_NORMAL_BINDING] == 0) + { + return normalize(v_Normal); + } + + vec3 texNormal = texture(t_Normal, GetUV(TEX_NORMAL_BINDING)).xyz * 2.0f - 1.0f; + + texNormal *= u_MatAttr.NormalScale; + texNormal = normalize(texNormal); + + vec3 N = normalize(v_Normal); + vec3 T = normalize(v_Tangent.xyz - N * dot(v_Tangent.xyz, N)); + vec3 B = normalize(cross(N, T)) * v_Tangent.w; + mat3 TBN = mat3(T, B, N); + + return normalize(TBN * texNormal); +} + +float DistributionGGX(vec3 N, vec3 H, float roughness) +{ + float a = roughness * roughness; + float a2 = a * a; + float NdotH = max(dot(N, H), 0.0f); + float NdotH2 = NdotH * NdotH; + + float nom = a2; + float denom = (NdotH2 * (a2 - 1.0f) + 1.0f); + denom = PI * denom * denom; + + return nom / max(denom, 0.0000001f); +} + +float CalcDirectKforSchlickGGX(float roughness) { + float r = (roughness + 1.0f); + return (r * r) / 8.0f; +} + +float CalcIndirectKforSchlickGGX(float roughness) { + return (roughness * roughness) / 2.0f; +} + +float GeometrySchlickGGX(float NdotV, float k) +{ + float nom = NdotV; + float denom = NdotV * (1.0f - k) + k; + + return nom / denom; +} + +float GeometrySmith(vec3 N, vec3 V, vec3 L, float k) +{ + float NdotV = max(dot(N, V), 0.0f); + float NdotL = max(dot(N, L), 0.0f); + + float ggx1 = GeometrySchlickGGX(NdotL, k); + float ggx2 = GeometrySchlickGGX(NdotV, k); + + return ggx1 * ggx2; +} + +vec3 fresnelSchlickDirect(float cosTheta, vec3 F0) +{ + return F0 + (1.0f - F0) * pow(clamp(1.0f - cosTheta, 0.0f, 1.0f), 5.0f); +} + +vec3 fresnelSchlickIndirect(float cosTheta, vec3 F0, float roughness) +{ + return F0 + (max(vec3(1.0f - roughness), F0) - F0) * pow(clamp(1.0f - cosTheta, 0.0f, 1.0f), 5.0f); +} + +void main() +{ + vec4 texOpacity = texture(t_Opacity, GetUV(TEX_OPACITY_BINDING)); + vec4 texAlbedo = texture(t_Albedo, GetUV(TEX_ALBEDO_BINDING)); + vec4 texMetallicRoughness = texture(t_MetallicRoughness, GetUV(TEX_METALLIC_ROUGHNESS_BINDING)); + vec4 texEmissive = texture(t_Emissive, GetUV(TEX_EMISSIVE_BINDING)); + vec4 texAO = texture(t_AmbientOcclusion, GetUV(TEX_AMBIENT_OCCLUSION_BINDING)); + + float finalOpacity = u_MatAttr.Opacity; + + if (u_MatAttr.HasTextures[3] == 1) { + finalOpacity *= texOpacity.r; + } + else if (u_MatAttr.HasTextures[5] == 1) { + finalOpacity *= texAlbedo.a; + } + + if (finalOpacity < u_MatAttr.AlphaThreshold) + { + discard; + } + + vec3 albedo = u_MatAttr.Albedo.rgb; + if (u_MatAttr.HasTextures[5] == 1) + { + albedo = texAlbedo.rgb; + } + + float metallic = u_MatAttr.Metallic; + float roughness = u_MatAttr.Roughness; + if (u_MatAttr.HasTextures[6] == 1) + { + roughness *= texMetallicRoughness.g; + metallic *= texMetallicRoughness.b; + } + + vec3 F0 = vec3(0.04f); + F0 = mix(F0, albedo, metallic); + + vec3 N = GetNormal(); + vec3 V = normalize(u_General.WorldCameraPos - v_WorldPos); + vec3 R = reflect(-V, N); + vec3 Lo = vec3(0.0f); + + for(int i = 0; i < u_Lights.DirLightCount; ++i) + { + vec3 L = normalize(-u_Lights.DirLights[i].Direction); + vec3 H = normalize(V + L); + vec3 lightRadiance = u_Lights.DirLights[i].Color * u_Lights.DirLights[i].Intensity; + + float NDF = DistributionGGX(N, H, roughness); + float k = CalcDirectKforSchlickGGX(roughness); + float G = GeometrySmith(N, V, L, k); + vec3 F = fresnelSchlickDirect(max(dot(H, V), 0.0f), F0); + + vec3 num = NDF * G * F; + float denom = 4.0f * max(dot(N, V), 0.0f) * max(dot(N, L), 0.0f) + 0.0001f; + vec3 specular = num / denom; + + vec3 kS = F; + vec3 kD = vec3(1.0f) - kS; + kD *= 1.0f - metallic; + + float NdotL = max(dot(N, L), 0.0f); + Lo += (kD * albedo / PI + specular) * lightRadiance * NdotL; + } + + for(int i = 0; i < u_Lights.PointLightCount; ++i) + { + float dist = length(u_Lights.PointLights[i].Position - v_WorldPos); + + vec3 L = (u_Lights.PointLights[i].Position - v_WorldPos) / dist; + vec3 H = normalize(V + L); + + float attenuation = 1.0f / (u_Lights.PointLights[i].Constant + u_Lights.PointLights[i].Linear * dist + u_Lights.PointLights[i].Quadratic * dist * dist); + vec3 lightRadiance = u_Lights.PointLights[i].Color * u_Lights.PointLights[i].Intensity * attenuation; + + float NDF = DistributionGGX(N, H, roughness); + float k = CalcDirectKforSchlickGGX(roughness); + float G = GeometrySmith(N, V, L, k); + vec3 F = fresnelSchlickDirect(max(dot(H, V), 0.0f), F0); + + vec3 num = NDF * G * F; + float denom = 4.0f * max(dot(N, V), 0.0f) * max(dot(N, L), 0.0f) + 0.0001f; + vec3 specular = num / denom; + + vec3 kS = F; + vec3 kD = vec3(1.0f) - kS; + kD *= 1.0f - metallic; + + float NdotL = max(dot(N, L), 0.0f); + Lo += (kD * albedo / PI + specular) * lightRadiance * NdotL; + } + + for(int i = 0; i < u_Lights.SpotLightCount; ++i) + { + float dist = length(u_Lights.SpotLights[i].Position - v_WorldPos); + vec3 L = (u_Lights.SpotLights[i].Position - v_WorldPos) / dist; + vec3 H = normalize(L + V); + + float theta = dot(L, normalize(-u_Lights.SpotLights[i].Direction)); + float epsilon = u_Lights.SpotLights[i].InnerCutOff - u_Lights.SpotLights[i].OuterCutOff; + float angularIntensity = clamp((theta - u_Lights.SpotLights[i].OuterCutOff) / epsilon, 0.0, 1.0); + + float distanceAttenuation = 1.0f / (u_Lights.SpotLights[i].Constant + u_Lights.SpotLights[i].Linear * dist + u_Lights.SpotLights[i].Quadratic * dist * dist); + + vec3 lightRadiance = u_Lights.SpotLights[i].Color * u_Lights.SpotLights[i].Intensity * distanceAttenuation * angularIntensity; + + float NDF = DistributionGGX(N, H, roughness); + float k = CalcDirectKforSchlickGGX(roughness); + float G = GeometrySmith(N, V, L, k); + vec3 F = fresnelSchlickDirect(max(dot(H, V), 0.0f), F0); + + vec3 num = NDF * G * F; + float denom = 4.0f * max(dot(N, V), 0.0f) * max(dot(N, L), 0.0f) + 0.0001f; + vec3 specular = num / denom; + + vec3 kS = F; + vec3 kD = vec3(1.0f) - kS; + kD *= 1.0f - metallic; + + float NdotL = max(dot(N, L), 0.0f); + Lo += (kD * albedo / PI + specular) * lightRadiance * NdotL; + } + + float ao = 1.0f; + if (u_MatAttr.HasTextures[8] == 1) + { + ao *= texture(t_AmbientOcclusion, GetUV(8)).r; + } + ao = mix(1.0f, ao, u_MatAttr.OcclusionStrength); + + vec3 ambient = u_MatAttr.Ambient.rgb * albedo * ao; + + vec3 emissive = u_MatAttr.Emissive.rgb * u_MatAttr.EmissiveIntensity; + if (u_MatAttr.HasTextures[7] == 1) + { + emissive *= texEmissive.rgb; + } + + vec3 finalColor = ambient + Lo + emissive; + + finalColor = finalColor / (finalColor + vec3(1.0f)); + finalColor = pow(finalColor, vec3(1.0f / 2.2f)); + + out_FragColor = vec4(finalColor.rgb, finalOpacity); +} \ No newline at end of file diff --git a/Subsystems/Rendering/GL/Shaders/DefaultPhong.frag b/Subsystems/Rendering/GL/Shaders/DefaultPhong.frag new file mode 100644 index 0000000..8f456d6 --- /dev/null +++ b/Subsystems/Rendering/GL/Shaders/DefaultPhong.frag @@ -0,0 +1,254 @@ +#version 460 core + +const int UNIFORM_MATERIAL_BINDING = 0; +const int UNIFORM_MATRICES_BINDING = 1; +const int UNIFORM_LIGHTS_BINDING = 2; +const int UNIFORM_GENERAL_BINDING = 3; + +const int TEX_DIFFUSE_BINDING = 1; +const int TEX_SPECULAR_BINDING = 2; +const int TEX_OPACITY_BINDING = 3; +const int TEX_NORMAL_BINDING = 4; +const int TEX_ALBEDO_BINDING = 5; +const int TEX_METALLIC_ROUGHNESS_BINDING = 6; +const int TEX_EMISSIVE_BINDING = 7; +const int TEX_AMBIENT_OCCLUSION_BINDING = 8; + +const int TEX_SKY_MAP_BINDING = 9; +const int TEX_IRRADIANCE_MAP_BINDING = 10; + +const int MAX_DIR_LIGHT_COUNT = 4; +const int MAX_POINT_LIGHT_COUNT = 32; +const int MAX_SPOT_LIGHT_COUNT = 16; + + +layout (std140, binding = UNIFORM_MATERIAL_BINDING) uniform Material +{ + vec4 Ambient; + vec4 Diffuse; + vec4 Specular; + vec4 Albedo; + vec4 Emissive; + + float Shininess; + float Metallic; + float Roughness; + float IndexOfRefraction; + + float Opacity; + float AlphaThreshold; + float EmissiveIntensity; + float NormalScale; + float OcclusionStrength; + + int UVChannels[9]; + mat4 UVMatrices[9]; + int HasTextures[9]; +} u_MatAttr; + +struct DirLight { + vec3 Direction; + vec3 Color; + float Intensity; +}; + +struct PointLight { + vec3 Position; + vec3 Color; + float Intensity; + float Constant; + float Linear; + float Quadratic; +}; + +struct SpotLight { + vec3 Position; + vec3 Direction; + vec3 Color; + float Intensity; + float InnerCutOff; + float OuterCutOff; + float Constant; + float Linear; + float Quadratic; +}; + +layout (std140, binding = UNIFORM_LIGHTS_BINDING) uniform Lights +{ + int DirLightCount; + DirLight DirLights[MAX_DIR_LIGHT_COUNT]; + int PointLightCount; + PointLight PointLights[MAX_POINT_LIGHT_COUNT]; + int SpotLightCount; + SpotLight SpotLights[MAX_SPOT_LIGHT_COUNT]; +} u_Lights; + +layout (std140, binding = UNIFORM_GENERAL_BINDING) uniform General +{ + vec3 WorldCameraPos; +} u_General; + +layout(location = 0, binding = TEX_DIFFUSE_BINDING) uniform sampler2D t_Diffuse; +layout(location = 1, binding = TEX_SPECULAR_BINDING) uniform sampler2D t_Specular; +layout(location = 2, binding = TEX_OPACITY_BINDING) uniform sampler2D t_Opacity; +layout(location = 3, binding = TEX_NORMAL_BINDING) uniform sampler2D t_Normal; +layout(location = 4, binding = TEX_ALBEDO_BINDING) uniform sampler2D t_Albedo; +layout(location = 5, binding = TEX_METALLIC_ROUGHNESS_BINDING) uniform sampler2D t_MetallicRoughness; +layout(location = 6, binding = TEX_EMISSIVE_BINDING) uniform sampler2D t_Emissive; +layout(location = 7, binding = TEX_AMBIENT_OCCLUSION_BINDING) uniform sampler2D t_AmbientOcclusion; + +layout(location = 8, binding = TEX_SKY_MAP_BINDING) uniform samplerCube t_SkyMap; +layout(location = 9, binding = TEX_IRRADIANCE_MAP_BINDING) uniform samplerCube t_IrradianceMap; + +layout(location = 0) in vec3 v_WorldPos; +layout(location = 1) in vec3 v_Normal; +layout(location = 2) in vec4 v_Tangent; +layout(location = 3) in vec2 v_UV0; +layout(location = 4) in vec2 v_UV1; +layout(location = 5) in vec4 v_Color; + +layout(location = 0) out vec4 out_FragColor; + +vec2 GetUV(int textureIndex) +{ + int channel = u_MatAttr.UVChannels[textureIndex]; + vec2 baseUV = (channel == 1) ? v_UV1 : v_UV0; + return (u_MatAttr.UVMatrices[textureIndex] * vec4(baseUV, 1.0f, 0.0f)).xy; +} + +vec3 GetNormal() +{ + if (u_MatAttr.HasTextures[TEX_NORMAL_BINDING] == 0) + { + return normalize(v_Normal); + } + + vec3 texNormal = texture(t_Normal, GetUV(TEX_NORMAL_BINDING)).xyz * 2.0f - 1.0f; + + texNormal *= u_MatAttr.NormalScale; + texNormal = normalize(texNormal); + + vec3 N = normalize(v_Normal); + vec3 T = normalize(v_Tangent.xyz - N * dot(v_Tangent.xyz, N)); + vec3 B = normalize(cross(N, T)) * v_Tangent.w; + mat3 TBN = mat3(T, B, N); + + return normalize(TBN * texNormal); +} + +void main() +{ + vec4 texDiffuse = texture(t_Diffuse, GetUV(TEX_DIFFUSE_BINDING)); + vec4 texSpecular = texture(t_Specular, GetUV(TEX_SPECULAR_BINDING)); + vec4 texOpacity = texture(t_Opacity, GetUV(TEX_OPACITY_BINDING)); + vec4 texAlbedo = texture(t_Albedo, GetUV(TEX_ALBEDO_BINDING)); + vec4 texEmissive = texture(t_Emissive, GetUV(TEX_EMISSIVE_BINDING)); + vec4 texAO = texture(t_AmbientOcclusion, GetUV(TEX_AMBIENT_OCCLUSION_BINDING)); + + float finalOpacity = u_MatAttr.Opacity; + if (u_MatAttr.HasTextures[3] == 1) + { + finalOpacity *= texOpacity.r; + } + + vec3 diffColor = u_MatAttr.Diffuse.rgb; + if (u_MatAttr.HasTextures[1] == 1) + { + diffColor = texDiffuse.rgb; + } + else if (u_MatAttr.HasTextures[5] == 1) + { + diffColor = texAlbedo.rgb; + } + + vec3 specColor = u_MatAttr.Specular.rgb; + if (u_MatAttr.HasTextures[2] == 1) + { + specColor *= texSpecular.rgb; + } + + vec3 N = GetNormal(); + vec3 V = normalize(u_General.WorldCameraPos - v_WorldPos); + + vec3 totalDiffuse = vec3(0.0f); + vec3 totalSpecular = vec3(0.0f); + + for(int i = 0; i < u_Lights.DirLightCount; ++i) + { + vec3 L = normalize(-u_Lights.DirLights[i].Direction); + vec3 H = normalize(L + V); + + float NdotL = max(dot(N, L), 0.0f); + float NdotH = max(dot(N, H), 0.0f); + + vec3 lightColor = u_Lights.DirLights[i].Color * u_Lights.DirLights[i].Intensity; + + totalDiffuse += diffColor * NdotL * lightColor; + + float specPower = pow(NdotH, u_MatAttr.Shininess); + totalSpecular += specColor * specPower * lightColor * step(0.0001, NdotL); + } + + for(int i = 0; i < u_Lights.PointLightCount; ++i) + { + float dist = length(u_Lights.PointLights[i].Position - v_WorldPos); + + vec3 L = (u_Lights.PointLights[i].Position - v_WorldPos) / dist; + vec3 H = normalize(L + V); + + float NdotL = max(dot(N, L), 0.0f); + float NdotH = max(dot(N, H), 0.0f); + + float attenuation = 1.0f / (u_Lights.PointLights[i].Constant + u_Lights.PointLights[i].Linear * dist + u_Lights.PointLights[i].Quadratic * dist * dist); + vec3 lightColor = u_Lights.PointLights[i].Color * u_Lights.PointLights[i].Intensity * attenuation; + + totalDiffuse += diffColor * NdotL * lightColor; + + float specPower = pow(NdotH, u_MatAttr.Shininess); + totalSpecular += specColor * specPower * lightColor * step(0.0001, NdotL); + } + + for(int i = 0; i < u_Lights.SpotLightCount; ++i) + { + float dist = length(u_Lights.SpotLights[i].Position - v_WorldPos); + vec3 L = (u_Lights.SpotLights[i].Position - v_WorldPos) / dist; + + float theta = dot(L, normalize(-u_Lights.SpotLights[i].Direction)); + float epsilon = u_Lights.SpotLights[i].InnerCutOff - u_Lights.SpotLights[i].OuterCutOff; + float angularIntensity = clamp((theta - u_Lights.SpotLights[i].OuterCutOff) / epsilon, 0.0, 1.0); + + float distanceAttenuation = 1.0f / (u_Lights.SpotLights[i].Constant + u_Lights.SpotLights[i].Linear * dist + u_Lights.SpotLights[i].Quadratic * dist * dist); + + vec3 lightColor = u_Lights.SpotLights[i].Color * u_Lights.SpotLights[i].Intensity * distanceAttenuation * angularIntensity; + + vec3 H = normalize(L + V); + float NdotL = max(dot(N, L), 0.0f); + float NdotH = max(dot(N, H), 0.0f); + + totalDiffuse += diffColor * NdotL * lightColor; + + float specPower = pow(NdotH, u_MatAttr.Shininess); + totalSpecular += specColor * specPower * lightColor * step(0.0001, NdotL); + } + + float ao = 1.0f; + if (u_MatAttr.HasTextures[8] == 1) + { + ao = texAO.r; + } + ao = mix(1.0f, ao, u_MatAttr.OcclusionStrength); + vec3 ambient = u_MatAttr.Ambient.rgb * ao; + + vec3 emissive = u_MatAttr.Emissive.rgb * u_MatAttr.EmissiveIntensity; + if (u_MatAttr.HasTextures[7] == 1) + { + emissive = texEmissive.rgb * u_MatAttr.EmissiveIntensity; + } + + vec3 finalColor = ambient + totalDiffuse + totalSpecular + emissive; + + finalColor = clamp(finalColor, 0.0f, 1.0f); + finalColor = pow(finalColor, vec3(1.0f / 2.2f)); + + out_FragColor = vec4(finalColor, finalOpacity); +} \ No newline at end of file diff --git a/Subsystems/Rendering/GL/Shaders/DefaultPhongMasked.frag b/Subsystems/Rendering/GL/Shaders/DefaultPhongMasked.frag new file mode 100644 index 0000000..5d45330 --- /dev/null +++ b/Subsystems/Rendering/GL/Shaders/DefaultPhongMasked.frag @@ -0,0 +1,266 @@ +#version 460 core + +const int UNIFORM_MATERIAL_BINDING = 0; +const int UNIFORM_MATRICES_BINDING = 1; +const int UNIFORM_LIGHTS_BINDING = 2; +const int UNIFORM_GENERAL_BINDING = 3; + +const int TEX_DIFFUSE_BINDING = 1; +const int TEX_SPECULAR_BINDING = 2; +const int TEX_OPACITY_BINDING = 3; +const int TEX_NORMAL_BINDING = 4; +const int TEX_ALBEDO_BINDING = 5; +const int TEX_METALLIC_ROUGHNESS_BINDING = 6; +const int TEX_EMISSIVE_BINDING = 7; +const int TEX_AMBIENT_OCCLUSION_BINDING = 8; + +const int TEX_SKY_MAP_BINDING = 9; +const int TEX_IRRADIANCE_MAP_BINDING = 10; + +const int MAX_DIR_LIGHT_COUNT = 4; +const int MAX_POINT_LIGHT_COUNT = 32; +const int MAX_SPOT_LIGHT_COUNT = 16; + + +layout (std140, binding = UNIFORM_MATERIAL_BINDING) uniform Material +{ + vec4 Ambient; + vec4 Diffuse; + vec4 Specular; + vec4 Albedo; + vec4 Emissive; + + float Shininess; + float Metallic; + float Roughness; + float IndexOfRefraction; + + float Opacity; + float AlphaThreshold; + float EmissiveIntensity; + float NormalScale; + float OcclusionStrength; + + int UVChannels[9]; + mat4 UVMatrices[9]; + int HasTextures[9]; +} u_MatAttr; + +struct DirLight { + vec3 Direction; + vec3 Color; + float Intensity; +}; + +struct PointLight { + vec3 Position; + vec3 Color; + float Intensity; + float Constant; + float Linear; + float Quadratic; +}; + +struct SpotLight { + vec3 Position; + vec3 Direction; + vec3 Color; + float Intensity; + float Range; + float InnerCutOff; + float OuterCutOff; + float Constant; + float Linear; + float Quadratic; +}; + +layout (std140, binding = UNIFORM_LIGHTS_BINDING) uniform Lights +{ + int DirLightCount; + DirLight DirLights[MAX_DIR_LIGHT_COUNT]; + int PointLightCount; + PointLight PointLights[MAX_POINT_LIGHT_COUNT]; + int SpotLightCount; + SpotLight SpotLights[MAX_SPOT_LIGHT_COUNT]; +} u_Lights; + +layout (std140, binding = UNIFORM_GENERAL_BINDING) uniform General +{ + vec3 WorldCameraPos; +} u_General; + +layout(location = 0, binding = TEX_DIFFUSE_BINDING) uniform sampler2D t_Diffuse; +layout(location = 1, binding = TEX_SPECULAR_BINDING) uniform sampler2D t_Specular; +layout(location = 2, binding = TEX_OPACITY_BINDING) uniform sampler2D t_Opacity; +layout(location = 3, binding = TEX_NORMAL_BINDING) uniform sampler2D t_Normal; +layout(location = 4, binding = TEX_ALBEDO_BINDING) uniform sampler2D t_Albedo; +layout(location = 5, binding = TEX_METALLIC_ROUGHNESS_BINDING) uniform sampler2D t_MetallicRoughness; +layout(location = 6, binding = TEX_EMISSIVE_BINDING) uniform sampler2D t_Emissive; +layout(location = 7, binding = TEX_AMBIENT_OCCLUSION_BINDING) uniform sampler2D t_AmbientOcclusion; + +layout(location = 8, binding = TEX_SKY_MAP_BINDING) uniform samplerCube t_SkyMap; +layout(location = 9, binding = TEX_IRRADIANCE_MAP_BINDING) uniform samplerCube t_IrradianceMap; + +layout(location = 0) in vec3 v_WorldPos; +layout(location = 1) in vec3 v_Normal; +layout(location = 2) in vec4 v_Tangent; +layout(location = 3) in vec2 v_UV0; +layout(location = 4) in vec2 v_UV1; +layout(location = 5) in vec4 v_Color; + +layout(location = 0) out vec4 out_FragColor; + +vec2 GetUV(int textureIndex) +{ + int channel = u_MatAttr.UVChannels[textureIndex]; + vec2 baseUV = (channel == 1) ? v_UV1 : v_UV0; + return (u_MatAttr.UVMatrices[textureIndex] * vec4(baseUV, 1.0f, 0.0f)).xy; +} + +vec3 GetNormal() +{ + if (u_MatAttr.HasTextures[TEX_NORMAL_BINDING] == 0) + { + return normalize(v_Normal); + } + + vec3 texNormal = texture(t_Normal, GetUV(TEX_NORMAL_BINDING)).xyz * 2.0f - 1.0f; + + texNormal *= u_MatAttr.NormalScale; + texNormal = normalize(texNormal); + + vec3 N = normalize(v_Normal); + vec3 T = normalize(v_Tangent.xyz - N * dot(v_Tangent.xyz, N)); + vec3 B = normalize(cross(N, T)) * v_Tangent.w; + mat3 TBN = mat3(T, B, N); + + return normalize(TBN * texNormal); +} + +void main() +{ + vec4 texDiffuse = texture(t_Diffuse, GetUV(TEX_DIFFUSE_BINDING)); + vec4 texSpecular = texture(t_Specular, GetUV(TEX_SPECULAR_BINDING)); + vec4 texOpacity = texture(t_Opacity, GetUV(TEX_OPACITY_BINDING)); + vec4 texAlbedo = texture(t_Albedo, GetUV(TEX_ALBEDO_BINDING)); + vec4 texEmissive = texture(t_Emissive, GetUV(TEX_EMISSIVE_BINDING)); + vec4 texAO = texture(t_AmbientOcclusion, GetUV(TEX_AMBIENT_OCCLUSION_BINDING)); + + float finalOpacity = u_MatAttr.Opacity; + + if (u_MatAttr.HasTextures[3] == 1) { + finalOpacity *= texOpacity.r; + } + else if (u_MatAttr.HasTextures[1] == 1) { + finalOpacity *= texDiffuse.a; + } + else if (u_MatAttr.HasTextures[5] == 1) { + finalOpacity *= texAlbedo.a; + } + + if (finalOpacity < u_MatAttr.AlphaThreshold) + { + discard; + } + + vec3 diffColor = u_MatAttr.Diffuse.rgb; + if (u_MatAttr.HasTextures[1] == 1) + { + diffColor = texDiffuse.rgb; + } + else if (u_MatAttr.HasTextures[5] == 1) + { + diffColor = texAlbedo.rgb; + } + + vec3 specColor = u_MatAttr.Specular.rgb; + if (u_MatAttr.HasTextures[2] == 1) + { + specColor *= texSpecular.rgb; + } + + vec3 N = GetNormal(); + vec3 V = normalize(u_General.WorldCameraPos - v_WorldPos); + + vec3 totalDiffuse = vec3(0.0f); + vec3 totalSpecular = vec3(0.0f); + + for(int i = 0; i < u_Lights.DirLightCount; ++i) + { + vec3 L = normalize(-u_Lights.DirLights[i].Direction); + vec3 H = normalize(L + V); + + float NdotL = max(dot(N, L), 0.0f); + float NdotH = max(dot(N, H), 0.0f); + + vec3 lightColor = u_Lights.DirLights[i].Color * u_Lights.DirLights[i].Intensity; + + totalDiffuse += diffColor * NdotL * lightColor; + + float specPower = pow(NdotH, u_MatAttr.Shininess); + totalSpecular += specColor * specPower * lightColor * step(0.0001, NdotL); + } + + for(int i = 0; i < u_Lights.PointLightCount; ++i) + { + float dist = length(u_Lights.PointLights[i].Position - v_WorldPos); + + vec3 L = (u_Lights.PointLights[i].Position - v_WorldPos) / dist; + vec3 H = normalize(L + V); + + float NdotL = max(dot(N, L), 0.0f); + float NdotH = max(dot(N, H), 0.0f); + + float attenuation = 1.0f / (u_Lights.PointLights[i].Constant + u_Lights.PointLights[i].Linear * dist + u_Lights.PointLights[i].Quadratic * dist * dist); + vec3 lightColor = u_Lights.PointLights[i].Color * u_Lights.PointLights[i].Intensity * attenuation; + + totalDiffuse += diffColor * NdotL * lightColor; + + float specPower = pow(NdotH, u_MatAttr.Shininess); + totalSpecular += specColor * specPower * lightColor * step(0.0001, NdotL); + } + + for(int i = 0; i < u_Lights.SpotLightCount; ++i) + { + float dist = length(u_Lights.SpotLights[i].Position - v_WorldPos); + vec3 L = (u_Lights.SpotLights[i].Position - v_WorldPos) / dist; + + float theta = dot(L, normalize(-u_Lights.SpotLights[i].Direction)); + float epsilon = u_Lights.SpotLights[i].InnerCutOff - u_Lights.SpotLights[i].OuterCutOff; + float angularIntensity = clamp((theta - u_Lights.SpotLights[i].OuterCutOff) / epsilon, 0.0, 1.0); + + float distanceAttenuation = 1.0f / (u_Lights.SpotLights[i].Constant + u_Lights.SpotLights[i].Linear * dist + u_Lights.SpotLights[i].Quadratic * dist * dist); + + vec3 lightColor = u_Lights.SpotLights[i].Color * u_Lights.SpotLights[i].Intensity * distanceAttenuation * angularIntensity; + + vec3 H = normalize(L + V); + float NdotL = max(dot(N, L), 0.0f); + float NdotH = max(dot(N, H), 0.0f); + + totalDiffuse += diffColor * NdotL * lightColor; + + float specPower = pow(NdotH, u_MatAttr.Shininess); + totalSpecular += specColor * specPower * lightColor * step(0.0001, NdotL); + } + + float ao = 1.0f; + if (u_MatAttr.HasTextures[8] == 1) + { + ao = texAO.r; + } + ao = mix(1.0f, ao, u_MatAttr.OcclusionStrength); + vec3 ambient = u_MatAttr.Ambient.rgb * ao; + + vec3 emissive = u_MatAttr.Emissive.rgb * u_MatAttr.EmissiveIntensity; + if (u_MatAttr.HasTextures[7] == 1) + { + emissive = texEmissive.rgb * u_MatAttr.EmissiveIntensity; + } + + vec3 finalColor = ambient + totalDiffuse + totalSpecular + emissive; + + finalColor = clamp(finalColor, 0.0f, 1.0f); + finalColor = pow(finalColor, vec3(1.0f / 2.2f)); + + out_FragColor = vec4(finalColor, finalOpacity); +} \ No newline at end of file diff --git a/Subsystems/Rendering/GL/Shaders/DefaultSkyMap.frag b/Subsystems/Rendering/GL/Shaders/DefaultSkyMap.frag new file mode 100644 index 0000000..5445164 --- /dev/null +++ b/Subsystems/Rendering/GL/Shaders/DefaultSkyMap.frag @@ -0,0 +1,16 @@ +#version 460 core + +const int TEX_SKY_MAP_BINDING = 9; + +layout(location = 8, binding = TEX_SKY_MAP_BINDING) uniform samplerCube t_SkyMap; + +layout(location = 0) in vec3 v_UVW; + +layout(location = 0) out vec4 out_FragColor; + +void main() { + vec3 finalColor = texture(t_SkyMap, v_UVW).rgb; + finalColor = finalColor / (finalColor + vec3(1.0f)); + finalColor = pow(finalColor, vec3(1.0f / 2.2f)); + out_FragColor = vec4(finalColor, 1.0f); +} \ No newline at end of file diff --git a/Subsystems/Rendering/GL/Shaders/DefaultSkyMap.vert b/Subsystems/Rendering/GL/Shaders/DefaultSkyMap.vert new file mode 100644 index 0000000..184410b --- /dev/null +++ b/Subsystems/Rendering/GL/Shaders/DefaultSkyMap.vert @@ -0,0 +1,48 @@ +#version 460 core + +const int UNIFORM_MATRICES_BINDING = 1; + +const vec3 skyboxVertices[36] = vec3[36]( + vec3(-1.0f, 1.0f, -1.0f), vec3(-1.0f, -1.0f, -1.0f), vec3( 1.0f, -1.0f, -1.0f), + vec3( 1.0f, -1.0f, -1.0f), vec3( 1.0f, 1.0f, -1.0f), vec3(-1.0f, 1.0f, -1.0f), + + vec3(-1.0f, -1.0f, 1.0f), vec3(-1.0f, -1.0f, -1.0f), vec3(-1.0f, 1.0f, -1.0f), + vec3(-1.0f, 1.0f, -1.0f), vec3(-1.0f, 1.0f, 1.0f), vec3(-1.0f, -1.0f, 1.0f), + + vec3( 1.0f, -1.0f, -1.0f), vec3( 1.0f, -1.0f, 1.0f), vec3( 1.0f, 1.0f, 1.0f), + vec3( 1.0f, 1.0f, 1.0f), vec3( 1.0f, 1.0f, -1.0f), vec3( 1.0f, -1.0f, -1.0f), + + vec3(-1.0f, -1.0f, 1.0f), vec3(-1.0f, 1.0f, 1.0f), vec3( 1.0f, 1.0f, 1.0f), + vec3( 1.0f, 1.0f, 1.0f), vec3( 1.0f, -1.0f, 1.0f), vec3(-1.0f, -1.0f, 1.0f), + + vec3(-1.0f, 1.0f, -1.0f), vec3( 1.0f, 1.0f, -1.0f), vec3( 1.0f, 1.0f, 1.0f), + vec3( 1.0f, 1.0f, 1.0f), vec3(-1.0f, 1.0f, 1.0f), vec3(-1.0f, 1.0f, -1.0f), + + vec3(-1.0f, -1.0f, -1.0f), vec3(-1.0f, -1.0f, 1.0f), vec3( 1.0f, -1.0f, -1.0f), + vec3( 1.0f, -1.0f, -1.0f), vec3(-1.0f, -1.0f, 1.0f), vec3( 1.0f, -1.0f, 1.0f) +); + +layout (std140, row_major, binding = UNIFORM_MATRICES_BINDING) uniform Matrices +{ + mat4 World; + mat4 InvWorld; + mat4 View; + mat4 Proj; +} u_Matrices; + +out gl_PerVertex +{ + vec4 gl_Position; +}; + +layout(location = 0) out vec3 v_UVW; + +void main() { + vec3 pos = skyboxVertices[gl_VertexID]; + + v_UVW = pos; + + vec4 clipPos = u_Matrices.Proj * mat4(mat3(u_Matrices.View)) * vec4(pos, 1.0); + + gl_Position = clipPos.xyww; +} \ No newline at end of file diff --git a/Subsystems/Rendering/GL/Shaders/WorldGizmo.frag b/Subsystems/Rendering/GL/Shaders/WorldGizmo.frag new file mode 100644 index 0000000..99113a4 --- /dev/null +++ b/Subsystems/Rendering/GL/Shaders/WorldGizmo.frag @@ -0,0 +1,47 @@ +#version 460 core + +const int UNIFORM_MATRICES_BINDING = 1; +const int UNIFORM_GENERAL_BINDING = 3; + +layout (std140, row_major, binding = UNIFORM_MATRICES_BINDING) uniform Matrices +{ + mat4 World; + mat4 InvWorld; + mat4 View; + mat4 Proj; +} u_Matrices; + + +layout (std140, binding = UNIFORM_GENERAL_BINDING) uniform GeneralUBO +{ + vec3 WorldCameraPos; +} u_General; + +layout(location = 0) in vec3 v_worldFarPlanePos; + +layout(location = 0) out vec4 out_FragColor; + +layout (depth_less) out float gl_FragDepth; + +void main() { + vec3 cameraToFarPlane = v_worldFarPlanePos - u_General.WorldCameraPos; + + float t = (-u_General.WorldCameraPos.y / cameraToFarPlane.y); + + if (t < -0.0f) { + discard; + } + + vec3 xzPlaneWorldPos = u_General.WorldCameraPos + cameraToFarPlane * t; + + if(!(abs(mod(xzPlaneWorldPos.x, 10.0f) - 0.0f) <= 0.4f || abs(mod(xzPlaneWorldPos.z, 10.0f) - 0.0f) <= 0.4f)) { + discard; + } + + vec4 xzPlaneClipCoord = u_Matrices.Proj * u_Matrices.View * vec4(xzPlaneWorldPos, 1.0f); + vec3 xzPlaneNDC = xzPlaneClipCoord.xyz / xzPlaneClipCoord.w; + + gl_FragDepth = (xzPlaneNDC.z + 1.0f) * 0.5f; + + out_FragColor = vec4(0.5f, 0.5f, 0.5f, 1.0f); +} \ No newline at end of file diff --git a/Subsystems/Rendering/GL/Shaders/WorldGizmo.vert b/Subsystems/Rendering/GL/Shaders/WorldGizmo.vert new file mode 100644 index 0000000..5787815 --- /dev/null +++ b/Subsystems/Rendering/GL/Shaders/WorldGizmo.vert @@ -0,0 +1,44 @@ +#version 460 core + +const int UNIFORM_MATRICES_BINDING = 1; +const int UNIFORM_GENERAL_BINDING = 3; + +const vec3 FarPlaneNDC[6] = vec3[6]( + vec3(-1.0f, -1.0f, 0.99999f), + vec3(1.0f, -1.0f, 0.99999f), + vec3(1.0f, 1.0f, 0.99999f), + + vec3(1.0f, 1.0f, 0.99999f), + vec3(-1.0f, 1.0f, 0.99999f), + vec3(-1.0f, -1.0f, 0.99999f) +); + +layout (std140, row_major, binding = UNIFORM_MATRICES_BINDING) uniform Matrices +{ + mat4 World; + mat4 InvWorld; + mat4 View; + mat4 Proj; +} u_Matrices; + +layout (std140, binding = UNIFORM_GENERAL_BINDING) uniform GeneralUBO +{ + vec3 WorldCameraPos; +} u_General; + +out gl_PerVertex +{ + vec4 gl_Position; +}; + +layout(location = 0) out vec3 v_worldFarPlanePos; + +void main() { + vec3 ndc = FarPlaneNDC[gl_VertexID]; + + vec4 farPlaneWorldHomogeneous = inverse(u_Matrices.Proj * u_Matrices.View) * vec4(ndc, 1.0); + + v_worldFarPlanePos = farPlaneWorldHomogeneous.xyz / farPlaneWorldHomogeneous.w; + + gl_Position = vec4(ndc, 1.0); +} \ No newline at end of file diff --git a/Subsystems/Rendering/GpuResources.h b/Subsystems/Rendering/GpuResources.h new file mode 100644 index 0000000..97474e1 --- /dev/null +++ b/Subsystems/Rendering/GpuResources.h @@ -0,0 +1,130 @@ +#pragma once + +#include + +#include "Core/Math/Matrix4x4.h" +#include "Core/Math/Vector4.h" +#include "Core/Templates/ObjectPool.h" +#include "Core/Templates/WeakLocalPoolIndex.h" +#include "Subsystems/Asset/AssetDefs.h" + +namespace ho +{ +struct StaticMeshAsset; +using StaticMeshHandle = WeakLocalPoolIndex; +struct MaterialAsset; +using MaterialHandle = WeakLocalPoolIndex; +struct ShaderAsset; +using ShaderHandle = WeakLocalPoolIndex; +struct TextureAsset; +using TextureHandle = WeakLocalPoolIndex; + +struct GpuStaticMesh; +using GpuStaticMeshHandle = WeakLocalPoolIndex; +struct GpuMaterial; +using GpuMaterialHandle = WeakLocalPoolIndex; +struct GpuTexture; +using GpuTextureHandle = WeakLocalPoolIndex; +struct GpuShader; +using GpuShaderHandle = WeakLocalPoolIndex; + +// 'uint32_t NativeHandlePoolIndex;' used to retrieve API-specific native handles from the rendering system's +// NativeHandle object pool. (index 0 means NULL.) +// +// Since different graphics APIs (GL, DX, Vulkan) require different types and quantities +// of native resources (e.g., VAO/VBO vs PipelineStateObject), this struct avoids direct +// API dependencies. The concrete RenderingSystem implementation uses this index to directly +// access its internal native handle struct with $O(1)$ lookup time. + +struct GpuStaticMesh +{ + uint32_t NativeHandlePoolIndex = 0; + + StaticMeshHandle hOrigin = StaticMeshHandle::sNULL; + uint64_t LastUsedFrame = 0; + GpuStaticMeshHandle hThis; + bool bPersistent = false; + bool bPendingUpload = true; +}; + +struct GpuMaterial +{ + struct GpuPipelineState + { + eMaterialAlphaMode AlphaMode = eMaterialAlphaMode::None; + eMaterialAlphaBlendMode AlphaBlendMode = eMaterialAlphaBlendMode::None; + bool bWireframe = false; + bool bBackfaceCulling = true; + }; + + struct AttributeLayout + { + Vector4 Ambient{0.f, 0.f, 0.f, 1.f}; + Vector4 Diffuse{1.f, 1.f, 1.f, 1.f}; + Vector4 Specular{1.f, 1.f, 1.f, 1.f}; + Vector4 Albedo{1.f, 1.f, 1.f, 1.f}; + Vector4 Emissive{0.f, 0.f, 0.f, 1.f}; + + float Shininess = 32.0f; + float Metallic = 0.0f; + float Roughness = 0.5f; + float IndexOfRefraction = 1.45f; + + float Opacity = 1.0f; + float AlphaThreshold = 0.5f; + float EmissiveIntensity = 1.0f; + float NormalScale = 1.0f; + float OcclusionStrength = 1.0f; + + struct alignas(16) GpuInt32 + { + int32_t Value = 0; + int32_t Padding[3] = {}; + }; + + alignas(16) GpuInt32 UVChannels[static_cast(eMaterialTextureUsage::Last)] = {}; + + alignas(16) Matrix4x4 UVMatrices[static_cast(eMaterialTextureUsage::Last)] = {}; + + alignas(16) GpuInt32 bHasTexture[static_cast(eMaterialTextureUsage::Last)] = {}; + }; + + GpuPipelineState PipelineState; + eMaterialAssetType Type = eMaterialAssetType::None; + + uint32_t NativeHandlePoolIndex = 0; + uint32_t TextureNativeHandlePoolIndices[static_cast(eMaterialTextureUsage::Last)] = {}; + uint32_t ShaderNativeHandlePoolIndices[static_cast(eShaderStage::Last)] = {}; + + GpuTextureHandle hGpuTextures[static_cast(eMaterialTextureUsage::Last)] = {}; + GpuShaderHandle hGpuShaders[static_cast(eShaderStage::Last)] = {}; + + MaterialHandle hOrigin = MaterialHandle::sNULL; + uint64_t LastUsedFrame = 0; + GpuMaterialHandle hThis = GpuMaterialHandle::sNULL; + bool bPersistent = false; + bool bPendingUpload = true; +}; + +struct GpuTexture +{ + uint32_t NativeHandlePoolIndex = 0; + + TextureHandle hOrigin; + uint64_t LastUsedFrame = 0; + GpuTextureHandle hThis; + bool bPersistent = false; + bool bPendingUpload = true; +}; + +struct GpuShader +{ + uint32_t NativeHandlePoolIndex = 0; + + ShaderHandle hOrigin; + uint64_t LastUsedFrame = 0; + GpuShaderHandle hThis; + bool bPersistent = false; + bool bPendingUpload = true; +}; +} // namespace ho \ No newline at end of file diff --git a/Subsystems/Rendering/IRenderingSystem.cc b/Subsystems/Rendering/IRenderingSystem.cc new file mode 100644 index 0000000..cbbec62 --- /dev/null +++ b/Subsystems/Rendering/IRenderingSystem.cc @@ -0,0 +1,1030 @@ +#include "IRenderingSystem.h" + +#include + +#include "GL/RenderingSystemGL.h" +#include "Platforms/IPlatformApplication.h" +#include "Subsystems/Asset/Assets.h" + +namespace ho +{ + +bool IRenderingSystem::BeginRenderPass(RenderPassDesc& renderPassDesc) +{ + if (mCurrentRenderPassIndex != -1) + { + HO_ASSERT(false, "Render pass was already begun. You must call 'EndRenderPass' before beginning a new one."); + return false; + } + + RenderQueue& queue = mRenderQueues[mFrontRenderQueueIndex]; + + if (queue.ActivePassCount >= sMaxRenderPassCount) + { + HO_ASSERT(false, "RenderPass count exceeded the maximum limit (sMaxRenderPassCount)."); + return false; + } + + RenderPass& currentPass = queue.RenderPasses[queue.ActivePassCount]; + auto it = mNameToFrameBufferMap.find(renderPassDesc.hFrameBufferName); + if (it == mNameToFrameBufferMap.end()) + { + HO_ASSERT(false, "FrameBuffer not found."); + return false; + } + currentPass.pFrameBuffer = &it->second; + for (int32_t i = 0; i < static_cast(eRenderTargetType::Last); ++i) + { + currentPass.bClearTarget[i] = renderPassDesc.bClearTarget[i]; + } + currentPass.Viewport = renderPassDesc.Viewport; + currentPass.WorldCameraPos = renderPassDesc.WorldCameraPos; + currentPass.ViewMat = renderPassDesc.ViewMat; + currentPass.ProjMat = renderPassDesc.ProjMat; + + HO_ASSERT(renderPassDesc.DirLightCount <= sMaxDirLightCount, + "Directional light count exceeds the maximum limit. Excess lights will be truncated."); + currentPass.Lights.DirLightCount = math::Min(renderPassDesc.DirLightCount, sMaxDirLightCount); + for (int32_t i = 0; i < currentPass.Lights.DirLightCount; ++i) + { + currentPass.Lights.DirLights[i].Direction[0] = renderPassDesc.DirLights[i].Direction.X; + currentPass.Lights.DirLights[i].Direction[1] = renderPassDesc.DirLights[i].Direction.Y; + currentPass.Lights.DirLights[i].Direction[2] = renderPassDesc.DirLights[i].Direction.Z; + currentPass.Lights.DirLights[i].Color[0] = renderPassDesc.DirLights[i].Color.R; + currentPass.Lights.DirLights[i].Color[1] = renderPassDesc.DirLights[i].Color.G; + currentPass.Lights.DirLights[i].Color[2] = renderPassDesc.DirLights[i].Color.B; + currentPass.Lights.DirLights[i].Intensity = renderPassDesc.DirLights[i].Intensity; + } + + HO_ASSERT(renderPassDesc.PointLightCount <= sMaxPointLightCount, + "Point light count exceeds the maximum limit. Excess lights will be truncated."); + currentPass.Lights.PointLightCount = math::Min(renderPassDesc.PointLightCount, sMaxPointLightCount); + for (int32_t i = 0; i < currentPass.Lights.PointLightCount; ++i) + { + currentPass.Lights.PointLights[i].Position[0] = renderPassDesc.PointLights[i].Position.X; + currentPass.Lights.PointLights[i].Position[1] = renderPassDesc.PointLights[i].Position.Y; + currentPass.Lights.PointLights[i].Position[2] = renderPassDesc.PointLights[i].Position.Z; + currentPass.Lights.PointLights[i].Color[0] = renderPassDesc.PointLights[i].Color.R; + currentPass.Lights.PointLights[i].Color[1] = renderPassDesc.PointLights[i].Color.G; + currentPass.Lights.PointLights[i].Color[2] = renderPassDesc.PointLights[i].Color.B; + currentPass.Lights.PointLights[i].Intensity = renderPassDesc.PointLights[i].Intensity; + currentPass.Lights.PointLights[i].Constant = renderPassDesc.PointLights[i].Constant; + currentPass.Lights.PointLights[i].Linear = renderPassDesc.PointLights[i].Linear; + currentPass.Lights.PointLights[i].Quadratic = renderPassDesc.PointLights[i].Quadratic; + } + + HO_ASSERT(renderPassDesc.SpotLightCount <= sMaxSpotLightCount, + "Spot light count exceeds the maximum limit. Excess lights will be truncated."); + currentPass.Lights.SpotLightCount = math::Min(renderPassDesc.SpotLightCount, sMaxSpotLightCount); + for (int32_t i = 0; i < currentPass.Lights.SpotLightCount; ++i) + { + currentPass.Lights.SpotLights[i].Position[0] = renderPassDesc.SpotLights[i].Position.X; + currentPass.Lights.SpotLights[i].Position[1] = renderPassDesc.SpotLights[i].Position.Y; + currentPass.Lights.SpotLights[i].Position[2] = renderPassDesc.SpotLights[i].Position.Z; + currentPass.Lights.SpotLights[i].Direction[0] = renderPassDesc.SpotLights[i].Direction.X; + currentPass.Lights.SpotLights[i].Direction[1] = renderPassDesc.SpotLights[i].Direction.Y; + currentPass.Lights.SpotLights[i].Direction[2] = renderPassDesc.SpotLights[i].Direction.Z; + currentPass.Lights.SpotLights[i].Color[0] = renderPassDesc.SpotLights[i].Color.R; + currentPass.Lights.SpotLights[i].Color[1] = renderPassDesc.SpotLights[i].Color.G; + currentPass.Lights.SpotLights[i].Color[2] = renderPassDesc.SpotLights[i].Color.B; + currentPass.Lights.SpotLights[i].Intensity = renderPassDesc.SpotLights[i].Intensity; + currentPass.Lights.SpotLights[i].InnerCutOff = renderPassDesc.SpotLights[i].InnerCutOff; + currentPass.Lights.SpotLights[i].OuterCutOff = renderPassDesc.SpotLights[i].OuterCutOff; + currentPass.Lights.SpotLights[i].Constant = renderPassDesc.SpotLights[i].Constant; + currentPass.Lights.SpotLights[i].Linear = renderPassDesc.SpotLights[i].Linear; + currentPass.Lights.SpotLights[i].Quadratic = renderPassDesc.SpotLights[i].Quadratic; + } + + if (renderPassDesc.hSkyMap.IsValid()) + { + currentPass.pSkyMap = renderPassDesc.hSkyMap.Get()->bPendingUpload ? nullptr : renderPassDesc.hSkyMap.Get(); + } + + if (renderPassDesc.hIrradianceMap.IsValid()) + { + currentPass.pIrradianceMap = + renderPassDesc.hIrradianceMap.Get()->bPendingUpload ? nullptr : renderPassDesc.hIrradianceMap.Get(); + } + + currentPass.OpaqueDrawCommandCount = 0; + currentPass.MaskedDrawCommandCount = 0; + currentPass.BlendedDrawCommandCount = 0; + + mCurrentRenderPassIndex = queue.ActivePassCount; + ++queue.ActivePassCount; + + return true; +} + +bool IRenderingSystem::SubmitDrawCommand(const DrawCommandDesc& commandDesc) +{ + HO_ASSERT(commandDesc.hGpuStaticMesh.IsValid(), "Static mesh is not uploaded at all."); + HO_ASSERT(commandDesc.hGpuMaterial.IsValid(), "Material is not uploaded at all."); + + if (mCurrentRenderPassIndex == -1) + { + HO_ASSERT(false, "Render pass is not begun. You must call 'BeginRenderPass' before submit draw command."); + return false; + } + + if (commandDesc.hGpuStaticMesh.Get()->bPendingUpload || commandDesc.hGpuMaterial.Get()->bPendingUpload) + { + return false; + } + + DrawCommand newCommand; + newCommand.hGpuStaticMesh = commandDesc.hGpuStaticMesh; + newCommand.hGpuMaterial = commandDesc.hGpuMaterial; + newCommand.VertexOffset = commandDesc.VertexOffset; + newCommand.IndexOffset = commandDesc.IndexOffset; + newCommand.IndexCount = commandDesc.IndexCount; + newCommand.WorldMat = commandDesc.WorldMat; + newCommand.SqrdDistanceFromCamera = + (mRenderQueues[mFrontRenderQueueIndex].RenderPasses[mCurrentRenderPassIndex].WorldCameraPos - + Vector3(newCommand.WorldMat.GetCol3())) + .SqrdMagnitude(); + + RenderPass& pass = mRenderQueues[mFrontRenderQueueIndex].RenderPasses[mCurrentRenderPassIndex]; + switch (commandDesc.AlphaMode) + { + case eMaterialAlphaMode::Opaque: + { + if (pass.OpaqueDrawCommandCount < sMaxOpaqueDrawCommandCount) + { + pass.OpaqueDrawCommands[pass.OpaqueDrawCommandCount] = newCommand; + ++pass.OpaqueDrawCommandCount; + } + else + { + HO_ASSERT(false, + "Opaque draw command count exceeds the maximum limit. Excess commands will be truncated."); + return false; + } + } + break; + case eMaterialAlphaMode::Mask: + { + if (pass.MaskedDrawCommandCount < sMaxMaskedDrawCommandCount) + { + pass.MaskedDrawCommands[pass.MaskedDrawCommandCount] = newCommand; + ++pass.MaskedDrawCommandCount; + } + else + { + HO_ASSERT(false, + "Masked draw command count exceeds the maximum limit. Excess commands will be truncated."); + return false; + } + } + break; + case eMaterialAlphaMode::Blend: + { + if (pass.BlendedDrawCommandCount < sMaxBlendedDrawCommandCount) + { + pass.BlendedDrawCommands[pass.BlendedDrawCommandCount] = newCommand; + ++pass.BlendedDrawCommandCount; + } + else + { + HO_ASSERT(false, + "Blended draw command count exceeds the maximum limit. Excess commands will be truncated."); + return false; + } + } + break; + default: + HO_ASSERT(false, "Invalid alpha mode."); + return false; + } + return true; +} + +bool IRenderingSystem::SubmitUIViewportData(const IPlatformWindow* pTargetWindow, const ImDrawData& srcDrawData) +{ + if (!srcDrawData.Valid) + { + return false; + } + + RenderQueue& queue = mRenderQueues[mFrontRenderQueueIndex]; + + if (queue.ActiveViewportCount >= sMaxViewportCount) + { + HO_ASSERT(false, "UI viewport data count exceeds the maximum limit. Excess data will be truncated."); + return false; + } + + UIViewportData& dstData = queue.UIViewports[queue.ActiveViewportCount]; + + dstData.pTargetWindow = pTargetWindow; + + dstData.pDrawData->Valid = srcDrawData.Valid; + dstData.pDrawData->CmdListsCount = srcDrawData.CmdListsCount; + dstData.pDrawData->TotalIdxCount = srcDrawData.TotalIdxCount; + dstData.pDrawData->TotalVtxCount = srcDrawData.TotalVtxCount; + + // deep copy ImDrawData::CmdLists + const int32_t oldSize = dstData.pDrawData->CmdLists.Size; + dstData.pDrawData->CmdLists.resize(srcDrawData.CmdLists.Size); + + for (int32_t i = oldSize; i < dstData.pDrawData->CmdLists.Size; ++i) + { + dstData.pDrawData->CmdLists[i] = nullptr; + } + + for (int32_t i = 0; i < srcDrawData.CmdLists.Size; ++i) + { + if (dstData.pDrawData->CmdLists[i] == nullptr) + { + dstData.pDrawData->CmdLists[i] = IM_NEW(ImDrawList)(nullptr); + } + + ImDrawList* pDstList = dstData.pDrawData->CmdLists[i]; + const ImDrawList* pSrcList = srcDrawData.CmdLists[i]; + + pDstList->Flags = pSrcList->Flags; + + pDstList->CmdBuffer = pSrcList->CmdBuffer; + pDstList->IdxBuffer = pSrcList->IdxBuffer; + pDstList->VtxBuffer = pSrcList->VtxBuffer; + } + + dstData.pDrawData->DisplayPos = srcDrawData.DisplayPos; + dstData.pDrawData->DisplaySize = srcDrawData.DisplaySize; + dstData.pDrawData->FramebufferScale = srcDrawData.FramebufferScale; + dstData.pDrawData->OwnerViewport = srcDrawData.OwnerViewport; + dstData.pDrawData->Textures = srcDrawData.Textures; + + ++queue.ActiveViewportCount; + return true; +} + +bool IRenderingSystem::EndRenderPass() +{ + if (mCurrentRenderPassIndex == -1) + { + HO_ASSERT(false, "Render pass was already ended. You must call 'BeginRenderPass' before ending."); + return false; + } + mCurrentRenderPassIndex = -1; + return true; +} + +GpuStaticMeshHandle IRenderingSystem::EnqueueUploadStaticMesh(StaticMeshHandle hStaticMesh, bool bPersistent) +{ + if (mhPendingStaticMeshQueue.IsFull()) + { + HO_ASSERT(false, "Pending queue is full."); + return GpuStaticMeshHandle::sNULL; + } + GpuStaticMeshHandle hGpuStaticMesh = prepareUploadStaticMesh(hStaticMesh, bPersistent); + if (hGpuStaticMesh.IsNULL()) + { + HO_ASSERT(false, "Preparing upload was failed. Asset might be invalid."); + return GpuStaticMeshHandle::sNULL; + } + + mPendingStaticMeshQueueLock.Lock(); + mhPendingStaticMeshQueue.Push(hGpuStaticMesh); + mPendingStaticMeshQueueLock.Unlock(); + return hGpuStaticMesh; +} + +GpuMaterialHandle IRenderingSystem::EnqueueUploadMaterial(MaterialHandle hMaterial, bool bPersistent) +{ + if (mhPendingMaterialQueue.IsFull()) + { + HO_ASSERT(false, "Pending queue is full."); + return GpuMaterialHandle::sNULL; + } + GpuMaterialHandle hGpuMaterial = prepareUploadMaterial(hMaterial, bPersistent); + if (hGpuMaterial.IsNULL()) + { + HO_ASSERT(false, "Preparing upload was failed. Asset might be invalid."); + return GpuMaterialHandle::sNULL; + } + + mPendingMaterialQueueLock.Lock(); + mhPendingMaterialQueue.Push(hGpuMaterial); + mPendingMaterialQueueLock.Unlock(); + return hGpuMaterial; +} + +GpuTextureHandle IRenderingSystem::EnqueueUploadTexture(TextureHandle hTexture, bool bPersistent) +{ + if (mhPendingTextureQueue.IsFull()) + { + HO_ASSERT(false, "Pending queue is full."); + return GpuTextureHandle::sNULL; + } + GpuTextureHandle hGpuTexture = prepareUploadTexture(hTexture, bPersistent); + if (hGpuTexture.IsNULL()) + { + HO_ASSERT(false, "Preparing upload was failed. Asset might be invalid."); + return GpuTextureHandle::sNULL; + } + + mPendingTextureQueueLock.Lock(); + mhPendingTextureQueue.Push(hGpuTexture); + mPendingTextureQueueLock.Unlock(); + return hGpuTexture; +} + +GpuShaderHandle IRenderingSystem::EnqueueUploadShader(ShaderHandle hShader, bool bPersistent) +{ + if (mhPendingShaderQueue.IsFull()) + { + HO_ASSERT(false, "Pending queue is full."); + return GpuShaderHandle::sNULL; + } + GpuShaderHandle hGpuShader = prepareUploadShader(hShader, bPersistent); + if (hGpuShader.IsNULL()) + { + HO_ASSERT(false, "Preparing upload was failed. Asset might be invalid."); + return GpuShaderHandle::sNULL; + } + + mPendingShaderQueueLock.Lock(); + mhPendingShaderQueue.Push(hGpuShader); + mPendingShaderQueueLock.Unlock(); + return hGpuShader; +} + +IRenderingSystem::IRenderingSystem() +{ + HO_ASSERT(!spInstance, "Rendering system muse be created by 'createInstance' function."); +} + +void IRenderingSystem::createInstance(eGraphicsAPI api) +{ + if (spInstance) + { + HO_ASSERT(false, "Rendering system is already created."); + } + else + { + switch (api) + { + case GL: + spInstance = new RenderingSystemGL(); + break; + default: + HO_ASSERT(false, "Unsupported API."); + } + } + + spInstance->mhPendingStaticMeshQueue = FixedQueue(sPendingQueueSize); + spInstance->mhPendingMaterialQueue = FixedQueue(sPendingQueueSize); + spInstance->mhPendingTextureQueue = FixedQueue(sPendingQueueSize); + spInstance->mhPendingShaderQueue = FixedQueue(sPendingQueueSize); +} + +void IRenderingSystem::deleteInstance() +{ + if (!spInstance) + { + HO_ASSERT(false, "Rendering system is not created."); + } + else + { + delete spInstance; + spInstance = nullptr; + } +} + +void IRenderingSystem::run() +{ + HO_ASSERT(spInstance, "Rendering system is not created."); + + // This deactivation is needed for render thread to activate context. + IPlatformApplication::GetInstance().GetMainWindow()->DeactivateContext(); + mpRenderThread = std::make_unique(reinterpret_cast(runInternal)); + mpRenderThread->SetName("Render Thread"); +} + +int IRenderingSystem::runInternal(void* unused) +{ + (void)unused; + IPlatformApplication::GetInstance().GetMainWindow()->ActivateContext(); + + while (spInstance->mbIsRunning) + { + spInstance->mRenderSync.WaitUntilEngineSwapComplete(); + + if (!spInstance->mbIsRunning) + { + break; + } + + const uint64_t currentFrame = spInstance->mRenderQueues[spInstance->mBackRenderQueueIndex].FrameCount; + + spInstance->uploadPendingResources(currentFrame); + + spInstance->executeRenderQueue(); + + spInstance->evictStaleResources(currentFrame, 1000); + + spInstance->flush(); // TODO: CPU might do something while waiting GPU completes own jobs. + + spInstance->renderUI(); + + spInstance->mRenderSync.NotifyRenderDone(); + } + + spInstance->releaseAllResources(); + + IPlatformApplication::GetInstance().GetMainWindow()->DeactivateContext(); + + return 0; +} + +void IRenderingSystem::shutdown() +{ + mbIsRunning = false; + + mRenderSync.NotifyEngineSwapComplete(); + + mpRenderThread->Join(); +} + +void IRenderingSystem::executeRenderQueue() +{ + prepareExecuteRenderQueue(); + + RenderQueue& queue = mRenderQueues[mBackRenderQueueIndex]; + + for (int32_t qi = 0; qi < queue.ActivePassCount; ++qi) + { + RenderPass& pass = queue.RenderPasses[qi]; + + sortDrawCommands(pass); + + prepareExecuteRenderPass(pass); + + if (pass.OpaqueDrawCommandCount > 0) + { + prepareExecuteOpaqueDrawCommands(); + + GpuMaterialHandle hPrevRenderedMaterial = GpuMaterialHandle::sNULL; + for (int32_t ci = 0; ci < pass.OpaqueDrawCommandCount; ++ci) + { + if (hPrevRenderedMaterial != pass.OpaqueDrawCommands[ci].hGpuMaterial) + { + applyMaterial(pass.OpaqueDrawCommands[ci].hGpuMaterial); + hPrevRenderedMaterial = pass.OpaqueDrawCommands[ci].hGpuMaterial; + } + executeDrawCommand(pass.OpaqueDrawCommands[ci]); + updateDrawCommandResourceUsageStates(pass.OpaqueDrawCommands[ci], queue.FrameCount); + } + } + + if (pass.MaskedDrawCommandCount > 0) + { + prepareExecuteMaskedDrawCommands(); + + GpuMaterialHandle hPrevRenderedMaterial = GpuMaterialHandle::sNULL; + for (int32_t ci = 0; ci < pass.MaskedDrawCommandCount; ++ci) + { + if (hPrevRenderedMaterial != pass.MaskedDrawCommands[ci].hGpuMaterial) + { + applyMaterial(pass.MaskedDrawCommands[ci].hGpuMaterial); + hPrevRenderedMaterial = pass.MaskedDrawCommands[ci].hGpuMaterial; + } + executeDrawCommand(pass.MaskedDrawCommands[ci]); + updateDrawCommandResourceUsageStates(pass.MaskedDrawCommands[ci], queue.FrameCount); + } + } + + if (pass.pSkyMap) + { + executeSkyMapPass(pass.pSkyMap); + } + + executeInWorldDebugDrawPass(); + + if (pass.BlendedDrawCommandCount > 0) + { + prepareExecuteBlendedDrawCommands(); + + GpuMaterialHandle hPrevRenderedMaterial = GpuMaterialHandle::sNULL; + for (int32_t ci = 0; ci < pass.BlendedDrawCommandCount; ++ci) + { + if (hPrevRenderedMaterial != pass.BlendedDrawCommands[ci].hGpuMaterial) + { + applyMaterial(pass.BlendedDrawCommands[ci].hGpuMaterial); + hPrevRenderedMaterial = pass.BlendedDrawCommands[ci].hGpuMaterial; + } + executeDrawCommand(pass.BlendedDrawCommands[ci]); + updateDrawCommandResourceUsageStates(pass.BlendedDrawCommands[ci], queue.FrameCount); + } + } + + executeOverlayDebugDrawPass(); + + resolveFrameBuffer(*pass.pFrameBuffer); + + finishExecuteRenderPass(pass); + + updateRenderPassResourceUsageStates(pass, queue.FrameCount); + } + + finishExecuteRenderQueue(); +} + +void IRenderingSystem::updateDrawCommandResourceUsageStates(DrawCommand& command, uint64_t currentFrame) +{ + command.hGpuStaticMesh.Get()->LastUsedFrame = currentFrame; + + if (command.hGpuMaterial.Get()->LastUsedFrame < currentFrame) + { + command.hGpuMaterial.Get()->LastUsedFrame = currentFrame; + for (auto& hGpuTexture : command.hGpuMaterial.Get()->hGpuTextures) + { + if (hGpuTexture.IsValid()) + { + hGpuTexture.Get()->LastUsedFrame = currentFrame; + } + } + for (auto& hGpuShader : command.hGpuMaterial.Get()->hGpuShaders) + { + if (hGpuShader.IsValid()) + { + hGpuShader.Get()->LastUsedFrame = currentFrame; + } + } + } +} + +void IRenderingSystem::updateRenderPassResourceUsageStates(RenderPass& pass, uint64_t currentFrame) +{ + if (pass.pSkyMap) + { + pass.pSkyMap->LastUsedFrame = currentFrame; + } + + if (pass.pIrradianceMap) + { + pass.pIrradianceMap->LastUsedFrame = currentFrame; + } +} + +void IRenderingSystem::sortDrawCommands(RenderPass& pass) +{ + std::sort(pass.OpaqueDrawCommands, + pass.OpaqueDrawCommands + pass.OpaqueDrawCommandCount, + [](const DrawCommand& a, const DrawCommand& b) + { + if (a.hGpuMaterial != b.hGpuMaterial) + { + return a.hGpuMaterial.GetIndex() < b.hGpuMaterial.GetIndex(); + } + return a.SqrdDistanceFromCamera < b.SqrdDistanceFromCamera; + }); + + std::sort(pass.MaskedDrawCommands, + pass.MaskedDrawCommands + pass.MaskedDrawCommandCount, + [](const DrawCommand& a, const DrawCommand& b) + { + if (a.hGpuMaterial != b.hGpuMaterial) + { + return a.hGpuMaterial.GetIndex() < b.hGpuMaterial.GetIndex(); + } + return a.SqrdDistanceFromCamera < b.SqrdDistanceFromCamera; + }); + + std::sort(pass.BlendedDrawCommands, + pass.BlendedDrawCommands + pass.BlendedDrawCommandCount, + [](const DrawCommand& a, const DrawCommand& b) + { + if (a.SqrdDistanceFromCamera != b.SqrdDistanceFromCamera) + { + return a.SqrdDistanceFromCamera > b.SqrdDistanceFromCamera; + } + return a.hGpuMaterial.GetIndex() < b.hGpuMaterial.GetIndex(); + }); +} + +void IRenderingSystem::swapRenderQueues(uint64_t currentFrame) +{ + mRenderSync.WaitUntilRenderDone(); + + mFrontRenderQueueIndex = (mFrontRenderQueueIndex + 1) % 2; + mRenderQueues[mFrontRenderQueueIndex].ActivePassCount = 0; + mRenderQueues[mFrontRenderQueueIndex].ActiveViewportCount = 0; + mCurrentRenderPassIndex = -1; + + mBackRenderQueueIndex = (mBackRenderQueueIndex + 1) % 2; + mRenderQueues[mBackRenderQueueIndex].FrameCount = currentFrame; + + mRenderSync.NotifyEngineSwapComplete(); +} + +bool IRenderingSystem::trySwapRenderQueues(uint64_t currentFrame) +{ + if (!mRenderSync.IsRenderDone()) + { + return false; + } + + swapRenderQueues(currentFrame); + return true; +} + +// TODO: Currently, always upload all pending resources. +// Need to apply time slicing method later. +void IRenderingSystem::uploadPendingResources(uint64_t currentFrame) +{ + mPendingStaticMeshQueueLock.Lock(); + int32_t pendingStaticMeshCount = static_cast(mhPendingStaticMeshQueue.GetCount()); + mPendingStaticMeshQueueLock.Unlock(); + + while (pendingStaticMeshCount > 0) + { + mPendingStaticMeshQueueLock.Lock(); + const GpuStaticMeshHandle hPending = mhPendingStaticMeshQueue.Front(); + mhPendingStaticMeshQueue.Pop(); + mPendingStaticMeshQueueLock.Unlock(); + + switch (uploadStaticMesh(hPending, currentFrame)) + { + case eResourceTransferResult::Success: + case eResourceTransferResult::Failed: + break; + case eResourceTransferResult::NeedsRetry: + mPendingStaticMeshQueueLock.Lock(); + mhPendingStaticMeshQueue.Push(hPending); + mPendingStaticMeshQueueLock.Unlock(); + break; + default: + HO_ASSERT(false, "Invalid transfer result."); + } + + --pendingStaticMeshCount; + } + + mPendingMaterialQueueLock.Lock(); + int32_t pendingMaterialCount = static_cast(mhPendingMaterialQueue.GetCount()); + mPendingMaterialQueueLock.Unlock(); + + while (pendingMaterialCount > 0) + { + mPendingMaterialQueueLock.Lock(); + const GpuMaterialHandle hPending = mhPendingMaterialQueue.Front(); + mhPendingMaterialQueue.Pop(); + mPendingMaterialQueueLock.Unlock(); + + switch (uploadMaterial(hPending, currentFrame)) + { + case eResourceTransferResult::Success: + case eResourceTransferResult::Failed: + break; + case eResourceTransferResult::NeedsRetry: + mPendingMaterialQueueLock.Lock(); + mhPendingMaterialQueue.Push(hPending); + mPendingMaterialQueueLock.Unlock(); + break; + default: + HO_ASSERT(false, "Invalid transfer result."); + } + + --pendingMaterialCount; + } + + mPendingTextureQueueLock.Lock(); + int32_t pendingTextureCount = static_cast(mhPendingTextureQueue.GetCount()); + mPendingTextureQueueLock.Unlock(); + + while (pendingTextureCount > 0) + { + mPendingTextureQueueLock.Lock(); + const GpuTextureHandle hPending = mhPendingTextureQueue.Front(); + mhPendingTextureQueue.Pop(); + mPendingTextureQueueLock.Unlock(); + + switch (uploadTexture(hPending, currentFrame)) + { + case eResourceTransferResult::Success: + case eResourceTransferResult::Failed: + break; + case eResourceTransferResult::NeedsRetry: + mPendingTextureQueueLock.Lock(); + mhPendingTextureQueue.Push(hPending); + mPendingTextureQueueLock.Unlock(); + break; + default: + HO_ASSERT(false, "Invalid transfer result."); + } + + --pendingTextureCount; + } + + mPendingShaderQueueLock.Lock(); + int32_t pendingShaderCount = static_cast(mhPendingShaderQueue.GetCount()); + mPendingShaderQueueLock.Unlock(); + + while (pendingShaderCount > 0) + { + mPendingShaderQueueLock.Lock(); + const GpuShaderHandle hPending = mhPendingShaderQueue.Front(); + mhPendingShaderQueue.Pop(); + mPendingShaderQueueLock.Unlock(); + + switch (uploadShader(hPending, currentFrame)) + { + case eResourceTransferResult::Success: + case eResourceTransferResult::Failed: + break; + case eResourceTransferResult::NeedsRetry: + mPendingShaderQueueLock.Lock(); + mhPendingShaderQueue.Push(hPending); + mPendingShaderQueueLock.Unlock(); + break; + default: + HO_ASSERT(false, "Invalid transfer result."); + } + + --pendingShaderCount; + } +} + +// TODO: Check cache hit rate 1 loop way vs 4 loop way later. +int32_t IRenderingSystem::evictStaleResources(uint64_t currentFrame, uint64_t frameThreshold) +{ + int32_t evictedCount = 0; + + for (auto& staticMesh : mGpuStaticMeshPool.GetMutableRawPool()) + { + if (staticMesh.has_value()) + { + if (!staticMesh.value().bPersistent && !staticMesh.value().bPendingUpload && + (currentFrame - staticMesh.value().LastUsedFrame) > frameThreshold) + { + unloadStaticMesh(staticMesh.value().hThis); + ++evictedCount; + } + } + } + for (auto& material : mGpuMaterialPool.GetMutableRawPool()) + { + if (material.has_value()) + { + if (!material.value().bPersistent && !material.value().bPendingUpload && + (currentFrame - material.value().LastUsedFrame) > frameThreshold) + { + unloadMaterial(material.value().hThis); + ++evictedCount; + } + } + } + for (auto& texture : mGpuTexturePool.GetMutableRawPool()) + { + if (texture.has_value()) + { + if (!texture.value().bPersistent && !texture.value().bPendingUpload && + (currentFrame - texture.value().LastUsedFrame) > frameThreshold) + { + unloadTexture(texture.value().hThis); + ++evictedCount; + } + } + } + for (auto& shader : mGpuShaderPool.GetMutableRawPool()) + { + if (shader.has_value()) + { + if (!shader.value().bPersistent && !shader.value().bPendingUpload && + (currentFrame - shader.value().LastUsedFrame) > frameThreshold) + { + unloadShader(shader.value().hThis); + ++evictedCount; + } + } + } + return evictedCount; +} + +GpuStaticMeshHandle IRenderingSystem::prepareUploadStaticMesh(StaticMeshHandle hStaticMesh, bool bPersistent) +{ + if (hStaticMesh.IsValid()) + { + const uint32_t idx = mGpuStaticMeshPool.Add(GpuStaticMesh()); + GpuStaticMeshHandle hNewGpuStaticMesh = GpuStaticMeshHandle(&mGpuStaticMeshPool, idx); + hStaticMesh.Get()->hRenderProxy = hNewGpuStaticMesh; + hNewGpuStaticMesh.Get()->hOrigin = hStaticMesh; + hNewGpuStaticMesh.Get()->LastUsedFrame = 0; + hNewGpuStaticMesh.Get()->hThis = hNewGpuStaticMesh; + hNewGpuStaticMesh.Get()->bPersistent = bPersistent; + hNewGpuStaticMesh.Get()->bPendingUpload = true; + return hNewGpuStaticMesh; + } + else + { + HO_ASSERT(false, "Original asset is invalid."); + return GpuStaticMeshHandle::sNULL; + } +} + +GpuMaterialHandle IRenderingSystem::prepareUploadMaterial(MaterialHandle hMaterial, bool bPersistent) +{ + if (hMaterial.IsValid()) + { + const uint32_t idx = mGpuMaterialPool.Add(GpuMaterial()); + GpuMaterialHandle hNewGpuMaterial = GpuMaterialHandle(&mGpuMaterialPool, idx); + hMaterial.Get()->hRenderProxy = hNewGpuMaterial; + + hNewGpuMaterial.Get()->PipelineState.AlphaMode = hMaterial.Get()->PipelineState.AlphaMode; + hNewGpuMaterial.Get()->PipelineState.AlphaBlendMode = hMaterial.Get()->PipelineState.AlphaBlendMode; + hNewGpuMaterial.Get()->PipelineState.bWireframe = hMaterial.Get()->PipelineState.bWireframe; + hNewGpuMaterial.Get()->PipelineState.bBackfaceCulling = hMaterial.Get()->PipelineState.bBackfaceCulling; + + hNewGpuMaterial.Get()->Type = hMaterial.Get()->Type; + + for (int32_t i = 0; i < static_cast(eMaterialTextureUsage::Last); ++i) + { + if (hMaterial.Get()->hTextures[i].IsValid()) + { + hNewGpuMaterial.Get()->hGpuTextures[i] = + prepareUploadTexture(hMaterial.Get()->hTextures[i], bPersistent); + if (hNewGpuMaterial.Get()->hGpuTextures[i].IsValid()) + { + mPendingTextureQueueLock.Lock(); + mhPendingTextureQueue.Push(hNewGpuMaterial.Get()->hGpuTextures[i]); + mPendingTextureQueueLock.Unlock(); + } + else + { + hNewGpuMaterial.Get()->hGpuTextures[i] = GpuTextureHandle::sNULL; + } + } + } + + for (int32_t i = 0; i < static_cast(eShaderStage::Last); ++i) + { + if (hMaterial.Get()->hShaders[i].IsValid()) + { + hNewGpuMaterial.Get()->hGpuShaders[i] = prepareUploadShader(hMaterial.Get()->hShaders[i], bPersistent); + if (hNewGpuMaterial.Get()->hGpuShaders[i].IsValid()) + { + mPendingShaderQueueLock.Lock(); + mhPendingShaderQueue.Push(hNewGpuMaterial.Get()->hGpuShaders[i]); + mPendingShaderQueueLock.Unlock(); + } + else + { + hNewGpuMaterial.Get()->hGpuShaders[i] = GpuShaderHandle::sNULL; + } + } + } + + hNewGpuMaterial.Get()->hOrigin = hMaterial; + hNewGpuMaterial.Get()->LastUsedFrame = 0; + hNewGpuMaterial.Get()->hThis = hNewGpuMaterial; + hNewGpuMaterial.Get()->bPersistent = bPersistent; + hNewGpuMaterial.Get()->bPendingUpload = true; + return hNewGpuMaterial; + } + else + { + HO_ASSERT(false, "Original asset is invalid."); + return GpuMaterialHandle::sNULL; + } +} + +GpuTextureHandle IRenderingSystem::prepareUploadTexture(TextureHandle hTexture, bool bPersistent) +{ + if (hTexture.IsValid() || (hTexture == TextureHandle::sNULL && bPersistent)) + { + const uint32_t idx = mGpuTexturePool.Add(GpuTexture()); + GpuTextureHandle hNewGpuTexture = GpuTextureHandle(&mGpuTexturePool, idx); + // when texture is from asset. + if (hTexture.IsValid()) + { + hTexture.Get()->hRenderProxy = hNewGpuTexture; + } + + hNewGpuTexture.Get()->hOrigin = hTexture; + hNewGpuTexture.Get()->LastUsedFrame = 0; + hNewGpuTexture.Get()->hThis = hNewGpuTexture; + hNewGpuTexture.Get()->bPersistent = bPersistent; + hNewGpuTexture.Get()->bPendingUpload = hTexture.IsValid(); + return hNewGpuTexture; + } + else + { + HO_ASSERT(false, "Original asset is invalid or mismatched with render target creation arguments."); + return GpuTextureHandle::sNULL; + } +} + +GpuShaderHandle IRenderingSystem::prepareUploadShader(ShaderHandle hShader, bool bPersistent) +{ + if (hShader.IsValid()) + { + const uint32_t idx = mGpuShaderPool.Add(GpuShader()); + GpuShaderHandle hNewGpuShader = GpuShaderHandle(&mGpuShaderPool, idx); + hShader.Get()->hRenderProxy = hNewGpuShader; + + hNewGpuShader.Get()->hOrigin = hShader; + hNewGpuShader.Get()->LastUsedFrame = 0; + hNewGpuShader.Get()->hThis = hNewGpuShader; + hNewGpuShader.Get()->bPersistent = bPersistent; + hNewGpuShader.Get()->bPendingUpload = true; + return hNewGpuShader; + } + else + { + HO_ASSERT(false, "Original asset is invalid."); + return GpuShaderHandle::sNULL; + } +} + +void IRenderingSystem::finishUnloadStaticMesh(GpuStaticMeshHandle hUnloadedGpuStaticMesh) +{ + if (hUnloadedGpuStaticMesh.IsValid()) + { + mGpuStaticMeshPool.Remove(hUnloadedGpuStaticMesh.GetIndex()); + } +} + +void IRenderingSystem::finishUnloadMaterial(GpuMaterialHandle hUnloadedGpuMaterial) +{ + if (hUnloadedGpuMaterial.IsValid()) + { // TODO: needs to handle bound textures, shaders on material. + mGpuMaterialPool.Remove(hUnloadedGpuMaterial.GetIndex()); + } +} + +void IRenderingSystem::finishUnloadTexture(GpuTextureHandle hUnloadedGpuTexture) +{ + if (hUnloadedGpuTexture.IsValid()) + { + mGpuTexturePool.Remove(hUnloadedGpuTexture.GetIndex()); + } +} + +void IRenderingSystem::finishUnloadShader(GpuShaderHandle hUnloadedGpuShader) +{ + if (hUnloadedGpuShader.IsValid()) + { + mGpuShaderPool.Remove(hUnloadedGpuShader.GetIndex()); + } +} + +bool IRenderingSystem::validateFrameBufferDesc(const FrameBufferDesc& desc) +{ + if (desc.Width == 0 || desc.Height == 0 || desc.SampleCount == 0) + { + return false; + } + + int32_t usedTargetCount = 0; + for (int32_t i = 0; i < static_cast(eRenderTargetType::Last); ++i) + { + if (i != static_cast(eRenderTargetType::Depth)) + { + ++usedTargetCount; + } + } + if (usedTargetCount == 0 && + desc.TargetDesc[static_cast(eRenderTargetType::Depth)].Format != eRenderTargetFormat::None) + { + return false; + } + for (int32_t i = 0; i < static_cast(eRenderTargetType::Last); ++i) + { + if (i != static_cast(eRenderTargetType::Depth)) + { + const eRenderTargetFormat format = desc.TargetDesc[i].Format; + if (format == eRenderTargetFormat::None || format == eRenderTargetFormat::D24_UNORM_S8_UINT || + format == eRenderTargetFormat::R32_FLOAT) + { + return false; + } + } + } + + if (desc.TargetDesc[static_cast(eRenderTargetType::Depth)].Format != eRenderTargetFormat::None) + { + const eRenderTargetFormat format = desc.TargetDesc[static_cast(eRenderTargetType::Depth)].Format; + + if (format != eRenderTargetFormat::D24_UNORM_S8_UINT && format != eRenderTargetFormat::R16G16_FLOAT) + { + return false; + } + } + + return true; +} + +IRenderingSystem* IRenderingSystem::spInstance = nullptr; + +IRenderingSystem::UIViewportData::UIViewportData() + : pTargetWindow(nullptr) + , pDrawData(std::make_unique()) +{ +} + +IRenderingSystem::UIViewportData::~UIViewportData() = default; +IRenderingSystem::UIViewportData::UIViewportData(UIViewportData&&) noexcept = default; +IRenderingSystem::UIViewportData& IRenderingSystem::UIViewportData::operator=(UIViewportData&&) noexcept = default; +} // namespace ho \ No newline at end of file diff --git a/Subsystems/Rendering/IRenderingSystem.h b/Subsystems/Rendering/IRenderingSystem.h new file mode 100644 index 0000000..418c77d --- /dev/null +++ b/Subsystems/Rendering/IRenderingSystem.h @@ -0,0 +1,464 @@ +#pragma once + +#include "Config.h" +#include "Core/Math/Color128.h" +#include "Core/Math/Matrix4x4.h" +#include "Core/Math/Vector3.h" +#include "Core/Templates/FixedArray.h" +#include "Core/Templates/FixedQueue.h" +#include "Core/Templates/GlobalPoolIndex.h" +#include "Core/Templates/ObjectPool.h" +#include "Core/Thread/SpinLock.h" +#include "GpuResources.h" +#include "RenderSync.h" + +// ================================================================================= +// Rendering System Overview +// ================================================================================= +// +// [ Architecture ] +// - Uses a Double-Buffering strategy with a Front and a Back render queue. +// - Each render queue contains a list of RenderPasses. +// - A RenderPass stores everything needed for drawing: +// - WHERE to draw : Frame buffer target +// - HOW to look : Camera position, View & Proj matrices +// - LIGHTS : Light source data (Directional lights, etc.) +// - WHAT to draw : A collection of DrawCommands (Opaque / Transparent) +// +// [ Frame Workflow ] +// All draw calls flow in the following order per frame: +// +// BeginRenderPass() -> SubmitDrawCommand() -> EndRenderPass() +// -> executeRenderQueue() -> renderUI() -> swapRenderQueues() +// +// ================================================================================= + +struct ImDrawData; + +namespace ho +{ +struct StaticMeshAsset; +using StaticMeshHandle = WeakLocalPoolIndex; +struct MaterialAsset; +using MaterialHandle = WeakLocalPoolIndex; +struct ShaderAsset; +using ShaderHandle = WeakLocalPoolIndex; +struct TextureAsset; +using TextureHandle = WeakLocalPoolIndex; + +class Thread; +class Engine; +class IPlatformWindow; + +class IRenderingSystem +{ + friend Engine; + + protected: + static const int32_t sMaxDirLightCount = 4; + static const int32_t sMaxPointLightCount = 32; + static const int32_t sMaxSpotLightCount = 16; + static const int32_t sMaxOpaqueDrawCommandCount = 4096; + static const int32_t sMaxMaskedDrawCommandCount = 1024; + static const int32_t sMaxBlendedDrawCommandCount = 1024; + static const int32_t sMaxRenderPassCount = 4; + static const int32_t sMaxViewportCount = 16; + static const int32_t sPendingQueueSize = 1024; + + public: + enum class eRenderTargetType + { + Color = 0, + Depth = 1, + Last = 2, + }; + + enum class eRenderTargetFormat + { + None = 0, + R8G8B8A8_UNORM, + B8G8R8A8_UNORM, + R16G16B16A16_FLOAT, + R32_FLOAT, + R16G16_FLOAT, + R32G32B32A32_FLOAT, + D24_UNORM_S8_UINT, + D32_FLOAT, + }; + + struct RenderTargetDesc + { + eRenderTargetFormat Format = eRenderTargetFormat::None; + Color128 ClearColor; + float ClearDepth = 1.0f; + uint8_t ClearStencil = 0; + }; + + struct FrameBufferDesc + { + StringHandle hName = StringHandle::sNULL; + int32_t Width = 0; + int32_t Height = 0; + int32_t SampleCount = 1; + RenderTargetDesc TargetDesc[static_cast(eRenderTargetType::Last)] = {}; + }; + + struct DirLightDesc + { + Vector3 Direction; + Color128 Color; + float Intensity = 1.0f; + }; + + struct PointLightDesc + { + Vector3 Position; + Color128 Color; + float Intensity = 1.0f; + + float Constant = 1.0f; + float Linear = 0.045f; + float Quadratic = 0.0075f; + }; + + struct SpotLightDesc + { + Vector3 Position; + Vector3 Direction; + Color128 Color; + float Intensity = 0.0f; + + float InnerCutOff = 0.0f; + float OuterCutOff = 0.0f; + + float Constant = 1.0f; + float Linear = 0.045f; + float Quadratic = 0.0075f; + }; + + struct DrawCommandDesc + { + GpuStaticMeshHandle hGpuStaticMesh = GpuStaticMeshHandle::sNULL; + GpuMaterialHandle hGpuMaterial = GpuMaterialHandle::sNULL; + int32_t VertexOffset = 0; + int32_t IndexCount = 0; + int32_t IndexOffset = 0; + Matrix4x4 WorldMat = Matrix4x4::sIdentity; + eMaterialAlphaMode AlphaMode = eMaterialAlphaMode::None; + }; + + struct Rect + { + int32_t X = 0; + int32_t Y = 0; + int32_t Width = 0; + int32_t Height = 0; + }; + + struct RenderPassDesc + { + StringHandle hFrameBufferName = StringHandle::sNULL; + bool bClearTarget[static_cast(eRenderTargetType::Last)] = {}; + Rect Viewport; + + Vector3 WorldCameraPos; + Matrix4x4 ViewMat = Matrix4x4::sIdentity; + Matrix4x4 ProjMat = Matrix4x4::sIdentity; + + DirLightDesc DirLights[sMaxDirLightCount] = {}; + int32_t DirLightCount = 0; + + PointLightDesc PointLights[sMaxPointLightCount] = {}; + int32_t PointLightCount = 0; + + SpotLightDesc SpotLights[sMaxSpotLightCount] = {}; + int32_t SpotLightCount = 0; + + GpuTextureHandle hSkyMap = GpuTextureHandle::sNULL; + GpuTextureHandle hIrradianceMap = GpuTextureHandle::sNULL; + }; + + static IRenderingSystem& GetInstance() + { + HO_ASSERT(spInstance, "RenderingSystem is not created"); + return *spInstance; + } + + virtual ~IRenderingSystem() = default; + + virtual bool CreateFrameBuffer(const FrameBufferDesc& frameBufferDesc) = 0; + + virtual bool DestroyFrameBuffer(StringHandle hFrameBufferName) = 0; + + [[nodiscard]] virtual void* GetRenderTargetNativeHandle(StringHandle hFrameBufferName, + eRenderTargetType type, + bool bRequireMultisample = false) const = 0; + + bool BeginRenderPass(RenderPassDesc& renderPassDesc); + + bool SubmitDrawCommand(const DrawCommandDesc& commandDesc); + + bool SubmitUIViewportData(const IPlatformWindow* pTargetWindow, const ImDrawData& srcDrawData); + + bool EndRenderPass(); + + GpuStaticMeshHandle EnqueueUploadStaticMesh(StaticMeshHandle hStaticMesh, bool bPersistent = false); + GpuMaterialHandle EnqueueUploadMaterial(MaterialHandle hMaterial, bool bPersistent = false); + GpuTextureHandle EnqueueUploadTexture(TextureHandle hTexture, bool bPersistent = false); + GpuShaderHandle EnqueueUploadShader(ShaderHandle hShader, bool bPersistent = false); + + protected: + struct FrameBuffer + { + StringHandle hName = StringHandle::sNULL; + int32_t Width = 0; + int32_t Height = 0; + int32_t SampleCount = 1; + // render targets are double buffered too. It's synchronized current render queue. + GpuTextureHandle hRenderTargets[2][static_cast(eRenderTargetType::Last)] = {}; + GpuTextureHandle hResolvedRenderTargets[2][static_cast(eRenderTargetType::Last)] = {}; + Vector4 ClearColors[static_cast(eRenderTargetType::Last)] = {}; + float ClearDepth = 1.f; + uint8_t ClearStencil = 0; + }; + + struct DrawCommand + { + GpuStaticMeshHandle hGpuStaticMesh = GpuStaticMeshHandle::sNULL; + GpuMaterialHandle hGpuMaterial = GpuMaterialHandle::sNULL; + int32_t VertexOffset = 0; + int32_t IndexCount = 0; + int32_t IndexOffset = 0; + Matrix4x4 WorldMat = Matrix4x4::sIdentity; + float SqrdDistanceFromCamera = 0.0f; + }; + + // Layout struct used for uploading to GPU. + struct MatrixLayout + { + Matrix4x4 WorldMat; + Matrix4x4 InvWorldMat; + Matrix4x4 ViewMat; + Matrix4x4 ProjMat; + }; + + struct DirLightLayout + { + float Direction[3] = {}; + float Padding = 0.0f; + + float Color[3] = {}; + float Intensity = 0.0f; + }; + + struct PointLightLayout + { + float Position[3] = {}; + float Padding0 = 0.0f; + + float Color[3] = {}; + + float Intensity = 0.0f; + float Constant = 0.0f; + float Linear = 0.0f; + float Quadratic = 0.0f; + float Padding1 = 0.0f; + }; + + struct SpotLightLayout + { + float Position[3] = {}; + float Padding0 = 0.0f; + + float Direction[3] = {}; + float Padding1 = 0.0f; + + float Color[3] = {}; + + float Intensity = 0.0f; + float InnerCutOff = 0.0f; + float OuterCutOff = 0.0f; + + float Constant = 1.0f; + float Linear = 0.045f; + float Quadratic = 0.0075f; + float Padding3[3] = {}; + }; + + struct LightLayout + { + int32_t DirLightCount = 0; + int32_t Padding0[3] = {}; + DirLightLayout DirLights[sMaxDirLightCount] = {}; + int32_t PointLightCount = 0; + int32_t Padding1[3] = {}; + PointLightLayout PointLights[sMaxPointLightCount] = {}; + int32_t SpotLightCount = 0; + int32_t Padding2[3] = {}; + SpotLightLayout SpotLights[sMaxSpotLightCount] = {}; + }; + + struct GeneralPurposeLayout + { + float WorldCameraPos[3] = {}; + float Padding = 0; + }; + + struct RenderPass + { + FrameBuffer* pFrameBuffer = nullptr; + bool bClearTarget[static_cast(eRenderTargetType::Last)] = {}; + Rect Viewport; + + Vector3 WorldCameraPos; + Matrix4x4 ViewMat = Matrix4x4::sIdentity; + Matrix4x4 ProjMat = Matrix4x4::sIdentity; + + LightLayout Lights; + DrawCommand OpaqueDrawCommands[sMaxOpaqueDrawCommandCount] = {}; + int32_t OpaqueDrawCommandCount = 0; + DrawCommand MaskedDrawCommands[sMaxMaskedDrawCommandCount] = {}; + int32_t MaskedDrawCommandCount = 0; + DrawCommand BlendedDrawCommands[sMaxBlendedDrawCommandCount] = {}; + int32_t BlendedDrawCommandCount = 0; + + GpuTexture* pSkyMap = nullptr; + GpuTexture* pIrradianceMap = nullptr; + }; + + struct UIViewportData + { + UIViewportData(); + ~UIViewportData(); + UIViewportData(UIViewportData&&) noexcept; + UIViewportData& operator=(UIViewportData&&) noexcept; + + const IPlatformWindow* pTargetWindow = nullptr; + std::unique_ptr pDrawData; + }; + + struct RenderQueue + { + uint64_t FrameCount = 0; + RenderPass RenderPasses[sMaxRenderPassCount] = {}; + int32_t ActivePassCount = 0; + + UIViewportData UIViewports[sMaxViewportCount] = {}; + int32_t ActiveViewportCount = 0; + }; + + enum class eResourceTransferResult + { + Success = 0, + Failed = 1, + NeedsRetry = 2, + }; + + IRenderingSystem(); + + static void createInstance(eGraphicsAPI api); + static void deleteInstance(); + + // 'init' called by engine. + virtual bool init() = 0; + // 'run' function creates rendering thread. + // Engine must be create all needed frame buffers before create render thread. + void run(); + static int runInternal(void* unused); + // 'shutdown' called by engine and it will terminate 'run' function + void shutdown(); + + virtual void releaseAllResources() = 0; + + void executeRenderQueue(); + + void updateDrawCommandResourceUsageStates(DrawCommand& command, uint64_t currentFrame); + void updateRenderPassResourceUsageStates(RenderPass& pass, uint64_t currentFrame); + + void sortDrawCommands(RenderPass& pass); + + virtual void applyMaterial(GpuMaterialHandle hGpuMaterial) = 0; + virtual void executeDrawCommand(const DrawCommand& command) = 0; + + virtual void executeSkyMapPass(const GpuTexture* pSkyMap) = 0; + virtual void executeInWorldDebugDrawPass() = 0; + virtual void executeOverlayDebugDrawPass() = 0; + + virtual void prepareExecuteRenderQueue() = 0; + virtual void prepareExecuteRenderPass(const RenderPass& pass) = 0; + virtual void prepareExecuteOpaqueDrawCommands() = 0; + virtual void prepareExecuteMaskedDrawCommands() = 0; + virtual void prepareExecuteBlendedDrawCommands() = 0; + virtual void finishExecuteRenderPass(const RenderPass& pass) = 0; + virtual void finishExecuteRenderQueue() = 0; + + virtual void resolveFrameBuffer(const FrameBuffer& frameBuffer) = 0; + + virtual void renderUI() = 0; + + virtual void flush() = 0; + + void swapRenderQueues(uint64_t currentFrame); + + bool trySwapRenderQueues(uint64_t currentFrame); + + void uploadPendingResources(uint64_t currentFrame); + + int32_t evictStaleResources(uint64_t currentFrame, uint64_t frameThreshold); + + GpuStaticMeshHandle prepareUploadStaticMesh(StaticMeshHandle hStaticMesh, bool bPersistent); + GpuMaterialHandle prepareUploadMaterial(MaterialHandle hMaterial, bool bPersistent); + // 'prepareUploadTexture' can be used for uploading render target. + // When hTextureTarget is TextureHandle::sNULL, bPersistent is true, it will create render target. + GpuTextureHandle prepareUploadTexture(TextureHandle hTexture, bool bPersistent); + GpuShaderHandle prepareUploadShader(ShaderHandle hShader, bool bPersistent); + + virtual eResourceTransferResult uploadStaticMesh(GpuStaticMeshHandle hPreparedGpuStaticMesh, + uint64_t currentFrame) = 0; + virtual eResourceTransferResult uploadMaterial(GpuMaterialHandle hPreparedGpuMaterial, uint64_t currentFrame) = 0; + virtual eResourceTransferResult uploadTexture(GpuTextureHandle hPreparedGpuTexture, uint64_t currentFrame) = 0; + virtual eResourceTransferResult uploadShader(GpuShaderHandle hPreparedGpuShader, uint64_t currentFrame) = 0; + + virtual eResourceTransferResult unloadStaticMesh(GpuStaticMeshHandle hGpuStaticMesh) = 0; + virtual eResourceTransferResult unloadMaterial(GpuMaterialHandle hGpuMaterial) = 0; + virtual eResourceTransferResult unloadTexture(GpuTextureHandle hGpuTexture) = 0; + virtual eResourceTransferResult unloadShader(GpuShaderHandle hGpuShader) = 0; + + void finishUnloadStaticMesh(GpuStaticMeshHandle hUnloadedGpuStaticMesh); + void finishUnloadMaterial(GpuMaterialHandle hUnloadedGpuMaterial); + void finishUnloadTexture(GpuTextureHandle hUnloadedGpuTexture); + void finishUnloadShader(GpuShaderHandle hUnloadedGpuShader); + + bool validateFrameBufferDesc(const FrameBufferDesc& desc); + + // -1 means render pass is not begun. + int32_t mCurrentRenderPassIndex = -1; + int32_t mFrontRenderQueueIndex = 0; + int32_t mBackRenderQueueIndex = 1; + RenderQueue mRenderQueues[2]; + + std::unordered_map mNameToFrameBufferMap; + + ObjectPool mGpuStaticMeshPool; + ObjectPool mGpuMaterialPool; + ObjectPool mGpuShaderPool; + ObjectPool mGpuTexturePool; + + FixedQueue mhPendingStaticMeshQueue; + SpinLock mPendingStaticMeshQueueLock; + FixedQueue mhPendingMaterialQueue; + SpinLock mPendingMaterialQueueLock; + FixedQueue mhPendingTextureQueue; + SpinLock mPendingTextureQueueLock; + FixedQueue mhPendingShaderQueue; + SpinLock mPendingShaderQueueLock; + + bool mbIsRunning = true; + RenderSync mRenderSync; + + std::unique_ptr mpRenderThread; + + static IRenderingSystem* spInstance; +}; + +} // namespace ho \ No newline at end of file diff --git a/Subsystems/Rendering/RenderSync.h b/Subsystems/Rendering/RenderSync.h new file mode 100644 index 0000000..0b78ca4 --- /dev/null +++ b/Subsystems/Rendering/RenderSync.h @@ -0,0 +1,58 @@ +#pragma once +#include + +#include "Config.h" +#include "Core/Thread/Thread.h" +#include "Macros.h" + +namespace ho +{ +class RenderSync final +{ + public: + RenderSync() + : mbRenderDone(true) + { + } + + FORCE_INLINE void WaitUntilRenderDone() const + { + while (!mbRenderDone.load(std::memory_order_acquire)) + { + CPU_PAUSE(); + } + } + + FORCE_INLINE void NotifyRenderDone() + { + mbRenderDone.store(true, std::memory_order_release); + } + + FORCE_INLINE void WaitUntilEngineSwapComplete() const + { + while (mbRenderDone.load(std::memory_order_acquire)) + { + CPU_PAUSE(); + } + } + + FORCE_INLINE void NotifyEngineSwapComplete() + { + mbRenderDone.store(false, std::memory_order_release); + } + + FORCE_INLINE bool IsRenderDone() const + { + return mbRenderDone.load(std::memory_order_acquire); + } + + private: + union + { + mutable std::atomic mbRenderDone; + + char mAligner[Thread::sCacheLineBytes]; + }; +}; + +} // namespace ho \ No newline at end of file From a2e9baf138c118523c9a3d2d4d02e0034b058d51 Mon Sep 17 00:00:00 2001 From: Hoin Lee Date: Thu, 4 Jun 2026 11:33:21 +0900 Subject: [PATCH 16/17] chore: Resolve clang-tidy warning. --- Subsystems/Asset/AssetDefs.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Subsystems/Asset/AssetDefs.h b/Subsystems/Asset/AssetDefs.h index ac5e832..99d183f 100644 --- a/Subsystems/Asset/AssetDefs.h +++ b/Subsystems/Asset/AssetDefs.h @@ -76,15 +76,17 @@ enum class eAnimInterpolationMode : uint8_t enum class eMaterialAssetType : uint8_t { None = 0, - Unlit = 1, - Legacy = 1 << Unlit, - StandardLit = 1 << Legacy, + Unlit = 1 << 0, + Legacy = 1 << 1, + StandardLit = 1 << 2, }; +// NOLINTBEGIN FORCE_INLINE eMaterialAssetType operator|(eMaterialAssetType lhs, eMaterialAssetType rhs) { return static_cast(static_cast(lhs) | static_cast(rhs)); } +// NOLINTEND enum class eShaderStage : uint8_t { From 55085e60e5964d28427c320c096af941024a8478 Mon Sep 17 00:00:00 2001 From: Hoin Lee Date: Thu, 4 Jun 2026 12:09:11 +0900 Subject: [PATCH 17/17] chore: Resolve clang-tidy warning. --- Subsystems/Rendering/GL/RenderingSystemGL.cc | 8 +++++--- Subsystems/Rendering/IRenderingSystem.cc | 4 +--- Subsystems/Rendering/IRenderingSystem.h | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Subsystems/Rendering/GL/RenderingSystemGL.cc b/Subsystems/Rendering/GL/RenderingSystemGL.cc index 84d0140..a6a7289 100644 --- a/Subsystems/Rendering/GL/RenderingSystemGL.cc +++ b/Subsystems/Rendering/GL/RenderingSystemGL.cc @@ -343,7 +343,7 @@ void* RenderingSystemGL::GetRenderTargetNativeHandle(StringHandle hFrameBufferNa } HO_ASSERT(nativeHandle, "Texture for render target was not created."); - return (void*)(uintptr_t)nativeHandle->GlTexture; // NOLINT + return reinterpret_cast(static_cast(nativeHandle->GlTexture)); // NOLINT } RenderingSystemGL::RenderingSystemGL() @@ -801,8 +801,10 @@ void RenderingSystemGL::executeDrawCommand(const DrawCommand& command) sizeof(Vertex)); glVertexArrayElementBuffer(mGlVAO, meshNativehandle->GlEbo); - glDrawElements( - GL_TRIANGLES, command.IndexCount, GL_UNSIGNED_INT, (void*)(command.IndexOffset * sizeof(uint32_t))); // NOLINT + glDrawElements(GL_TRIANGLES, + command.IndexCount, + GL_UNSIGNED_INT, + reinterpret_cast(command.IndexOffset * sizeof(uint32_t))); // NOLINT ASSERT_GL(); } diff --git a/Subsystems/Rendering/IRenderingSystem.cc b/Subsystems/Rendering/IRenderingSystem.cc index cbbec62..acc25ce 100644 --- a/Subsystems/Rendering/IRenderingSystem.cc +++ b/Subsystems/Rendering/IRenderingSystem.cc @@ -402,7 +402,7 @@ void IRenderingSystem::run() mpRenderThread->SetName("Render Thread"); } -int IRenderingSystem::runInternal(void* unused) +void IRenderingSystem::runInternal(void* unused) { (void)unused; IPlatformApplication::GetInstance().GetMainWindow()->ActivateContext(); @@ -434,8 +434,6 @@ int IRenderingSystem::runInternal(void* unused) spInstance->releaseAllResources(); IPlatformApplication::GetInstance().GetMainWindow()->DeactivateContext(); - - return 0; } void IRenderingSystem::shutdown() diff --git a/Subsystems/Rendering/IRenderingSystem.h b/Subsystems/Rendering/IRenderingSystem.h index 418c77d..bfbdefd 100644 --- a/Subsystems/Rendering/IRenderingSystem.h +++ b/Subsystems/Rendering/IRenderingSystem.h @@ -364,7 +364,7 @@ class IRenderingSystem // 'run' function creates rendering thread. // Engine must be create all needed frame buffers before create render thread. void run(); - static int runInternal(void* unused); + static void runInternal(void* unused); // 'shutdown' called by engine and it will terminate 'run' function void shutdown();