diff --git a/.gitmodules b/.gitmodules index 0832ec49..cab80ed4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -25,3 +25,6 @@ [submodule "3rdparty/Vulkan-Headers"] path = 3rdparty/Vulkan-Headers url = https://github.com/KhronosGroup/Vulkan-Headers.git +[submodule "3rdparty/SPIRV-Reflect"] + path = 3rdparty/SPIRV-Reflect + url = https://github.com/KhronosGroup/SPIRV-Reflect.git diff --git a/3rdparty/SPIRV-Reflect b/3rdparty/SPIRV-Reflect new file mode 160000 index 00000000..10b4f09a --- /dev/null +++ b/3rdparty/SPIRV-Reflect @@ -0,0 +1 @@ +Subproject commit 10b4f09a24d7ac1603071e767c089551dc6a3949 diff --git a/CMakeLists.txt b/CMakeLists.txt index 354dca10..79f6f78b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -104,12 +104,20 @@ set(SPIRV_CROSS_ENABLE_CPP OFF CACHE BOOL "Disable C++ API target" FORCE) set(SPIRV_CROSS_CLI OFF CACHE BOOL "Disable CLI binary" FORCE) set(SPIRV_CROSS_STATIC ON CACHE BOOL "Prefer static libs" FORCE) +set(SPIRV_REFLECT_EXECUTABLE OFF CACHE BOOL "Build spirv-reflect executable" FORCE) +set(SPIRV_REFLECT_STATIC_LIB ON CACHE BOOL "Build a SPIRV-Reflect static library" FORCE) +set(SPIRV_REFLECT_BUILD_TESTS OFF CACHE BOOL "Build the SPIRV-Reflect test suite" FORCE) +set(SPIRV_REFLECT_ENABLE_ASSERTS OFF CACHE BOOL "Enable asserts for debugging" FORCE) +set(SPIRV_REFLECT_ENABLE_ASAN OFF CACHE BOOL "Use address sanitization" FORCE) +set(SPIRV_REFLECT_INSTALL OFF CACHE BOOL "Whether to install" FORCE) + # add_subdirectory(3rdparty/DiligentCore) add_subdirectory(3rdparty/glslang) add_subdirectory(3rdparty/SPIRV-Cross) add_subdirectory(3rdparty/VulkanMemoryAllocator) add_subdirectory(3rdparty/Vulkan-Headers) add_subdirectory(3rdparty/Vulkan-Utility-Libraries) +add_subdirectory(3rdparty/SPIRV-Reflect) set(XXHASH_BUILD_XXHSUM OFF) option(BUILD_SHARED_LIBS OFF) @@ -223,7 +231,9 @@ set(SOURCE_FILES MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.cpp MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.cpp MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp - MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.cpp + MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp + MobileGL/MG_Backend/DirectVulkan/Renderer/BufferArena.cpp + MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateBuilder.cpp MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferObject.cpp @@ -268,6 +278,7 @@ set(MOBILEGL_LINK_LIBRARIES xxHash::xxhash GPUOpen::VulkanMemoryAllocator Vulkan::UtilityHeaders + spirv-reflect-static ) set(MOBILEGL_COMPILE_DEF diff --git a/MobileGL/Defines.h b/MobileGL/Defines.h index 80c7366e..a183d4f8 100644 --- a/MobileGL/Defines.h +++ b/MobileGL/Defines.h @@ -34,7 +34,7 @@ #define MOBILEGL_EGL_API MOBILEGL_API // ====================== MobileGL configurations ======================= // -#define MOBILEGL_LOG_ACTIVE_LEVEL MOBILEGL_LOG_LEVEL_INFO +#define MOBILEGL_LOG_ACTIVE_LEVEL MOBILEGL_LOG_LEVEL_DEBUG #define MOBILEGL_LOG_ENABLE_CONSOLE 0 #define MOBILEGL_LOG_ENABLE_FILE 1 diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.cpp b/MobileGL/MG_Backend/DirectGLES/Managers.cpp index 3195b39f..12772093 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Managers.cpp @@ -1075,7 +1075,8 @@ namespace MobileGL::MG_Backend::DirectGLES { String source; auto& spirvCode = shaderSpirvs[index]; - MG_Util::ShaderTranspiler::SpvcSession spvcSession(spirvCode); + MG_Util::ShaderTranspiler::SpvcSession spvcSession(spirvCode, + MG_Util::ShaderTranspiler::SessionUsageBit::Transpile); spvc_compiler_options options; spvcSession.CreateOptions(&options); diff --git a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp index 3ec0a0fd..61e0aa8c 100644 --- a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp @@ -17,10 +17,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { void ClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat* value) {} void ClearBufferuiv(GLenum buffer, GLint drawbuffer, const GLuint* value) {} void ClearBufferiv(GLenum buffer, GLint drawbuffer, const GLint* value) {} - void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices, GLint basevertex) {} - - void MultiDrawElementsBaseVertex(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices, - GLsizei drawcount, const GLint* basevertex) {} void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride) {} void MultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride) {} @@ -47,65 +43,120 @@ namespace MobileGL::MG_Backend::DirectVulkan { void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels) {} void GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels) {} - void MultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices, - GLsizei drawcount) { - MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::MultiDrawElements called with null VulkanRenderer"); - MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::MultiDrawElements called with null GL context"); - - Vector cmds; - cmds.reserve(static_cast(drawcount)); - for (GLsizei i = 0; i < drawcount; ++i) { - if (count[i] == 0) { - continue; - } - - DrawElementCmd payload{}; - payload.mode = mode; - payload.first = 0; - payload.count = count[i]; - payload.indexType = type; - payload.indexByteOffset = reinterpret_cast(indices[i]); - cmds.push_back(payload); - } - - if (cmds.empty()) { - return; - } - pVulkanRenderer->MultiDrawElements(cmds); - } - void Clear(GLbitfield mask) { MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::Clear called with null VulkanRenderer"); MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::Clear called with null GL context"); pVulkanRenderer->Clear(mask); } - void DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices) { - MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::DrawElements called with null VulkanRenderer"); - MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::DrawElements called with null GL context"); - - DrawElementCmd payload{}; - payload.mode = mode; - payload.first = 0; - payload.count = count; - payload.indexType = type; - payload.indexByteOffset = reinterpret_cast(indices); - - pVulkanRenderer->DrawElements(payload); - } - void DrawArrays(GLenum mode, GLint first, GLsizei count) { MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::DrawArrays called with null VulkanRenderer"); MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::DrawArrays called with null GL context"); - DrawArrayCmd payload{}; + DrawCmd payload{}; payload.mode = mode; - payload.first = first; - payload.count = count; + payload.params.firstVertex = first; + payload.params.vertexCount = count; pVulkanRenderer->DrawArrays(payload); } + void DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices) { + MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::DrawElements called with null VulkanRenderer"); + MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::DrawElements called with null GL context"); + + DrawIndexedCmd payload{}; + payload.mode = mode; + payload.indexBufferView.indexType = type; + payload.indexBufferView.indexByteOffset = reinterpret_cast(indices); + payload.indexBufferView.indexByteSize = count * MG_Util::GetGLTypeSize(type); + payload.params.indexCount = count; + payload.params.instanceCount = 1; + + pVulkanRenderer->DrawElements(payload); + } + + void MultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices, + GLsizei drawcount) { + MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::MultiDrawElements called with null VulkanRenderer"); + MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::MultiDrawElements called with null GL context"); + + // Vector cmds; + // cmds.reserve(static_cast(drawcount)); + // for (GLsizei i = 0; i < drawcount; ++i) { + // if (count[i] == 0) { + // continue; + // } + // + // DrawElementCmd payload{}; + // payload.mode = mode; + // payload.firstVertex = 0; + // payload.indexCount = count[i]; + // payload.indexType = type; + // payload.indexByteOffset = reinterpret_cast(indices[i]); + // cmds.push_back(payload); + // } + // + // if (cmds.empty()) { + // return; + // } + // pVulkanRenderer->MultiDrawElements(cmds); + } + + void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices, GLint basevertex) { + MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::DrawElementsBaseVertex called with null VulkanRenderer"); + MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::DrawElementsBaseVertex called with null GL context"); + DrawIndexedCmd payload{}; + payload.mode = mode; + payload.indexBufferView.indexType = type; + payload.indexBufferView.indexByteOffset = reinterpret_cast(indices); + payload.indexBufferView.indexByteSize = count * MG_Util::GetGLTypeSize(type); + payload.params.indexCount = count; + payload.params.instanceCount = 1; + payload.params.firstIndex = 0; + payload.params.vertexOffset = basevertex; + payload.params.firstInstance = 0; + pVulkanRenderer->DrawElements(payload); + } + + void MultiDrawElementsBaseVertex(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices, + GLsizei drawcount, const GLint* basevertex) { + MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::MultiDrawElements called with null VulkanRenderer"); + MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::MultiDrawElements called with null GL context"); + MultiDrawIndexedCmd payload{}; + payload.mode = mode; + payload.indexBufferView.indexType = type; + + // TODO: allocate draw cmd buf elsewhere + static Vector params; + params.clear(); + params.resize(drawcount); + + for (GLsizei i = 0; i < drawcount; ++i) { + if (count[i] == 0) { + continue; + } + + // TODO: this index view needs a redesign, now there's a lotta redundant uploads + + payload.indexBufferView.indexByteOffset = 0; + payload.indexBufferView.indexByteSize = + std::max(reinterpret_cast(indices[i]) + count[i] * MG_Util::GetGLTypeSize(type), + payload.indexBufferView.indexByteSize); + + auto& param = params[i]; + + param.indexCount = count[i]; + param.instanceCount = 1; + param.firstIndex = reinterpret_cast(indices[i]) / MG_Util::GetGLTypeSize(type); + param.vertexOffset = basevertex[i]; + param.firstInstance = 0; + } + payload.drawCount = drawcount; + payload.pParams = params.data(); + pVulkanRenderer->MultiDrawElements(payload); + } + void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter) { MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::BlitFramebuffer called with null VulkanRenderer"); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/BufferArena.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/BufferArena.cpp new file mode 100644 index 00000000..14b55776 --- /dev/null +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/BufferArena.cpp @@ -0,0 +1,138 @@ +// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/BufferArena.cpp +// Copyright (c) 2025-2026 MobileGL-Dev +// Licensed under the GNU Lesser General Public License v3.0: +// https://www.gnu.org/licenses/gpl-3.0.txt +// https://www.gnu.org/licenses/lgpl-3.0.txt +// SPDX-License-Identifier: LGPL-3.0-only +// End of Source File Header + +#include "BufferArena.h" + +namespace MobileGL::MG_Backend::DirectVulkan { + Bool BufferArena::Initialize(const BufferArenaDesc& desc) { + Shutdown(); + + MOBILEGL_ASSERT(desc.allocator != nullptr, "BufferArena::Initialize requires valid allocator"); + MOBILEGL_ASSERT(desc.frameCount > 0, "BufferArena::Initialize requires non-zero frame count"); + MOBILEGL_ASSERT(desc.usage != 0, "BufferArena::Initialize requires non-zero buffer usage"); + + m_desc = desc; + m_frames.clear(); + m_frames.resize(desc.frameCount); + m_deferredReleases.resize(desc.frameCount); + return true; + } + + void BufferArena::Shutdown() { + for (auto& frame : m_frames) { + frame.buffer.Destroy(); + frame.writeCursor = 0; + } + m_frames.clear(); + m_deferredReleases.clear(); + m_desc = {}; + } + + void BufferArena::BeginFrame(Uint32 frameIndex) { + CollectDeferredReleases(frameIndex); + ResetFrame(frameIndex); + } + + void BufferArena::ResetFrame(Uint32 frameIndex) { + AssertValidFrameIndex(frameIndex); + m_frames[frameIndex].writeCursor = 0; + } + + void BufferArena::CollectDeferredReleases(Uint32 frameIndex) { + AssertValidFrameIndex(frameIndex); + m_deferredReleases[frameIndex].clear(); + } + + Bool BufferArena::Allocate(Uint32 frameIndex, VkDeviceSize size, VkDeviceSize alignment, BufferSlice& outSlice) { + AssertValidFrameIndex(frameIndex); + MOBILEGL_ASSERT(size > 0, "BufferArena::Allocate requires non-zero size"); + + auto& frame = m_frames[frameIndex]; + const VkDeviceSize resolvedAlignment = alignment > 0 ? alignment : 1; + const VkDeviceSize offset = (frame.writeCursor + resolvedAlignment - 1) & ~(resolvedAlignment - 1); + const VkDeviceSize endOffset = offset + size; + + if (!EnsureCapacity(frameIndex, endOffset)) { + return false; + } + + frame.writeCursor = endOffset; + outSlice = frame.buffer.GetSlice(offset, size); + return outSlice.IsValid(); + } + + Bool BufferArena::Upload(Uint32 frameIndex, const void* data, VkDeviceSize size, VkDeviceSize alignment, + BufferSlice& outSlice) { + MOBILEGL_ASSERT(data != nullptr || size == 0, "BufferArena::Upload data pointer is null"); + if (!Allocate(frameIndex, size, alignment, outSlice)) { + return false; + } + + if (outSlice.mapped != nullptr) { + Memcpy(outSlice.mapped, data, static_cast(size)); + return true; + } + + return m_frames[frameIndex].buffer.Upload(data, size, outSlice.offset); + } + + VkDeviceSize BufferArena::GetWriteCursor(Uint32 frameIndex) const { + AssertValidFrameIndex(frameIndex); + return m_frames[frameIndex].writeCursor; + } + + Uint32 BufferArena::GetFrameCount() const { + return static_cast(m_frames.size()); + } + + Bool BufferArena::EnsureCapacity(Uint32 frameIndex, VkDeviceSize requiredEndOffset) { + AssertValidFrameIndex(frameIndex); + auto& frame = m_frames[frameIndex]; + auto& buffer = frame.buffer; + + if (buffer.IsValid() && buffer.GetSize() >= requiredEndOffset) { + return true; + } + + VkDeviceSize newCapacity = buffer.IsValid() ? buffer.GetSize() : 0; + if (newCapacity < m_desc.minBufferSize) { + newCapacity = m_desc.minBufferSize; + } + if (newCapacity == 0) { + newCapacity = requiredEndOffset; + } + while (newCapacity < requiredEndOffset) { + newCapacity *= 2; + } + + if (buffer.IsValid()) { + m_deferredReleases[frameIndex].push_back(std::move(buffer)); + } + + VkBufferObjectDesc bufferDesc{}; + bufferDesc.allocator = m_desc.allocator; + bufferDesc.size = newCapacity; + bufferDesc.usage = m_desc.usage; + bufferDesc.memoryUsage = m_desc.memoryUsage; + bufferDesc.allocationFlags = m_desc.allocationFlags; + if (!buffer.Create(bufferDesc)) { + return false; + } + if (m_desc.persistentlyMapped && buffer.Map() == nullptr) { + buffer.Destroy(); + return false; + } + + frame.writeCursor = 0; + return true; + } + + void BufferArena::AssertValidFrameIndex(Uint32 frameIndex) const { + MOBILEGL_ASSERT(frameIndex < m_frames.size(), "BufferArena frame index out of range"); + } +} // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/BufferArena.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/BufferArena.h new file mode 100644 index 00000000..e9fc509a --- /dev/null +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/BufferArena.h @@ -0,0 +1,56 @@ +// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/BufferArena.h +// Copyright (c) 2025-2026 MobileGL-Dev +// Licensed under the GNU Lesser General Public License v3.0: +// https://www.gnu.org/licenses/gpl-3.0.txt +// https://www.gnu.org/licenses/lgpl-3.0.txt +// SPDX-License-Identifier: LGPL-3.0-only +// End of Source File Header + +#pragma once + +#include "BufferSlice.h" +#include "VkBufferObject.h" +#include "../VkIncludes.h" +#include +#include + +namespace MobileGL::MG_Backend::DirectVulkan { + struct BufferArenaDesc { + VmaAllocator allocator = nullptr; + Uint32 frameCount = 0; + VkBufferUsageFlags usage = 0; + VmaMemoryUsage memoryUsage = VMA_MEMORY_USAGE_AUTO; + VmaAllocationCreateFlags allocationFlags = 0; + VkDeviceSize minBufferSize = 0; + Bool persistentlyMapped = false; + }; + + class BufferArena { + public: + Bool Initialize(const BufferArenaDesc& desc); + void Shutdown(); + + void BeginFrame(Uint32 frameIndex); + void ResetFrame(Uint32 frameIndex); + void CollectDeferredReleases(Uint32 frameIndex); + + Bool Allocate(Uint32 frameIndex, VkDeviceSize size, VkDeviceSize alignment, BufferSlice& outSlice); + Bool Upload(Uint32 frameIndex, const void* data, VkDeviceSize size, VkDeviceSize alignment, BufferSlice& outSlice); + + VkDeviceSize GetWriteCursor(Uint32 frameIndex) const; + Uint32 GetFrameCount() const; + + private: + struct FrameResources { + VkBufferObject buffer; + VkDeviceSize writeCursor = 0; + }; + + Bool EnsureCapacity(Uint32 frameIndex, VkDeviceSize requiredEndOffset); + void AssertValidFrameIndex(Uint32 frameIndex) const; + + BufferArenaDesc m_desc{}; + Vector m_frames; + Vector> m_deferredReleases; + }; +} // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/BufferSlice.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/BufferSlice.h new file mode 100644 index 00000000..e4645e0c --- /dev/null +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/BufferSlice.h @@ -0,0 +1,23 @@ +// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/BufferSlice.h +// Copyright (c) 2025-2026 MobileGL-Dev +// Licensed under the GNU Lesser General Public License v3.0: +// https://www.gnu.org/licenses/gpl-3.0.txt +// https://www.gnu.org/licenses/lgpl-3.0.txt +// SPDX-License-Identifier: LGPL-3.0-only +// End of Source File Header + +#pragma once + +#include "../VkIncludes.h" +#include + +namespace MobileGL::MG_Backend::DirectVulkan { + struct BufferSlice { + VkBuffer buffer = VK_NULL_HANDLE; + VkDeviceSize offset = 0; + VkDeviceSize size = 0; + void* mapped = nullptr; + + Bool IsValid() const { return buffer != VK_NULL_HANDLE; } + }; +} // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp index ad1f254b..efdbffb9 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp @@ -8,6 +8,9 @@ #include "ProgramFactory.h" +#include "MG_Util/ShaderTranspiler/SpvcSession.h" +#include "MG_Util/ShaderTranspiler/Types.h" +#include #include #include #include @@ -21,6 +24,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { namespace { using ShaderObject = MG_State::GLState::ShaderObject; + using SpvcSession = MG_Util::ShaderTranspiler::SpvcSession; + using SessionUsageBit = MG_Util::ShaderTranspiler::SessionUsageBit; struct PositionTargetInfo { Uint32 variableId = 0; @@ -321,8 +326,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { } } // namespace - ProgramFactory::~ProgramFactory() = default; - VkShaderStageFlagBits ProgramFactory::ToVkStage(ShaderStage stage) { switch (stage) { case ShaderStage::Vertex: @@ -351,16 +354,181 @@ namespace MobileGL::MG_Backend::DirectVulkan { XXHASH_VERIFY(XXH64_update(m_hashState, spv.data(), spv.size() * sizeof(Uint))); } XXHASH_VERIFY(XXH64_update(m_hashState, &flags, sizeof(CompileOptionFlags))); + + // Include UBO block bindings in hash so different binding configurations produce different entries + const Uint32 blockCount = static_cast(program.GetActiveUniformBlocksCount()); + XXHASH_VERIFY(XXH64_update(m_hashState, &blockCount, sizeof(blockCount))); + for (Uint32 i = 0; i < blockCount; ++i) { + const Uint32 binding = program.GetUniformBlockBinding(i); + XXHASH_VERIFY(XXH64_update(m_hashState, &binding, sizeof(binding))); + } + HashType hash = XXH64_digest(m_hashState); return hash; } - Vector& ProgramFactory::GetOrCreatePipelineShaderStages( + TextureTarget ProgramFactory::UniformTypeToTextureTarget(GLenum glType) { + switch (glType) { + case GL_SAMPLER_1D: + case GL_INT_SAMPLER_1D: + case GL_UNSIGNED_INT_SAMPLER_1D: + return TextureTarget::Texture1D; + case GL_SAMPLER_3D: + case GL_INT_SAMPLER_3D: + case GL_UNSIGNED_INT_SAMPLER_3D: + return TextureTarget::Texture3D; + case GL_SAMPLER_CUBE: + case GL_SAMPLER_CUBE_SHADOW: + case GL_INT_SAMPLER_CUBE: + case GL_UNSIGNED_INT_SAMPLER_CUBE: + return TextureTarget::TextureCubeMap; + case GL_SAMPLER_2D_MULTISAMPLE: + case GL_INT_SAMPLER_2D_MULTISAMPLE: + case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE: + return TextureTarget::Texture2DMultisample; + case GL_SAMPLER_BUFFER: + case GL_INT_SAMPLER_BUFFER: + case GL_UNSIGNED_INT_SAMPLER_BUFFER: + return TextureTarget::TextureBuffer; + case GL_SAMPLER_1D_ARRAY: + case GL_SAMPLER_1D_ARRAY_SHADOW: + case GL_INT_SAMPLER_1D_ARRAY: + case GL_UNSIGNED_INT_SAMPLER_1D_ARRAY: + return TextureTarget::Texture1DArray; + case GL_SAMPLER_2D_ARRAY: + case GL_SAMPLER_2D_ARRAY_SHADOW: + case GL_INT_SAMPLER_2D_ARRAY: + case GL_UNSIGNED_INT_SAMPLER_2D_ARRAY: + return TextureTarget::Texture2DArray; + case GL_SAMPLER_2D_MULTISAMPLE_ARRAY: + case GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: + case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: + return TextureTarget::Texture2DMultisampleArray; + case GL_SAMPLER_2D_RECT: + case GL_SAMPLER_2D_RECT_SHADOW: + case GL_INT_SAMPLER_2D_RECT: + case GL_UNSIGNED_INT_SAMPLER_2D_RECT: + return TextureTarget::TextureRectangle; + case GL_SAMPLER_2D: + case GL_SAMPLER_2D_SHADOW: + case GL_INT_SAMPLER_2D: + case GL_UNSIGNED_INT_SAMPLER_2D: + default: + return TextureTarget::Texture2D; + } + } + + void ProgramFactory::ReflectLayout(const MG_State::GLState::ProgramObject& program, + VkProgramObject& entry) const { + // Initialize layout vectors + entry.bindingKinds.assign(m_maxBindings, DescriptorBindingKind::None); + entry.samplerUniformLocationByBinding.assign(m_maxBindings, -1); + entry.samplerTextureTargetByBinding.assign(m_maxBindings, TextureTarget::Texture2D); + entry.globalUboBinding = -1; + entry.dynamicBindings.clear(); + + // Use SpvcSession (Reflection mode) to reflect all SPIR-V modules in a single pass per module + const auto& spirv = program.GetGeneratedSpirv(); + for (const auto& module : spirv) { + if (module.empty()) { + continue; + } + + SpvcSession session(module, SessionUsageBit::Reflection); + + // Reflect uniform buffers + auto ubos = session.GetShaderInterface(SPVC_RESOURCE_TYPE_UNIFORM_BUFFER); + for (const auto& ubo : ubos) { + const Uint32 binding = ubo.location; // GetShaderInterface stores binding in location field + if (binding >= m_maxBindings) { + continue; + } + if (entry.bindingKinds[binding] == DescriptorBindingKind::None) { + entry.bindingKinds[binding] = DescriptorBindingKind::UniformBufferDynamic; + } + + // Check for global UBO + if (entry.globalUboBinding < 0 && + std::strstr(ubo.name.c_str(), MG_Util::ShaderTranspiler::GLOBAL_UBO_NAME) != nullptr) { + entry.globalUboBinding = static_cast(binding); + } + } + + // Reflect sampled images + auto samplers = session.GetShaderInterface(SPVC_RESOURCE_TYPE_SAMPLED_IMAGE); + for (const auto& sampler : samplers) { + const Uint32 binding = sampler.location; // GetShaderInterface stores binding in location field + if (binding >= m_maxBindings) { + continue; + } + + // Sampler always wins over UBO for a binding slot + entry.bindingKinds[binding] = DescriptorBindingKind::CombinedImageSampler; + + // Resolve uniform location for this sampler + String uniformName = sampler.name; + Int location = program.GetUniformLocation(uniformName); + if (location < 0) { + const auto arraySuffix = uniformName.find("[0]"); + if (arraySuffix != String::npos) { + uniformName = uniformName.substr(0, arraySuffix); + location = program.GetUniformLocation(uniformName); + } + } + if (location < 0) { + continue; + } + + entry.samplerUniformLocationByBinding[binding] = location; + entry.samplerTextureTargetByBinding[binding] = + UniformTypeToTextureTarget(program.GetUniformType(static_cast(location))); + } + } + + // Build Vulkan descriptor set layout and pipeline layout from reflected binding kinds + Vector bindings; + bindings.reserve(m_maxBindings); + for (Uint32 binding = 0; binding < m_maxBindings; ++binding) { + const auto kind = entry.bindingKinds[binding]; + if (kind == DescriptorBindingKind::None) { + continue; + } + + VkDescriptorSetLayoutBinding layoutBinding{}; + layoutBinding.binding = binding; + layoutBinding.descriptorCount = 1; + layoutBinding.stageFlags = VK_SHADER_STAGE_ALL_GRAPHICS; + layoutBinding.pImmutableSamplers = nullptr; + if (kind == DescriptorBindingKind::UniformBufferDynamic) { + layoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; + entry.dynamicBindings.push_back(binding); + } else { + layoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + } + bindings.push_back(layoutBinding); + } + + VkDescriptorSetLayoutCreateInfo setLayoutInfo{}; + setLayoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + setLayoutInfo.bindingCount = static_cast(bindings.size()); + setLayoutInfo.pBindings = bindings.data(); + VK_VERIFY(vkCreateDescriptorSetLayout(m_device, &setLayoutInfo, nullptr, &entry.descriptorSetLayout), + "ProgramFactory::ReflectLayout, vkCreateDescriptorSetLayout"); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; + pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutInfo.setLayoutCount = 1; + pipelineLayoutInfo.pSetLayouts = &entry.descriptorSetLayout; + VK_VERIFY(vkCreatePipelineLayout(m_device, &pipelineLayoutInfo, nullptr, &entry.pipelineLayout), + "ProgramFactory::ReflectLayout, vkCreatePipelineLayout"); + } + + const ProgramFactory::VkProgramObject& ProgramFactory::GetOrCreateProgram( const MG_State::GLState::ProgramObject& program, CompileOptionFlags flags) { auto hash = ComputeHash(program, flags); auto it = m_cache.find(hash); if (it != m_cache.end()) { - return it->second.stages; + return it->second; } auto& entry = m_cache[hash]; @@ -400,6 +568,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { entry.stages.push_back(stage); } - return entry.stages; + // Reflect and create layout as part of the program object + ReflectLayout(program, entry); + + return entry; } } // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.h index b44358cd..06fe897c 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.h @@ -11,11 +11,19 @@ #include "../VkIncludes.h" #include "MG_State/GLState/ProgramState/ProgramObject.h" #include "MG_State/GLState/ProgramState/ShaderObject.h" +#include "MG_State/GLState/TextureState/TextureEnum.h" + #include namespace MobileGL::MG_Backend::DirectVulkan { class ProgramFactory { public: + enum class DescriptorBindingKind : Uint8 { + None = 0, + UniformBufferDynamic, + CombinedImageSampler + }; + enum class CompileOptionBit : Uint { None = 0, PositionYFlip = 1 << 0, @@ -26,10 +34,21 @@ namespace MobileGL::MG_Backend::DirectVulkan { }; using CompileOptionFlags = Flags; using HashType = Uint64; + struct VkProgramObject { HashType hash = 0; Vector stages; Vector modules; + + // Layout data (previously in separate VkProgramLayout) + VkDescriptorSetLayout descriptorSetLayout = VK_NULL_HANDLE; + VkPipelineLayout pipelineLayout = VK_NULL_HANDLE; + Vector bindingKinds; + Vector dynamicBindings; + Vector samplerUniformLocationByBinding; + Vector samplerTextureTargetByBinding; + Int globalUboBinding = -1; + static inline VkDevice s_device = VK_NULL_HANDLE; VkProgramObject() = default; @@ -39,52 +58,85 @@ namespace MobileGL::MG_Backend::DirectVulkan { hash = other.hash; stages = std::move(other.stages); modules = std::move(other.modules); + descriptorSetLayout = other.descriptorSetLayout; + pipelineLayout = other.pipelineLayout; + bindingKinds = std::move(other.bindingKinds); + dynamicBindings = std::move(other.dynamicBindings); + samplerUniformLocationByBinding = std::move(other.samplerUniformLocationByBinding); + samplerTextureTargetByBinding = std::move(other.samplerTextureTargetByBinding); + globalUboBinding = other.globalUboBinding; other.hash = 0; + other.descriptorSetLayout = VK_NULL_HANDLE; + other.pipelineLayout = VK_NULL_HANDLE; + other.globalUboBinding = -1; } VkProgramObject& operator=(VkProgramObject&& other) noexcept { if (this == &other) { return *this; } - DestroyModules(); - stages.clear(); + Destroy(); hash = other.hash; stages = std::move(other.stages); modules = std::move(other.modules); + descriptorSetLayout = other.descriptorSetLayout; + pipelineLayout = other.pipelineLayout; + bindingKinds = std::move(other.bindingKinds); + dynamicBindings = std::move(other.dynamicBindings); + samplerUniformLocationByBinding = std::move(other.samplerUniformLocationByBinding); + samplerTextureTargetByBinding = std::move(other.samplerTextureTargetByBinding); + globalUboBinding = other.globalUboBinding; other.hash = 0; + other.descriptorSetLayout = VK_NULL_HANDLE; + other.pipelineLayout = VK_NULL_HANDLE; + other.globalUboBinding = -1; return *this; } ~VkProgramObject() { - DestroyModules(); - stages.clear(); + Destroy(); } private: - void DestroyModules() { - for (auto module : modules) { - if (module != VK_NULL_HANDLE && s_device != VK_NULL_HANDLE) { - vkDestroyShaderModule(s_device, module, nullptr); + void Destroy() { + if (s_device != VK_NULL_HANDLE) { + if (pipelineLayout != VK_NULL_HANDLE) { + vkDestroyPipelineLayout(s_device, pipelineLayout, nullptr); + pipelineLayout = VK_NULL_HANDLE; + } + if (descriptorSetLayout != VK_NULL_HANDLE) { + vkDestroyDescriptorSetLayout(s_device, descriptorSetLayout, nullptr); + descriptorSetLayout = VK_NULL_HANDLE; + } + for (auto module : modules) { + if (module != VK_NULL_HANDLE) { + vkDestroyShaderModule(s_device, module, nullptr); + } } } modules.clear(); + stages.clear(); } }; - explicit ProgramFactory(VkDevice device, const VulkanRendererConfig& config) - : m_device(device), m_config(config) { + explicit ProgramFactory(VkDevice device, const VulkanRendererConfig& config, Uint32 maxBindings = 16) + : m_device(device), m_config(config), m_maxBindings(maxBindings) { VkProgramObject::s_device = device; } - ~ProgramFactory(); + ~ProgramFactory() = default; ProgramFactory(const ProgramFactory&) = delete; HashType ComputeHash(const MG_State::GLState::ProgramObject& program, CompileOptionFlags flags) const; - Vector& GetOrCreatePipelineShaderStages( + const VkProgramObject& GetOrCreateProgram( const MG_State::GLState::ProgramObject& program, CompileOptionFlags flags); static VkShaderStageFlagBits ToVkStage(ShaderStage stage); private: + static TextureTarget UniformTypeToTextureTarget(GLenum glType); + void ReflectLayout(const MG_State::GLState::ProgramObject& program, VkProgramObject& entry) const; + VkDevice m_device = VK_NULL_HANDLE; + Uint32 m_maxBindings = 0; UnorderedMap m_cache; const VulkanRendererConfig& m_config; static inline XXH64_state_t* m_hashState = XXH64_createState(); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.cpp deleted file mode 100644 index 5d973ccc..00000000 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.cpp +++ /dev/null @@ -1,985 +0,0 @@ -// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.cpp -// Copyright (c) 2025-2026 MobileGL-Dev -// Licensed under the GNU Lesser General Public License v3.0: -// https://www.gnu.org/licenses/gpl-3.0.txt -// https://www.gnu.org/licenses/lgpl-3.0.txt -// SPDX-License-Identifier: LGPL-3.0-only -// End of Source File Header - -#include "UniformDescriptorBinder.h" - -#include "MG_State/GLState/Core.h" -#include "MG_State/GLState/ProgramState/ProgramObject.h" -#include "MG_Util/Converters/MGToStr/FramebufferEnumConverter.h" -#include "MG_Util/ShaderTranspiler/Types.h" -#include - -namespace MobileGL::MG_Backend::DirectVulkan { - static Bool FindFramebufferAttachmentForTexture(const MG_State::GLState::FramebufferObject& framebuffer, - const MG_State::GLState::ITextureObject& texture, - FramebufferAttachmentType& outAttachment, Int& outLevel) { - const auto& attachments = framebuffer.GetAllAttachmentObjects(); - for (SizeT i = 0; i < attachments.size(); ++i) { - const auto attachmentType = static_cast(i); - if (attachmentType == FramebufferAttachmentType::None) { - continue; - } - - const auto& attachment = attachments[i]; - if (!attachment.IsTexture()) { - continue; - } - - auto attachedTexture = attachment.GetTexture(); - if (attachedTexture && attachedTexture.get() == &texture) { - outAttachment = attachmentType; - outLevel = attachment.GetTextureLevel(); - return true; - } - } - - return false; - } - - static Bool IsValidSampledImageLayout(VkImageLayout layout) { - switch (layout) { - case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: - case VK_IMAGE_LAYOUT_GENERAL: - case VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL: - case VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL: - case VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL: - return true; - default: - return false; - } - } - - VkDeviceSize UniformDescriptorBinder::AlignUp(VkDeviceSize value, VkDeviceSize alignment) { - if (alignment == 0) { - return value; - } - return (value + alignment - 1) / alignment * alignment; - } - - Uint64 UniformDescriptorBinder::ComputeProgramHash(const MG_State::GLState::ProgramObject& program) { - XXH64_state_t* state = XXH64_createState(); - XXHASH_VERIFY(XXH64_reset(state, 0xC0D3A11ULL)); - const auto& spirv = program.GetGeneratedSpirv(); - for (const auto& module : spirv) { - XXHASH_VERIFY(XXH64_update(state, module.data(), module.size() * sizeof(Uint))); - } - const Uint32 blockCount = static_cast(program.GetActiveUniformBlocksCount()); - XXHASH_VERIFY(XXH64_update(state, &blockCount, sizeof(blockCount))); - for (Uint32 i = 0; i < blockCount; ++i) { - const Uint32 binding = program.GetUniformBlockBinding(i); - XXHASH_VERIFY(XXH64_update(state, &binding, sizeof(binding))); - } - const Uint64 hash = XXH64_digest(state); - XXH64_freeState(state); - return hash; - } - - Bool UniformDescriptorBinder::IsSamplerUniformType(GLenum glType) { - switch (glType) { - case GL_SAMPLER_1D: - case GL_SAMPLER_2D: - case GL_SAMPLER_3D: - case GL_SAMPLER_CUBE: - case GL_SAMPLER_1D_SHADOW: - case GL_SAMPLER_2D_SHADOW: - case GL_SAMPLER_1D_ARRAY: - case GL_SAMPLER_2D_ARRAY: - case GL_SAMPLER_1D_ARRAY_SHADOW: - case GL_SAMPLER_2D_ARRAY_SHADOW: - case GL_SAMPLER_2D_MULTISAMPLE: - case GL_SAMPLER_2D_MULTISAMPLE_ARRAY: - case GL_SAMPLER_CUBE_SHADOW: - case GL_SAMPLER_BUFFER: - case GL_SAMPLER_2D_RECT: - case GL_SAMPLER_2D_RECT_SHADOW: - case GL_INT_SAMPLER_1D: - case GL_INT_SAMPLER_2D: - case GL_INT_SAMPLER_3D: - case GL_INT_SAMPLER_CUBE: - case GL_INT_SAMPLER_1D_ARRAY: - case GL_INT_SAMPLER_2D_ARRAY: - case GL_INT_SAMPLER_2D_MULTISAMPLE: - case GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: - case GL_INT_SAMPLER_BUFFER: - case GL_INT_SAMPLER_2D_RECT: - case GL_UNSIGNED_INT_SAMPLER_1D: - case GL_UNSIGNED_INT_SAMPLER_2D: - case GL_UNSIGNED_INT_SAMPLER_3D: - case GL_UNSIGNED_INT_SAMPLER_CUBE: - case GL_UNSIGNED_INT_SAMPLER_1D_ARRAY: - case GL_UNSIGNED_INT_SAMPLER_2D_ARRAY: - case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE: - case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: - case GL_UNSIGNED_INT_SAMPLER_BUFFER: - case GL_UNSIGNED_INT_SAMPLER_2D_RECT: - return true; - default: - return false; - } - } - - TextureTarget UniformDescriptorBinder::UniformTypeToTextureTarget(GLenum glType) { - switch (glType) { - case GL_SAMPLER_1D: - case GL_INT_SAMPLER_1D: - case GL_UNSIGNED_INT_SAMPLER_1D: - return TextureTarget::Texture1D; - case GL_SAMPLER_3D: - case GL_INT_SAMPLER_3D: - case GL_UNSIGNED_INT_SAMPLER_3D: - return TextureTarget::Texture3D; - case GL_SAMPLER_CUBE: - case GL_SAMPLER_CUBE_SHADOW: - case GL_INT_SAMPLER_CUBE: - case GL_UNSIGNED_INT_SAMPLER_CUBE: - return TextureTarget::TextureCubeMap; - case GL_SAMPLER_2D_MULTISAMPLE: - case GL_INT_SAMPLER_2D_MULTISAMPLE: - case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE: - return TextureTarget::Texture2DMultisample; - case GL_SAMPLER_BUFFER: - case GL_INT_SAMPLER_BUFFER: - case GL_UNSIGNED_INT_SAMPLER_BUFFER: - return TextureTarget::TextureBuffer; - case GL_SAMPLER_1D_ARRAY: - case GL_SAMPLER_1D_ARRAY_SHADOW: - case GL_INT_SAMPLER_1D_ARRAY: - case GL_UNSIGNED_INT_SAMPLER_1D_ARRAY: - return TextureTarget::Texture1DArray; - case GL_SAMPLER_2D_ARRAY: - case GL_SAMPLER_2D_ARRAY_SHADOW: - case GL_INT_SAMPLER_2D_ARRAY: - case GL_UNSIGNED_INT_SAMPLER_2D_ARRAY: - return TextureTarget::Texture2DArray; - case GL_SAMPLER_2D_MULTISAMPLE_ARRAY: - case GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: - case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: - return TextureTarget::Texture2DMultisampleArray; - case GL_SAMPLER_2D_RECT: - case GL_SAMPLER_2D_RECT_SHADOW: - case GL_INT_SAMPLER_2D_RECT: - case GL_UNSIGNED_INT_SAMPLER_2D_RECT: - return TextureTarget::TextureRectangle; - case GL_SAMPLER_2D: - case GL_SAMPLER_2D_SHADOW: - case GL_INT_SAMPLER_2D: - case GL_UNSIGNED_INT_SAMPLER_2D: - default: - return TextureTarget::Texture2D; - } - } - - Bool UniformDescriptorBinder::Initialize(VkDevice device, VmaAllocator allocator, - VkDeviceSize minUniformBufferOffsetAlignment, Uint32 frameCount, - Uint32 maxBindings, Uint32 setsPerFrame, VkDeviceSize perFrameUploadBytes, - VkTextureManager* textureManager, VkSamplerManager* samplerManager) { - Shutdown(); - - MOBILEGL_ASSERT(device != VK_NULL_HANDLE, "UniformDescriptorBinder::Initialize requires valid VkDevice"); - MOBILEGL_ASSERT(allocator != nullptr, "UniformDescriptorBinder::Initialize requires valid VMA allocator"); - MOBILEGL_ASSERT(frameCount > 0, "UniformDescriptorBinder::Initialize requires frameCount > 0"); - MOBILEGL_ASSERT(maxBindings > 0, "UniformDescriptorBinder::Initialize requires maxBindings > 0"); - MOBILEGL_ASSERT(setsPerFrame > 0, "UniformDescriptorBinder::Initialize requires setsPerFrame > 0"); - MOBILEGL_ASSERT(textureManager != nullptr, - "UniformDescriptorBinder::Initialize requires valid texture manager"); - MOBILEGL_ASSERT(samplerManager != nullptr, - "UniformDescriptorBinder::Initialize requires valid sampler manager"); - - m_device = device; - m_allocator = allocator; - m_minDynamicOffsetAlignment = std::max(1, minUniformBufferOffsetAlignment); - m_perFrameUploadBytes = perFrameUploadBytes; - m_frameCount = frameCount; - m_maxBindings = maxBindings; - m_setsPerFrame = setsPerFrame; - m_peakDescriptorSetsObserved = 0; - m_textureManager = textureManager; - m_samplerManager = samplerManager; - - m_frames.resize(m_frameCount); - for (Uint32 frameIndex = 0; frameIndex < m_frameCount; ++frameIndex) { - auto& frame = m_frames[frameIndex]; - frame.writeCursor = 0; - frame.activeDescriptorPoolIndex = 0; - frame.allocatedSetsThisFrame = 0; - frame.peakAllocatedSetsThisFrame = 0; - frame.descriptorPools.clear(); - - VkDescriptorPool initialPool = VK_NULL_HANDLE; - if (!CreateDescriptorPool(m_setsPerFrame, initialPool)) { - MGLOG_E("UniformDescriptorBinder::Initialize failed: cannot create frame descriptor pool %u", - frameIndex); - Shutdown(); - return false; - } - frame.descriptorPools.push_back({initialPool, m_setsPerFrame, 0}); - MGLOG_D("UniformDescriptorBinder: frame %u descriptor pool created (maxSets=%u)", frameIndex, m_setsPerFrame); - - const Bool created = frame.uploadBuffer.Create( - m_allocator, m_perFrameUploadBytes, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VMA_MEMORY_USAGE_AUTO, - VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT); - if (!created) { - MGLOG_E("UniformDescriptorBinder::Initialize failed: cannot create frame upload buffer %u", frameIndex); - Shutdown(); - return false; - } - } - - return true; - } - - void UniformDescriptorBinder::Shutdown() { - for (auto& frame : m_frames) { - frame.uploadBuffer.Destroy(); - if (m_device != VK_NULL_HANDLE) { - for (auto& bucket : frame.descriptorPools) { - if (bucket.handle != VK_NULL_HANDLE) { - vkDestroyDescriptorPool(m_device, bucket.handle, nullptr); - bucket.handle = VK_NULL_HANDLE; - } - } - } - frame.descriptorPools.clear(); - frame.activeDescriptorPoolIndex = 0; - frame.allocatedSetsThisFrame = 0; - frame.peakAllocatedSetsThisFrame = 0; - frame.writeCursor = 0; - } - m_frames.clear(); - DestroyProgramLayouts(); - - m_allocator = nullptr; - m_device = VK_NULL_HANDLE; - m_minDynamicOffsetAlignment = 1; - m_perFrameUploadBytes = 0; - m_frameCount = 0; - m_maxBindings = 0; - m_setsPerFrame = 0; - m_peakDescriptorSetsObserved = 0; - m_textureManager = nullptr; - m_samplerManager = nullptr; - } - - void UniformDescriptorBinder::BeginFrame(Uint32 frameIndex) { - MOBILEGL_ASSERT(frameIndex < m_frames.size(), "UniformDescriptorBinder::BeginFrame invalid frame index"); - auto& frame = m_frames[frameIndex]; - if (frame.peakAllocatedSetsThisFrame > m_peakDescriptorSetsObserved) { - m_peakDescriptorSetsObserved = frame.peakAllocatedSetsThisFrame; - MGLOG_D( - "UniformDescriptorBinder: new descriptor set peak observed=%u (base setsPerFrame=%u, frame=%u, pools=%zu)", - m_peakDescriptorSetsObserved, m_setsPerFrame, frameIndex, frame.descriptorPools.size()); - } - frame.writeCursor = 0; - frame.activeDescriptorPoolIndex = 0; - frame.allocatedSetsThisFrame = 0; - frame.peakAllocatedSetsThisFrame = 0; - for (auto& bucket : frame.descriptorPools) { - bucket.allocatedSets = 0; - if (bucket.handle == VK_NULL_HANDLE) { - continue; - } - VK_VERIFY(vkResetDescriptorPool(m_device, bucket.handle, 0), - "UniformDescriptorBinder::BeginFrame, vkResetDescriptorPool"); - } - } - - Bool UniformDescriptorBinder::ReflectBindingKinds(const MG_State::GLState::ProgramObject& program, - Vector& outKinds) const { - outKinds.assign(m_maxBindings, BindingKind::None); - - const auto& spirv = program.GetGeneratedSpirv(); - for (const auto& module : spirv) { - if (module.empty()) { - continue; - } - - spvc_context context = nullptr; - spvc_parsed_ir ir = nullptr; - spvc_compiler compiler = nullptr; - spvc_resources resources = nullptr; - - if (spvc_context_create(&context) != SPVC_SUCCESS) { - return false; - } - - const spvc_result parseResult = spvc_context_parse_spirv(context, module.data(), module.size(), &ir); - if (parseResult != SPVC_SUCCESS) { - spvc_context_destroy(context); - continue; - } - - const spvc_result compilerResult = - spvc_context_create_compiler(context, SPVC_BACKEND_GLSL, ir, SPVC_CAPTURE_MODE_TAKE_OWNERSHIP, &compiler); - if (compilerResult != SPVC_SUCCESS) { - spvc_context_destroy(context); - continue; - } - - if (spvc_compiler_create_shader_resources(compiler, &resources) != SPVC_SUCCESS) { - spvc_context_destroy(context); - continue; - } - - const auto applyBindings = [&](spvc_resource_type resourceType, BindingKind kind) { - const spvc_reflected_resource* list = nullptr; - size_t count = 0; - if (spvc_resources_get_resource_list_for_type(resources, resourceType, &list, &count) != SPVC_SUCCESS) { - return; - } - for (size_t i = 0; i < count; ++i) { - const Uint32 binding = - spvc_compiler_get_decoration(compiler, list[i].id, SpvDecorationBinding); - if (binding >= m_maxBindings) { - continue; - } - if (kind == BindingKind::CombinedImageSampler) { - outKinds[binding] = BindingKind::CombinedImageSampler; - } else if (outKinds[binding] == BindingKind::None) { - outKinds[binding] = kind; - } - } - }; - - applyBindings(SPVC_RESOURCE_TYPE_UNIFORM_BUFFER, BindingKind::UniformBufferDynamic); - applyBindings(SPVC_RESOURCE_TYPE_SAMPLED_IMAGE, BindingKind::CombinedImageSampler); - - spvc_context_destroy(context); - } - - return true; - } - - Bool UniformDescriptorBinder::ReflectSamplerBindings(const MG_State::GLState::ProgramObject& program, - ProgramLayout& layout) const { - layout.samplerUniformLocationByBinding.assign(m_maxBindings, -1); - layout.samplerTextureTargetByBinding.assign(m_maxBindings, TextureTarget::Texture2D); - - const auto& spirv = program.GetGeneratedSpirv(); - for (const auto& module : spirv) { - if (module.empty()) { - continue; - } - - spvc_context context = nullptr; - spvc_parsed_ir ir = nullptr; - spvc_compiler compiler = nullptr; - spvc_resources resources = nullptr; - - if (spvc_context_create(&context) != SPVC_SUCCESS) { - return false; - } - if (spvc_context_parse_spirv(context, module.data(), module.size(), &ir) != SPVC_SUCCESS) { - spvc_context_destroy(context); - continue; - } - if (spvc_context_create_compiler(context, SPVC_BACKEND_GLSL, ir, SPVC_CAPTURE_MODE_TAKE_OWNERSHIP, - &compiler) != SPVC_SUCCESS) { - spvc_context_destroy(context); - continue; - } - if (spvc_compiler_create_shader_resources(compiler, &resources) != SPVC_SUCCESS) { - spvc_context_destroy(context); - continue; - } - - const spvc_reflected_resource* list = nullptr; - size_t count = 0; - if (spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_SAMPLED_IMAGE, &list, &count) == - SPVC_SUCCESS) { - for (size_t i = 0; i < count; ++i) { - const Uint32 binding = - spvc_compiler_get_decoration(compiler, list[i].id, SpvDecorationBinding); - if (binding >= m_maxBindings) { - continue; - } - - String uniformName = list[i].name ? list[i].name : ""; - Int location = program.GetUniformLocation(uniformName); - if (location < 0) { - const auto arraySuffix = uniformName.find("[0]"); - if (arraySuffix != String::npos) { - uniformName = uniformName.substr(0, arraySuffix); - location = program.GetUniformLocation(uniformName); - } - } - if (location < 0) { - continue; - } - - layout.samplerUniformLocationByBinding[binding] = location; - layout.samplerTextureTargetByBinding[binding] = - UniformTypeToTextureTarget(program.GetUniformType(static_cast(location))); - } - } - - spvc_context_destroy(context); - } - - return true; - } - - Bool UniformDescriptorBinder::ReflectGlobalUboBinding(const MG_State::GLState::ProgramObject& program, - ProgramLayout& layout) const { - layout.globalUboBinding = -1; - - const auto& spirv = program.GetGeneratedSpirv(); - for (const auto& module : spirv) { - if (module.empty()) { - continue; - } - - spvc_context context = nullptr; - spvc_parsed_ir ir = nullptr; - spvc_compiler compiler = nullptr; - spvc_resources resources = nullptr; - - if (spvc_context_create(&context) != SPVC_SUCCESS) { - return false; - } - if (spvc_context_parse_spirv(context, module.data(), module.size(), &ir) != SPVC_SUCCESS) { - spvc_context_destroy(context); - continue; - } - if (spvc_context_create_compiler(context, SPVC_BACKEND_GLSL, ir, SPVC_CAPTURE_MODE_TAKE_OWNERSHIP, - &compiler) != SPVC_SUCCESS) { - spvc_context_destroy(context); - continue; - } - if (spvc_compiler_create_shader_resources(compiler, &resources) != SPVC_SUCCESS) { - spvc_context_destroy(context); - continue; - } - - const spvc_reflected_resource* list = nullptr; - size_t count = 0; - if (spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_UNIFORM_BUFFER, &list, &count) == - SPVC_SUCCESS) { - for (size_t i = 0; i < count; ++i) { - const char* name = list[i].name ? list[i].name : ""; - if (std::strstr(name, MG_Util::ShaderTranspiler::GLOBAL_UBO_NAME) == nullptr) { - continue; - } - const Uint32 binding = - spvc_compiler_get_decoration(compiler, list[i].id, SpvDecorationBinding); - if (binding < m_maxBindings) { - layout.globalUboBinding = static_cast(binding); - } - break; - } - } - - spvc_context_destroy(context); - if (layout.globalUboBinding >= 0) { - break; - } - } - return true; - } - - Bool UniformDescriptorBinder::ResolveSamplerDescriptor(VkCommandBuffer commandBuffer, - const MG_State::GLState::ProgramObject& program, - const ProgramLayout& layout, Uint32 binding, - VkDescriptorImageInfo& outImageInfo) const { - (void)commandBuffer; - MOBILEGL_ASSERT(m_textureManager != nullptr, "ResolveSamplerDescriptor: texture manager is null"); - MOBILEGL_ASSERT(m_samplerManager != nullptr, "ResolveSamplerDescriptor: sampler manager is null"); - SharedPtr texture; - if (!ResolveSamplerTexture(program, layout, binding, texture)) { - return false; - } - - const Int location = layout.samplerUniformLocationByBinding[binding]; - const Int unit = program.GetUniformSamplerOrImageUnitIndex(static_cast(location)); - auto& textureUnit = MG_State::pGLContext->GetTextureUnitObject(unit); - const auto samplerOverride = textureUnit.GetSamplerObject(); - if (!texture) { - return false; - } - - const MG_State::GLState::SamplerObject* samplerToUse = samplerOverride ? samplerOverride.get() : texture->GetSamplerObject().get(); - if (!samplerToUse) { - return false; - } - VkTextureManager::TextureResource* resource = - m_textureManager->SyncTextureAndGetDescriptor(*texture); - if (resource == nullptr) { - return false; - } - if (!IsValidSampledImageLayout(resource->layout)) { - auto drawFbo = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject(); - FramebufferAttachmentType attachmentType = FramebufferAttachmentType::None; - Int attachmentLevel = 0; - if (drawFbo && - FindFramebufferAttachmentForTexture(*drawFbo, *texture, attachmentType, attachmentLevel)) { - MOBILEGL_ASSERT(false, - "ResolveSamplerDescriptor: framebuffer feedback loop detected: textureId=%d is bound " - "for sampling at binding=%u, but is also attached to drawFbo=%u as %s (level=%d, " - "trackedLayout=%d)", - texture->GetExternalIndex(), binding, drawFbo->GetExternalIndex(), - MG_Util::ConvertFramebufferAttachmentTypeToString(attachmentType).c_str(), - attachmentLevel, static_cast(resource->layout)); - } - - MOBILEGL_ASSERT(false, - "ResolveSamplerDescriptor: invalid sampled image layout=%d for textureId=%d, binding=%u", - static_cast(resource->layout), texture->GetExternalIndex(), binding); - return false; - } - outImageInfo = { - .sampler = m_samplerManager->GetOrCreateSampler(*samplerToUse), - .imageView = resource->view, - .imageLayout = resource->layout, - }; - return outImageInfo.sampler != VK_NULL_HANDLE; - } - - Bool UniformDescriptorBinder::ResolveSamplerDescriptorOverride( - const SamplerBindingOverride& samplerBindingOverride, - VkDescriptorImageInfo& outImageInfo) const { - MOBILEGL_ASSERT(m_textureManager != nullptr, "ResolveSamplerDescriptorOverride: texture manager is null"); - MOBILEGL_ASSERT(m_samplerManager != nullptr, "ResolveSamplerDescriptorOverride: sampler manager is null"); - if (samplerBindingOverride.texture == nullptr || samplerBindingOverride.sampler == nullptr) { - return false; - } - - auto* resource = m_textureManager->SyncTextureAndGetDescriptor(*samplerBindingOverride.texture); - if (resource == nullptr || !IsValidSampledImageLayout(resource->layout)) { - return false; - } - - outImageInfo = { - .sampler = m_samplerManager->GetOrCreateSampler(*samplerBindingOverride.sampler), - .imageView = resource->view, - .imageLayout = resource->layout, - }; - return outImageInfo.sampler != VK_NULL_HANDLE; - } - - Bool UniformDescriptorBinder::ResolveSamplerTexture(const MG_State::GLState::ProgramObject& program, - const ProgramLayout& layout, Uint32 binding, - SharedPtr& outTexture) const { - outTexture.reset(); - if (!MG_State::pGLContext || binding >= layout.samplerUniformLocationByBinding.size()) { - return false; - } - - const Int location = layout.samplerUniformLocationByBinding[binding]; - if (location < 0) { - return false; - } - - const Int unit = program.GetUniformSamplerOrImageUnitIndex(static_cast(location)); - if (unit < 0) { - return false; - } - - auto& textureUnit = MG_State::pGLContext->GetTextureUnitObject(unit); - const TextureTarget preferredTarget = layout.samplerTextureTargetByBinding[binding]; - outTexture = textureUnit.GetBindingSlot(preferredTarget).GetBoundObject(); - return outTexture != nullptr; - } - - Bool UniformDescriptorBinder::CollectSampledTextures(const MG_State::GLState::ProgramObject& program, - Vector& outTextures) { - outTextures.clear(); - ProgramLayout* layout = GetOrCreateProgramLayout(program); - if (layout == nullptr) { - return false; - } - - for (Uint32 binding = 0; binding < m_maxBindings; ++binding) { - if (layout->bindingKinds[binding] != BindingKind::CombinedImageSampler) { - continue; - } - - SharedPtr texture; - if (!ResolveSamplerTexture(program, *layout, binding, texture) || !texture) { - continue; - } - - auto found = std::find(outTextures.begin(), outTextures.end(), texture.get()); - if (found == outTextures.end()) { - outTextures.push_back(texture.get()); - } - } - return true; - } - - UniformDescriptorBinder::ProgramLayout* UniformDescriptorBinder::GetOrCreateProgramLayout( - const MG_State::GLState::ProgramObject& program) { - const Uint64 hash = ComputeProgramHash(program); - auto it = m_programLayouts.find(hash); - if (it != m_programLayouts.end()) { - return &it->second; - } - - ProgramLayout layout{}; - layout.hash = hash; - if (!ReflectBindingKinds(program, layout.bindingKinds)) { - MGLOG_E("UniformDescriptorBinder::GetOrCreateProgramLayout failed: reflection failed"); - return nullptr; - } - if (!ReflectSamplerBindings(program, layout)) { - MGLOG_E("UniformDescriptorBinder::GetOrCreateProgramLayout failed: sampler reflection failed"); - return nullptr; - } - if (!ReflectGlobalUboBinding(program, layout)) { - MGLOG_E("UniformDescriptorBinder::GetOrCreateProgramLayout failed: global UBO reflection failed"); - return nullptr; - } - - Vector bindings; - bindings.reserve(m_maxBindings); - for (Uint32 binding = 0; binding < m_maxBindings; ++binding) { - const auto kind = layout.bindingKinds[binding]; - if (kind == BindingKind::None) { - continue; - } - - VkDescriptorSetLayoutBinding layoutBinding{}; - layoutBinding.binding = binding; - layoutBinding.descriptorCount = 1; - layoutBinding.stageFlags = VK_SHADER_STAGE_ALL_GRAPHICS; - layoutBinding.pImmutableSamplers = nullptr; - if (kind == BindingKind::UniformBufferDynamic) { - layoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; - layout.dynamicBindings.push_back(binding); - } else { - layoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; - } - bindings.push_back(layoutBinding); - } - - VkDescriptorSetLayoutCreateInfo setLayoutInfo{}; - setLayoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; - setLayoutInfo.bindingCount = static_cast(bindings.size()); - setLayoutInfo.pBindings = bindings.data(); - VK_VERIFY(vkCreateDescriptorSetLayout(m_device, &setLayoutInfo, nullptr, &layout.descriptorSetLayout), - "UniformDescriptorBinder::GetOrCreateProgramLayout, vkCreateDescriptorSetLayout"); - - VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; - pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; - pipelineLayoutInfo.setLayoutCount = 1; - pipelineLayoutInfo.pSetLayouts = &layout.descriptorSetLayout; - VK_VERIFY(vkCreatePipelineLayout(m_device, &pipelineLayoutInfo, nullptr, &layout.pipelineLayout), - "UniformDescriptorBinder::GetOrCreateProgramLayout, vkCreatePipelineLayout"); - - auto [insertIt, _] = m_programLayouts.emplace(hash, std::move(layout)); - return &insertIt->second; - } - - VkPipelineLayout UniformDescriptorBinder::GetOrCreatePipelineLayout(const MG_State::GLState::ProgramObject& program) { - auto* layout = GetOrCreateProgramLayout(program); - return layout ? layout->pipelineLayout : VK_NULL_HANDLE; - } - - Bool UniformDescriptorBinder::AllocateUploadRegion(FrameResources& frame, VkDeviceSize size, VkDeviceSize& outOffset) { - const VkDeviceSize alignedOffset = AlignUp(frame.writeCursor, m_minDynamicOffsetAlignment); - if (alignedOffset + size > m_perFrameUploadBytes) { - return false; - } - outOffset = alignedOffset; - frame.writeCursor = alignedOffset + size; - return true; - } - - Bool UniformDescriptorBinder::GatherBindingPayloads(const MG_State::GLState::ProgramObject& program, - Vector& outData, - Vector& outSizes) const { - outData.assign(m_maxBindings, nullptr); - outSizes.assign(m_maxBindings, 0); - - const Uint32 activeUniformBlockCount = static_cast(program.GetActiveUniformBlocksCount()); - const Uint32 uniformBindingPointCount = - static_cast(MG_State::pGLContext->GetBufferBindingPointCount(BufferTarget::Uniform)); - - for (Uint32 blockIndex = 0; blockIndex < activeUniformBlockCount; ++blockIndex) { - const Uint32 binding = program.GetUniformBlockBinding(blockIndex); - if (binding >= m_maxBindings) { - continue; - } - - VkDeviceSize blockSize = static_cast(program.GetUBOSizeAt(blockIndex)); - if (blockSize == 0) { - continue; - } - - if (binding >= uniformBindingPointCount) { - continue; - } - auto& bindingPoint = MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::Uniform, binding); - const auto bufferObject = bindingPoint.GetBoundObject(); - if (!bufferObject) { - continue; - } - - const auto bufferData = bufferObject->GetDataReadOnly(); - if (!bufferData || bufferData->empty()) { - continue; - } - - const auto range = bindingPoint.GetRange(); - const VkDeviceSize bufferSize = static_cast(bufferObject->GetSize()); - VkDeviceSize rangeStart = static_cast(range.start); - VkDeviceSize rangeEnd = static_cast(range.end); - - if (rangeStart >= bufferSize) { - continue; - } - if (rangeEnd <= rangeStart || rangeEnd > bufferSize) { - rangeEnd = bufferSize; - } - - VkDeviceSize available = rangeEnd - rangeStart; - if (available == 0) { - continue; - } - - outData[binding] = bufferData->data() + static_cast(rangeStart); - outSizes[binding] = std::min(blockSize, available); - } - - return true; - } - - Bool UniformDescriptorBinder::CreateDescriptorPool(Uint32 maxSets, VkDescriptorPool& outPool) const { - outPool = VK_NULL_HANDLE; - if (m_device == VK_NULL_HANDLE || maxSets == 0 || m_maxBindings == 0) { - return false; - } - - const Uint64 descriptorCount64 = static_cast(maxSets) * static_cast(m_maxBindings); - if (descriptorCount64 > static_cast(std::numeric_limits::max())) { - MGLOG_E("UniformDescriptorBinder::CreateDescriptorPool failed: descriptorCount overflow"); - return false; - } - - const Uint32 descriptorCount = static_cast(descriptorCount64); - VkDescriptorPoolSize poolSizes[2]{}; - poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; - poolSizes[0].descriptorCount = descriptorCount; - poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; - poolSizes[1].descriptorCount = descriptorCount; - - VkDescriptorPoolCreateInfo poolInfo{}; - poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; - poolInfo.maxSets = maxSets; - poolInfo.poolSizeCount = static_cast(std::size(poolSizes)); - poolInfo.pPoolSizes = poolSizes; - - const VkResult result = vkCreateDescriptorPool(m_device, &poolInfo, nullptr, &outPool); - if (result != VK_SUCCESS) { - MGLOG_E("UniformDescriptorBinder::CreateDescriptorPool failed: vkCreateDescriptorPool returned %d", result); - return false; - } - return true; - } - - Bool UniformDescriptorBinder::GrowFrameDescriptorPool(FrameResources& frame, Uint32 frameIndex) { - if (frame.descriptorPools.empty()) { - return false; - } - - const auto& currentBucket = frame.descriptorPools[frame.activeDescriptorPoolIndex]; - const Uint32 currentMaxSets = std::max(1, currentBucket.maxSets); - const Uint32 grownMaxSets = currentMaxSets <= (std::numeric_limits::max() / 2) ? (currentMaxSets * 2) - : currentMaxSets; - - VkDescriptorPool grownPool = VK_NULL_HANDLE; - if (!CreateDescriptorPool(grownMaxSets, grownPool)) { - MGLOG_E("UniformDescriptorBinder::GrowFrameDescriptorPool failed: cannot create grown pool (%u -> %u sets)", - currentMaxSets, grownMaxSets); - return false; - } - - frame.descriptorPools.push_back({grownPool, grownMaxSets, 0}); - frame.activeDescriptorPoolIndex = static_cast(frame.descriptorPools.size() - 1); - MGLOG_D( - "UniformDescriptorBinder: frame %u descriptor pool exhausted, grew pool (%u -> %u sets), poolCount=%zu", - frameIndex, currentMaxSets, grownMaxSets, frame.descriptorPools.size()); - return true; - } - - Bool UniformDescriptorBinder::BindProgramUniformBuffers(VkCommandBuffer commandBuffer, - const MG_State::GLState::ProgramObject& program, - Uint32 frameIndex) { - return BindProgramUniformBuffers(commandBuffer, program, frameIndex, nullptr); - } - - Bool UniformDescriptorBinder::BindProgramUniformBuffers(VkCommandBuffer commandBuffer, - const MG_State::GLState::ProgramObject& program, - Uint32 frameIndex, - const SamplerBindingOverride* samplerBindingOverride) { - ProgramLayout* layout = GetOrCreateProgramLayout(program); - MOBILEGL_ASSERT(layout != nullptr, - "UniformDescriptorBinder::BindProgramUniformBuffers: program layout is null"); - - auto& frame = m_frames[frameIndex]; - if (frame.descriptorPools.empty()) { - MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: frame descriptor pools are invalid"); - return false; - } - if (frame.activeDescriptorPoolIndex >= frame.descriptorPools.size()) { - frame.activeDescriptorPoolIndex = 0; - } - - VkDescriptorSetAllocateInfo allocInfo{}; - allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; - allocInfo.descriptorSetCount = 1; - allocInfo.pSetLayouts = &layout->descriptorSetLayout; - VkDescriptorSet descriptorSet = VK_NULL_HANDLE; - - auto allocateFromActivePool = [&](VkResult& outResult) { - auto& bucket = frame.descriptorPools[frame.activeDescriptorPoolIndex]; - allocInfo.descriptorPool = bucket.handle; - outResult = vkAllocateDescriptorSets(m_device, &allocInfo, &descriptorSet); - if (outResult == VK_SUCCESS) { - ++bucket.allocatedSets; - ++frame.allocatedSetsThisFrame; - frame.peakAllocatedSetsThisFrame = std::max(frame.peakAllocatedSetsThisFrame, frame.allocatedSetsThisFrame); - } - }; - - VkResult allocResult = VK_SUCCESS; - allocateFromActivePool(allocResult); - if (allocResult == VK_ERROR_OUT_OF_POOL_MEMORY || allocResult == VK_ERROR_FRAGMENTED_POOL) { - if (!GrowFrameDescriptorPool(frame, frameIndex)) { - MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: descriptor pool growth failed"); - return false; - } - allocateFromActivePool(allocResult); - } - if (allocResult != VK_SUCCESS || descriptorSet == VK_NULL_HANDLE) { - MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: vkAllocateDescriptorSets returned %d", - allocResult); - return false; - } - - Vector bindingData; - Vector bindingSizes; - if (!GatherBindingPayloads(program, bindingData, bindingSizes)) { - MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: cannot gather UBO payloads"); - return false; - } - - static const Uint8 kFallbackData[16] = {}; - MOBILEGL_ASSERT(m_textureManager != nullptr, "BindProgramUniformBuffers: texture manager is null"); - MOBILEGL_ASSERT(m_samplerManager != nullptr, "BindProgramUniformBuffers: sampler manager is null"); - - Vector writes; - writes.reserve(m_maxBindings); - Vector bufferInfos; - Vector imageInfos; - Vector dynamicOffsets; - bufferInfos.reserve(m_maxBindings); - imageInfos.reserve(m_maxBindings); - dynamicOffsets.reserve(layout->dynamicBindings.size()); - - for (Uint32 binding = 0; binding < m_maxBindings; ++binding) { - const auto kind = layout->bindingKinds[binding]; - if (kind == BindingKind::None) { - continue; - } - - VkWriteDescriptorSet write{}; - write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; - write.dstSet = descriptorSet; - write.dstBinding = binding; - write.dstArrayElement = 0; - write.descriptorCount = 1; - - if (kind == BindingKind::UniformBufferDynamic) { - const void* payload = bindingData[binding]; - VkDeviceSize payloadSize = bindingSizes[binding]; - if (payload == nullptr || payloadSize == 0) { - if (layout->globalUboBinding == static_cast(binding)) { - const void* globalUboData = program.GetUBOData(); - const VkDeviceSize globalUboSize = static_cast(program.GetUBOSize()); - if (globalUboData != nullptr && globalUboSize > 0) { - payload = globalUboData; - payloadSize = globalUboSize; - } - } - if (payload == nullptr || payloadSize == 0) { - payload = kFallbackData; - payloadSize = sizeof(kFallbackData); - } - } - - VkDeviceSize payloadOffset = 0; - if (!AllocateUploadRegion(frame, payloadSize, payloadOffset)) { - MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: frame upload buffer exhausted"); - return false; - } - if (!frame.uploadBuffer.Upload(payload, payloadSize, payloadOffset)) { - MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: UBO upload failed on binding %u", - binding); - return false; - } - - VkDescriptorBufferInfo bufferInfo{}; - bufferInfo.buffer = frame.uploadBuffer.GetHandle(); - bufferInfo.offset = 0; - bufferInfo.range = payloadSize; - bufferInfos.push_back(bufferInfo); - - write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; - write.pBufferInfo = &bufferInfos.back(); - writes.push_back(write); - dynamicOffsets.push_back(static_cast(payloadOffset)); - } else { - VkDescriptorImageInfo imageInfo{}; - Bool hasImage = false; - if (samplerBindingOverride != nullptr && - samplerBindingOverride->binding == binding && - samplerBindingOverride->texture != nullptr && - samplerBindingOverride->sampler != nullptr) { - hasImage = ResolveSamplerDescriptorOverride(*samplerBindingOverride, imageInfo); - } else { - hasImage = ResolveSamplerDescriptor(commandBuffer, program, *layout, binding, imageInfo); - } - if (!hasImage) { - MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: sampler binding %u has no valid texture descriptor", - binding); - return false; - } - if (imageInfo.sampler == VK_NULL_HANDLE || imageInfo.imageView == VK_NULL_HANDLE) { - MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: sampler binding %u has null sampler or imageView", - binding); - return false; - } - imageInfos.push_back(imageInfo); - write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; - write.pImageInfo = &imageInfos.back(); - writes.push_back(write); - } - } - - if (!writes.empty()) { - vkUpdateDescriptorSets(m_device, static_cast(writes.size()), writes.data(), 0, nullptr); - } - - vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, layout->pipelineLayout, 0, 1, &descriptorSet, - static_cast(dynamicOffsets.size()), dynamicOffsets.data()); - return true; - } - - void UniformDescriptorBinder::DestroyProgramLayouts() { - for (auto& [_, layout] : m_programLayouts) { - if (layout.pipelineLayout != VK_NULL_HANDLE) { - vkDestroyPipelineLayout(m_device, layout.pipelineLayout, nullptr); - layout.pipelineLayout = VK_NULL_HANDLE; - } - if (layout.descriptorSetLayout != VK_NULL_HANDLE) { - vkDestroyDescriptorSetLayout(m_device, layout.descriptorSetLayout, nullptr); - layout.descriptorSetLayout = VK_NULL_HANDLE; - } - } - m_programLayouts.clear(); - } -} // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp new file mode 100644 index 00000000..7f16d652 --- /dev/null +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp @@ -0,0 +1,556 @@ +// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.cpp +// Copyright (c) 2025-2026 MobileGL-Dev +// Licensed under the GNU Lesser General Public License v3.0: +// https://www.gnu.org/licenses/gpl-3.0.txt +// https://www.gnu.org/licenses/lgpl-3.0.txt +// SPDX-License-Identifier: LGPL-3.0-only +// End of Source File Header + +#include "UniformManager.h" + +#include "MG_State/GLState/Core.h" +#include "MG_State/GLState/ProgramState/ProgramObject.h" +#include "MG_Util/Converters/MGToStr/FramebufferEnumConverter.h" +#include + +namespace MobileGL::MG_Backend::DirectVulkan { + static Bool FindFramebufferAttachmentForTexture(const MG_State::GLState::FramebufferObject& framebuffer, + const MG_State::GLState::ITextureObject& texture, + FramebufferAttachmentType& outAttachment, Int& outLevel) { + const auto& attachments = framebuffer.GetAllAttachmentObjects(); + for (SizeT i = 0; i < attachments.size(); ++i) { + const auto attachmentType = static_cast(i); + if (attachmentType == FramebufferAttachmentType::None) { + continue; + } + + const auto& attachment = attachments[i]; + if (!attachment.IsTexture()) { + continue; + } + + auto attachedTexture = attachment.GetTexture(); + if (attachedTexture && attachedTexture.get() == &texture) { + outAttachment = attachmentType; + outLevel = attachment.GetTextureLevel(); + return true; + } + } + + return false; + } + + static Bool IsValidSampledImageLayout(VkImageLayout layout) { + switch (layout) { + case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: + case VK_IMAGE_LAYOUT_GENERAL: + case VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL: + case VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL: + case VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL: + return true; + default: + return false; + } + } + + Bool UniformManager::Initialize(VkDevice device, VkBufferManager* bufferManager, + ProgramFactory* programFactory, + VkDeviceSize minUniformBufferOffsetAlignment, Uint32 frameCount, + Uint32 maxBindings, Uint32 setsPerFrame, + VkTextureManager* textureManager, VkSamplerManager* samplerManager) { + Shutdown(); + + MOBILEGL_ASSERT(device != VK_NULL_HANDLE, "UniformDescriptorBinder::Initialize requires valid VkDevice"); + MOBILEGL_ASSERT(bufferManager != nullptr, "UniformDescriptorBinder::Initialize requires valid buffer manager"); + MOBILEGL_ASSERT(programFactory != nullptr, + "UniformDescriptorBinder::Initialize requires valid program factory"); + MOBILEGL_ASSERT(frameCount > 0, "UniformDescriptorBinder::Initialize requires frameCount > 0"); + MOBILEGL_ASSERT(maxBindings > 0, "UniformDescriptorBinder::Initialize requires maxBindings > 0"); + MOBILEGL_ASSERT(setsPerFrame > 0, "UniformDescriptorBinder::Initialize requires setsPerFrame > 0"); + MOBILEGL_ASSERT(textureManager != nullptr, + "UniformDescriptorBinder::Initialize requires valid texture manager"); + MOBILEGL_ASSERT(samplerManager != nullptr, + "UniformDescriptorBinder::Initialize requires valid sampler manager"); + + m_device = device; + m_bufferManager = bufferManager; + m_programFactory = programFactory; + m_minDynamicOffsetAlignment = std::max(1, minUniformBufferOffsetAlignment); + m_frameCount = frameCount; + m_maxBindings = maxBindings; + m_setsPerFrame = setsPerFrame; + m_peakDescriptorSetsObserved = 0; + m_textureManager = textureManager; + m_samplerManager = samplerManager; + + m_frames.resize(m_frameCount); + for (Uint32 frameIndex = 0; frameIndex < m_frameCount; ++frameIndex) { + auto& frame = m_frames[frameIndex]; + frame.activeDescriptorPoolIndex = 0; + frame.allocatedSetsThisFrame = 0; + frame.peakAllocatedSetsThisFrame = 0; + frame.descriptorPools.clear(); + + VkDescriptorPool initialPool = VK_NULL_HANDLE; + if (!CreateDescriptorPool(m_setsPerFrame, initialPool)) { + MGLOG_E("UniformDescriptorBinder::Initialize failed: cannot create frame descriptor pool %u", + frameIndex); + Shutdown(); + return false; + } + frame.descriptorPools.push_back({initialPool, m_setsPerFrame, 0}); + MGLOG_D("UniformDescriptorBinder: frame %u descriptor pool created (maxSets=%u)", frameIndex, + m_setsPerFrame); + } + + return true; + } + + void UniformManager::Shutdown() { + for (auto& frame : m_frames) { + if (m_device != VK_NULL_HANDLE) { + for (auto& bucket : frame.descriptorPools) { + if (bucket.handle != VK_NULL_HANDLE) { + vkDestroyDescriptorPool(m_device, bucket.handle, nullptr); + bucket.handle = VK_NULL_HANDLE; + } + } + } + frame.descriptorPools.clear(); + frame.activeDescriptorPoolIndex = 0; + frame.allocatedSetsThisFrame = 0; + frame.peakAllocatedSetsThisFrame = 0; + } + m_frames.clear(); + + m_bufferManager = nullptr; + m_programFactory = nullptr; + m_device = VK_NULL_HANDLE; + m_minDynamicOffsetAlignment = 1; + m_frameCount = 0; + m_maxBindings = 0; + m_setsPerFrame = 0; + m_peakDescriptorSetsObserved = 0; + m_textureManager = nullptr; + m_samplerManager = nullptr; + } + + void UniformManager::BeginFrame(Uint32 frameIndex) { + MOBILEGL_ASSERT(frameIndex < m_frames.size(), "UniformDescriptorBinder::BeginFrame invalid frame index"); + auto& frame = m_frames[frameIndex]; + if (frame.peakAllocatedSetsThisFrame > m_peakDescriptorSetsObserved) { + m_peakDescriptorSetsObserved = frame.peakAllocatedSetsThisFrame; + MGLOG_D( + "UniformDescriptorBinder: new descriptor set peak observed=%u (base setsPerFrame=%u, frame=%u, pools=%zu)", + m_peakDescriptorSetsObserved, m_setsPerFrame, frameIndex, frame.descriptorPools.size()); + } + frame.activeDescriptorPoolIndex = 0; + frame.allocatedSetsThisFrame = 0; + frame.peakAllocatedSetsThisFrame = 0; + for (auto& bucket : frame.descriptorPools) { + bucket.allocatedSets = 0; + if (bucket.handle == VK_NULL_HANDLE) { + continue; + } + VK_VERIFY(vkResetDescriptorPool(m_device, bucket.handle, 0), + "UniformDescriptorBinder::BeginFrame, vkResetDescriptorPool"); + } + } + + Bool UniformManager::ResolveSamplerDescriptor(VkCommandBuffer commandBuffer, + const MG_State::GLState::ProgramObject& program, + const ProgramFactory::VkProgramObject& programObj, + Uint32 binding, VkDescriptorImageInfo& outImageInfo) const { + (void)commandBuffer; + MOBILEGL_ASSERT(m_textureManager != nullptr, "ResolveSamplerDescriptor: texture manager is null"); + MOBILEGL_ASSERT(m_samplerManager != nullptr, "ResolveSamplerDescriptor: sampler manager is null"); + SharedPtr texture; + if (!ResolveSamplerTexture(program, programObj, binding, texture)) { + return false; + } + + const Int location = programObj.samplerUniformLocationByBinding[binding]; + const Int unit = program.GetUniformSamplerOrImageUnitIndex(static_cast(location)); + auto& textureUnit = MG_State::pGLContext->GetTextureUnitObject(unit); + const auto samplerOverride = textureUnit.GetSamplerObject(); + if (!texture) { + return false; + } + + const MG_State::GLState::SamplerObject* samplerToUse = + samplerOverride ? samplerOverride.get() : texture->GetSamplerObject().get(); + if (!samplerToUse) { + return false; + } + VkTextureManager::TextureResource* resource = m_textureManager->SyncTextureAndGetDescriptor(*texture); + if (resource == nullptr) { + return false; + } + if (!IsValidSampledImageLayout(resource->layout)) { + auto drawFbo = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject(); + FramebufferAttachmentType attachmentType = FramebufferAttachmentType::None; + Int attachmentLevel = 0; + if (drawFbo && + FindFramebufferAttachmentForTexture(*drawFbo, *texture, attachmentType, attachmentLevel)) { + MOBILEGL_ASSERT(false, + "ResolveSamplerDescriptor: framebuffer feedback loop detected: textureId=%d is bound " + "for sampling at binding=%u, but is also attached to drawFbo=%u as %s (level=%d, " + "trackedLayout=%d)", + texture->GetExternalIndex(), binding, drawFbo->GetExternalIndex(), + MG_Util::ConvertFramebufferAttachmentTypeToString(attachmentType).c_str(), + attachmentLevel, static_cast(resource->layout)); + } + + MOBILEGL_ASSERT(false, + "ResolveSamplerDescriptor: invalid sampled image layout=%d for textureId=%d, binding=%u", + static_cast(resource->layout), texture->GetExternalIndex(), binding); + return false; + } + outImageInfo = { + .sampler = m_samplerManager->GetOrCreateSampler(*samplerToUse), + .imageView = resource->fullView, + .imageLayout = resource->layout, + }; + return outImageInfo.sampler != VK_NULL_HANDLE; + } + + Bool UniformManager::ResolveSamplerDescriptorOverride( + const SamplerBindingOverride& samplerBindingOverride, VkDescriptorImageInfo& outImageInfo) const { + MOBILEGL_ASSERT(m_textureManager != nullptr, "ResolveSamplerDescriptorOverride: texture manager is null"); + MOBILEGL_ASSERT(m_samplerManager != nullptr, "ResolveSamplerDescriptorOverride: sampler manager is null"); + if (samplerBindingOverride.texture == nullptr || samplerBindingOverride.sampler == nullptr) { + return false; + } + + auto* resource = m_textureManager->SyncTextureAndGetDescriptor(*samplerBindingOverride.texture); + if (resource == nullptr || !IsValidSampledImageLayout(resource->layout)) { + return false; + } + + outImageInfo = { + .sampler = m_samplerManager->GetOrCreateSampler(*samplerBindingOverride.sampler), + .imageView = resource->fullView, + .imageLayout = resource->layout, + }; + return outImageInfo.sampler != VK_NULL_HANDLE; + } + + Bool UniformManager::ResolveSamplerTexture(const MG_State::GLState::ProgramObject& program, + const ProgramFactory::VkProgramObject& programObj, Uint32 binding, + SharedPtr& outTexture) const { + outTexture.reset(); + if (!MG_State::pGLContext || binding >= programObj.samplerUniformLocationByBinding.size()) { + return false; + } + + const Int location = programObj.samplerUniformLocationByBinding[binding]; + if (location < 0) { + return false; + } + + const Int unit = program.GetUniformSamplerOrImageUnitIndex(static_cast(location)); + if (unit < 0) { + return false; + } + + auto& textureUnit = MG_State::pGLContext->GetTextureUnitObject(unit); + const TextureTarget preferredTarget = programObj.samplerTextureTargetByBinding[binding]; + outTexture = textureUnit.GetBindingSlot(preferredTarget).GetBoundObject(); + return outTexture != nullptr; + } + + Bool UniformManager::CollectSampledTextures(const MG_State::GLState::ProgramObject& program, + const ProgramFactory::VkProgramObject& programObj, + Vector& outTextures) { + outTextures.clear(); + + const Uint32 bindingCount = + std::min(m_maxBindings, static_cast(programObj.bindingKinds.size())); + for (Uint32 binding = 0; binding < bindingCount; ++binding) { + if (programObj.bindingKinds[binding] != ProgramFactory::DescriptorBindingKind::CombinedImageSampler) { + continue; + } + + SharedPtr texture; + if (!ResolveSamplerTexture(program, programObj, binding, texture) || !texture) { + continue; + } + + auto found = std::find(outTextures.begin(), outTextures.end(), texture.get()); + if (found == outTextures.end()) { + outTextures.push_back(texture.get()); + } + } + return true; + } + + Bool UniformManager::GatherBindingPayloads(const MG_State::GLState::ProgramObject& program, + Vector& outData, + Vector& outSizes) const { + outData.assign(m_maxBindings, nullptr); + outSizes.assign(m_maxBindings, 0); + + const Uint32 activeUniformBlockCount = static_cast(program.GetActiveUniformBlocksCount()); + const Uint32 uniformBindingPointCount = + static_cast(MG_State::pGLContext->GetBufferBindingPointCount(BufferTarget::Uniform)); + + for (Uint32 blockIndex = 0; blockIndex < activeUniformBlockCount; ++blockIndex) { + const Uint32 binding = program.GetUniformBlockBinding(blockIndex); + if (binding >= m_maxBindings) { + continue; + } + + VkDeviceSize blockSize = static_cast(program.GetUBOSizeAt(blockIndex)); + if (blockSize == 0) { + continue; + } + + if (binding >= uniformBindingPointCount) { + continue; + } + auto& bindingPoint = MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::Uniform, binding); + const auto bufferObject = bindingPoint.GetBoundObject(); + if (!bufferObject) { + continue; + } + + const auto bufferData = bufferObject->GetDataReadOnly(); + if (!bufferData || bufferData->empty()) { + continue; + } + + const auto range = bindingPoint.GetRange(); + const VkDeviceSize bufferSize = static_cast(bufferObject->GetSize()); + VkDeviceSize rangeStart = static_cast(range.start); + VkDeviceSize rangeEnd = static_cast(range.end); + + if (rangeStart >= bufferSize) { + continue; + } + if (rangeEnd <= rangeStart || rangeEnd > bufferSize) { + rangeEnd = bufferSize; + } + + VkDeviceSize available = rangeEnd - rangeStart; + if (available == 0) { + continue; + } + + outData[binding] = bufferData->data() + static_cast(rangeStart); + outSizes[binding] = std::min(blockSize, available); + } + + return true; + } + + Bool UniformManager::CreateDescriptorPool(Uint32 maxSets, VkDescriptorPool& outPool) const { + outPool = VK_NULL_HANDLE; + if (m_device == VK_NULL_HANDLE || maxSets == 0 || m_maxBindings == 0) { + return false; + } + + const Uint64 descriptorCount64 = static_cast(maxSets) * static_cast(m_maxBindings); + if (descriptorCount64 > static_cast(std::numeric_limits::max())) { + MGLOG_E("UniformDescriptorBinder::CreateDescriptorPool failed: descriptorCount overflow"); + return false; + } + + const Uint32 descriptorCount = static_cast(descriptorCount64); + VkDescriptorPoolSize poolSizes[2]{}; + poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; + poolSizes[0].descriptorCount = descriptorCount; + poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + poolSizes[1].descriptorCount = descriptorCount; + + VkDescriptorPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + poolInfo.maxSets = maxSets; + poolInfo.poolSizeCount = static_cast(std::size(poolSizes)); + poolInfo.pPoolSizes = poolSizes; + + const VkResult result = vkCreateDescriptorPool(m_device, &poolInfo, nullptr, &outPool); + if (result != VK_SUCCESS) { + MGLOG_E("UniformDescriptorBinder::CreateDescriptorPool failed: vkCreateDescriptorPool returned %d", + result); + return false; + } + return true; + } + + Bool UniformManager::GrowFrameDescriptorPool(FrameResources& frame, Uint32 frameIndex) { + if (frame.descriptorPools.empty()) { + return false; + } + + const auto& currentBucket = frame.descriptorPools[frame.activeDescriptorPoolIndex]; + const Uint32 currentMaxSets = std::max(1, currentBucket.maxSets); + const Uint32 grownMaxSets = currentMaxSets <= (std::numeric_limits::max() / 2) ? (currentMaxSets * 2) + : currentMaxSets; + + VkDescriptorPool grownPool = VK_NULL_HANDLE; + if (!CreateDescriptorPool(grownMaxSets, grownPool)) { + MGLOG_E("UniformDescriptorBinder::GrowFrameDescriptorPool failed: cannot create grown pool (%u -> %u sets)", + currentMaxSets, grownMaxSets); + return false; + } + + frame.descriptorPools.push_back({grownPool, grownMaxSets, 0}); + frame.activeDescriptorPoolIndex = static_cast(frame.descriptorPools.size() - 1); + MGLOG_D( + "UniformDescriptorBinder: frame %u descriptor pool exhausted, grew pool (%u -> %u sets), poolCount=%zu", + frameIndex, currentMaxSets, grownMaxSets, frame.descriptorPools.size()); + return true; + } + + VkResult UniformManager::AllocateDescriptorSetsFromActivePool(Uint32 frameIndex, const ProgramFactory::VkProgramObject& programObj, VkDescriptorSet& outDescriptorSet) { + auto& frame = m_frames[frameIndex]; + auto& bucket = frame.descriptorPools[frame.activeDescriptorPoolIndex]; + VkDescriptorSetAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorSetCount = 1; + allocInfo.pSetLayouts = &programObj.descriptorSetLayout; + + allocInfo.descriptorPool = bucket.handle; + VkResult result = vkAllocateDescriptorSets(m_device, &allocInfo, &outDescriptorSet); + if (result == VK_SUCCESS) { + ++bucket.allocatedSets; + ++frame.allocatedSetsThisFrame; + frame.peakAllocatedSetsThisFrame = + std::max(frame.peakAllocatedSetsThisFrame, frame.allocatedSetsThisFrame); + } + return result; + } + + Bool UniformManager::BindProgramUniformBuffers(VkCommandBuffer commandBuffer, + const MG_State::GLState::ProgramObject& program, + const ProgramFactory::VkProgramObject& programObj, + Uint32 frameIndex, + const SamplerBindingOverride* samplerBindingOverride) { + auto& frame = m_frames[frameIndex]; + if (frame.descriptorPools.empty()) { + MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: frame descriptor pools are invalid"); + return false; + } + if (frame.activeDescriptorPoolIndex >= frame.descriptorPools.size()) { + frame.activeDescriptorPoolIndex = 0; + } + + VkDescriptorSet descriptorSet = VK_NULL_HANDLE; + VkResult allocResult = AllocateDescriptorSetsFromActivePool(frameIndex, programObj, descriptorSet); + if (allocResult == VK_ERROR_OUT_OF_POOL_MEMORY || allocResult == VK_ERROR_FRAGMENTED_POOL) { + if (!GrowFrameDescriptorPool(frame, frameIndex)) { + MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: descriptor pool growth failed"); + return false; + } + allocResult = AllocateDescriptorSetsFromActivePool(frameIndex, programObj, descriptorSet); + } + if (allocResult != VK_SUCCESS || descriptorSet == VK_NULL_HANDLE) { + MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: vkAllocateDescriptorSets returned %d", + allocResult); + return false; + } + + Vector bindingData; + Vector bindingSizes; + if (!GatherBindingPayloads(program, bindingData, bindingSizes)) { + MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: cannot gather UBO payloads"); + return false; + } + + MOBILEGL_ASSERT(m_textureManager != nullptr, "BindProgramUniformBuffers: texture manager is null"); + MOBILEGL_ASSERT(m_samplerManager != nullptr, "BindProgramUniformBuffers: sampler manager is null"); + MOBILEGL_ASSERT(m_bufferManager != nullptr, "BindProgramUniformBuffers: buffer manager is null"); + + Vector writes; + writes.reserve(m_maxBindings); + Vector bufferInfos; + Vector imageInfos; + Vector dynamicOffsets; + bufferInfos.reserve(m_maxBindings); + imageInfos.reserve(m_maxBindings); + dynamicOffsets.reserve(programObj.dynamicBindings.size()); + + const Uint32 bindingCount = + std::min(m_maxBindings, static_cast(programObj.bindingKinds.size())); + for (Uint32 binding = 0; binding < bindingCount; ++binding) { + const auto kind = programObj.bindingKinds[binding]; + if (kind == ProgramFactory::DescriptorBindingKind::None) { + continue; + } + + VkWriteDescriptorSet write{}; + write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write.dstSet = descriptorSet; + write.dstBinding = binding; + write.dstArrayElement = 0; + write.descriptorCount = 1; + + if (kind == ProgramFactory::DescriptorBindingKind::UniformBufferDynamic) { + const void* payload = bindingData[binding]; + VkDeviceSize payloadSize = bindingSizes[binding]; + if (payload == nullptr || payloadSize == 0) { + if (programObj.globalUboBinding == static_cast(binding)) { + const void* globalUboData = program.GetUBOData(); + const VkDeviceSize globalUboSize = static_cast(program.GetUBOSize()); + if (globalUboData != nullptr && globalUboSize > 0) { + payload = globalUboData; + payloadSize = globalUboSize; + } + } + } + + BufferSlice slice{}; + if (!m_bufferManager->UploadTransient(BufferKind::Uniform, frameIndex, payload, payloadSize, + m_minDynamicOffsetAlignment, slice)) { + MOBILEGL_ASSERT(false, "UniformDescriptorBinder::BindProgramUniformBuffers failed: UBO upload failed on binding %u", + binding); + return false; + } + + VkDescriptorBufferInfo bufferInfo{}; + bufferInfo.buffer = slice.buffer; + bufferInfo.offset = 0; + bufferInfo.range = payloadSize; + bufferInfos.push_back(bufferInfo); + + write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; + write.pBufferInfo = &bufferInfos.back(); + writes.push_back(write); + dynamicOffsets.push_back(static_cast(slice.offset)); + } else { + VkDescriptorImageInfo imageInfo{}; + Bool hasImage = false; + if (samplerBindingOverride != nullptr && + samplerBindingOverride->binding == binding && + samplerBindingOverride->texture != nullptr && + samplerBindingOverride->sampler != nullptr) { + hasImage = ResolveSamplerDescriptorOverride(*samplerBindingOverride, imageInfo); + } else { + hasImage = ResolveSamplerDescriptor(commandBuffer, program, programObj, binding, imageInfo); + } + if (!hasImage) { + MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: sampler binding %u has no valid texture descriptor", + binding); + return false; + } + if (imageInfo.sampler == VK_NULL_HANDLE || imageInfo.imageView == VK_NULL_HANDLE) { + MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: sampler binding %u has null sampler or imageView", + binding); + return false; + } + imageInfos.push_back(imageInfo); + write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + write.pImageInfo = &imageInfos.back(); + writes.push_back(write); + } + } + + if (!writes.empty()) { + vkUpdateDescriptorSets(m_device, static_cast(writes.size()), writes.data(), 0, nullptr); + } + + vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, programObj.pipelineLayout, 0, 1, + &descriptorSet, static_cast(dynamicOffsets.size()), dynamicOffsets.data()); + return true; + } +} // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h similarity index 53% rename from MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.h rename to MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h index 64ff2310..36d43997 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h @@ -8,12 +8,12 @@ #pragma once -#include "VkBufferObject.h" +#include "ProgramFactory.h" +#include "VkBufferManager.h" #include "VkSamplerManager.h" #include "VkTextureManager.h" #include "../VkIncludes.h" #include -#include namespace MobileGL::MG_State::GLState { class ITextureObject; @@ -22,35 +22,30 @@ namespace MobileGL::MG_State::GLState { } namespace MobileGL::MG_Backend::DirectVulkan { - class UniformDescriptorBinder { + class UniformManager { public: - enum class BindingKind : Uint8 { - None = 0, - UniformBufferDynamic, - CombinedImageSampler - }; - struct SamplerBindingOverride { Uint32 binding = 0; MG_State::GLState::ITextureObject* texture = nullptr; const MG_State::GLState::SamplerObject* sampler = nullptr; }; - Bool Initialize(VkDevice device, VmaAllocator allocator, VkDeviceSize minUniformBufferOffsetAlignment, - Uint32 frameCount, Uint32 maxBindings = 16, Uint32 setsPerFrame = 64, - VkDeviceSize perFrameUploadBytes = 4 * 1024 * 1024, + Bool Initialize(VkDevice device, VkBufferManager* bufferManager, + ProgramFactory* programFactory, + VkDeviceSize minUniformBufferOffsetAlignment, Uint32 frameCount, + Uint32 maxBindings = 16, Uint32 setsPerFrame = 64, VkTextureManager* textureManager = nullptr, VkSamplerManager* samplerManager = nullptr); void Shutdown(); void BeginFrame(Uint32 frameIndex); - VkPipelineLayout GetOrCreatePipelineLayout(const MG_State::GLState::ProgramObject& program); Bool CollectSampledTextures(const MG_State::GLState::ProgramObject& program, + const ProgramFactory::VkProgramObject& programObj, Vector& outTextures); Bool BindProgramUniformBuffers(VkCommandBuffer commandBuffer, - const MG_State::GLState::ProgramObject& program, Uint32 frameIndex); - Bool BindProgramUniformBuffers(VkCommandBuffer commandBuffer, - const MG_State::GLState::ProgramObject& program, Uint32 frameIndex, - const SamplerBindingOverride* samplerBindingOverride); + const MG_State::GLState::ProgramObject& program, + const ProgramFactory::VkProgramObject& programObj, + Uint32 frameIndex, + const SamplerBindingOverride* samplerBindingOverride = nullptr); private: struct DescriptorPoolBucket { @@ -60,54 +55,33 @@ namespace MobileGL::MG_Backend::DirectVulkan { }; struct FrameResources { - VkBufferObject uploadBuffer; Vector descriptorPools; Uint32 activeDescriptorPoolIndex = 0; Uint32 allocatedSetsThisFrame = 0; Uint32 peakAllocatedSetsThisFrame = 0; - VkDeviceSize writeCursor = 0; }; - struct ProgramLayout { - Uint64 hash = 0; - VkDescriptorSetLayout descriptorSetLayout = VK_NULL_HANDLE; - VkPipelineLayout pipelineLayout = VK_NULL_HANDLE; - Vector bindingKinds; - Vector dynamicBindings; - Vector samplerUniformLocationByBinding; - Vector samplerTextureTargetByBinding; - Int globalUboBinding = -1; - }; - - static VkDeviceSize AlignUp(VkDeviceSize value, VkDeviceSize alignment); - static Uint64 ComputeProgramHash(const MG_State::GLState::ProgramObject& program); - static Bool IsSamplerUniformType(GLenum glType); - static TextureTarget UniformTypeToTextureTarget(GLenum glType); - Bool ReflectSamplerBindings(const MG_State::GLState::ProgramObject& program, ProgramLayout& layout) const; - Bool ReflectGlobalUboBinding(const MG_State::GLState::ProgramObject& program, ProgramLayout& layout) const; - Bool ResolveSamplerTexture(const MG_State::GLState::ProgramObject& program, const ProgramLayout& layout, - Uint32 binding, SharedPtr& outTexture) const; + Bool ResolveSamplerTexture(const MG_State::GLState::ProgramObject& program, + const ProgramFactory::VkProgramObject& programObj, Uint32 binding, + SharedPtr& outTexture) const; Bool ResolveSamplerDescriptor(VkCommandBuffer commandBuffer, const MG_State::GLState::ProgramObject& program, - const ProgramLayout& layout, Uint32 binding, + const ProgramFactory::VkProgramObject& programObj, Uint32 binding, VkDescriptorImageInfo& outImageInfo) const; Bool ResolveSamplerDescriptorOverride(const SamplerBindingOverride& samplerBindingOverride, VkDescriptorImageInfo& outImageInfo) const; - Bool ReflectBindingKinds(const MG_State::GLState::ProgramObject& program, Vector& outKinds) const; - ProgramLayout* GetOrCreateProgramLayout(const MG_State::GLState::ProgramObject& program); - Bool AllocateUploadRegion(FrameResources& frame, VkDeviceSize size, VkDeviceSize& outOffset); Bool GatherBindingPayloads(const MG_State::GLState::ProgramObject& program, Vector& outData, Vector& outSizes) const; Bool CreateDescriptorPool(Uint32 maxSets, VkDescriptorPool& outPool) const; Bool GrowFrameDescriptorPool(FrameResources& frame, Uint32 frameIndex); - void DestroyProgramLayouts(); + VkResult AllocateDescriptorSetsFromActivePool( + Uint32 frameIndex, const ProgramFactory::VkProgramObject& programObj, VkDescriptorSet& outDescriptorSet); VkDevice m_device = VK_NULL_HANDLE; - VmaAllocator m_allocator = nullptr; + VkBufferManager* m_bufferManager = nullptr; + ProgramFactory* m_programFactory = nullptr; Vector m_frames; - UnorderedMap m_programLayouts; VkDeviceSize m_minDynamicOffsetAlignment = 1; - VkDeviceSize m_perFrameUploadBytes = 0; Uint32 m_frameCount = 0; Uint32 m_maxBindings = 0; Uint32 m_setsPerFrame = 0; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp index a1268758..e8987524 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp @@ -148,65 +148,49 @@ namespace MobileGL::MG_Backend::DirectVulkan { case DataType::Int16: switch (size) { case 1: - return isInteger ? VK_FORMAT_R16_SINT - : (normalized ? VK_FORMAT_R16_SNORM : VK_FORMAT_R16_SSCALED); + return isInteger ? VK_FORMAT_R16_SINT : VK_FORMAT_R16_SNORM; case 2: - return isInteger ? VK_FORMAT_R16G16_SINT - : (normalized ? VK_FORMAT_R16G16_SNORM : VK_FORMAT_R16G16_SSCALED); + return isInteger ? VK_FORMAT_R16G16_SINT : VK_FORMAT_R16G16_SNORM; case 3: - return isInteger ? VK_FORMAT_R16G16B16_SINT - : (normalized ? VK_FORMAT_R16G16B16_SNORM : VK_FORMAT_R16G16B16_SSCALED); + return isInteger ? VK_FORMAT_R16G16B16_SINT : VK_FORMAT_R16G16B16_SNORM; case 4: - return isInteger ? VK_FORMAT_R16G16B16A16_SINT - : (normalized ? VK_FORMAT_R16G16B16A16_SNORM : VK_FORMAT_R16G16B16A16_SSCALED); + return isInteger ? VK_FORMAT_R16G16B16A16_SINT : VK_FORMAT_R16G16B16A16_SNORM; default: return VK_FORMAT_UNDEFINED; } case DataType::Uint16: switch (size) { case 1: - return isInteger ? VK_FORMAT_R16_UINT - : (normalized ? VK_FORMAT_R16_UNORM : VK_FORMAT_R16_USCALED); + return isInteger ? VK_FORMAT_R16_UINT : VK_FORMAT_R16_UNORM; case 2: - return isInteger ? VK_FORMAT_R16G16_UINT - : (normalized ? VK_FORMAT_R16G16_UNORM : VK_FORMAT_R16G16_USCALED); + return isInteger ? VK_FORMAT_R16G16_UINT : VK_FORMAT_R16G16_UNORM; case 3: - return isInteger ? VK_FORMAT_R16G16B16_UINT - : (normalized ? VK_FORMAT_R16G16B16_UNORM : VK_FORMAT_R16G16B16_USCALED); + return isInteger ? VK_FORMAT_R16G16B16_UINT : VK_FORMAT_R16G16B16_UNORM; case 4: - return isInteger ? VK_FORMAT_R16G16B16A16_UINT - : (normalized ? VK_FORMAT_R16G16B16A16_UNORM : VK_FORMAT_R16G16B16A16_USCALED); + return isInteger ? VK_FORMAT_R16G16B16A16_UINT : VK_FORMAT_R16G16B16A16_UNORM; default: return VK_FORMAT_UNDEFINED; } case DataType::Int8: switch (size) { case 1: - return isInteger ? VK_FORMAT_R8_SINT - : (normalized ? VK_FORMAT_R8_SNORM : VK_FORMAT_R8_SSCALED); + return isInteger ? VK_FORMAT_R8_SINT : VK_FORMAT_R8_SNORM; case 2: - return isInteger ? VK_FORMAT_R8G8_SINT - : (normalized ? VK_FORMAT_R8G8_SNORM : VK_FORMAT_R8G8_SSCALED); + return isInteger ? VK_FORMAT_R8G8_SINT : VK_FORMAT_R8G8_SNORM; case 3: - return isInteger ? VK_FORMAT_R8G8B8_SINT - : (normalized ? VK_FORMAT_R8G8B8_SNORM : VK_FORMAT_R8G8B8_SSCALED); + return isInteger ? VK_FORMAT_R8G8B8_SINT : VK_FORMAT_R8G8B8_SNORM; case 4: - return isInteger ? VK_FORMAT_R8G8B8A8_SINT - : (normalized ? VK_FORMAT_R8G8B8A8_SNORM : VK_FORMAT_R8G8B8A8_SSCALED); + return isInteger ? VK_FORMAT_R8G8B8A8_SINT : VK_FORMAT_R8G8B8A8_SNORM; default: return VK_FORMAT_UNDEFINED; } case DataType::Uint8: switch (size) { case 1: - return isInteger ? VK_FORMAT_R8_UINT - : (normalized ? VK_FORMAT_R8_UNORM : VK_FORMAT_R8_USCALED); + return isInteger ? VK_FORMAT_R8_UINT : VK_FORMAT_R8_UNORM; case 2: - return isInteger ? VK_FORMAT_R8G8_UINT - : (normalized ? VK_FORMAT_R8G8_UNORM : VK_FORMAT_R8G8_USCALED); + return isInteger ? VK_FORMAT_R8G8_UINT : VK_FORMAT_R8G8_UNORM; case 3: - return isInteger ? VK_FORMAT_R8G8B8_UINT - : (normalized ? VK_FORMAT_R8G8B8_UNORM : VK_FORMAT_R8G8B8_USCALED); + return isInteger ? VK_FORMAT_R8G8B8_UINT : VK_FORMAT_R8G8B8_UNORM; case 4: - return isInteger ? VK_FORMAT_R8G8B8A8_UINT - : (normalized ? VK_FORMAT_R8G8B8A8_UNORM : VK_FORMAT_R8G8B8A8_USCALED); + return isInteger ? VK_FORMAT_R8G8B8A8_UINT : VK_FORMAT_R8G8B8A8_UNORM; default: return VK_FORMAT_UNDEFINED; } default: diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp new file mode 100644 index 00000000..69f13c68 --- /dev/null +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp @@ -0,0 +1,246 @@ +// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp +// Copyright (c) 2025-2026 MobileGL-Dev +// Licensed under the GNU Lesser General Public License v3.0: +// https://www.gnu.org/licenses/gpl-3.0.txt +// https://www.gnu.org/licenses/lgpl-3.0.txt +// SPDX-License-Identifier: LGPL-3.0-only +// End of Source File Header + +#include "VkBufferManager.h" + +namespace MobileGL::MG_Backend::DirectVulkan { + namespace { + constexpr Uint32 kResidentBufferGCInterval = 60; + constexpr VmaAllocationCreateFlags kResidentBufferAllocationFlags = + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; + } // namespace + + Bool VkBufferManager::Initialize(const VkBufferManagerInitInfo& initInfo) { + Shutdown(); + + MOBILEGL_ASSERT(initInfo.allocator != nullptr, "VkBufferManager::Initialize requires valid allocator"); + MOBILEGL_ASSERT(initInfo.frameCount > 0, "VkBufferManager::Initialize requires non-zero frame count"); + + m_initInfo = initInfo; + m_deferredResidentReleases.resize(initInfo.frameCount); + m_currentFrameIndex = 0; + return InitializeTransientArenas(); + } + + void VkBufferManager::Shutdown() { + m_transientUploadArena.Shutdown(); + DestroyResidentBuffers(); + DestroyDeferredResidentReleases(); + m_initInfo = {}; + m_currentFrameIndex = 0; + m_residentGcTick = 0; + } + + Bool VkBufferManager::RecreateTransientArenas(Uint32 frameCount) { + MOBILEGL_ASSERT(m_initInfo.allocator != nullptr, "VkBufferManager::RecreateTransientArenas requires initialized manager"); + MOBILEGL_ASSERT(frameCount > 0, "VkBufferManager::RecreateTransientArenas requires non-zero frame count"); + + m_transientUploadArena.Shutdown(); + m_initInfo.frameCount = frameCount; + DestroyDeferredResidentReleases(); + m_deferredResidentReleases.resize(frameCount); + m_currentFrameIndex = 0; + return InitializeTransientArenas(); + } + + void VkBufferManager::BeginFrame(Uint32 frameIndex) { + MOBILEGL_ASSERT(frameIndex < m_deferredResidentReleases.size(), + "VkBufferManager::BeginFrame frame index out of range"); + m_currentFrameIndex = frameIndex; + CollectDeferredResidentReleases(frameIndex); + m_transientUploadArena.BeginFrame(frameIndex); + } + + Bool VkBufferManager::UploadTransient(BufferKind kind, Uint32 frameIndex, const void* data, + VkDeviceSize size, VkDeviceSize alignment, BufferSlice& outSlice) { + (void)kind; + return m_transientUploadArena.Upload(frameIndex, data, size, alignment, outSlice); + } + + Bool VkBufferManager::InitializeTransientArenas() { + return m_transientUploadArena.Initialize({ + .allocator = m_initInfo.allocator, + .frameCount = m_initInfo.frameCount, + .usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | + VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, + .memoryUsage = m_initInfo.transientMemoryUsage, + .allocationFlags = m_initInfo.transientAllocationFlags, + .minBufferSize = m_initInfo.minUploadBytes, + .persistentlyMapped = m_initInfo.transientPersistentMapping, + }); + } + + Bool VkBufferManager::SyncResidentBuffer(BufferKind kind, + const SharedPtr& bufferObject, + BufferSlice& outSlice) { + const VkBufferUsageFlags requiredUsage = GetVkBufferUsage(kind); + MOBILEGL_ASSERT(requiredUsage != 0, + "VkBufferManager::SyncResidentBuffer only supports resident vertex/index buffers"); + MOBILEGL_ASSERT(bufferObject != nullptr, "VkBufferManager::SyncResidentBuffer requires valid buffer object"); + CollectResidentGarbageIfNeeded(); + + const auto* bufferData = bufferObject->GetDataReadOnly().get(); + MOBILEGL_ASSERT(bufferData != nullptr, "VkBufferManager::SyncResidentBuffer requires frontend buffer data"); + + const VkDeviceSize bufferSize = static_cast(bufferObject->GetSize()); + if (bufferSize == 0) { + MGLOG_E("VkBufferManager::SyncResidentBuffer failed: buffer size is zero"); + return false; + } + + auto& entry = m_residentBuffers[bufferObject.get()]; + entry.aliveRef = bufferObject; + + const auto changeBits = bufferObject->GetChangeBits(); + const Bool needsRecreate = !entry.buffer.IsValid() || entry.size != bufferSize || + ((entry.usage & requiredUsage) != requiredUsage) || + (changeBits & BufferChangeBits::PreferReallocationBit); + if (needsRecreate) { + const VkBufferUsageFlags recreatedUsage = entry.usage | requiredUsage; + DeferResidentRelease(std::move(entry.buffer)); + const Bool created = entry.buffer.Create({ + .allocator = m_initInfo.allocator, + .size = bufferSize, + .usage = recreatedUsage, + .memoryUsage = VMA_MEMORY_USAGE_AUTO, + .allocationFlags = kResidentBufferAllocationFlags, + }); + if (!created || entry.buffer.Map() == nullptr) { + MGLOG_E("VkBufferManager::SyncResidentBuffer failed: unable to create resident buffer"); + entry.buffer.Destroy(); + entry.size = 0; + entry.usage = 0; + return false; + } + if (!entry.buffer.Upload(bufferData->data(), bufferSize, 0)) { + MGLOG_E("VkBufferManager::SyncResidentBuffer failed: initial upload failed"); + entry.buffer.Destroy(); + entry.size = 0; + entry.usage = 0; + return false; + } + entry.size = bufferSize; + entry.usage = recreatedUsage; + bufferObject->ClearDirty(); + outSlice = entry.buffer.GetSlice(0, bufferSize); + return true; + } + + if (changeBits & BufferChangeBits::DirtyBit) { + const auto& dirtyRanges = bufferObject->GetDirtyRanges(); + for (const auto& range : dirtyRanges) { + const VkDeviceSize rangeOffset = static_cast(range.start); + const VkDeviceSize rangeSize = static_cast(range.end - range.start); + if (rangeSize == 0) { + continue; + } + if (!entry.buffer.Upload(bufferData->data() + range.start, rangeSize, rangeOffset)) { + MGLOG_E("VkBufferManager::SyncResidentBuffer failed: dirty range upload failed"); + return false; + } + } + bufferObject->ClearDirty(); + } + + outSlice = entry.buffer.GetSlice(0, bufferSize); + return true; + } + + void VkBufferManager::DowngradeResidentBufferToTransient(const SharedPtr& bufferObject) { + if (bufferObject == nullptr) { + return; + } + + auto it = m_residentBuffers.find(bufferObject.get()); + if (it == m_residentBuffers.end()) { + return; + } + + DeferResidentRelease(std::move(it->second.buffer)); + m_residentBuffers.erase(it); + } + + void VkBufferManager::DeferResidentRelease(VkBufferObject&& buffer) { + if (!buffer.IsValid()) { + return; + } + + if (m_deferredResidentReleases.empty()) { + buffer.Destroy(); + return; + } + + MOBILEGL_ASSERT(m_currentFrameIndex < m_deferredResidentReleases.size(), + "VkBufferManager::DeferResidentRelease current frame index out of range"); + m_deferredResidentReleases[m_currentFrameIndex].push_back(std::move(buffer)); + } + + void VkBufferManager::CollectDeferredResidentReleases(Uint32 frameIndex) { + MOBILEGL_ASSERT(frameIndex < m_deferredResidentReleases.size(), + "VkBufferManager::CollectDeferredResidentReleases frame index out of range"); + m_deferredResidentReleases[frameIndex].clear(); + } + + VkBufferUsageFlags VkBufferManager::GetVkBufferUsage(BufferKind kind) { + switch (kind) { + case BufferKind::Vertex: + case BufferKind::Index: + // A GL buffer can be rebound between ARRAY_BUFFER and ELEMENT_ARRAY_BUFFER, + // and may even be used as both within the same draw setup. Keep resident + // vertex/index buffers compatible with both roles from the start so we + // never need to recreate a buffer after it has already been bound. + return VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT; + case BufferKind::Uniform: + default: + return 0; + } + } + + void VkBufferManager::CollectResidentGarbageIfNeeded() { + ++m_residentGcTick; + if (m_residentGcTick < kResidentBufferGCInterval) { + return; + } + CollectResidentGarbageNow(); + m_residentGcTick = 0; + } + + void VkBufferManager::CollectResidentGarbageNow() { + Vector staleBuffers; + staleBuffers.reserve(m_residentBuffers.size()); + + for (const auto& [rawBuffer, entry] : m_residentBuffers) { + if (entry.aliveRef.expired()) { + staleBuffers.push_back(rawBuffer); + } + } + + for (const auto* rawBuffer : staleBuffers) { + auto it = m_residentBuffers.find(const_cast(rawBuffer)); + if (it == m_residentBuffers.end()) { + continue; + } + DeferResidentRelease(std::move(it->second.buffer)); + m_residentBuffers.erase(it); + } + } + + void VkBufferManager::DestroyDeferredResidentReleases() { + for (auto& deferredReleases : m_deferredResidentReleases) { + deferredReleases.clear(); + } + m_deferredResidentReleases.clear(); + } + + void VkBufferManager::DestroyResidentBuffers() { + for (auto& [_, entry] : m_residentBuffers) { + entry.buffer.Destroy(); + } + m_residentBuffers.clear(); + } +} // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.h new file mode 100644 index 00000000..096cdb90 --- /dev/null +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.h @@ -0,0 +1,72 @@ +// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.h +// Copyright (c) 2025-2026 MobileGL-Dev +// Licensed under the GNU Lesser General Public License v3.0: +// https://www.gnu.org/licenses/gpl-3.0.txt +// https://www.gnu.org/licenses/lgpl-3.0.txt +// SPDX-License-Identifier: LGPL-3.0-only +// End of Source File Header + +#pragma once + +#include "BufferArena.h" +#include "MG_State/GLState/BufferState/BufferObject.h" +#include "../VkIncludes.h" +#include +#include + +namespace MobileGL::MG_Backend::DirectVulkan { + enum class BufferKind : Uint8 { + Vertex, + Index, + Uniform, + }; + + struct VkBufferManagerInitInfo { + VmaAllocator allocator = nullptr; + Uint32 frameCount = 0; + VkDeviceSize minUploadBytes = 4 * 1024 * 1024; + VmaMemoryUsage transientMemoryUsage = VMA_MEMORY_USAGE_AUTO; + VmaAllocationCreateFlags transientAllocationFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; + Bool transientPersistentMapping = false; + }; + + class VkBufferManager { + public: + Bool Initialize(const VkBufferManagerInitInfo& initInfo); + void Shutdown(); + + // Recreate all per-frame transient arenas + Bool RecreateTransientArenas(Uint32 frameCount); + void BeginFrame(Uint32 frameIndex); + + Bool UploadTransient(BufferKind kind, Uint32 frameIndex, const void* data, VkDeviceSize size, + VkDeviceSize alignment, BufferSlice& outSlice); + Bool SyncResidentBuffer(BufferKind kind, const SharedPtr& bufferObject, + BufferSlice& outSlice); + void DowngradeResidentBufferToTransient(const SharedPtr& bufferObject); + + private: + struct ResidentBufferEntry { + WeakPtr aliveRef; + VkBufferObject buffer; + VkDeviceSize size = 0; + VkBufferUsageFlags usage = 0; + }; + + Bool InitializeTransientArenas(); + static VkBufferUsageFlags GetVkBufferUsage(BufferKind kind); + void DeferResidentRelease(VkBufferObject&& buffer); + void CollectDeferredResidentReleases(Uint32 frameIndex); + void CollectResidentGarbageIfNeeded(); + void CollectResidentGarbageNow(); + void DestroyDeferredResidentReleases(); + void DestroyResidentBuffers(); + + VkBufferManagerInitInfo m_initInfo{}; + BufferArena m_transientUploadArena; + UnorderedMap m_residentBuffers; + Vector> m_deferredResidentReleases; + Uint32 m_currentFrameIndex = 0; + Uint32 m_residentGcTick = 0; + }; +} // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferObject.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferObject.cpp index dbcf7b19..07818111 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferObject.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferObject.cpp @@ -13,11 +13,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_allocator = other.m_allocator; m_buffer = other.m_buffer; m_allocation = other.m_allocation; + m_mappedData = other.m_mappedData; m_size = other.m_size; other.m_allocator = nullptr; other.m_buffer = VK_NULL_HANDLE; other.m_allocation = nullptr; + other.m_mappedData = nullptr; other.m_size = 0; } @@ -31,11 +33,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_allocator = other.m_allocator; m_buffer = other.m_buffer; m_allocation = other.m_allocation; + m_mappedData = other.m_mappedData; m_size = other.m_size; other.m_allocator = nullptr; other.m_buffer = VK_NULL_HANDLE; other.m_allocation = nullptr; + other.m_mappedData = nullptr; other.m_size = 0; return *this; } @@ -44,6 +48,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { Destroy(); } + Bool VkBufferObject::Create(const VkBufferObjectDesc& desc) { + return Create(desc.allocator, desc.size, desc.usage, desc.memoryUsage, desc.allocationFlags); + } + Bool VkBufferObject::Create(VmaAllocator allocator, VkDeviceSize size, VkBufferUsageFlags usage, VmaMemoryUsage memoryUsage, VmaAllocationCreateFlags allocationFlags) { MOBILEGL_ASSERT(allocator != nullptr, "VkBufferObject::Create requires valid VMA allocator"); @@ -78,6 +86,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { } void VkBufferObject::Destroy() { + Unmap(); if (m_allocator != nullptr && m_buffer != VK_NULL_HANDLE && m_allocation != nullptr) { vmaDestroyBuffer(m_allocator, m_buffer, m_allocation); } @@ -87,6 +96,33 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_size = 0; } + void* VkBufferObject::Map() { + MOBILEGL_ASSERT(IsValid(), "VkBufferObject::Map called on invalid buffer"); + + if (m_mappedData != nullptr) { + return m_mappedData; + } + + const VkResult mapResult = vmaMapMemory(m_allocator, m_allocation, &m_mappedData); + if (mapResult != VK_SUCCESS || m_mappedData == nullptr) { + MGLOG_E("VkBufferObject::Map failed: vmaMapMemory returned %d", mapResult); + m_mappedData = nullptr; + return nullptr; + } + + return m_mappedData; + } + + void VkBufferObject::Unmap() { + if (!IsValid() || m_mappedData == nullptr) { + m_mappedData = nullptr; + return; + } + + vmaUnmapMemory(m_allocator, m_allocation); + m_mappedData = nullptr; + } + Bool VkBufferObject::Upload(const void* data, VkDeviceSize size, VkDeviceSize offset) { MOBILEGL_ASSERT(IsValid(), "VkBufferObject::Upload called on invalid buffer"); MOBILEGL_ASSERT(data != nullptr || size == 0, "VkBufferObject::Upload data pointer is null"); @@ -96,15 +132,30 @@ namespace MobileGL::MG_Backend::DirectVulkan { return true; } - void* mapped = nullptr; - const VkResult mapResult = vmaMapMemory(m_allocator, m_allocation, &mapped); - if (mapResult != VK_SUCCESS || mapped == nullptr) { - MGLOG_E("VkBufferObject::Upload failed: vmaMapMemory returned %d", mapResult); + const Bool wasMapped = IsMapped(); + void* mapped = wasMapped ? m_mappedData : Map(); + if (mapped == nullptr) { + MGLOG_E("VkBufferObject::Upload failed: unable to map buffer"); return false; } Memcpy(static_cast(mapped) + offset, data, static_cast(size)); - vmaUnmapMemory(m_allocator, m_allocation); + if (!wasMapped) { + Unmap(); + } return true; } + + BufferSlice VkBufferObject::GetSlice(VkDeviceSize offset, VkDeviceSize size) const { + MOBILEGL_ASSERT(offset <= m_size, "VkBufferObject::GetSlice offset out of range"); + const VkDeviceSize resolvedSize = (size == VK_WHOLE_SIZE) ? (m_size - offset) : size; + MOBILEGL_ASSERT(offset + resolvedSize <= m_size, "VkBufferObject::GetSlice range out of bounds"); + + BufferSlice slice{}; + slice.buffer = m_buffer; + slice.offset = offset; + slice.size = resolvedSize; + slice.mapped = (m_mappedData != nullptr) ? static_cast(m_mappedData) + offset : nullptr; + return slice; + } } // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferObject.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferObject.h index 48a06152..80be2981 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferObject.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferObject.h @@ -8,11 +8,20 @@ #pragma once +#include "BufferSlice.h" #include "../VkIncludes.h" #include #include namespace MobileGL::MG_Backend::DirectVulkan { + struct VkBufferObjectDesc { + VmaAllocator allocator = nullptr; + VkDeviceSize size = 0; + VkBufferUsageFlags usage = 0; + VmaMemoryUsage memoryUsage = VMA_MEMORY_USAGE_AUTO; + VmaAllocationCreateFlags allocationFlags = 0; + }; + class VkBufferObject { public: VkBufferObject() = default; @@ -23,20 +32,27 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkBufferObject(VkBufferObject&& other) noexcept; VkBufferObject& operator=(VkBufferObject&& other) noexcept; + Bool Create(const VkBufferObjectDesc& desc); Bool Create(VmaAllocator allocator, VkDeviceSize size, VkBufferUsageFlags usage, VmaMemoryUsage memoryUsage, VmaAllocationCreateFlags allocationFlags = 0); void Destroy(); + void* Map(); + void Unmap(); Bool Upload(const void* data, VkDeviceSize size, VkDeviceSize offset = 0); VkBuffer GetHandle() const { return m_buffer; } VkDeviceSize GetSize() const { return m_size; } + BufferSlice GetSlice(VkDeviceSize offset = 0, VkDeviceSize size = VK_WHOLE_SIZE) const; + void* GetMappedData() const { return m_mappedData; } + Bool IsMapped() const { return m_mappedData != nullptr; } Bool IsValid() const { return m_allocator != nullptr && m_buffer != VK_NULL_HANDLE && m_allocation != nullptr; } private: VmaAllocator m_allocator = nullptr; VkBuffer m_buffer = VK_NULL_HANDLE; VmaAllocation m_allocation = nullptr; + void* m_mappedData = nullptr; VkDeviceSize m_size = 0; }; } // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkClearManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkClearManager.cpp index fe6dddc9..83e5af54 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkClearManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkClearManager.cpp @@ -8,6 +8,9 @@ #include "VkClearManager.h" +#include "MG_Util/Converters/MGToStr/FramebufferEnumConverter.h" +#include "MG_Util/Converters/MGToStr/TextureEnumConverter.h" + namespace MobileGL::MG_Backend::DirectVulkan { Bool VkClearManager::Initialize() { return true; @@ -31,6 +34,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { .color = clearPayload.color, .attachmentType = drawbuf }, drawFbo.GetAttachment(drawbuf).GetTexture()); + MGLOG_D("%s: %s (texture %d) - color = (%.2f, %.2f, %.2f, %.2f)", __func__, + MG_Util::ConvertFramebufferAttachmentTypeToString(drawbuf).c_str(), + drawFbo.GetAttachment(drawbuf).GetTexture()->GetExternalIndex(), + clearPayload.color[0], clearPayload.color[1], clearPayload.color[2], clearPayload.color[3]); } } @@ -40,6 +47,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { .depth = clearPayload.depth, .attachmentType = FramebufferAttachmentType::Depth, }, drawFbo.GetAttachment(FramebufferAttachmentType::Depth).GetTexture()); + MGLOG_D("%s: Depth (texture %d) - depth = (%.2f)", __func__, + drawFbo.GetAttachment(FramebufferAttachmentType::Depth).GetTexture()->GetExternalIndex(), clearPayload.depth); } if (mask & GL_STENCIL_BUFFER_BIT && @@ -48,6 +57,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { .stencil = clearPayload.stencil, .attachmentType = FramebufferAttachmentType::Stencil, }, drawFbo.GetAttachment(FramebufferAttachmentType::Stencil).GetTexture()); + MGLOG_D("%s: Stencil (texture %d) - stencil = (%u)", __func__, + drawFbo.GetAttachment(FramebufferAttachmentType::Stencil).GetTexture()->GetExternalIndex(), clearPayload.stencil); } } @@ -68,14 +79,22 @@ namespace MobileGL::MG_Backend::DirectVulkan { Bool VkClearManager::GetPendingClear(MG_State::GLState::ITextureObject* texture, ClearAttachmentPayload& outPayload) { if (m_aliveObjects.find(texture) == m_aliveObjects.end() || m_pendingClears.find(texture) == m_pendingClears.end()) { + MGLOG_D("%s: Failed getting pending clear for texture %d", __func__, texture->GetExternalIndex()); return false; } outPayload = m_pendingClears[texture]; + MGLOG_D("%s: Got pending clear for texture %d (%s), clear value: color = (%.2f, %.2f, %.2f, %.2f), depth = (%.2f), stencil = (%u)", __func__, + texture->GetExternalIndex(), + MG_Util::ConvertTextureInternalFormatToString(texture->GetFormat()).c_str(), + outPayload.color[0], outPayload.color[1], outPayload.color[2], outPayload.color[3], + outPayload.depth, + outPayload.stencil); return true; } void VkClearManager::PopPendingClear(MG_State::GLState::ITextureObject* texture) { + MGLOG_D("%s: Pop pending clear for texture %d", __func__, texture->GetExternalIndex()); m_aliveObjects.erase(texture); m_pendingClears.erase(texture); } diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp index 97dbe669..d67e1c59 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp @@ -66,6 +66,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { else if (att.IsRenderbuffer()) contentPtr = att.GetRenderbuffer().get(); XXHASH_VERIFY(XXH64_update(m_hashState, &contentPtr, sizeof(contentPtr))); + if (att.IsTexture()) { + const Int textureLevel = att.GetTextureLevel(); + XXHASH_VERIFY(XXH64_update(m_hashState, &textureLevel, sizeof(textureLevel))); + } if (includePendingClear && att.IsTexture()) { auto* texture = att.GetTexture().get(); @@ -113,10 +117,38 @@ namespace MobileGL::MG_Backend::DirectVulkan { RenderPassEntry& VkRenderPassManager::GetOrCreateRenderPass(const MG_State::GLState::FramebufferObject& fbo, Uint32 swapchainImageIndex) { + auto hasPendingClearOnFramebuffer = [&]() -> Bool { + const auto& drawBuffers = fbo.GetDrawBuffers(); + for (auto attachment : drawBuffers) { + if (attachment == FramebufferAttachmentType::None) { + continue; + } + + const auto& att = fbo.GetAttachment(attachment); + if (att.IsTexture() && m_clearManager.HasPendingClear(att.GetTexture().get())) { + return true; + } + } + + const auto& depthAtt = fbo.GetAttachment(FramebufferAttachmentType::Depth); + if (depthAtt.IsTexture() && m_clearManager.HasPendingClear(depthAtt.GetTexture().get())) { + return true; + } + + const auto& stencilAtt = fbo.GetAttachment(FramebufferAttachmentType::Stencil); + if (stencilAtt.IsTexture() && m_clearManager.HasPendingClear(stencilAtt.GetTexture().get())) { + return true; + } + + return false; + }; + // retrieve from cache first auto* activeRenderPass = GetActiveRenderPass(); auto compatibilityHash = ComputeHash(fbo, swapchainImageIndex, false); - if (activeRenderPass != nullptr && activeRenderPass->CompatibleWith(compatibilityHash)) { + if (activeRenderPass != nullptr && + activeRenderPass->CompatibleWith(compatibilityHash) && + !hasPendingClearOnFramebuffer()) { auto activeIt = m_renderPasses.find(activeRenderPass->hash); MOBILEGL_ASSERT(activeIt != m_renderPasses.end(), "GetOrCreateRenderPass: active render pass hash=0x%llx is missing from cache", @@ -158,6 +190,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { auto& att = fbo.GetAttachment(drawbuf); auto* texture = att.GetTexture().get(); + const Uint32 attachmentMipLevel = static_cast(std::max(att.GetTextureLevel(), 0)); const auto textureTarget = texture->GetTarget(); // Color attachment description @@ -190,9 +223,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { }); } if (width == 0) - width = texture2d->GetBaseSize().x(); + width = att.GetSize().x(); if (height == 0) - height = texture2d->GetBaseSize().y(); + height = att.GetSize().y(); if (isDefaultFbo) { const auto& swapchainViews = m_swapchainObject.GetImageViews(); @@ -215,7 +248,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { .texture = texture, .finalLayout = desc.finalLayout, }); - attachmentViews[i] = textureResources[i]->view; + attachmentViews[i] = m_textureManager.GetOrCreateViewAtMipLevel(*texture, attachmentMipLevel); + MOBILEGL_ASSERT(attachmentViews[i] != VK_NULL_HANDLE, + "GetOrCreateRenderPass: GetOrCreateAttachmentView failed at color attachment %d", i); } if (!hasClear && trackedColorLayout == VK_IMAGE_LAYOUT_UNDEFINED) { @@ -249,6 +284,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkTextureManager::TextureResource* depthTextureResource = nullptr; if (depthAtt.IsComplete() && depthAtt.IsTexture()) { auto& texture = *depthAtt.GetTexture(); + const Uint32 attachmentMipLevel = static_cast(std::max(depthAtt.GetTextureLevel(), 0)); const Uint32 depthAttachmentIndex = static_cast(attachmentDescriptions.size()); ClearAttachmentPayload clearPayload{}; Bool hasClear = m_clearManager.GetPendingClear(&texture, clearPayload); @@ -311,11 +347,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { .finalLayout = depthAttachmentDescription.finalLayout, }); textureResources.emplace_back(depthTextureResource); - attachmentViews.emplace_back(depthTextureResource->view); + attachmentViews.emplace_back(m_textureManager.GetOrCreateViewAtMipLevel(texture, attachmentMipLevel)); + MOBILEGL_ASSERT(attachmentViews.back() != VK_NULL_HANDLE, + "GetOrCreateRenderPass: GetOrCreateAttachmentView failed at depth attachment"); if (width == 0 || height == 0) { - auto texture2d = static_cast(&texture); - width = texture2d->GetBaseSize().x(); - height = texture2d->GetBaseSize().y(); + width = depthAtt.GetSize().x(); + height = depthAtt.GetSize().y(); } } attachmentDescriptions.emplace_back(depthAttachmentDescription); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp index 29b029a5..8068209f 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp @@ -74,6 +74,30 @@ namespace MobileGL::MG_Backend::DirectVulkan { return &(it->second); } + VkImageView VkTextureManager::GetOrCreateViewAtMipLevel(MG_State::GLState::ITextureObject& texture, Uint32 mipLevel) { + TextureResource* resource = SyncTextureAndGetDescriptor(texture); + if (resource == nullptr || resource->image == VK_NULL_HANDLE || mipLevel >= resource->mipLevels) { + return VK_NULL_HANDLE; + } + + if (resource->perMipViews.size() != resource->mipLevels) { + resource->perMipViews.resize(resource->mipLevels, VK_NULL_HANDLE); + } + + VkImageView& perMipView = resource->perMipViews[mipLevel]; + if (perMipView != VK_NULL_HANDLE) { + return perMipView; + } + + perMipView = CreateImageView(resource->image, resource->format, resource->aspect, mipLevel, 1); + if (perMipView == VK_NULL_HANDLE) { + MGLOG_D("%s: CreateImageView failed for textureId=%d mipLevel=%u", __func__, texture.GetExternalIndex(), mipLevel); + return VK_NULL_HANDLE; + } + + return perMipView; + } + void VkTextureManager::UpdateTrackedImageLayout(MG_State::GLState::ITextureObject* texture, VkImageLayout newLayout) { MOBILEGL_ASSERT(texture != nullptr, "UpdateTrackedImageLayout: texture is null"); auto it = m_textureResources.find(texture); @@ -127,7 +151,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { const Bool ok = TransitionImageLayout(commandBuffer, resource->image, resource->layout, targetLayout, srcStageMask, kGraphicsSampledReadStages, srcAccessMask, - VK_ACCESS_SHADER_READ_BIT, resource->aspect); + VK_ACCESS_SHADER_READ_BIT, resource->aspect, 0, resource->mipLevels); MOBILEGL_ASSERT(ok, "TransitionTextureForSampling: transition failed for textureId=%d", texture.GetExternalIndex()); return ok; } @@ -136,7 +160,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkImageLayout& trackedLayout, VkImageLayout newLayout, VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask, VkAccessFlags srcAccessMask, VkAccessFlags dstAccessMask, - VkImageAspectFlags aspectMask) { + VkImageAspectFlags aspectMask, Uint32 baseMipLevel, Uint32 levelCount) { MOBILEGL_ASSERT(image != VK_NULL_HANDLE, "TransitionImageLayout: m_image == VK_NULL_HANDLE"); MOBILEGL_ASSERT(!((dstAccessMask & VK_ACCESS_TRANSFER_READ_BIT) != 0 && (dstStageMask & VK_PIPELINE_STAGE_TRANSFER_BIT) == 0), @@ -158,8 +182,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barrier.image = image; barrier.subresourceRange.aspectMask = aspectMask; - barrier.subresourceRange.baseMipLevel = 0; - barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.baseMipLevel = baseMipLevel; + barrier.subresourceRange.levelCount = levelCount; barrier.subresourceRange.baseArrayLayer = 0; barrier.subresourceRange.layerCount = 1; vkCmdPipelineBarrier(commandBuffer, srcStageMask, dstStageMask, 0, 0, nullptr, 0, nullptr, 1, &barrier); @@ -205,6 +229,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { MGLOG_D("%s: SyncTextureResource failed", __func__); return false; } + if (!SyncTextureViews(texture, outResource)) { + MGLOG_D("%s: SyncTextureViews failed", __func__); + return false; + } Bool hasDirtyMipLevel = false; for (Uint32 level = 0; level < mipLevelCount; ++level) { @@ -251,6 +279,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { resource.extent.height == static_cast(texelSize.y()) && resource.mipLevels == mipLevels; if (compatible) { + if (resource.perMipViews.size() != mipLevels) { + resource.perMipViews.resize(mipLevels, VK_NULL_HANDLE); + } return true; } @@ -283,26 +314,68 @@ namespace MobileGL::MG_Backend::DirectVulkan { VK_VERIFY(vmaCreateImage(m_allocator, &imageInfo, &allocationInfo, &resource.image, &resource.allocation, nullptr), "vmaCreateImage(texture)"); - VkImageViewCreateInfo viewInfo{}; - viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - viewInfo.image = resource.image; - viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; - viewInfo.format = format; - viewInfo.subresourceRange.aspectMask = aspect; - viewInfo.subresourceRange.baseMipLevel = 0; - viewInfo.subresourceRange.levelCount = mipLevels; - viewInfo.subresourceRange.baseArrayLayer = 0; - viewInfo.subresourceRange.layerCount = 1; - VK_VERIFY(vkCreateImageView(m_device, &viewInfo, nullptr, &resource.view), "vkCreateImageView(texture)"); - resource.layout = VK_IMAGE_LAYOUT_UNDEFINED; resource.extent = {static_cast(texelSize.x()), static_cast(texelSize.y())}; resource.mipLevels = mipLevels; + resource.perMipViews.assign(mipLevels, VK_NULL_HANDLE); + resource.sampledBaseMipLevel = 0; + resource.sampledLevelCount = mipLevels; resource.format = format; - resource.aspect = viewInfo.subresourceRange.aspectMask; + resource.aspect = aspect; + resource.syncedTextureParamsVersion = 0; return true; } + Bool VkTextureManager::SyncTextureViews(const MG_State::GLState::ITextureObject& texture, TextureResource& resource) { + MOBILEGL_ASSERT(resource.image != VK_NULL_HANDLE, "SyncTextureViews: image == VK_NULL_HANDLE"); + + Uint32 baseMipLevel = 0; + Uint32 levelCount = 1; + ResolveViewMipRange(texture, resource.mipLevels, baseMipLevel, levelCount); + + const Bool needsRecreate = + resource.fullView == VK_NULL_HANDLE || + resource.sampledBaseMipLevel != baseMipLevel || + resource.sampledLevelCount != levelCount || + resource.syncedTextureParamsVersion != texture.GetTextureParamsVersion(); + if (!needsRecreate) { + return true; + } + + if (resource.fullView != VK_NULL_HANDLE) { + vkDestroyImageView(m_device, resource.fullView, nullptr); + resource.fullView = VK_NULL_HANDLE; + } + + resource.fullView = CreateImageView(resource.image, resource.format, resource.aspect, baseMipLevel, levelCount); + if (resource.fullView == VK_NULL_HANDLE) { + return false; + } + + resource.sampledBaseMipLevel = baseMipLevel; + resource.sampledLevelCount = levelCount; + resource.syncedTextureParamsVersion = texture.GetTextureParamsVersion(); + return true; + } + + VkImageView VkTextureManager::CreateImageView(VkImage image, VkFormat format, VkImageAspectFlags aspect, + Uint32 baseMipLevel, Uint32 levelCount) const { + VkImageViewCreateInfo viewInfo{}; + viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + viewInfo.image = image; + viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + viewInfo.format = format; + viewInfo.subresourceRange.aspectMask = aspect; + viewInfo.subresourceRange.baseMipLevel = baseMipLevel; + viewInfo.subresourceRange.levelCount = levelCount; + viewInfo.subresourceRange.baseArrayLayer = 0; + viewInfo.subresourceRange.layerCount = 1; + + VkImageView view = VK_NULL_HANDLE; + VK_VERIFY(vkCreateImageView(m_device, &viewInfo, nullptr, &view), "vkCreateImageView(texture)"); + return view; + } + Bool VkTextureManager::UploadDirtyMipLevels(MG_State::GLState::TextureObjectMipmap &mipmapTexture, TextureUploadTarget uploadTarget, TextureResource &outResource) { @@ -390,7 +463,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { VK_PIPELINE_STAGE_TRANSFER_BIT, outResource.layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL ? VK_ACCESS_SHADER_READ_BIT : 0, VK_ACCESS_TRANSFER_WRITE_BIT, - aspectMask); + aspectMask, 0, outResource.mipLevels); MOBILEGL_ASSERT(ok, "TransitionImageLayout to VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL failed"); for (const auto& item : uploadItems) { @@ -415,7 +488,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { kGraphicsSampledReadStages, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, - aspectMask); + aspectMask, 0, outResource.mipLevels); MOBILEGL_ASSERT(ok, "TransitionImageLayout to VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL failed"); outResource.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; @@ -472,15 +545,17 @@ namespace MobileGL::MG_Backend::DirectVulkan { continue; } - const auto level0TexelSize = mipTexture->GetMipmapTexelSize(target, 0); - const auto level0ByteSize = mipTexture->GetMipmapByteSize(target, 0); - if (level0TexelSize.x() <= 0 || level0TexelSize.y() <= 0 /*|| level0ByteSize == 0*/) { + // Backing VkImage allocation still uses storage mip 0 as the physical image extent. + // GL_TEXTURE_BASE_LEVEL / MAX_LEVEL are applied later when building the sampled view. + const auto storageBaseTexelSize = mipTexture->GetMipmapTexelSize(target, 0); + const auto storageBaseByteSize = mipTexture->GetMipmapByteSize(target, 0); + if (storageBaseTexelSize.x() <= 0 || storageBaseTexelSize.y() <= 0 /*|| storageBaseByteSize == 0*/) { continue; } outTarget = target; - outTexelSize = level0TexelSize; - outByteSize = level0ByteSize; + outTexelSize = storageBaseTexelSize; + outByteSize = storageBaseByteSize; outMipLevelCount = mipLevelCount; return true; } @@ -507,6 +582,22 @@ namespace MobileGL::MG_Backend::DirectVulkan { return validLevelCount; } + void VkTextureManager::ResolveViewMipRange(const MG_State::GLState::ITextureObject& texture, Uint32 mipLevels, + Uint32& outBaseMipLevel, Uint32& outLevelCount) { + MOBILEGL_ASSERT(mipLevels > 0, "ResolveViewMipRange: mipLevels must be > 0"); + + const auto& levelRange = texture.GetLevelRange(); + const Uint32 maxAvailableMipLevel = mipLevels - 1; + const Uint32 requestedBaseMipLevel = std::min(static_cast(levelRange.x()), maxAvailableMipLevel); + Uint32 requestedMaxMipLevel = std::min(static_cast(levelRange.y()), maxAvailableMipLevel); + if (requestedMaxMipLevel < requestedBaseMipLevel) { + requestedMaxMipLevel = requestedBaseMipLevel; + } + + outBaseMipLevel = requestedBaseMipLevel; + outLevelCount = requestedMaxMipLevel - requestedBaseMipLevel + 1; + } + VkImageAspectFlags VkTextureManager::GetAspectMaskForFormat(VkFormat format) { switch (format) { case VK_FORMAT_D16_UNORM: diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h index a180f2e2..724b53e3 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h @@ -31,41 +31,58 @@ public: struct TextureResource { VkImage image = VK_NULL_HANDLE; VmaAllocation allocation = nullptr; - VkImageView view = VK_NULL_HANDLE; + VkImageView fullView = VK_NULL_HANDLE; + Vector perMipViews; VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED; VkExtent2D extent = {0, 0}; Uint32 mipLevels = 1; + Uint32 sampledBaseMipLevel = 0; + Uint32 sampledLevelCount = 1; VkFormat format = VK_FORMAT_UNDEFINED; VkImageAspectFlags aspect = VK_IMAGE_ASPECT_NONE; + Uint16 syncedTextureParamsVersion = 0; TextureResource() = default; TextureResource(const TextureResource&) = delete; TextureResource(TextureResource&& that) noexcept { std::swap(this->image, that.image); std::swap(this->allocation, that.allocation); - std::swap(this->view, that.view); + std::swap(this->fullView, that.fullView); + std::swap(this->perMipViews, that.perMipViews); std::swap(this->layout, that.layout); std::swap(this->extent, that.extent); std::swap(this->mipLevels, that.mipLevels); + std::swap(this->sampledBaseMipLevel, that.sampledBaseMipLevel); + std::swap(this->sampledLevelCount, that.sampledLevelCount); std::swap(this->format, that.format); std::swap(this->aspect, that.aspect); + std::swap(this->syncedTextureParamsVersion, that.syncedTextureParamsVersion); } void Reset() { - if (view != VK_NULL_HANDLE) { - vkDestroyImageView(s_device, view, nullptr); + if (fullView != VK_NULL_HANDLE) { + vkDestroyImageView(s_device, fullView, nullptr); + } + for (const auto attachmentView : perMipViews) { + if (attachmentView != VK_NULL_HANDLE) { + vkDestroyImageView(s_device, attachmentView, nullptr); + } } if (image != VK_NULL_HANDLE && allocation != nullptr) { vmaDestroyImage(s_allocator, image, allocation); } - view = VK_NULL_HANDLE; + fullView = VK_NULL_HANDLE; + perMipViews.clear(); image = VK_NULL_HANDLE; allocation = nullptr; layout = VK_IMAGE_LAYOUT_UNDEFINED; extent = {0, 0}; mipLevels = 1; + sampledBaseMipLevel = 0; + sampledLevelCount = 1; format = VK_FORMAT_UNDEFINED; aspect = VK_IMAGE_ASPECT_NONE; + syncedTextureParamsVersion = 0; } ~TextureResource() { @@ -81,13 +98,15 @@ public: TextureResource* SyncTextureAndGetDescriptor( MG_State::GLState::ITextureObject& texture); + VkImageView GetOrCreateViewAtMipLevel(MG_State::GLState::ITextureObject& texture, Uint32 mipLevel); void UpdateTrackedImageLayout(MG_State::GLState::ITextureObject* texture, VkImageLayout newLayout); Bool TransitionTextureForSampling(VkCommandBuffer commandBuffer, MG_State::GLState::ITextureObject& texture); static Bool TransitionImageLayout(VkCommandBuffer commandBuffer, VkImage image, VkImageLayout& trackedLayout, VkImageLayout newLayout, VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask, VkAccessFlags srcAccessMask, - VkAccessFlags dstAccessMask, VkImageAspectFlags aspectMask); + VkAccessFlags dstAccessMask, VkImageAspectFlags aspectMask, + Uint32 baseMipLevel = 0, Uint32 levelCount = 1); SizeT CollectGarbage(); private: @@ -98,6 +117,9 @@ private: TextureUploadTarget uploadTarget, const IntVec3 &texelSize, SizeT byteSize, Uint32 mipLevels, TextureResource &resource); + Bool SyncTextureViews(const MG_State::GLState::ITextureObject& texture, TextureResource& resource); + VkImageView CreateImageView(VkImage image, VkFormat format, VkImageAspectFlags aspect, + Uint32 baseMipLevel, Uint32 levelCount) const; Bool UploadDirtyMipLevels(MG_State::GLState::TextureObjectMipmap &mipmapTexture, TextureUploadTarget uploadTarget, TextureResource &outResource); @@ -107,6 +129,8 @@ private: SizeT& outByteSize, Uint32& outMipLevelCount); static Uint32 GetUploadMipLevelCount(const MG_State::GLState::TextureObjectMipmap& texture, TextureUploadTarget target); + static void ResolveViewMipRange(const MG_State::GLState::ITextureObject& texture, Uint32 mipLevels, + Uint32& outBaseMipLevel, Uint32& outLevelCount); static VkImageAspectFlags GetAspectMaskForFormat(VkFormat format); VkDevice m_device = VK_NULL_HANDLE; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index c5f29cbf..d4cd3101 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -19,6 +19,29 @@ #include namespace MobileGL::MG_Backend::DirectVulkan { + static Bool ShouldUseTransientVertexIndexBuffer(const MG_State::GLState::BufferObject& bufferObject) { + switch (bufferObject.GetUsage()) { + case BufferUsage::StreamDraw: + case BufferUsage::StreamRead: + case BufferUsage::StreamCopy: + case BufferUsage::DynamicDraw: + case BufferUsage::DynamicRead: + case BufferUsage::DynamicCopy: + return true; + case BufferUsage::StaticDraw: + case BufferUsage::StaticRead: + case BufferUsage::StaticCopy: + default: + return false; + } + } + + static Bool HasTransientVertexIndexBufferThisFrame( + const Vector& buffers, + const MG_State::GLState::BufferObject* buffer) { + return std::find(buffers.begin(), buffers.end(), buffer) != buffers.end(); + } + static const char* VkImageLayoutToString(VkImageLayout layout) { switch (layout) { case VK_IMAGE_LAYOUT_UNDEFINED: @@ -75,6 +98,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { } namespace { + static constexpr Uint32 kMaxProgramBindings = 16; + static constexpr Uint32 kDescriptorSetsPerFrame = 64; static constexpr Uint kHiddenBlitProgramId = 0xFFFFFFF0u; static constexpr Uint kHiddenBlitVertexShaderId = 0xFFFFFFF1u; static constexpr Uint kHiddenBlitFragmentShaderId = 0xFFFFFFF2u; @@ -93,6 +118,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkImageLayout* trackedLayout = nullptr; VkImageAspectFlags aspectMask = VK_IMAGE_ASPECT_NONE; IntVec2 extent = {0, 0}; + Uint32 mipLevel = 0; + Uint32 mipLevelCount = 1; const char* label = nullptr; }; @@ -164,6 +191,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { outBinding.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; const auto extent = swapchainObject.GetExtent(); outBinding.extent = {static_cast(extent.width), static_cast(extent.height)}; + outBinding.mipLevel = 0; + outBinding.mipLevelCount = 1; return true; } @@ -182,7 +211,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { outBinding.image = resource->image; outBinding.trackedLayout = &resource->layout; outBinding.aspectMask = resource->aspect; - outBinding.extent = {static_cast(resource->extent.width), static_cast(resource->extent.height)}; + const auto attachmentExtent = attachment.GetSize(); + outBinding.extent = {attachmentExtent.x(), attachmentExtent.y()}; + outBinding.mipLevel = static_cast(std::max(attachment.GetTextureLevel(), 0)); + outBinding.mipLevelCount = resource->mipLevels; return true; } @@ -269,15 +301,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { return flags; } - void VulkanRenderer::CreateFrameContexts() { - VK_VERIFY(m_frameContext.Initialize(m_device, m_commandPool, m_config.MaxFramesInFlight), - "CreateFrameContexts"); - VK_VERIFY(m_frameContext.InitializeSwapchainSemaphores(m_device, - static_cast(m_swapchainObject.GetImageCount())), - "CreateFrameContexts, InitializeSwapchainSemaphores"); - MGLOG_I("CreateFrameContexts completed"); - } - void VulkanRenderer::Initialize() { CreateInstance(); CreateSurface(); @@ -286,9 +309,21 @@ namespace MobileGL::MG_Backend::DirectVulkan { CreateAllocator(); CreateCommandPool(); + VK_VERIFY(m_frameContext.Initialize(m_device, m_commandPool, m_config.MaxFramesInFlight), + "CreateFrameContexts"); + MGLOG_I("CreateFrameContexts completed"); + auto succeeded = false; + succeeded = m_bufferManager.Initialize({ + .allocator = m_allocator, + .frameCount = m_frameContext.GetFrameCount(), + .minUploadBytes = 4 * 1024 * 1024, + .transientMemoryUsage = VMA_MEMORY_USAGE_AUTO, + .transientAllocationFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + .transientPersistentMapping = false, + }); + MOBILEGL_ASSERT(succeeded, "VkBufferManager initialization failed."); m_textureManager = MakeUnique(); MOBILEGL_ASSERT(m_textureManager != nullptr, "VkTextureManager creation failed."); - auto succeeded = false; succeeded = m_textureManager->Initialize( {m_device, m_physicalDevice.handle, m_allocator, m_commandPool, m_graphicsQueue}); MOBILEGL_ASSERT(succeeded, "VkTextureManager initialization failed."); @@ -306,7 +341,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_pipelineFactory = MakeUnique(m_device, m_config); MOBILEGL_ASSERT(m_pipelineFactory != nullptr, "PipelineFactory creation failed."); - m_programFactory = MakeUnique(m_device, m_config); + m_programFactory = MakeUnique(m_device, m_config, kMaxProgramBindings); MOBILEGL_ASSERT(m_programFactory != nullptr, "ProgramFactory creation failed."); m_samplerManager = MakeUnique(); @@ -316,27 +351,21 @@ namespace MobileGL::MG_Backend::DirectVulkan { succeeded = InitializeBlitResources(); MOBILEGL_ASSERT(succeeded, "Blit pipeline resource initialization failed."); - m_uniformDescriptorBinder = MakeUnique(); - MOBILEGL_ASSERT(m_uniformDescriptorBinder != nullptr, "UniformDescriptorBinder creation failed."); - succeeded = m_uniformDescriptorBinder->Initialize(m_device, m_allocator, - m_physicalDevice.properties.limits.minUniformBufferOffsetAlignment, - m_config.MaxFramesInFlight, 16, 64, 4 * 1024 * 1024, - m_textureManager.get(), m_samplerManager.get()); + m_uniformManager = MakeUnique(); + MOBILEGL_ASSERT(m_uniformManager != nullptr, "UniformDescriptorBinder creation failed."); + succeeded = m_uniformManager->Initialize( + m_device, &m_bufferManager, m_programFactory.get(), + m_physicalDevice.properties.limits.minUniformBufferOffsetAlignment, m_config.MaxFramesInFlight, + kMaxProgramBindings, kDescriptorSetsPerFrame, m_textureManager.get(), m_samplerManager.get()); MOBILEGL_ASSERT(succeeded, "UniformDescriptorBinder initialization failed."); m_vertexInputStateFactory = MakeUnique(m_config); MOBILEGL_ASSERT(m_vertexInputStateFactory != nullptr, "VertexInputStateFactory creation failed."); - CreateFrameContexts(); - m_frameVertexUploadBuffers.resize(m_frameContext.GetFrameCount()); - m_frameVertexUploadHeads.assign(m_frameContext.GetFrameCount(), 0); - m_frameIndexUploadBuffers.resize(m_frameContext.GetFrameCount()); - m_frameIndexUploadHeads.assign(m_frameContext.GetFrameCount(), 0); - m_deferredBufferReleases.clear(); - m_deferredBufferReleases.resize(m_frameContext.GetFrameCount()); - // Prime the first frame so Render() always targets an acquired swapchain image. VK_VERIFY(m_frameContext.WaitAndAcquireNextImage(m_device, m_swapchainObject.GetHandle(), m_imageIndexAcquired), "Initialize, WaitAndAcquireNextImage"); + m_bufferManager.BeginFrame(m_frameContext.GetCurrentFrameIndex()); + m_transientVertexIndexBuffersThisFrame.clear(); MGLOG_D("VulkanRenderer initialized"); } @@ -345,7 +374,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { VK_VERIFY(vkDeviceWaitIdle(m_device)); m_pipelineFactory.reset(); - m_programFactory.reset(); ShutdownBlitResources(); if (m_samplerManager) { m_samplerManager->Shutdown(); @@ -356,24 +384,16 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_textureManager.reset(); } m_vertexInputStateFactory.reset(); - for (auto& buffer : m_frameVertexUploadBuffers) { - buffer.Destroy(); - } - for (auto& buffer : m_frameIndexUploadBuffers) { - buffer.Destroy(); - } - m_frameVertexUploadBuffers.clear(); - m_frameVertexUploadHeads.clear(); - m_frameIndexUploadBuffers.clear(); - m_frameIndexUploadHeads.clear(); - m_deferredBufferReleases.clear(); + m_bufferManager.Shutdown(); + m_transientVertexIndexBuffersThisFrame.clear(); m_frameContext.Destroy(m_device, m_commandPool); - if (m_uniformDescriptorBinder) { - m_uniformDescriptorBinder->Shutdown(); - m_uniformDescriptorBinder.reset(); + if (m_uniformManager) { + m_uniformManager->Shutdown(); + m_uniformManager.reset(); } + m_programFactory.reset(); ShutdownSwapchain(); m_renderPassManager.reset(); @@ -392,7 +412,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { vkDestroyDevice(m_device, nullptr); m_device = VK_NULL_HANDLE; } - m_cmdDrawIndexedIndirectCount = nullptr; + s_vkCmdDrawIndexedIndirectCount = nullptr; if (m_surface != VK_NULL_HANDLE) { vkDestroySurfaceKHR(m_instance, m_surface, nullptr); @@ -411,59 +431,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { MGLOG_I("VulkanRenderer shut down completed"); } - void VulkanRenderer::DeferDestroyBuffer(VkBufferObject& buffer) { - if (!buffer.IsValid()) { - return; - } - const Uint32 frameIndex = m_frameContext.GetCurrentFrameIndex(); - if (m_deferredBufferReleases.size() < m_frameContext.GetFrameCount()) { - m_deferredBufferReleases.resize(m_frameContext.GetFrameCount()); - } - m_deferredBufferReleases[frameIndex].push_back(std::move(buffer)); - } - - void VulkanRenderer::CollectDeferredBufferReleases(Uint32 frameIndex) { - if (frameIndex >= m_deferredBufferReleases.size()) { - return; - } - m_deferredBufferReleases[frameIndex].clear(); - } - - Bool VulkanRenderer::EnsureFrameUploadBufferCapacity(Uint32 frameIndex, Bool isIndexBuffer, - VkDeviceSize requiredEndOffset, VkDeviceSize minCapacity, - VkBufferUsageFlags usage) { - auto& buffers = isIndexBuffer ? m_frameIndexUploadBuffers : m_frameVertexUploadBuffers; - auto& heads = isIndexBuffer ? m_frameIndexUploadHeads : m_frameVertexUploadHeads; - if (frameIndex >= buffers.size() || frameIndex >= heads.size()) { - return false; - } - - auto& uploadBuffer = buffers[frameIndex]; - if (uploadBuffer.IsValid() && uploadBuffer.GetSize() >= requiredEndOffset) { - return true; - } - - VkDeviceSize newCapacity = uploadBuffer.IsValid() ? uploadBuffer.GetSize() : 0; - if (newCapacity < minCapacity) { - newCapacity = minCapacity; - } - while (newCapacity < requiredEndOffset) { - newCapacity *= 2; - } - - DeferDestroyBuffer(uploadBuffer); - if (!uploadBuffer.Create(m_allocator, newCapacity, usage, VMA_MEMORY_USAGE_AUTO, - VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT)) { - MGLOG_E("EnsureFrameUploadBufferCapacity failed: create upload buffer (index=%d, capacity=%zu)", - isIndexBuffer, static_cast(newCapacity)); - return false; - } - - heads[frameIndex] = 0; - return true; - } - - Bool VulkanRenderer::UploadAndBindVertexStreams( + Bool VulkanRenderer::UploadAndBindVertexBuffers( VkCommandBuffer commandBuffer, const MG_State::GLState::VertexArrayObject& vao) { auto& vertexInputState = m_vertexInputStateFactory->GetOrCreateVertexInputState(vao); @@ -471,7 +439,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { Vector vkBuffers(bindingCount, VK_NULL_HANDLE); Vector vkOffsets(bindingCount, 0); - const Uint32 frameIndex = m_frameContext.GetCurrentFrameIndex(); auto findBufferByKey = [&](SizeT bufferKey) -> const MG_State::GLState::BufferObject* { const auto& attrs = vao.GetAllAttributes(); @@ -491,32 +458,102 @@ namespace MobileGL::MG_Backend::DirectVulkan { for (SizeT binding = 0; binding < bindingCount; ++binding) { const SizeT bufferKey = vertexInputState.bindingBufferKeys[binding]; const MG_State::GLState::BufferObject* sourceBuffer = findBufferByKey(bufferKey); - - const auto sourceData = sourceBuffer->GetDataReadOnly(); - - const SizeT sourceSize = sourceBuffer->GetSize(); - VkDeviceSize& frameHead = m_frameVertexUploadHeads[frameIndex]; - const VkDeviceSize writeOffset = (frameHead + 0x0F) & ~VkDeviceSize(0x0F); - const VkDeviceSize writeEnd = writeOffset + static_cast(sourceSize); - if (!EnsureFrameUploadBufferCapacity(frameIndex, false, writeEnd, 4 * 1024 * 1024, - VK_BUFFER_USAGE_VERTEX_BUFFER_BIT)) { - return false; + MOBILEGL_ASSERT(sourceBuffer != nullptr, "UploadAndBindVertexStreams failed to resolve source buffer"); + auto sourceBufferShared = MG_State::pGLContext->GetBufferObject(sourceBuffer->GetExternalIndex()); + MOBILEGL_ASSERT(sourceBufferShared != nullptr, + "UploadAndBindVertexStreams failed to resolve shared source buffer"); + BufferSlice slice{}; + const Bool transientThisFrame = + HasTransientVertexIndexBufferThisFrame(m_transientVertexIndexBuffersThisFrame, sourceBufferShared.get()); + const Bool isDirty = (sourceBufferShared->GetChangeBits() & BufferChangeBits::DirtyBit); + if (ShouldUseTransientVertexIndexBuffer(*sourceBufferShared) || transientThisFrame || isDirty) { + const auto sourceData = sourceBufferShared->GetDataReadOnly(); + const SizeT sourceSize = sourceBufferShared->GetSize(); + if (!m_bufferManager.UploadTransient(BufferKind::Vertex, m_frameContext.GetCurrentFrameIndex(), + sourceData->data(), static_cast(sourceSize), 16, + slice)) { + MOBILEGL_ASSERT(false, "UploadAndBindVertexStreams skipped: failed to upload transient binding %zu", binding); + return false; + } + if (!transientThisFrame) { + m_transientVertexIndexBuffersThisFrame.push_back(sourceBufferShared.get()); + } + m_bufferManager.DowngradeResidentBufferToTransient(sourceBufferShared); + sourceBufferShared->ClearDirty(); + } else { + if (!m_bufferManager.SyncResidentBuffer(BufferKind::Vertex, sourceBufferShared, slice)) { + MGLOG_E("UploadAndBindVertexStreams skipped: failed to sync resident binding %zu", binding); + return false; + } } - auto& frameUploadBuffer = m_frameVertexUploadBuffers[frameIndex]; - if (!frameUploadBuffer.Upload(sourceData->data(), static_cast(sourceSize), writeOffset)) { - MGLOG_E("UploadAndBindVertexStreams skipped: failed to upload binding %zu", binding); - return false; - } - - frameHead = writeEnd; - vkBuffers[binding] = frameUploadBuffer.GetHandle(); - vkOffsets[binding] = writeOffset; + vkBuffers[binding] = slice.buffer; + vkOffsets[binding] = slice.offset; } vkCmdBindVertexBuffers(commandBuffer, 0, static_cast(bindingCount), vkBuffers.data(), vkOffsets.data()); return true; } + Bool VulkanRenderer::UploadAndBindIndexBuffer(FrameContext::FrameData& frame, + const MG_State::GLState::VertexArrayObject& vao, + const IndexBufferView* pIndexBufferView) { + VkIndexType vkIndexType = VK_INDEX_TYPE_MAX_ENUM; + switch (pIndexBufferView->indexType) { + case GL_UNSIGNED_BYTE: + MOBILEGL_ASSERT(m_indexTypeUint8ExtensionEnabled, + "DrawElements with GL_UNSIGNED_BYTE requires VK_KHR_index_type_uint8 or VK_EXT_index_type_uint8"); + vkIndexType = VK_INDEX_TYPE_UINT8; + break; + case GL_UNSIGNED_SHORT: + vkIndexType = VK_INDEX_TYPE_UINT16; + break; + case GL_UNSIGNED_INT: + vkIndexType = VK_INDEX_TYPE_UINT32; + break; + default: + MGLOG_D("DrawElements skipped: index type %u is not supported yet", pIndexBufferView->indexType); + return false; + } + + const auto* indexBuffer = vao.GetIndexBufferBindingSlot().GetBoundObject().get(); + MOBILEGL_ASSERT(indexBuffer != nullptr, "UploadAndBindIndexBuffer requires bound EBO"); + const SizeT indexSize = MG_Util::GetGLTypeSize(pIndexBufferView->indexType); + const SizeT indexDataSizeBytes = pIndexBufferView->indexByteSize; + MOBILEGL_ASSERT(pIndexBufferView->indexByteOffset + indexDataSizeBytes <= indexBuffer->GetSize(), + "DrawElements index range out of bounds"); + + BufferSlice slice{}; + auto indexBufferShared = MG_State::pGLContext->GetBufferObject(indexBuffer->GetExternalIndex()); + MOBILEGL_ASSERT(indexBufferShared != nullptr, "UploadAndBindIndexBuffer failed to resolve shared EBO"); + const Bool transientThisFrame = + HasTransientVertexIndexBufferThisFrame(m_transientVertexIndexBuffersThisFrame, indexBufferShared.get()); + const Bool isDirty = (indexBufferShared->GetChangeBits() & BufferChangeBits::DirtyBit); + if (ShouldUseTransientVertexIndexBuffer(*indexBufferShared) || transientThisFrame || isDirty) { + const auto indexData = indexBufferShared->GetDataReadOnly(); + MOBILEGL_ASSERT(indexData != nullptr && !indexData->empty(), "DrawElements requires non-empty EBO data"); + if (!m_bufferManager.UploadTransient(BufferKind::Index, m_frameContext.GetCurrentFrameIndex(), + indexData->data() + pIndexBufferView->indexByteOffset, + static_cast(indexDataSizeBytes), indexSize, slice)) { + MOBILEGL_ASSERT(false, "DrawElements skipped: failed to prepare transient index buffer"); + return false; + } + if (!transientThisFrame) { + m_transientVertexIndexBuffersThisFrame.push_back(indexBufferShared.get()); + } + m_bufferManager.DowngradeResidentBufferToTransient(indexBufferShared); + indexBufferShared->ClearDirty(); + vkCmdBindIndexBuffer(frame.commandBuffer, slice.buffer, slice.offset, vkIndexType); + return true; + } + if (!m_bufferManager.SyncResidentBuffer(BufferKind::Index, indexBufferShared, slice)) { + MGLOG_E("DrawElements skipped: failed to sync resident index buffer"); + return false; + } + vkCmdBindIndexBuffer(frame.commandBuffer, slice.buffer, + slice.offset + static_cast(pIndexBufferView->indexByteOffset), vkIndexType); + return true; + } + Bool VulkanRenderer::InitializeBlitResources() { ShutdownBlitResources(); @@ -624,17 +661,17 @@ void main() { VkPipeline VulkanRenderer::GetOrCreateBlitPipeline(const RenderPassEntry& renderPassEntry) { MOBILEGL_ASSERT(m_blitResources.program != nullptr, "GetOrCreateBlitPipeline: blit program is null"); MOBILEGL_ASSERT(m_programFactory != nullptr, "GetOrCreateBlitPipeline: program factory is null"); - MOBILEGL_ASSERT(m_uniformDescriptorBinder != nullptr, "GetOrCreateBlitPipeline: descriptor binder is null"); + MOBILEGL_ASSERT(m_uniformManager != nullptr, "GetOrCreateBlitPipeline: descriptor binder is null"); static const VkPipelineVertexInputStateCreateInfo kEmptyVertexInputState { VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO }; ProgramFactory::CompileOptionFlags transformFlags = 0; - auto& stages = m_programFactory->GetOrCreatePipelineShaderStages(*m_blitResources.program, transformFlags); + const auto& programObj = m_programFactory->GetOrCreateProgram(*m_blitResources.program, transformFlags); PipelineFactory::PipelineCreatePayload payload{ - .programHash = m_programFactory->ComputeHash(*m_blitResources.program, transformFlags), + .programHash = programObj.hash, .vertexInputHash = 0, - .pipelineLayout = m_uniformDescriptorBinder->GetOrCreatePipelineLayout(*m_blitResources.program), + .pipelineLayout = programObj.pipelineLayout, .renderPass = renderPassEntry.renderPass, .subpass = 0, .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, @@ -650,7 +687,7 @@ void main() { .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO, .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT, - .stages = &stages, + .stages = &programObj.stages, .vertexInputState = &kEmptyVertexInputState }; return m_pipelineFactory->GetOrCreatePipeline(payload); @@ -663,16 +700,14 @@ void main() { const RenderPassEntry& renderPassEntry) { ProgramFactory::CompileOptionFlags transformFlags = GetShaderTransformFlags(m_swapchainObject.GetPreTransform()); Bool invertClockwise = transformFlags & ProgramFactory::CompileOptionBit::PositionYFlip; - auto& stages = m_programFactory->GetOrCreatePipelineShaderStages(program, transformFlags); - if (stages.empty()) { + const auto& programObj = m_programFactory->GetOrCreateProgram(program, transformFlags); + if (programObj.stages.empty()) { MGLOG_D("GetOrCreatePipeline skipped: program has no shader stages"); return VK_NULL_HANDLE; } - const Uint64 programHash = m_programFactory->ComputeHash(program, transformFlags); auto vertexInputHash = m_vertexInputStateFactory->ComputeHash(vao); auto& vis = m_vertexInputStateFactory->GetOrCreateVertexInputState(vao); - auto pipelineLayout = m_uniformDescriptorBinder->GetOrCreatePipelineLayout(program); auto cullFaceEnabled = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::CullFace); auto depthTestEnabled = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::DepthTest); BlendFactor srcRGB = BlendFactor::One; @@ -683,9 +718,9 @@ void main() { auto mask = MG_State::pGLContext->GetColorMask(); PipelineFactory::PipelineCreatePayload payload { - .programHash = programHash, + .programHash = programObj.hash, .vertexInputHash = vertexInputHash, - .pipelineLayout = pipelineLayout, + .pipelineLayout = programObj.pipelineLayout, .renderPass = renderPassEntry.renderPass, .subpass = 0, .topology = MG_Util::ConvertPrimitiveModeToVkEnum(mode), @@ -706,28 +741,36 @@ void main() { (mask.g() ? VK_COLOR_COMPONENT_G_BIT : 0u) | (mask.b() ? VK_COLOR_COMPONENT_B_BIT : 0u) | (mask.a() ? VK_COLOR_COMPONENT_A_BIT : 0u) ), - .stages = &stages, + .stages = &programObj.stages, .vertexInputState = &vis.state }; return m_pipelineFactory->GetOrCreatePipeline(payload); } - void VulkanRenderer::SetupDraw(FrameContext::FrameData& frame, GLenum mode, Flags aspects) { + Bool VulkanRenderer::SetupDraw(FrameContext::FrameData& frame, GLenum mode, Flags aspects, + const IndexBufferView* pIndexBufferView) { m_textureManager->CollectGarbage(); const auto& drawFbo = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject(); const auto& vao = *MG_State::pGLContext->GetBoundVertexArray(); const auto& program = *MG_State::pGLContext->GetCurrentProgram(); + ProgramFactory::CompileOptionFlags transformFlags = GetShaderTransformFlags(m_swapchainObject.GetPreTransform()); + const auto& programObj = m_programFactory->GetOrCreateProgram(program, transformFlags); // Begin command recording if not yet if (!frame.isCommandRecording) { m_frameContext.BeginCommandRecording(); - m_uniformDescriptorBinder->BeginFrame(m_frameContext.GetCurrentFrameIndex()); + m_uniformManager->BeginFrame(m_frameContext.GetCurrentFrameIndex()); } auto* activeRenderPass = VkRenderPassManager::GetActiveRenderPass(); + + // Check if any of the textures to sample have pending clears, + // which probably indicates it's been gone through codepath like `fbo attach` -> `clear` -> `fbo detach`, and + // without draws in between to give it a chance to materialize such clear. + // Deal with this situation here. Vector sampledTextures; - Bool hasSampledTextures = m_uniformDescriptorBinder->CollectSampledTextures(program, sampledTextures); + Bool hasSampledTextures = m_uniformManager->CollectSampledTextures(program, programObj, sampledTextures); MOBILEGL_ASSERT(hasSampledTextures, "%s: CollectSampledTextures failed", __func__); MGLOG_D("SetupDraw: program=%u drawFbo=%u sampledTextureCount=%zu activeRenderPass=%s", program.GetExternalIndex(), drawFbo ? drawFbo->GetExternalIndex() : 0u, sampledTextures.size(), @@ -800,7 +843,6 @@ void main() { activeRenderPass = VkRenderPassManager::GetActiveRenderPass(); // Begin render pass, and handle clear - if (activeRenderPass && activeRenderPass->CompatibleWith(renderPassEntry)) { ClearAttachmentsOnActiveRenderPass(frame.commandBuffer, renderPassEntry); } else { @@ -814,10 +856,16 @@ void main() { vkCmdBindPipeline(frame.commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); - m_uniformDescriptorBinder->BindProgramUniformBuffers(frame.commandBuffer, program, + m_uniformManager->BindProgramUniformBuffers(frame.commandBuffer, program, programObj, m_frameContext.GetCurrentFrameIndex()); - UploadAndBindVertexStreams(frame.commandBuffer, vao); + auto vtxUploadOk = UploadAndBindVertexBuffers(frame.commandBuffer, vao); + MOBILEGL_ASSERT(vtxUploadOk, "SetupDraw skipped: failed to upload vertex buffers"); + + if (aspects & DrawSetupAspect::IndexBuffer) { + auto idxUploadOk = UploadAndBindIndexBuffer(frame, vao, pIndexBufferView); + MOBILEGL_ASSERT(idxUploadOk, "SetupDraw skipped: failed to upload index buffer"); + } VkViewport viewport{}; viewport.x = 0.0f; @@ -839,6 +887,7 @@ void main() { scissor.extent = { (Uint)renderPassEntry.extent.x(), (Uint)renderPassEntry.extent.y() }; } vkCmdSetScissor(frame.commandBuffer, 0, 1, &scissor); + return true; } void VulkanRenderer::Clear(GLbitfield mask) { @@ -875,7 +924,7 @@ void main() { Bool ok = VkTextureManager::TransitionImageLayout( commandBuffer, resource->image, resource->layout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, srcStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT, srcAccessMask, VK_ACCESS_TRANSFER_WRITE_BIT, - resource->aspect); + resource->aspect, 0, resource->mipLevels); MOBILEGL_ASSERT(ok, "MaterializePendingClearForTexture: failed to transition textureId=%d to TRANSFER_DST", texture.GetExternalIndex()); @@ -908,7 +957,7 @@ void main() { ok = VkTextureManager::TransitionImageLayout( commandBuffer, resource->image, resource->layout, sampledLayout, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, - VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, resource->aspect); + VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, resource->aspect, 0, resource->mipLevels); MOBILEGL_ASSERT(ok, "MaterializePendingClearForTexture: failed to transition textureId=%d to sampled layout", texture.GetExternalIndex()); @@ -1031,14 +1080,16 @@ void main() { writeUniform(m_blitResources.surfaceTransformLocation, &blitUniformData.surfaceTransform, sizeof(blitUniformData.surfaceTransform)); - const auto samplerBindingOverride = UniformDescriptorBinder::SamplerBindingOverride{ + const auto samplerBindingOverride = UniformManager::SamplerBindingOverride{ .binding = m_blitResources.samplerBinding, .texture = sourceTexture.get(), .sampler = (filter == GL_LINEAR ? m_blitResources.linearSampler.get() : m_blitResources.nearestSampler.get()), }; - const Bool bound = m_uniformDescriptorBinder->BindProgramUniformBuffers( - frame.commandBuffer, *m_blitResources.program, m_frameContext.GetCurrentFrameIndex(), + ProgramFactory::CompileOptionFlags blitTransformFlags = 0; + const auto& blitProgramObj = m_programFactory->GetOrCreateProgram(*m_blitResources.program, blitTransformFlags); + const Bool bound = m_uniformManager->BindProgramUniformBuffers( + frame.commandBuffer, *m_blitResources.program, blitProgramObj, m_frameContext.GetCurrentFrameIndex(), &samplerBindingOverride); MOBILEGL_ASSERT(bound, "TryBlitToDefaultFramebufferWithShader: BindProgramUniformBuffers failed"); vkCmdDraw(frame.commandBuffer, 3, 1, 0, 0); @@ -1069,7 +1120,7 @@ void main() { auto& frame = m_frameContext.GetCurrent(); if (!frame.isCommandRecording) { m_frameContext.BeginCommandRecording(); - m_uniformDescriptorBinder->BeginFrame(m_frameContext.GetCurrentFrameIndex()); + m_uniformManager->BeginFrame(m_frameContext.GetCurrentFrameIndex()); } auto* activeRenderPass = VkRenderPassManager::GetActiveRenderPass(); @@ -1138,7 +1189,7 @@ void main() { Bool ok = VkTextureManager::TransitionImageLayout( frame.commandBuffer, srcBinding.image, *srcBinding.trackedLayout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, srcStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT, - srcAccessMask, VK_ACCESS_TRANSFER_READ_BIT, srcBinding.aspectMask); + srcAccessMask, VK_ACCESS_TRANSFER_READ_BIT, srcBinding.aspectMask, 0, srcBinding.mipLevelCount); MOBILEGL_ASSERT(ok, "%s: failed to transition source image", __func__); } @@ -1156,19 +1207,19 @@ void main() { Bool ok = VkTextureManager::TransitionImageLayout( frame.commandBuffer, dstBinding.image, *dstBinding.trackedLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, dstStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT, - dstAccessMask, VK_ACCESS_TRANSFER_WRITE_BIT, dstBinding.aspectMask); + dstAccessMask, VK_ACCESS_TRANSFER_WRITE_BIT, dstBinding.aspectMask, 0, dstBinding.mipLevelCount); MOBILEGL_ASSERT(ok, "%s: failed to transition destination image", __func__); } VkImageBlit blitRegion{}; blitRegion.srcSubresource.aspectMask = srcBinding.aspectMask; - blitRegion.srcSubresource.mipLevel = 0; + blitRegion.srcSubresource.mipLevel = srcBinding.mipLevel; blitRegion.srcSubresource.baseArrayLayer = 0; blitRegion.srcSubresource.layerCount = 1; blitRegion.srcOffsets[0] = {srcX0, srcY0, 0}; blitRegion.srcOffsets[1] = {srcX1, srcY1, 1}; blitRegion.dstSubresource.aspectMask = dstBinding.aspectMask; - blitRegion.dstSubresource.mipLevel = 0; + blitRegion.dstSubresource.mipLevel = dstBinding.mipLevel; blitRegion.dstSubresource.baseArrayLayer = 0; blitRegion.dstSubresource.layerCount = 1; blitRegion.dstOffsets[0] = {dstX0, dstY0, 0}; @@ -1180,7 +1231,7 @@ void main() { 1, &blitRegion, filter == GL_LINEAR ? VK_FILTER_LINEAR : VK_FILTER_NEAREST); } - void VulkanRenderer::DrawArrays(const DrawArrayCmd& payload) { + void VulkanRenderer::DrawArrays(const DrawCmd& payload) { auto& frame = m_frameContext.GetCurrent(); SetupDraw(frame, payload.mode, 0); @@ -1189,68 +1240,49 @@ void main() { VkCommandBuffer& commandBuffer = frame.commandBuffer; - vkCmdDraw(commandBuffer, static_cast(payload.count), 1, static_cast(payload.first), 0); + vkCmdDraw(commandBuffer, + payload.params.vertexCount, + payload.params.instanceCount, + payload.params.firstVertex, + payload.params.firstInstance); } - void VulkanRenderer::DrawElements(const DrawElementCmd& payload) { + void VulkanRenderer::DrawElements(const DrawIndexedCmd& payload) { auto& frame = m_frameContext.GetCurrent(); - SetupDraw(frame, payload.mode, 0); + SetupDraw(frame, payload.mode, DrawSetupAspect::IndexBuffer, + &payload.indexBufferView); - if (!frame.isCommandRecording) { - MGLOG_D("DrawElements skipped: frame recording was not started"); - return; - } - - VkIndexType vkIndexType = VK_INDEX_TYPE_MAX_ENUM; - switch (payload.indexType) { - case GL_UNSIGNED_SHORT: - vkIndexType = VK_INDEX_TYPE_UINT16; - break; - case GL_UNSIGNED_INT: - vkIndexType = VK_INDEX_TYPE_UINT32; - break; - default: - MGLOG_D("DrawElements skipped: index type %u is not supported yet", payload.indexType); - return; - } - - auto* vao = MG_State::pGLContext->GetBoundVertexArray().get(); - const auto* indexBuffer = vao->GetIndexBufferBindingSlot().GetBoundObject().get(); - const auto indexData = indexBuffer->GetDataReadOnly(); - MOBILEGL_ASSERT(indexData != nullptr && !indexData->empty(), "DrawElements requires non-empty EBO data"); - const SizeT indexSize = (payload.indexType == GL_UNSIGNED_SHORT) ? sizeof(Uint16) : sizeof(Uint32); - const SizeT indexDataSizeBytes = static_cast(payload.count) * indexSize; - MOBILEGL_ASSERT(payload.indexByteOffset + indexDataSizeBytes <= indexBuffer->GetSize(), - "DrawElements index range out of bounds"); - - const Uint32 frameIndex = m_frameContext.GetCurrentFrameIndex(); - VkDeviceSize& frameIndexHead = m_frameIndexUploadHeads[frameIndex]; - const VkDeviceSize alignment = static_cast(indexSize); - const VkDeviceSize writeOffset = (frameIndexHead + alignment - 1) & ~(alignment - 1); - const VkDeviceSize writeEnd = writeOffset + static_cast(indexDataSizeBytes); - if (!EnsureFrameUploadBufferCapacity(frameIndex, true, writeEnd, 1 * 1024 * 1024, - VK_BUFFER_USAGE_INDEX_BUFFER_BIT)) { - MGLOG_E("DrawElements skipped: failed to prepare index upload buffer"); - return; - } - auto& frameIndexUploadBuffer = m_frameIndexUploadBuffers[frameIndex]; - if (!frameIndexUploadBuffer.Upload(indexData->data() + payload.indexByteOffset, - static_cast(indexDataSizeBytes), writeOffset)) { - MGLOG_E("DrawElements skipped: failed to upload index data"); - return; - } - frameIndexHead = writeEnd; + MOBILEGL_ASSERT(frame.isCommandRecording, "%s: frame recording was not started", __func__); VkCommandBuffer& commandBuffer = frame.commandBuffer; - vkCmdBindIndexBuffer(commandBuffer, frameIndexUploadBuffer.GetHandle(), writeOffset, vkIndexType); - vkCmdDrawIndexed(commandBuffer, static_cast(payload.count), 1, 0, - static_cast(payload.baseVertex), 0); + vkCmdDrawIndexed(commandBuffer, + payload.params.indexCount, + payload.params.instanceCount, + payload.params.firstIndex, + payload.params.vertexOffset, + payload.params.firstInstance); } - void VulkanRenderer::MultiDrawElements(const Vector& payloads) { + void VulkanRenderer::MultiDrawElements(const MultiDrawIndexedCmd& payload) { + auto& frame = m_frameContext.GetCurrent(); + SetupDraw(frame, payload.mode, DrawSetupAspect::IndexBuffer, + &payload.indexBufferView); + + MOBILEGL_ASSERT(frame.isCommandRecording, "%s: frame recording was not started", __func__); + + VkCommandBuffer& commandBuffer = frame.commandBuffer; + + for (Uint32 idraw = 0; idraw < payload.drawCount; ++idraw) { + vkCmdDrawIndexed(commandBuffer, + payload.pParams[idraw].indexCount, + payload.pParams[idraw].instanceCount, + payload.pParams[idraw].firstIndex, + payload.pParams[idraw].vertexOffset, + payload.pParams[idraw].firstInstance); + } } void VulkanRenderer::Present() { @@ -1298,13 +1330,8 @@ void main() { result = VK_SUCCESS; } VK_VERIFY(result, "Present, vkAcquireNextImageKHR"); - CollectDeferredBufferReleases(m_frameContext.GetCurrentFrameIndex()); - if (m_frameContext.GetCurrentFrameIndex() < m_frameVertexUploadHeads.size()) { - m_frameVertexUploadHeads[m_frameContext.GetCurrentFrameIndex()] = 0; - } - if (m_frameContext.GetCurrentFrameIndex() < m_frameIndexUploadHeads.size()) { - m_frameIndexUploadHeads[m_frameContext.GetCurrentFrameIndex()] = 0; - } + m_bufferManager.BeginFrame(m_frameContext.GetCurrentFrameIndex()); + m_transientVertexIndexBuffersThisFrame.clear(); } void VulkanRenderer::CreateInstance() { @@ -1601,19 +1628,60 @@ void main() { ResolveOptionalDeviceExtensions(availableExtensions, enabledDeviceExtensions); MGLOG_I("VK_KHR_draw_indirect_count enabled: %s", m_drawIndirectCountExtensionEnabled ? "true" : "false"); + m_indexTypeUint8ExtensionEnabled = false; + const char* indexTypeUint8ExtensionName = nullptr; + if (IsExtensionSupported(availableExtensions, VK_KHR_INDEX_TYPE_UINT8_EXTENSION_NAME)) { + indexTypeUint8ExtensionName = VK_KHR_INDEX_TYPE_UINT8_EXTENSION_NAME; + } else if (IsExtensionSupported(availableExtensions, VK_EXT_INDEX_TYPE_UINT8_EXTENSION_NAME)) { + indexTypeUint8ExtensionName = VK_EXT_INDEX_TYPE_UINT8_EXTENSION_NAME; + } + + VkPhysicalDeviceIndexTypeUint8Features indexTypeUint8Features{}; + indexTypeUint8Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INDEX_TYPE_UINT8_FEATURES; + if (indexTypeUint8ExtensionName != nullptr) { + VkPhysicalDeviceFeatures2 featureQuery{}; + featureQuery.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + featureQuery.pNext = &indexTypeUint8Features; + auto getPhysicalDeviceFeatures2 = reinterpret_cast( + vkGetInstanceProcAddr(m_instance, "vkGetPhysicalDeviceFeatures2")); + if (getPhysicalDeviceFeatures2 == nullptr) { + getPhysicalDeviceFeatures2 = reinterpret_cast( + vkGetInstanceProcAddr(m_instance, "vkGetPhysicalDeviceFeatures2KHR")); + } + MOBILEGL_ASSERT(getPhysicalDeviceFeatures2 != nullptr, + "CreateLogicalDeviceAndQueues: vkGetPhysicalDeviceFeatures2 is unavailable"); + getPhysicalDeviceFeatures2(m_physicalDevice.handle, &featureQuery); + if (indexTypeUint8Features.indexTypeUint8 == VK_TRUE) { + if (!IsExtensionAlreadyEnabled(enabledDeviceExtensions, indexTypeUint8ExtensionName)) { + enabledDeviceExtensions.push_back(indexTypeUint8ExtensionName); + } + m_indexTypeUint8ExtensionEnabled = true; + indexTypeUint8Features.pNext = const_cast(deviceCreateInfo.pNext); + deviceCreateInfo.pNext = &indexTypeUint8Features; + MGLOG_I("Enabled optional device extension: %s", indexTypeUint8ExtensionName); + } else { + MGLOG_W("%s is advertised, but indexTypeUint8 feature is unavailable; uint8 index buffers will stay disabled", + indexTypeUint8ExtensionName); + } + } else { + MGLOG_W("VK_KHR_index_type_uint8 / VK_EXT_index_type_uint8 not supported; uint8 index buffers will stay disabled"); + } + deviceCreateInfo.enabledExtensionCount = static_cast(enabledDeviceExtensions.size()); deviceCreateInfo.ppEnabledExtensionNames = enabledDeviceExtensions.data(); VK_VERIFY(vkCreateDevice(m_physicalDevice.handle, &deviceCreateInfo, nullptr, &m_device), "vkCreateDevice"); - m_cmdDrawIndexedIndirectCount = reinterpret_cast( + s_vkCmdDrawIndexedIndirectCount = reinterpret_cast( vkGetDeviceProcAddr(m_device, "vkCmdDrawIndexedIndirectCountKHR")); - if (m_cmdDrawIndexedIndirectCount == nullptr) { - m_cmdDrawIndexedIndirectCount = reinterpret_cast( + if (s_vkCmdDrawIndexedIndirectCount == nullptr) { + s_vkCmdDrawIndexedIndirectCount = reinterpret_cast( vkGetDeviceProcAddr(m_device, "vkCmdDrawIndexedIndirectCount")); } - if (m_drawIndirectCountExtensionEnabled && m_cmdDrawIndexedIndirectCount == nullptr) { - MGLOG_W("VK_KHR_draw_indirect_count enabled but vkCmdDrawIndexedIndirectCount entry point is missing"); + if (m_drawIndirectCountExtensionEnabled && s_vkCmdDrawIndexedIndirectCount == nullptr) { + MGLOG_W("VK_KHR_draw_indirect_count enabled but vkCmdDrawIndexedIndirectCount entry point is missing, will continue as if VK_KHR_draw_indirect_count is not supported!"); + m_drawIndirectCountExtensionEnabled = false; } + MGLOG_I("index type uint8 enabled: %s", m_indexTypeUint8ExtensionEnabled ? "true" : "false"); MGLOG_I("Logical device created."); // Queues @@ -1658,11 +1726,6 @@ void main() { m_config.MaxFramesInFlight); } - Uint64 VulkanRenderer::BuildPendingClearKey(Uint drawFboExternalIndex, Bool targetsDefaultFramebuffer) { - return (static_cast(targetsDefaultFramebuffer ? 1 : 0) << 63) | - static_cast(drawFboExternalIndex); - } - void VulkanRenderer::CreateCommandPool() { VkCommandPoolCreateInfo createInfo{VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO}; createInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; @@ -1844,12 +1907,12 @@ void main() { m_frameContext.GetCurrent().isCommandRecording = false; m_frameContext.GetCurrent().hasCommandBufferRecorded = false; } - m_deferredBufferReleases.clear(); - m_deferredBufferReleases.resize(m_frameContext.GetFrameCount()); - m_frameVertexUploadBuffers.resize(m_frameContext.GetFrameCount()); - m_frameVertexUploadHeads.assign(m_frameContext.GetFrameCount(), 0); - m_frameIndexUploadBuffers.resize(m_frameContext.GetFrameCount()); - m_frameIndexUploadHeads.assign(m_frameContext.GetFrameCount(), 0); + const Bool okArena = m_bufferManager.RecreateTransientArenas(m_frameContext.GetFrameCount()); + MOBILEGL_ASSERT(okArena, "RecreateSwapchain: buffer manager transient arena initialization failed"); + if (m_frameContext.GetFrameCount() > 0) { + m_bufferManager.BeginFrame(m_frameContext.GetCurrentFrameIndex()); + m_transientVertexIndexBuffersThisFrame.clear(); + } } const PhysicalDevice& VulkanRenderer::GetPhysicalDevice() const { diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h index 3763cc9c..cc829d80 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h @@ -12,9 +12,10 @@ #include "PipelineFactory.h" #include "ProgramFactory.h" #include "SwapchainObject.h" -#include "UniformDescriptorBinder.h" +#include "UniformManager.h" #include "VertexInputStateFactory.h" #include "VkBufferObject.h" +#include "VkBufferManager.h" #include "VkClearManager.h" #include "VkRenderPassManager.h" #include "VkSamplerManager.h" @@ -34,25 +35,55 @@ namespace MobileGL::MG_State::GLState { namespace MobileGL::MG_Backend::DirectVulkan { enum class DrawSetupAspect: Uint8 { - FramebufferObject = 1 << 0, - VertexArrayObject = 1 << 1, - UniformBuffer = 1 << 2, - VertexBuffer = 1 << 3, - IndexBuffer = 1 << 4, - Viewport = 1 << 5, - Scissor = 1 << 6, + FramebufferObject = 1 << 0, + VertexArrayObject = 1 << 1, + UniformBuffer = 1 << 2, + VertexBuffer = 1 << 3, + IndexBuffer = 1 << 4, + IndirectDrawBuffer = 1 << 5, + Viewport = 1 << 6, + Scissor = 1 << 7, }; - struct DrawArrayCmd { + struct DrawCmdParam { + Uint32 vertexCount = 0; + Uint32 instanceCount = 1; + Uint32 firstVertex = 0; + Uint32 firstInstance = 0; + }; + + struct DrawIndexedCmdParam { + Uint32 indexCount = 0; + Uint32 instanceCount = 1; + Uint32 firstIndex = 0; + Int32 vertexOffset = 0; + Int32 firstInstance = 0; + }; + + struct DrawCmd { GLenum mode = GL_TRIANGLES; - GLint first = 0; - GLsizei count = 0; + DrawCmdParam params; }; - struct DrawElementCmd: public DrawArrayCmd { + struct IndexBufferView { GLenum indexType = GL_UNSIGNED_SHORT; SizeT indexByteOffset = 0; - GLint baseVertex = 0; + SizeT indexByteSize = 0; + }; + + struct DrawIndexedCmd { + GLenum mode = GL_TRIANGLES; + IndexBufferView indexBufferView; + + DrawIndexedCmdParam params; + }; + + struct MultiDrawIndexedCmd { + GLenum mode = GL_TRIANGLES; + IndexBufferView indexBufferView; + + Uint32 drawCount = 0; + DrawIndexedCmdParam* pParams = nullptr; }; struct QueueFamilyIndices { @@ -78,7 +109,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { void Initialize(); void Shutdown(); - void SetupDraw(FrameContext::FrameData& frame, GLenum mode, Flags aspects); + Bool SetupDraw(FrameContext::FrameData& frame, GLenum mode, Flags aspects, + const IndexBufferView* pIndexBufferView = nullptr); void ClearAttachmentsOnActiveRenderPass(VkCommandBuffer commandBuffer, const RenderPassEntry& compatibleRenderPassEntry); @@ -86,9 +118,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); - void DrawArrays(const DrawArrayCmd& payload); - void DrawElements(const DrawElementCmd& payload); - void MultiDrawElements(const Vector& payloads); + void DrawArrays(const DrawCmd& payload); + void DrawElements(const DrawIndexedCmd& payload); + void MultiDrawElements(const MultiDrawIndexedCmd& payloads); void Present(); const PhysicalDevice& GetPhysicalDevice() const; @@ -131,26 +163,24 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkQueue m_graphicsQueue = VK_NULL_HANDLE; VkQueue m_presentQueue = VK_NULL_HANDLE; Bool m_drawIndirectCountExtensionEnabled = false; + Bool m_indexTypeUint8ExtensionEnabled = false; using PFNDrawIndexedIndirectCountFunc = void(VKAPI_PTR*)(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset, VkBuffer countBuffer, VkDeviceSize countBufferOffset, Uint32 maxDrawCount, Uint32 stride); - PFNDrawIndexedIndirectCountFunc m_cmdDrawIndexedIndirectCount = nullptr; + static inline PFNDrawIndexedIndirectCountFunc s_vkCmdDrawIndexedIndirectCount = nullptr; VkCommandPool m_commandPool = VK_NULL_HANDLE; - Vector m_frameVertexUploadBuffers; - Vector m_frameVertexUploadHeads; - Vector m_frameIndexUploadBuffers; - Vector m_frameIndexUploadHeads; - Vector> m_deferredBufferReleases; + VkBufferManager m_bufferManager; + Vector m_transientVertexIndexBuffersThisFrame; Uint m_imageIndexAcquired = 0; FrameContext m_frameContext; UniquePtr m_pipelineFactory; UniquePtr m_programFactory; - UniquePtr m_uniformDescriptorBinder; + UniquePtr m_uniformManager; UniquePtr m_vertexInputStateFactory; UniquePtr m_clearManager; UniquePtr m_renderPassManager; @@ -169,7 +199,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { void DestroyAllocator(); void CreateSwapchain(); void CreateCommandPool(); - void CreateFrameContexts(); VkPipeline GetOrCreatePipeline( GLenum mode, @@ -177,11 +206,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { const MG_State::GLState::VertexArrayObject& vao, const RenderPassEntry& renderPassEntry); - void DeferDestroyBuffer(VkBufferObject& buffer); - void CollectDeferredBufferReleases(Uint32 frameIndex); - Bool EnsureFrameUploadBufferCapacity(Uint32 frameIndex, Bool isIndexBuffer, VkDeviceSize requiredEndOffset, - VkDeviceSize minCapacity, VkBufferUsageFlags usage); - Bool UploadAndBindVertexStreams(VkCommandBuffer commandBuffer, const MG_State::GLState::VertexArrayObject& vao); + Bool UploadAndBindVertexBuffers(VkCommandBuffer commandBuffer, const MG_State::GLState::VertexArrayObject& vao); + Bool UploadAndBindIndexBuffer(FrameContext::FrameData& frame, + const MG_State::GLState::VertexArrayObject& vao, + const IndexBufferView* pIndexBufferView = nullptr); Bool InitializeBlitResources(); void ShutdownBlitResources(); Bool TryBlitToDefaultFramebufferWithShader(FrameContext::FrameData& frame, @@ -216,8 +244,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { static Bool GetMoreCapablePhysicalDevice(VkPhysicalDevice newVkDevice, VkSurfaceKHR surface, const PhysicalDevice& compareWithDevice, PhysicalDevice& outBetterDevice); - static Uint64 BuildPendingClearKey(Uint drawFboExternalIndex, Bool targetsDefaultFramebuffer); - static constexpr VkDynamicState s_dynamicStates[] = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}; static constexpr const char* s_validationLayerNames[] = {"VK_LAYER_KHRONOS_validation"}; static constexpr const char* s_deviceExtensionNames[] = {VK_KHR_SWAPCHAIN_EXTENSION_NAME}; static Bool CheckValidationLayerSupport(); diff --git a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp index 11e5ed62..0edf6125 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp +++ b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp @@ -464,7 +464,7 @@ namespace MobileGL::MG_State::GLState { MGLOG_D("ProgramObject %u: GenerateBinary - parsing SPIR-V meta data for module %zu " "(shaderType=%u, wordCount=%zu)", m_externalIndex, i, shaderType, spv.size()); - SpvcSession session(spv); + SpvcSession session(spv, SessionUsageBit::Reflection); auto result = session.ParseMetaData(); if (result < 0) { MGLOG_D("ProgramObject %u: GenerateBinary - SpvcSession::ParseMetaData failed for module %zu, " @@ -473,16 +473,16 @@ namespace MobileGL::MG_State::GLState { (result == SPVC_ERROR_INVALID_SPIRV ? ". Probably no global UBO?" : "")); m_uniformSizesInBytes.clear(); m_uniformOffsets.clear(); - m_uboScratch.clear(); + m_globalUboScratch.clear(); continue; } else { auto& meta = session.GetMetadata(); - auto size = meta.uboSize; + auto size = meta.globalUboSize; MGLOG_D("ProgramObject %u: GenerateBinary - SPIR-V meta: uboSize=%zu plainUniformCount=%zu " "plainUniformOffsets=%zu", - m_externalIndex, meta.uboSize, meta.plainUniformMemberSizesInBytes.size(), + m_externalIndex, meta.globalUboSize, meta.plainUniformMemberSizesInBytes.size(), meta.plainUniformOffsetsInUBO.size()); - m_uboScratch.resize(size); + m_globalUboScratch.resize(size); m_uniformOffsets.resize(m_maxUniformLocation + 1); for (const auto& [name, offset] : meta.plainUniformOffsetsInUBO) { if (m_uniformLocations.find(name) != m_uniformLocations.end()) { diff --git a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h index 2a97f653..26f22bd9 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h +++ b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h @@ -65,9 +65,9 @@ namespace MobileGL::MG_State::GLState { } GLenum GetAttribType(Uint index) const { return m_attribTypes[index]; } const String& GetAttribName(Uint index) const { return m_attribs[index]; } - void* MapUBO() { return m_uboScratch.data(); } - const void* GetUBOData() const { return m_uboScratch.data(); } - Uint GetUBOSize() const { return static_cast(m_uboScratch.size()); } + void* MapUBO() { return m_globalUboScratch.data(); } + const void* GetUBOData() const { return m_globalUboScratch.data(); } + Uint GetUBOSize() const { return static_cast(m_globalUboScratch.size()); } void SetUniformSamplerOrImageUnitIndex(Uint location, Int unit) { m_uniformSamplerOrImageUnitIndex[location] = unit; @@ -121,9 +121,6 @@ namespace MobileGL::MG_State::GLState { Uint GetExternalIndex() const { return m_externalIndex; } - // const UnorderedMap& GetAttribLocationMap() const { return - // m_attribLocation; } - private: void DoReflection(); void GenerateBinary(); @@ -142,8 +139,6 @@ namespace MobileGL::MG_State::GLState { UnorderedMap m_explicitAttribLocations; Vector m_attribs; Vector m_attribTypes; - // For SpvcSession::SetVertexAttribLocation() - // UnorderedMap m_attribLocation; // FragData (Frag out) UnorderedMap m_explicitFragDataLocation; @@ -162,13 +157,15 @@ namespace MobileGL::MG_State::GLState { // Let's define UniformBlockIndex == the order at glslang getUniformBlock() // aka `i = glGetUniformBlockIndex(prog, "BlockName")` implies: // `prog->getUniformBlock(i) == "BlockName"` + // These stuff are present for GL semantics, not for backend inspection + // These may change after-link (because GL spec decided to have `glUniformBlockBinding`) UnorderedMap m_uniformBlockIndexByName; Vector m_uniformBlockBinding; // Need to be reflected after linking of SPIR-V binary Vector m_uniformOffsets; Vector m_uniformSizesInBytes; - Vector m_uboScratch; + Vector m_globalUboScratch; Uint m_activeUniformCount = 0; Uint m_maxUniformLocation = 0; diff --git a/MobileGL/MG_State/GLState/VertexArrayState/VertexArrayObject.cpp b/MobileGL/MG_State/GLState/VertexArrayState/VertexArrayObject.cpp index f5aad6dd..eca3eb92 100644 --- a/MobileGL/MG_State/GLState/VertexArrayState/VertexArrayObject.cpp +++ b/MobileGL/MG_State/GLState/VertexArrayState/VertexArrayObject.cpp @@ -85,6 +85,11 @@ namespace MobileGL::MG_State::GLState { return m_indexBufferBindingSlot; } + const BindingSlot& VertexArrayObject::GetIndexBufferBindingSlot() const { + return m_indexBufferBindingSlot; + } + + const VertexAttribute& VertexArrayObject::GetAttribute(Uint index) const { static VertexAttribute emptyAttr; if (index >= MAX_VERTEX_ATTRIBS) return emptyAttr; diff --git a/MobileGL/MG_State/GLState/VertexArrayState/VertexArrayObject.h b/MobileGL/MG_State/GLState/VertexArrayState/VertexArrayObject.h index 93f6aa47..b8087586 100644 --- a/MobileGL/MG_State/GLState/VertexArrayState/VertexArrayObject.h +++ b/MobileGL/MG_State/GLState/VertexArrayState/VertexArrayObject.h @@ -48,6 +48,7 @@ namespace MobileGL { void BindAttributeBuffer(Uint index, const SharedPtr& buffer); BindingSlot& GetIndexBufferBindingSlot(); + const BindingSlot& GetIndexBufferBindingSlot() const; const VertexAttribute& GetAttribute(Uint index) const; const Array& GetAllAttributes() const; diff --git a/MobileGL/MG_Test/Buffer/BufferTest.cpp b/MobileGL/MG_Test/Buffer/BufferTest.cpp index c065e451..9d724fcd 100644 --- a/MobileGL/MG_Test/Buffer/BufferTest.cpp +++ b/MobileGL/MG_Test/Buffer/BufferTest.cpp @@ -25,7 +25,8 @@ protected: }; TEST_F(BufferTest, Binding) { - auto bufferNames = MobileGL::MG_State::pGLContext->GenBufferNames(3); + Vector bufferNames; + MobileGL::MG_State::pGLContext->GenBufferNames(3, bufferNames); auto& arraySlot = MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Vertex); auto& indexSlot = MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Uniform); @@ -49,7 +50,8 @@ TEST_F(BufferTest, PingPong) { auto& readSlot = MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::CopyRead); auto& writeSlot = MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::CopyWrite); { - auto bufferNames = MobileGL::MG_State::pGLContext->GenBufferNames(1); + Vector bufferNames; + MobileGL::MG_State::pGLContext->GenBufferNames(1, bufferNames); auto bufObj = MobileGL::MG_State::pGLContext->CreateBufferObject(bufferNames[0]); writeSlot.Bind(bufObj); @@ -80,7 +82,9 @@ TEST_F(BufferTest, PingPong) { TEST_F(BufferTest, GenerateManyNames_NoPrematureCreation) { const SizeT largeCount = 100000; // generate tons of buffer names - auto names = MobileGL::MG_State::pGLContext->GenBufferNames(largeCount); + + Vector names; + MobileGL::MG_State::pGLContext->GenBufferNames(largeCount, names); std::vector indices = {0, 600, 5000, 32768, 99999}; // only create a few buffer objects for (SizeT idx : indices) { @@ -104,7 +108,8 @@ TEST_F(BufferTest, GenerateManyNames_NoPrematureCreation) { TEST_F(BufferTest, AcquireMemory) { auto& slot = MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Uniform); - auto bufferNames = MobileGL::MG_State::pGLContext->GenBufferNames(1); + Vector bufferNames; + MobileGL::MG_State::pGLContext->GenBufferNames(1, bufferNames); auto bufObj = MobileGL::MG_State::pGLContext->CreateBufferObject(bufferNames[0]); slot.Bind(bufObj); Vector initData{10, 20, 30, 40, 50}; @@ -132,7 +137,8 @@ TEST_F(BufferTest, AcquireMemory) { TEST_F(BufferTest, AcquireMemoryRangeWithoutExplicit) { auto& slot = MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Uniform); - auto bufferNames = MobileGL::MG_State::pGLContext->GenBufferNames(1); + Vector bufferNames; + MobileGL::MG_State::pGLContext->GenBufferNames(1, bufferNames); auto bufObj = MobileGL::MG_State::pGLContext->CreateBufferObject(bufferNames[0]); slot.Bind(bufObj); Vector initData{10, 20, 30, 40, 50}; @@ -160,7 +166,8 @@ TEST_F(BufferTest, AcquireMemoryRangeWithoutExplicit) { TEST_F(BufferTest, AcquireMemoryRangeWithExplicit) { auto& slot = MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Uniform); - auto bufferNames = MobileGL::MG_State::pGLContext->GenBufferNames(1); + Vector bufferNames; + MobileGL::MG_State::pGLContext->GenBufferNames(1, bufferNames); auto bufObj = MobileGL::MG_State::pGLContext->CreateBufferObject(bufferNames[0]); slot.Bind(bufObj); @@ -208,7 +215,8 @@ TEST_F(BufferTest, CopyBufferSubData) { auto& srcSlot = MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::CopyRead); auto& dstSlot = MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::CopyWrite); - auto srcNames = MobileGL::MG_State::pGLContext->GenBufferNames(1); + Vector srcNames; + MobileGL::MG_State::pGLContext->GenBufferNames(1, srcNames); auto srcObj = MobileGL::MG_State::pGLContext->CreateBufferObject(srcNames[0]); srcSlot.Bind(srcObj); @@ -218,7 +226,8 @@ TEST_F(BufferTest, CopyBufferSubData) { DataPtr srcPtr{.data = srcData.data(), .size = srcSize}; srcObj->UploadData(srcPtr, 0); - auto dstNames = MobileGL::MG_State::pGLContext->GenBufferNames(1); + Vector dstNames; + MobileGL::MG_State::pGLContext->GenBufferNames(1, dstNames); auto dstObj = MobileGL::MG_State::pGLContext->CreateBufferObject(dstNames[0]); dstSlot.Bind(dstObj); @@ -249,7 +258,8 @@ TEST_F(BufferTest, CopyBufferSubData) { TEST_F(BufferTest, WriteWhileMapped) { auto& slot = MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::ShaderStorage); - auto bufferNames = MobileGL::MG_State::pGLContext->GenBufferNames(1); + Vector bufferNames; + MobileGL::MG_State::pGLContext->GenBufferNames(1, bufferNames); auto bufObj = MobileGL::MG_State::pGLContext->CreateBufferObject(bufferNames[0]); slot.Bind(bufObj); @@ -280,7 +290,8 @@ TEST_F(BufferTest, WriteWhileMapped) { TEST_F(BufferTest, PartialUpdate) { auto& slot = MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Vertex); - auto bufferNames = MobileGL::MG_State::pGLContext->GenBufferNames(1); + Vector bufferNames; + MobileGL::MG_State::pGLContext->GenBufferNames(1, bufferNames); auto bufObj = MobileGL::MG_State::pGLContext->CreateBufferObject(bufferNames[0]); slot.Bind(bufObj); @@ -308,7 +319,8 @@ TEST_F(BufferTest, PartialUpdate) { } TEST_F(BufferTest, DeleteBufferObject) { - auto bufferNames = MobileGL::MG_State::pGLContext->GenBufferNames(1); + Vector bufferNames; + MobileGL::MG_State::pGLContext->GenBufferNames(1, bufferNames); auto& slot = MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Vertex); auto bufObj = MobileGL::MG_State::pGLContext->CreateBufferObject(bufferNames[0]); slot.Bind(bufObj); @@ -322,7 +334,7 @@ using namespace MobileGL::MG_Impl::GLImpl; class GeneralBufferTest : public ::testing::Test { protected: - void SetUp() override { MG_State::pGLContext = new MG_State::GLState::GLContext(); } + void SetUp() override { MG_State::pGLContext = MakeUnique(); } GLuint CreateBoundBuffer(GLenum target, GLsizeiptr size, GLenum usage) { GLuint buffer; diff --git a/MobileGL/MG_Test/Program/ProgramTest.cpp b/MobileGL/MG_Test/Program/ProgramTest.cpp index 1da44ad5..7f9f631e 100644 --- a/MobileGL/MG_Test/Program/ProgramTest.cpp +++ b/MobileGL/MG_Test/Program/ProgramTest.cpp @@ -191,7 +191,7 @@ TEST_F(ProgramTest, CompileAndLink) { String source; auto& spirvCode = shaderSpirvs[index]; - MG_Util::ShaderTranspiler::SpvcSession spvcSession(spirvCode); + MG_Util::ShaderTranspiler::SpvcSession spvcSession(spirvCode, MG_Util::ShaderTranspiler::SessionUsageBit::Transpile); spvc_compiler_options options; spvcSession.CreateOptions(&options); @@ -926,7 +926,7 @@ TEST_F(ProgramTest, CompileAndLinkWithExplicitVertexIn) { char* pSrcVertIn = nullptr; const char* needle = "layout(location = 2) in vec2 UV0;"; for (auto spirv : spirvs) { - MG_Util::ShaderTranspiler::SpvcSession spvcSession(spirv); + MG_Util::ShaderTranspiler::SpvcSession spvcSession(spirv, MG_Util::ShaderTranspiler::SessionUsageBit::Transpile); spvc_compiler_options options; spvcSession.CreateOptions(&options); @@ -987,7 +987,7 @@ TEST_F(ProgramTest, CompileAndLinkWithExplicitFragmentOut) { char* pSrcfragOut = nullptr; const char* needle = "layout(location = 7) out vec4 fragColor;"; // for (auto spirv: spirvs) { - MG_Util::ShaderTranspiler::SpvcSession spvcSession(fragSpirv); + MG_Util::ShaderTranspiler::SpvcSession spvcSession(fragSpirv, MG_Util::ShaderTranspiler::SessionUsageBit::Transpile); spvc_compiler_options options; spvcSession.CreateOptions(&options); @@ -1063,6 +1063,7 @@ float fog_cylindrical_distance(vec3 pos) { return max(distXZ, distY); } +uniform float fTime; layout(std140) uniform Globals { ivec3 CameraBlockPos; @@ -1165,7 +1166,7 @@ vec4 sampleRGSS(sampler2D source, vec2 uv, vec2 pixelSize) { void main() { vec4 color = (UseRgss == 1 ? sampleRGSS(Sampler0, texCoord0, 1.0f / TextureSize) : sampleNearest(Sampler0, texCoord0, 1.0f / TextureSize)) * vertexColor; - color = mix(FogColor * vec4(1, 1, 1, color.a), color, ChunkVisibility); + color = mix(FogColor * vec4(1, 1, 1, color.a * fTime), color, ChunkVisibility); #ifdef ALPHA_CUTOUT if (color.a < ALPHA_CUTOUT) { discard; @@ -1207,9 +1208,8 @@ TEST_F(ProgramTest, CompileShaderWithSamplerAsVarName) { auto programObject = MG_State::pGLContext->GetCurrentProgram(); auto& spirvs = programObject->GetGeneratedSpirv(); - auto& fragSpirv = spirvs[0]; // 0 - fragment, 1 - vertex - char* pSrcfragOut = nullptr; - MG_Util::ShaderTranspiler::SpvcSession spvcSession(fragSpirv); + auto& fragSpirv = spirvs[programObject->GetShaderIndexByStage(ShaderStage::Fragment)]; + MG_Util::ShaderTranspiler::SpvcSession spvcSession(fragSpirv, MG_Util::ShaderTranspiler::SessionUsageBit::Transpile); spvc_compiler_options options; spvcSession.CreateOptions(&options); @@ -1221,5 +1221,5 @@ TEST_F(ProgramTest, CompileShaderWithSamplerAsVarName) { const char* result = nullptr; spvcSession.Compile(&result); - printf("%s\n\n", result); + printf("decomp from fragSpirv:\n%s\n\n", result); } diff --git a/MobileGL/MG_Test/Program/ProgramUtilTest.cpp b/MobileGL/MG_Test/Program/ProgramUtilTest.cpp index 88068a54..f507e468 100644 --- a/MobileGL/MG_Test/Program/ProgramUtilTest.cpp +++ b/MobileGL/MG_Test/Program/ProgramUtilTest.cpp @@ -153,7 +153,7 @@ TEST_F(ProgramUtilTest, CompileFragmentShaderWithDiscard) { Vector sessions(spirvs.size()); for (SizeT i = 0; i < spirvs.size(); ++i) { - sessions[i] = SpvcSession(spirvs[i]); + sessions[i] = SpvcSession(spirvs[i], SessionUsageBit::Transpile); } for (SizeT i = 0; i < spirvs.size(); ++i) { @@ -278,7 +278,7 @@ TEST_F(ProgramUtilTest, DecompProgram) { Vector sessions(spirvs.size()); for (SizeT i = 0; i < spirvs.size(); ++i) { - sessions[i] = SpvcSession(spirvs[i]); + sessions[i] = SpvcSession(spirvs[i], SessionUsageBit::Transpile); } for (SizeT i = 0; i < spirvs.size(); ++i) { @@ -439,7 +439,7 @@ TEST_F(ProgramUtilTest, CompileAndLinkBlitProgram) { auto spirvs = bin_res.value(); Vector sessions(spirvs.size()); for (SizeT i = 0; i < spirvs.size(); ++i) { - sessions[i] = SpvcSession(spirvs[i]); + sessions[i] = SpvcSession(spirvs[i], SessionUsageBit::Transpile); } for (SizeT i = 0; i < spirvs.size(); ++i) { diff --git a/MobileGL/MG_Test/VertexArray/VertexArrayTest.cpp b/MobileGL/MG_Test/VertexArray/VertexArrayTest.cpp index 49714eb7..26c2f425 100644 --- a/MobileGL/MG_Test/VertexArray/VertexArrayTest.cpp +++ b/MobileGL/MG_Test/VertexArray/VertexArrayTest.cpp @@ -21,7 +21,8 @@ using namespace MobileGL; class VertexArrayTest : public ::testing::Test { protected: SharedPtr CreateTestVBO() { - auto bufferNames = MobileGL::MG_State::pGLContext->GenBufferNames(1); + Vector bufferNames; + MobileGL::MG_State::pGLContext->GenBufferNames(1, bufferNames); auto vbo = MobileGL::MG_State::pGLContext->CreateBufferObject(bufferNames[0]); MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Vertex).Bind(vbo); @@ -39,7 +40,8 @@ protected: }; TEST_F(VertexArrayTest, GenerateAndBindVAO) { - auto vaoNames = MobileGL::MG_State::pGLContext->GenVertexArrayNames(2); + Vector vaoNames; + MobileGL::MG_State::pGLContext->GenVertexArrayNames(2, vaoNames); auto vao0 = MobileGL::MG_State::pGLContext->CreateVertexArrayObject(vaoNames[0]); auto vao1 = MobileGL::MG_State::pGLContext->CreateVertexArrayObject(vaoNames[1]); @@ -55,7 +57,8 @@ TEST_F(VertexArrayTest, GenerateAndBindVAO) { } TEST_F(VertexArrayTest, VertexAttributeSetup) { - auto vaoNames = MobileGL::MG_State::pGLContext->GenVertexArrayNames(1); + Vector vaoNames; + MobileGL::MG_State::pGLContext->GenVertexArrayNames(1, vaoNames); auto vao = MobileGL::MG_State::pGLContext->CreateVertexArrayObject(vaoNames[0]); MobileGL::MG_State::pGLContext->BindVertexArray(vaoNames[0]); @@ -86,11 +89,13 @@ TEST_F(VertexArrayTest, VertexAttributeSetup) { } TEST_F(VertexArrayTest, IndexBufferBinding) { - auto vaoNames = MobileGL::MG_State::pGLContext->GenVertexArrayNames(1); + Vector vaoNames; + MobileGL::MG_State::pGLContext->GenVertexArrayNames(1, vaoNames); auto vao = MobileGL::MG_State::pGLContext->CreateVertexArrayObject(vaoNames[0]); MobileGL::MG_State::pGLContext->BindVertexArray(vaoNames[0]); - auto bufferNames = MobileGL::MG_State::pGLContext->GenBufferNames(1); + Vector bufferNames; + MobileGL::MG_State::pGLContext->GenBufferNames(1, bufferNames); auto ebo = MobileGL::MG_State::pGLContext->CreateBufferObject(bufferNames[0]); MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Index).Bind(ebo); @@ -103,14 +108,16 @@ TEST_F(VertexArrayTest, IndexBufferBinding) { MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Index).Bind(ebo); ASSERT_EQ(MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Index).GetBoundObject(), ebo); - auto newEboNames = MobileGL::MG_State::pGLContext->GenBufferNames(1); + Vector newEboNames; + MobileGL::MG_State::pGLContext->GenBufferNames(1, newEboNames); auto newEbo = MobileGL::MG_State::pGLContext->CreateBufferObject(newEboNames[0]); MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Index).Bind(newEbo); ASSERT_EQ(MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Index).GetBoundObject(), newEbo); } TEST_F(VertexArrayTest, DeleteVAO) { - auto vaoNames = MobileGL::MG_State::pGLContext->GenVertexArrayNames(1); + Vector vaoNames; + MobileGL::MG_State::pGLContext->GenVertexArrayNames(1, vaoNames); auto vao = MobileGL::MG_State::pGLContext->CreateVertexArrayObject(vaoNames[0]); MobileGL::MG_State::pGLContext->BindVertexArray(vaoNames[0]); @@ -125,7 +132,8 @@ TEST_F(VertexArrayTest, DeleteVAO) { TEST_F(VertexArrayTest, ValidateNamesAndObjects) { const Uint count = 5; - auto vaoNames = MobileGL::MG_State::pGLContext->GenVertexArrayNames(count); + Vector vaoNames; + MobileGL::MG_State::pGLContext->GenVertexArrayNames(count, vaoNames); for (Uint i = 0; i < count; i++) { ASSERT_TRUE(MobileGL::MG_State::pGLContext->ValidateVertexArrayName(vaoNames[i])); @@ -144,12 +152,14 @@ TEST_F(VertexArrayTest, ValidateNamesAndObjects) { } TEST_F(VertexArrayTest, MultipleAttributes) { - auto vaoNames = MobileGL::MG_State::pGLContext->GenVertexArrayNames(1); + Vector vaoNames; + MobileGL::MG_State::pGLContext->GenVertexArrayNames(1, vaoNames); auto vao = MobileGL::MG_State::pGLContext->CreateVertexArrayObject(vaoNames[0]); MobileGL::MG_State::pGLContext->BindVertexArray(vaoNames[0]); auto vboPos = CreateTestVBO(); - auto vboNormalNames = MobileGL::MG_State::pGLContext->GenBufferNames(1); + Vector vboNormalNames; + MobileGL::MG_State::pGLContext->GenBufferNames(1, vboNormalNames); auto vboNormal = MobileGL::MG_State::pGLContext->CreateBufferObject(vboNormalNames[0]); Vector normals(12, 0.5f); @@ -187,7 +197,8 @@ TEST_F(VertexArrayTest, MultipleAttributes) { } TEST_F(VertexArrayTest, BoundVAOPreservesState) { - auto vaoNames = MobileGL::MG_State::pGLContext->GenVertexArrayNames(2); + Vector vaoNames; + MobileGL::MG_State::pGLContext->GenVertexArrayNames(2, vaoNames); auto vao1 = MobileGL::MG_State::pGLContext->CreateVertexArrayObject(vaoNames[0]); auto vao2 = MobileGL::MG_State::pGLContext->CreateVertexArrayObject(vaoNames[1]); @@ -216,11 +227,9 @@ using namespace MobileGL::MG_Impl::GLImpl; class GeneralVertexArrayTest : public ::testing::Test { protected: - void SetUp() override { MG_State::pGLContext = new MG_State::GLState::GLContext(); } + void SetUp() override { MG_State::pGLContext = MakeUnique(); } void TearDown() override { - delete MG_State::pGLContext; - MG_State::pGLContext = nullptr; } GLuint CreateVAO() { diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpvcSession.cpp b/MobileGL/MG_Util/ShaderTranspiler/SpvcSession.cpp index 6caa19ad..7c31ecb0 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpvcSession.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/SpvcSession.cpp @@ -11,65 +11,201 @@ namespace MobileGL { namespace MG_Util { namespace ShaderTranspiler { - SpvcSession::SpvcSession(const Vector& spirv) { - const SpvId* p_spirv = spirv.data(); - size_t word_count = spirv.size(); - spvc_context_create(&context); - spvc_context_parse_spirv(context, p_spirv, word_count, &ir); - spvc_context_create_compiler(context, SPVC_BACKEND_GLSL, ir, SPVC_CAPTURE_MODE_TAKE_OWNERSHIP, - &compiler); - spvc_compiler_create_shader_resources(compiler, &resources); + static spvc_basetype MapReflectToSpvcBasetype(const SpvReflectBlockVariable& member) { + if (!member.type_description) return SPVC_BASETYPE_UNKNOWN; + auto flags = member.type_description->type_flags; + auto width = member.numeric.scalar.width; + auto signedness = member.numeric.scalar.signedness; + if (flags & SPV_REFLECT_TYPE_FLAG_FLOAT) { + switch (width) { + case 16: return SPVC_BASETYPE_FP16; + case 32: return SPVC_BASETYPE_FP32; + case 64: return SPVC_BASETYPE_FP64; + default: return SPVC_BASETYPE_UNKNOWN; + } + } else if (flags & SPV_REFLECT_TYPE_FLAG_INT) { + if (signedness) { + switch (width) { + case 8: return SPVC_BASETYPE_INT8; + case 16: return SPVC_BASETYPE_INT16; + case 32: return SPVC_BASETYPE_INT32; + case 64: return SPVC_BASETYPE_INT64; + default: return SPVC_BASETYPE_UNKNOWN; + } + } else { + switch (width) { + case 8: return SPVC_BASETYPE_UINT8; + case 16: return SPVC_BASETYPE_UINT16; + case 32: return SPVC_BASETYPE_UINT32; + case 64: return SPVC_BASETYPE_UINT64; + default: return SPVC_BASETYPE_UNKNOWN; + } + } + } else if (flags & SPV_REFLECT_TYPE_FLAG_BOOL) { + return SPVC_BASETYPE_BOOLEAN; + } + return SPVC_BASETYPE_UNKNOWN; + } + + SpvcSession::SpvcSession(const Vector& spirv, Flags usage) + : usage(usage) { + if (usage & SessionUsageBit::Transpile) { + const SpvId* p_spirv = spirv.data(); + size_t word_count = spirv.size(); + + spvc_context_create(&context); + spvc_context_parse_spirv(context, p_spirv, word_count, &ir); + spvc_context_create_compiler(context, SPVC_BACKEND_GLSL, ir, SPVC_CAPTURE_MODE_TAKE_OWNERSHIP, + &compiler); + spvc_compiler_create_shader_resources(compiler, &resources); + } else if (usage & SessionUsageBit::Reflection) { + SpvReflectResult result = spvReflectCreateShaderModule( + spirv.size() * sizeof(uint32_t), spirv.data(), &reflectModule); + reflectModuleValid = (result == SPV_REFLECT_RESULT_SUCCESS); + } } SpvcSession::SpvcSession(SpvcSession&& that) { + std::swap(this->usage, that.usage); std::swap(this->context, that.context); std::swap(this->compiler, that.compiler); std::swap(this->ir, that.ir); std::swap(this->compiler_options, that.compiler_options); std::swap(this->resources, that.resources); + std::swap(this->reflectModule, that.reflectModule); + std::swap(this->reflectModuleValid, that.reflectModuleValid); } SpvcSession& SpvcSession::operator=(SpvcSession&& that) { + std::swap(this->usage, that.usage); std::swap(this->context, that.context); std::swap(this->compiler, that.compiler); std::swap(this->ir, that.ir); std::swap(this->compiler_options, that.compiler_options); std::swap(this->resources, that.resources); + std::swap(this->reflectModule, that.reflectModule); + std::swap(this->reflectModuleValid, that.reflectModuleValid); return *this; } spvc_result SpvcSession::CreateOptions(spvc_compiler_options* options) { + if (!(usage & SessionUsageBit::Transpile)) return SPVC_ERROR_INVALID_ARGUMENT; return spvc_compiler_create_compiler_options(compiler, options); } spvc_result SpvcSession::SetOptions(spvc_compiler_options options) { + if (!(usage & SessionUsageBit::Transpile)) return SPVC_ERROR_INVALID_ARGUMENT; compiler_options = options; return spvc_compiler_install_compiler_options(compiler, options); } Vector SpvcSession::GetShaderInterface(spvc_resource_type resource_type) const { - const spvc_reflected_resource* list = nullptr; - size_t count = 0; - spvc_resources_get_resource_list_for_type(resources, resource_type, &list, &count); + if (usage & SessionUsageBit::Transpile) { + // SPIRV-Cross path + const spvc_reflected_resource* list = nullptr; + size_t count = 0; + spvc_resources_get_resource_list_for_type(resources, resource_type, &list, &count); + + Vector variables; + for (size_t i = 0; i < count; ++i) { + if (spvc_compiler_has_decoration(compiler, list[i].id, SpvDecorationBuiltIn)) { + continue; + } + + InterfaceVariable var; + var.name = list[i].name; + var.location = spvc_compiler_get_decoration(compiler, list[i].id, SpvDecorationLocation); + variables.push_back(var); + } + std::sort(variables.begin(), variables.end()); + return variables; + } + + // SPIRV-Reflect path (Reflection only, no Transpile) + if (!reflectModuleValid) return {}; Vector variables; - for (size_t i = 0; i < count; ++i) { - if (spvc_compiler_has_decoration(compiler, list[i].id, SpvDecorationBuiltIn)) { - continue; + switch (resource_type) { + case SPVC_RESOURCE_TYPE_STAGE_INPUT: { + uint32_t count = 0; + spvReflectEnumerateInputVariables(&reflectModule, &count, nullptr); + Vector vars(count); + spvReflectEnumerateInputVariables(&reflectModule, &count, vars.data()); + for (uint32_t i = 0; i < count; ++i) { + if (vars[i]->decoration_flags & SPV_REFLECT_DECORATION_BUILT_IN) continue; + InterfaceVariable var; + var.name = vars[i]->name; + var.location = vars[i]->location; + variables.push_back(var); } - - InterfaceVariable var; - var.name = list[i].name; - var.location = spvc_compiler_get_decoration(compiler, list[i].id, SpvDecorationLocation); - variables.push_back(var); + break; + } + case SPVC_RESOURCE_TYPE_STAGE_OUTPUT: { + uint32_t count = 0; + spvReflectEnumerateOutputVariables(&reflectModule, &count, nullptr); + Vector vars(count); + spvReflectEnumerateOutputVariables(&reflectModule, &count, vars.data()); + for (uint32_t i = 0; i < count; ++i) { + if (vars[i]->decoration_flags & SPV_REFLECT_DECORATION_BUILT_IN) continue; + InterfaceVariable var; + var.name = vars[i]->name; + var.location = vars[i]->location; + variables.push_back(var); + } + break; + } + case SPVC_RESOURCE_TYPE_SAMPLED_IMAGE: { + uint32_t count = 0; + spvReflectEnumerateDescriptorBindings(&reflectModule, &count, nullptr); + Vector bindings(count); + spvReflectEnumerateDescriptorBindings(&reflectModule, &count, bindings.data()); + for (uint32_t i = 0; i < count; ++i) { + if (bindings[i]->descriptor_type == SPV_REFLECT_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER || + bindings[i]->descriptor_type == SPV_REFLECT_DESCRIPTOR_TYPE_SAMPLED_IMAGE) { + InterfaceVariable var; + var.name = bindings[i]->name; + var.location = bindings[i]->binding; + variables.push_back(var); + } + } + break; + } + case SPVC_RESOURCE_TYPE_UNIFORM_BUFFER: { + uint32_t count = 0; + spvReflectEnumerateDescriptorBindings(&reflectModule, &count, nullptr); + Vector bindings(count); + spvReflectEnumerateDescriptorBindings(&reflectModule, &count, bindings.data()); + for (uint32_t i = 0; i < count; ++i) { + if (bindings[i]->descriptor_type == SPV_REFLECT_DESCRIPTOR_TYPE_UNIFORM_BUFFER) { + InterfaceVariable var; + // Use the block/type name (e.g. "MGL_GLOBAL_UBO") rather than + // the variable name, which may be empty or meaningless for UBOs. + // This is consistent with ParseMetaData() which uses type_description->type_name. + if (bindings[i]->type_description && bindings[i]->type_description->type_name) { + var.name = bindings[i]->type_description->type_name; + } else { + var.name = bindings[i]->name; + } + var.location = bindings[i]->binding; + variables.push_back(var); + } + } + break; + } + case SPVC_RESOURCE_TYPE_GL_PLAIN_UNIFORM: + // GL plain uniforms are a SPIRV-Cross-specific concept. + // In reflection-only mode, not available. + break; + default: + break; } std::sort(variables.begin(), variables.end()); return variables; } spvc_result SpvcSession::SetVertexAttribLocation(const UnorderedMap& location) { - // TODO: We should assert we're really dealing with vertex shader here + if (!(usage & SessionUsageBit::Transpile)) return SPVC_ERROR_INVALID_ARGUMENT; SPVC_CHK_INIT const spvc_reflected_resource* list = nullptr; @@ -80,8 +216,6 @@ namespace MobileGL { auto& resource = list[i]; auto it = location.find(resource.name); if (it != location.end()) { - // realize glBindVertexAttribLocation here - // it->second should be the location explicitly requested spvc_compiler_set_decoration(compiler, resource.id, SpvDecorationLocation, it->second); } } @@ -89,59 +223,99 @@ namespace MobileGL { } spvc_result SpvcSession::Compile(const char** result) { + if (!(usage & SessionUsageBit::Transpile)) return SPVC_ERROR_INVALID_ARGUMENT; SPVC_CHK_INIT SPVC_CHK_RESULT(spvc_compiler_compile(compiler, result)); - // SPVC_CHK_RESULT(ParseMetaData()); SPVC_CHK_RETURN } spvc_result SpvcSession::ParseMetaData() { - SPVC_CHK_INIT + if (usage & SessionUsageBit::Transpile) { + // SPIRV-Cross path + SPVC_CHK_INIT + + metadata = SpvcMetadata(); + + const spvc_reflected_resource* list = nullptr; + size_t count = 0; + + SPVC_CHK_RESULT(spvc_resources_get_resource_list_for_type( + resources, SPVC_RESOURCE_TYPE_UNIFORM_BUFFER, &list, &count);) + for (size_t i = 0; i < count; ++i) { + if (spvc_compiler_has_decoration(compiler, list[i].id, SpvDecorationBuiltIn)) { + continue; + } + + if (strcmp(list[i].name, GLOBAL_UBO_NAME) == 0) { + spvc_type type = spvc_compiler_get_type_handle(compiler, list[i].base_type_id); + spvc_compiler_get_declared_struct_size(compiler, type, &metadata.globalUboSize); + size_t num_members = spvc_type_get_num_member_types(type); + for (size_t j = 0; j < num_members; ++j) { + const char* memberName = + spvc_compiler_get_member_name(compiler, list[i].base_type_id, j); + + unsigned memberOffset = 0; + SPVC_CHK_RESULT( + spvc_compiler_type_struct_member_offset(compiler, type, j, &memberOffset);) + metadata.plainUniformOffsetsInUBO[memberName] = memberOffset; + SizeT memberSize = 0; + SPVC_CHK_RESULT( + spvc_compiler_get_declared_struct_member_size(compiler, type, j, &memberSize);) + metadata.plainUniformMemberSizesInBytes[memberName] = memberSize; + + auto memberTypeId = spvc_type_get_member_type(type, j); + spvc_type memberType = spvc_compiler_get_type_handle(compiler, memberTypeId); + spvc_basetype basetype = spvc_type_get_basetype(memberType); + auto vectorSize = spvc_type_get_vector_size(memberType); + auto matCol = spvc_type_get_columns(memberType); + metadata.plainUniformMemberTypes[memberName] = { + .basetype = basetype, + .vectorSize = vectorSize, + .matCol = matCol, + }; + } + SPVC_CHK_RETURN + } + } + return SPVC_ERROR_INVALID_SPIRV; + } + + // SPIRV-Reflect path (Reflection only) + if (!reflectModuleValid) return SPVC_ERROR_INVALID_SPIRV; metadata = SpvcMetadata(); - const spvc_reflected_resource* list = nullptr; - size_t count = 0; + uint32_t bindingCount = 0; + spvReflectEnumerateDescriptorBindings(&reflectModule, &bindingCount, nullptr); + Vector bindings(bindingCount); + spvReflectEnumerateDescriptorBindings(&reflectModule, &bindingCount, bindings.data()); - SPVC_CHK_RESULT(spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_UNIFORM_BUFFER, - &list, &count);) - for (size_t i = 0; i < count; ++i) { - if (spvc_compiler_has_decoration(compiler, list[i].id, SpvDecorationBuiltIn)) { - continue; - } + for (uint32_t i = 0; i < bindingCount; ++i) { + auto* binding = bindings[i]; + if (binding->descriptor_type != SPV_REFLECT_DESCRIPTOR_TYPE_UNIFORM_BUFFER) continue; + if (strcmp(binding->type_description->type_name, GLOBAL_UBO_NAME) != 0) continue; - if (strcmp(list[i].name, GLOBAL_UBO_NAME) == 0) { - spvc_type type = spvc_compiler_get_type_handle(compiler, list[i].base_type_id); - spvc_compiler_get_declared_struct_size(compiler, type, &metadata.uboSize); - size_t num_members = spvc_type_get_num_member_types(type); - for (size_t j = 0; j < num_members; ++j) { - const char* memberName = spvc_compiler_get_member_name(compiler, list[i].base_type_id, j); + auto& block = binding->block; + metadata.globalUboSize = block.size; - unsigned memberOffset = 0; - SPVC_CHK_RESULT(spvc_compiler_type_struct_member_offset(compiler, type, j, &memberOffset);) - metadata.plainUniformOffsetsInUBO[memberName] = memberOffset; - SizeT memberSize = 0; - SPVC_CHK_RESULT( - spvc_compiler_get_declared_struct_member_size(compiler, type, j, &memberSize);) - metadata.plainUniformMemberSizesInBytes[memberName] = memberSize; + for (uint32_t j = 0; j < block.member_count; ++j) { + auto& member = block.members[j]; + metadata.plainUniformOffsetsInUBO[member.name] = member.offset; + metadata.plainUniformMemberSizesInBytes[member.name] = member.size; - auto memberTypeId = spvc_type_get_member_type(type, j); - spvc_type memberType = spvc_compiler_get_type_handle(compiler, memberTypeId); - spvc_basetype basetype = spvc_type_get_basetype(memberType); - auto vectorSize = spvc_type_get_vector_size(memberType); - auto matCol = spvc_type_get_columns(memberType); - // auto dim = spvc_type_get_num_array_dimensions(type); - metadata.plainUniformMemberTypes[memberName] = { - .basetype = basetype, - .vectorSize = vectorSize, - .matCol = matCol, - }; - } - SPVC_CHK_RETURN + Uint32 vectorSize = member.numeric.vector.component_count; + if (vectorSize == 0) vectorSize = 1; + Uint32 matCol = member.numeric.matrix.column_count; + if (matCol == 0) matCol = 1; + + metadata.plainUniformMemberTypes[member.name] = { + .basetype = MapReflectToSpvcBasetype(member), + .vectorSize = vectorSize, + .matCol = matCol, + }; } + return SPVC_SUCCESS; } - // This means this spv binary does not have - // auto-generated UBO in it return SPVC_ERROR_INVALID_SPIRV; } @@ -150,11 +324,17 @@ namespace MobileGL { } const char* SpvcSession::GetLastErrorString() const { - return spvc_context_get_last_error_string(context); + if (context) { + return spvc_context_get_last_error_string(context); + } + return ""; } SpvcSession::~SpvcSession() { spvc_context_destroy(context); + if (reflectModuleValid) { + spvReflectDestroyShaderModule(&reflectModule); + } } } // namespace ShaderTranspiler } // namespace MG_Util diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpvcSession.h b/MobileGL/MG_Util/ShaderTranspiler/SpvcSession.h index a552b291..7bbc406b 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpvcSession.h +++ b/MobileGL/MG_Util/ShaderTranspiler/SpvcSession.h @@ -8,6 +8,7 @@ #pragma once #include +#include #include "Types.h" #define SPVC_CHK_INIT auto __r = SPVC_SUCCESS; @@ -55,18 +56,24 @@ namespace MobileGL { } }; + enum class SessionUsageBit { + Reflection = 1 << 0, + Transpile = 1 << 1, + }; + struct SpvcMetadata { UnorderedMap plainUniformOffsetsInUBO; UnorderedMap plainUniformMemberSizesInBytes; UnorderedMap plainUniformMemberTypes; - SizeT uboSize = 0; + SizeT globalUboSize = 0; }; class SpvcSession { public: SpvcSession() {} - explicit SpvcSession(const Vector& spirv); + explicit SpvcSession(const Vector& spirv, + Flags usage); SpvcSession(SpvcSession&) = delete; @@ -90,12 +97,19 @@ namespace MobileGL { spvc_result ParseMetaData(); private: + Flags usage; + + // SPIRV-Cross state (used when Transpile flag is set) spvc_context context = nullptr; spvc_parsed_ir ir = nullptr; spvc_compiler compiler = nullptr; spvc_compiler_options compiler_options = nullptr; spvc_resources resources = nullptr; + // SPIRV-Reflect state (used when only Reflection flag is set) + SpvReflectShaderModule reflectModule = {}; + bool reflectModuleValid = false; + SpvcMetadata metadata; }; } // namespace ShaderTranspiler