From e82815802e83f4f7c6750c2b8080f92f83ffa91c Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Mon, 9 Mar 2026 15:06:23 +0800 Subject: [PATCH 01/31] [Feat] (MG_Backend/DirectVulkan): implement texture mipmap --- .../Renderer/UniformDescriptorBinder.cpp | 4 +- .../Renderer/VkRenderPassManager.cpp | 23 ++- .../Renderer/VkTextureManager.cpp | 139 +++++++++++++++--- .../DirectVulkan/Renderer/VkTextureManager.h | 36 ++++- .../DirectVulkan/Renderer/VulkanRenderer.cpp | 21 ++- 5 files changed, 177 insertions(+), 46 deletions(-) diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.cpp index 5d973ccc..9786233d 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.cpp @@ -532,7 +532,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { } outImageInfo = { .sampler = m_samplerManager->GetOrCreateSampler(*samplerToUse), - .imageView = resource->view, + .imageView = resource->fullView, .imageLayout = resource->layout, }; return outImageInfo.sampler != VK_NULL_HANDLE; @@ -554,7 +554,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { outImageInfo = { .sampler = m_samplerManager->GetOrCreateSampler(*samplerBindingOverride.sampler), - .imageView = resource->view, + .imageView = resource->fullView, .imageLayout = resource->layout, }; return outImageInfo.sampler != VK_NULL_HANDLE; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp index 97dbe669..ab53ddee 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(); @@ -158,6 +162,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 +195,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 +220,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 +256,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 +319,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..f3d55ba6 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -93,6 +93,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 +166,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 +186,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; } @@ -875,7 +882,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 +915,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()); @@ -1138,7 +1145,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 +1163,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}; From 8a91225eb13be5fc9f3200b127d8d97c47b7c117 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Mon, 9 Mar 2026 16:22:51 +0800 Subject: [PATCH 02/31] [Chore] (MG_Backend/DirectVulkan): debug log for clear manager --- .../DirectVulkan/Renderer/VkClearManager.cpp | 19 +++++++++++++++++++ .../DirectVulkan/Renderer/VulkanRenderer.cpp | 1 - 2 files changed, 19 insertions(+), 1 deletion(-) 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/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index f3d55ba6..752fe502 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -807,7 +807,6 @@ void main() { activeRenderPass = VkRenderPassManager::GetActiveRenderPass(); // Begin render pass, and handle clear - if (activeRenderPass && activeRenderPass->CompatibleWith(renderPassEntry)) { ClearAttachmentsOnActiveRenderPass(frame.commandBuffer, renderPassEntry); } else { From 9e42e40705929f178c91f126da07b345ea85e402 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Mon, 9 Mar 2026 17:13:56 +0800 Subject: [PATCH 03/31] [Chore] (MG_Backend/DirectVulkan): notes for `MaterializePendingClearForTexture` --- MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 752fe502..a1598ca7 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -733,6 +733,11 @@ void main() { } 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); MOBILEGL_ASSERT(hasSampledTextures, "%s: CollectSampledTextures failed", __func__); From 8b9adc5121f76326255ac9a753e8fcc0c21fcaf4 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Tue, 17 Mar 2026 14:34:50 +0800 Subject: [PATCH 04/31] [Fix] (MG_Backend/DirectVulkan/VkRenderPassManager): avoid reusing active render pass when draw FBO attachments still have pending clears --- .../Renderer/VkRenderPassManager.cpp | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp index ab53ddee..d67e1c59 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp @@ -117,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", From 87122973bed0fc0f44efb2fdd12bff2013ff822f Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Tue, 17 Mar 2026 15:10:17 +0800 Subject: [PATCH 05/31] [Chore] (MG_Backend/DirectVulkan): refactor draw cmds --- .../MG_Backend/DirectVulkan/DirectVulkan.cpp | 105 ++++++++++-------- .../DirectVulkan/Renderer/VulkanRenderer.cpp | 22 ++-- .../DirectVulkan/Renderer/VulkanRenderer.h | 30 +++-- 3 files changed, 94 insertions(+), 63 deletions(-) diff --git a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp index 3ec0a0fd..39af70f6 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,78 @@ 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.firstVertex = first; + payload.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.indexType = type; + payload.indexByteOffset = reinterpret_cast(indices); + payload.indexCount = count; + payload.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"); + + } + + 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"); + + } + 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/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index a1598ca7..5cb6e811 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -1191,7 +1191,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); @@ -1200,10 +1200,14 @@ void main() { VkCommandBuffer& commandBuffer = frame.commandBuffer; - vkCmdDraw(commandBuffer, static_cast(payload.count), 1, static_cast(payload.first), 0); + vkCmdDraw(commandBuffer, + payload.vertexCount, + payload.instanceCount, + payload.firstVertex, + payload.firstInstance); } - void VulkanRenderer::DrawElements(const DrawElementCmd& payload) { + void VulkanRenderer::DrawElements(const DrawIndexedCmd& payload) { auto& frame = m_frameContext.GetCurrent(); SetupDraw(frame, payload.mode, 0); @@ -1231,7 +1235,7 @@ void main() { 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; + const SizeT indexDataSizeBytes = static_cast(payload.indexCount) * indexSize; MOBILEGL_ASSERT(payload.indexByteOffset + indexDataSizeBytes <= indexBuffer->GetSize(), "DrawElements index range out of bounds"); @@ -1256,11 +1260,15 @@ void main() { 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.indexCount, + payload.instanceCount, + payload.firstIndex, + payload.vertexOffset, + payload.firstInstance); } - void VulkanRenderer::MultiDrawElements(const Vector& payloads) { + void VulkanRenderer::MultiDrawElements(const Vector& payloads) { } diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h index 3763cc9c..29df7585 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h @@ -43,16 +43,30 @@ namespace MobileGL::MG_Backend::DirectVulkan { Scissor = 1 << 6, }; - struct DrawArrayCmd { + struct DrawBaseCmd { GLenum mode = GL_TRIANGLES; - GLint first = 0; - GLsizei count = 0; }; - struct DrawElementCmd: public DrawArrayCmd { + struct DrawCmd: public DrawBaseCmd { + Uint32 vertexCount = 0; + Uint32 instanceCount = 1; + Uint32 firstVertex = 0; + Uint32 firstInstance = 0; + }; + + struct DrawIndexedCmd: public DrawBaseCmd { GLenum indexType = GL_UNSIGNED_SHORT; SizeT indexByteOffset = 0; - GLint baseVertex = 0; + + Uint32 indexCount = 0; + Uint32 instanceCount = 1; + Uint32 firstIndex = 0; + Int32 vertexOffset = 0; + Int32 firstInstance = 0; + }; + + struct MultiDrawElementsCmd { + }; struct QueueFamilyIndices { @@ -86,9 +100,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 Vector& payloads); void Present(); const PhysicalDevice& GetPhysicalDevice() const; From 09aeee4c0379e3300ed45b10e53439a8d99c9a2e Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Tue, 17 Mar 2026 15:55:16 +0800 Subject: [PATCH 06/31] [Chore] (MG_State/VertexArrayState): add const getter to index buffer binding slot --- .../MG_State/GLState/VertexArrayState/VertexArrayObject.cpp | 5 +++++ .../MG_State/GLState/VertexArrayState/VertexArrayObject.h | 1 + 2 files changed, 6 insertions(+) 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; From 75ea7f8c0c2a12f11ad04b53aee841d69bc38058 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Tue, 17 Mar 2026 16:09:02 +0800 Subject: [PATCH 07/31] [Chore] (MG_Backend/DirectVulkan): include index buffer upload into `SetupDraw` --- .../DirectVulkan/Renderer/VulkanRenderer.cpp | 116 +++++++++++------- .../DirectVulkan/Renderer/VulkanRenderer.h | 8 +- 2 files changed, 76 insertions(+), 48 deletions(-) diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 5cb6e811..44ff6df6 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -524,6 +524,57 @@ namespace MobileGL::MG_Backend::DirectVulkan { return true; } + Bool VulkanRenderer::UploadAndBindIndexBuffer(FrameContext::FrameData& frame, + const MG_State::GLState::VertexArrayObject& vao, + GLenum indexType, + SizeT indexByteOffset, + Uint32 indexCount) { + VkIndexType vkIndexType = VK_INDEX_TYPE_MAX_ENUM; + switch (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", indexType); + return false; + } + + const auto* indexBuffer = vao.GetIndexBufferBindingSlot().GetBoundObject().get(); + MOBILEGL_ASSERT(indexBuffer != nullptr, "UploadAndBindIndexBuffer requires bound EBO"); + const auto indexData = indexBuffer->GetDataReadOnly(); + MOBILEGL_ASSERT(indexData != nullptr && !indexData->empty(), "DrawElements requires non-empty EBO data"); + + const SizeT indexSize = (indexType == GL_UNSIGNED_SHORT) ? sizeof(Uint16) : sizeof(Uint32); + const SizeT indexDataSizeBytes = static_cast(indexCount) * indexSize; + MOBILEGL_ASSERT(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 false; + } + + auto& frameIndexUploadBuffer = m_frameIndexUploadBuffers[frameIndex]; + if (!frameIndexUploadBuffer.Upload(indexData->data() + indexByteOffset, + static_cast(indexDataSizeBytes), writeOffset)) { + MGLOG_E("DrawElements skipped: failed to upload index data"); + return false; + } + + frameIndexHead = writeEnd; + vkCmdBindIndexBuffer(frame.commandBuffer, frameIndexUploadBuffer.GetHandle(), writeOffset, vkIndexType); + return true; + } + Bool VulkanRenderer::InitializeBlitResources() { ShutdownBlitResources(); @@ -719,7 +770,8 @@ void main() { 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, + GLenum indexType, SizeT indexByteOffset, Uint32 indexCount) { m_textureManager->CollectGarbage(); const auto& drawFbo = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject(); @@ -828,7 +880,15 @@ void main() { m_uniformDescriptorBinder->BindProgramUniformBuffers(frame.commandBuffer, program, m_frameContext.GetCurrentFrameIndex()); - UploadAndBindVertexStreams(frame.commandBuffer, vao); + if (!UploadAndBindVertexStreams(frame.commandBuffer, vao)) { + MGLOG_E("SetupDraw skipped: failed to upload vertex streams"); + return false; + } + if (aspects & DrawSetupAspect::IndexBuffer) { + if (!UploadAndBindIndexBuffer(frame, vao, indexType, indexByteOffset, indexCount)) { + return false; + } + } VkViewport viewport{}; viewport.x = 0.0f; @@ -850,6 +910,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) { @@ -1194,7 +1255,9 @@ void main() { void VulkanRenderer::DrawArrays(const DrawCmd& payload) { auto& frame = m_frameContext.GetCurrent(); - SetupDraw(frame, payload.mode, 0); + if (!SetupDraw(frame, payload.mode, 0)) { + return; + } MOBILEGL_ASSERT(frame.isCommandRecording, "%s: frame recording was not started", __func__); @@ -1210,56 +1273,15 @@ void main() { void VulkanRenderer::DrawElements(const DrawIndexedCmd& payload) { auto& frame = m_frameContext.GetCurrent(); - SetupDraw(frame, payload.mode, 0); - - if (!frame.isCommandRecording) { - MGLOG_D("DrawElements skipped: frame recording was not started"); + if (!SetupDraw(frame, payload.mode, DrawSetupAspect::IndexBuffer, + payload.indexType, payload.indexByteOffset, payload.indexCount)) { 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.indexCount) * 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, payload.indexCount, payload.instanceCount, diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h index 29df7585..cd29223c 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h @@ -92,7 +92,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, + GLenum indexType = 0, SizeT indexByteOffset = 0, Uint32 indexCount = 0); void ClearAttachmentsOnActiveRenderPass(VkCommandBuffer commandBuffer, const RenderPassEntry& compatibleRenderPassEntry); @@ -196,6 +197,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { Bool EnsureFrameUploadBufferCapacity(Uint32 frameIndex, Bool isIndexBuffer, VkDeviceSize requiredEndOffset, VkDeviceSize minCapacity, VkBufferUsageFlags usage); Bool UploadAndBindVertexStreams(VkCommandBuffer commandBuffer, const MG_State::GLState::VertexArrayObject& vao); + Bool UploadAndBindIndexBuffer(FrameContext::FrameData& frame, + const MG_State::GLState::VertexArrayObject& vao, + GLenum indexType, + SizeT indexByteOffset, + Uint32 indexCount); Bool InitializeBlitResources(); void ShutdownBlitResources(); Bool TryBlitToDefaultFramebufferWithShader(FrameContext::FrameData& frame, From a654b15190cdbc8ff21c24a5a40bc0dcdb15619c Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Wed, 18 Mar 2026 10:43:30 +0800 Subject: [PATCH 08/31] [Feat] (MG_Backend/DirectVulkan): naively implement `DrawElementsBaseVertex` and `MultiDrawElementsBaseVertex` --- .../MG_Backend/DirectVulkan/DirectVulkan.cpp | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp index 39af70f6..0d7fedfc 100644 --- a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp @@ -105,14 +105,29 @@ namespace MobileGL::MG_Backend::DirectVulkan { 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.indexType = type; + payload.indexByteOffset = reinterpret_cast(indices); + payload.indexCount = count; + payload.instanceCount = 1; + payload.firstIndex = 0; + payload.vertexOffset = basevertex; + payload.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"); - + // TODO: properly batch the draw calls + for (GLsizei i = 0; i < drawcount; ++i) { + if (count[i] == 0) { + continue; + } + DrawElementsBaseVertex(mode, count[i], type, indices[i], basevertex[i]); + } } void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, From b324363db00c1dfd91ef1b83f4de1f9c3a29f273 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Wed, 18 Mar 2026 11:01:00 +0800 Subject: [PATCH 09/31] [Chore] (MG_Backend/DirectVulkan): hard assert `SetupDraw` failure --- .../DirectVulkan/Renderer/VulkanRenderer.cpp | 25 +++++++------------ .../DirectVulkan/Renderer/VulkanRenderer.h | 2 +- 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 44ff6df6..f487b7fd 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -770,7 +770,7 @@ void main() { return m_pipelineFactory->GetOrCreatePipeline(payload); } - Bool VulkanRenderer::SetupDraw(FrameContext::FrameData& frame, GLenum mode, Flags aspects, + void VulkanRenderer::SetupDraw(FrameContext::FrameData& frame, GLenum mode, Flags aspects, GLenum indexType, SizeT indexByteOffset, Uint32 indexCount) { m_textureManager->CollectGarbage(); const auto& drawFbo = @@ -880,14 +880,12 @@ void main() { m_uniformDescriptorBinder->BindProgramUniformBuffers(frame.commandBuffer, program, m_frameContext.GetCurrentFrameIndex()); - if (!UploadAndBindVertexStreams(frame.commandBuffer, vao)) { - MGLOG_E("SetupDraw skipped: failed to upload vertex streams"); - return false; - } + auto vtxUploadOk = UploadAndBindVertexStreams(frame.commandBuffer, vao); + MOBILEGL_ASSERT(vtxUploadOk, "SetupDraw skipped: failed to upload vertex streams"); + if (aspects & DrawSetupAspect::IndexBuffer) { - if (!UploadAndBindIndexBuffer(frame, vao, indexType, indexByteOffset, indexCount)) { - return false; - } + auto idxUploadOk = UploadAndBindIndexBuffer(frame, vao, indexType, indexByteOffset, indexCount); + MOBILEGL_ASSERT(idxUploadOk, "SetupDraw skipped: failed to upload index buffer"); } VkViewport viewport{}; @@ -910,7 +908,6 @@ 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) { @@ -1255,9 +1252,7 @@ void main() { void VulkanRenderer::DrawArrays(const DrawCmd& payload) { auto& frame = m_frameContext.GetCurrent(); - if (!SetupDraw(frame, payload.mode, 0)) { - return; - } + SetupDraw(frame, payload.mode, 0); MOBILEGL_ASSERT(frame.isCommandRecording, "%s: frame recording was not started", __func__); @@ -1273,10 +1268,8 @@ void main() { void VulkanRenderer::DrawElements(const DrawIndexedCmd& payload) { auto& frame = m_frameContext.GetCurrent(); - if (!SetupDraw(frame, payload.mode, DrawSetupAspect::IndexBuffer, - payload.indexType, payload.indexByteOffset, payload.indexCount)) { - return; - } + SetupDraw(frame, payload.mode, DrawSetupAspect::IndexBuffer, + payload.indexType, payload.indexByteOffset, payload.indexCount); MOBILEGL_ASSERT(frame.isCommandRecording, "%s: frame recording was not started", __func__); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h index cd29223c..ae5a4bb5 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h @@ -92,7 +92,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { void Initialize(); void Shutdown(); - Bool SetupDraw(FrameContext::FrameData& frame, GLenum mode, Flags aspects, + void SetupDraw(FrameContext::FrameData& frame, GLenum mode, Flags aspects, GLenum indexType = 0, SizeT indexByteOffset = 0, Uint32 indexCount = 0); void ClearAttachmentsOnActiveRenderPass(VkCommandBuffer commandBuffer, const RenderPassEntry& compatibleRenderPassEntry); From 1e11a5950e474d915b14ddf17a1bf49c0359ea5b Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Wed, 18 Mar 2026 11:07:56 +0800 Subject: [PATCH 10/31] [Fix] (MG_Test/BufferTest): fix BufferTest compilation error --- MobileGL/MG_Test/Buffer/BufferTest.cpp | 36 +++++++++++++++++--------- 1 file changed, 24 insertions(+), 12 deletions(-) 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; From 2ecba4d70cf21aeb5f2473865aaf45c483d943eb Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Wed, 18 Mar 2026 13:07:57 +0800 Subject: [PATCH 11/31] [Fix] (MG_Test/VertexArrayTest): fix VertexArrayTest compilation error --- .../MG_Test/VertexArray/VertexArrayTest.cpp | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) 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() { From bc018c9513535eaef407c30da55e8a3aa857a5bb Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Wed, 18 Mar 2026 13:25:10 +0800 Subject: [PATCH 12/31] [Chore] (MG_Backend/DirectVulkan): add `indexBufferView` field for draw cmds --- MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp | 8 ++++---- .../MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp | 2 +- .../MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h | 6 +++++- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp index 0d7fedfc..c4cadc97 100644 --- a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp @@ -67,8 +67,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { DrawIndexedCmd payload{}; payload.mode = mode; - payload.indexType = type; - payload.indexByteOffset = reinterpret_cast(indices); + payload.indexBufferView.indexType = type; + payload.indexBufferView.indexByteOffset = reinterpret_cast(indices); payload.indexCount = count; payload.instanceCount = 1; @@ -107,8 +107,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::DrawElementsBaseVertex called with null GL context"); DrawIndexedCmd payload{}; payload.mode = mode; - payload.indexType = type; - payload.indexByteOffset = reinterpret_cast(indices); + payload.indexBufferView.indexType = type; + payload.indexBufferView.indexByteOffset = reinterpret_cast(indices); payload.indexCount = count; payload.instanceCount = 1; payload.firstIndex = 0; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index f487b7fd..2650cde1 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -1269,7 +1269,7 @@ void main() { auto& frame = m_frameContext.GetCurrent(); SetupDraw(frame, payload.mode, DrawSetupAspect::IndexBuffer, - payload.indexType, payload.indexByteOffset, payload.indexCount); + payload.indexBufferView.indexType, payload.indexBufferView.indexByteOffset, payload.indexCount); MOBILEGL_ASSERT(frame.isCommandRecording, "%s: frame recording was not started", __func__); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h index ae5a4bb5..f205a544 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h @@ -54,9 +54,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { Uint32 firstInstance = 0; }; - struct DrawIndexedCmd: public DrawBaseCmd { + struct IndexBufferView { GLenum indexType = GL_UNSIGNED_SHORT; SizeT indexByteOffset = 0; + }; + + struct DrawIndexedCmd: public DrawBaseCmd { + IndexBufferView indexBufferView; Uint32 indexCount = 0; Uint32 instanceCount = 1; From e4455aed9abf3dcbfd8e738d17590a288f77ad42 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Wed, 18 Mar 2026 16:08:00 +0800 Subject: [PATCH 13/31] [Feat] (MG_Backend/DirectVulkan): add draw cmd `MultiDrawIndexedCmd` --- .../MG_Backend/DirectVulkan/DirectVulkan.cpp | 51 ++++++++++--- .../DirectVulkan/Renderer/VulkanRenderer.cpp | 73 +++++++++++-------- .../DirectVulkan/Renderer/VulkanRenderer.h | 64 +++++++++------- 3 files changed, 120 insertions(+), 68 deletions(-) diff --git a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp index c4cadc97..61e0aa8c 100644 --- a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp @@ -55,8 +55,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { DrawCmd payload{}; payload.mode = mode; - payload.firstVertex = first; - payload.vertexCount = count; + payload.params.firstVertex = first; + payload.params.vertexCount = count; pVulkanRenderer->DrawArrays(payload); } @@ -69,8 +69,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { payload.mode = mode; payload.indexBufferView.indexType = type; payload.indexBufferView.indexByteOffset = reinterpret_cast(indices); - payload.indexCount = count; - payload.instanceCount = 1; + payload.indexBufferView.indexByteSize = count * MG_Util::GetGLTypeSize(type); + payload.params.indexCount = count; + payload.params.instanceCount = 1; pVulkanRenderer->DrawElements(payload); } @@ -109,11 +110,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { payload.mode = mode; payload.indexBufferView.indexType = type; payload.indexBufferView.indexByteOffset = reinterpret_cast(indices); - payload.indexCount = count; - payload.instanceCount = 1; - payload.firstIndex = 0; - payload.vertexOffset = basevertex; - payload.firstInstance = 0; + 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); } @@ -121,13 +123,38 @@ namespace MobileGL::MG_Backend::DirectVulkan { 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"); - // TODO: properly batch the draw calls + 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; + continue; } - DrawElementsBaseVertex(mode, count[i], type, indices[i], basevertex[i]); + + // 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, diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 2650cde1..388159c6 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -399,7 +399,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); @@ -526,11 +526,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { Bool VulkanRenderer::UploadAndBindIndexBuffer(FrameContext::FrameData& frame, const MG_State::GLState::VertexArrayObject& vao, - GLenum indexType, - SizeT indexByteOffset, - Uint32 indexCount) { + const IndexBufferView* pIndexBufferView) { VkIndexType vkIndexType = VK_INDEX_TYPE_MAX_ENUM; - switch (indexType) { + switch (pIndexBufferView->indexType) { case GL_UNSIGNED_SHORT: vkIndexType = VK_INDEX_TYPE_UINT16; break; @@ -538,7 +536,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { vkIndexType = VK_INDEX_TYPE_UINT32; break; default: - MGLOG_D("DrawElements skipped: index type %u is not supported yet", indexType); + MGLOG_D("DrawElements skipped: index type %u is not supported yet", pIndexBufferView->indexType); return false; } @@ -547,14 +545,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { const auto indexData = indexBuffer->GetDataReadOnly(); MOBILEGL_ASSERT(indexData != nullptr && !indexData->empty(), "DrawElements requires non-empty EBO data"); - const SizeT indexSize = (indexType == GL_UNSIGNED_SHORT) ? sizeof(Uint16) : sizeof(Uint32); - const SizeT indexDataSizeBytes = static_cast(indexCount) * indexSize; - MOBILEGL_ASSERT(indexByteOffset + indexDataSizeBytes <= indexBuffer->GetSize(), + 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"); const Uint32 frameIndex = m_frameContext.GetCurrentFrameIndex(); VkDeviceSize& frameIndexHead = m_frameIndexUploadHeads[frameIndex]; - const VkDeviceSize alignment = static_cast(indexSize); + const VkDeviceSize alignment = indexSize; const VkDeviceSize writeOffset = (frameIndexHead + alignment - 1) & ~(alignment - 1); const VkDeviceSize writeEnd = writeOffset + static_cast(indexDataSizeBytes); if (!EnsureFrameUploadBufferCapacity(frameIndex, true, writeEnd, 1 * 1024 * 1024, @@ -564,7 +562,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { } auto& frameIndexUploadBuffer = m_frameIndexUploadBuffers[frameIndex]; - if (!frameIndexUploadBuffer.Upload(indexData->data() + indexByteOffset, + if (!frameIndexUploadBuffer.Upload(indexData->data() + pIndexBufferView->indexByteOffset, static_cast(indexDataSizeBytes), writeOffset)) { MGLOG_E("DrawElements skipped: failed to upload index data"); return false; @@ -771,7 +769,7 @@ void main() { } void VulkanRenderer::SetupDraw(FrameContext::FrameData& frame, GLenum mode, Flags aspects, - GLenum indexType, SizeT indexByteOffset, Uint32 indexCount) { + const IndexBufferView* pIndexBufferView) { m_textureManager->CollectGarbage(); const auto& drawFbo = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject(); @@ -884,7 +882,7 @@ void main() { MOBILEGL_ASSERT(vtxUploadOk, "SetupDraw skipped: failed to upload vertex streams"); if (aspects & DrawSetupAspect::IndexBuffer) { - auto idxUploadOk = UploadAndBindIndexBuffer(frame, vao, indexType, indexByteOffset, indexCount); + auto idxUploadOk = UploadAndBindIndexBuffer(frame, vao, pIndexBufferView); MOBILEGL_ASSERT(idxUploadOk, "SetupDraw skipped: failed to upload index buffer"); } @@ -1259,32 +1257,48 @@ void main() { VkCommandBuffer& commandBuffer = frame.commandBuffer; vkCmdDraw(commandBuffer, - payload.vertexCount, - payload.instanceCount, - payload.firstVertex, - payload.firstInstance); + payload.params.vertexCount, + payload.params.instanceCount, + payload.params.firstVertex, + payload.params.firstInstance); } void VulkanRenderer::DrawElements(const DrawIndexedCmd& payload) { auto& frame = m_frameContext.GetCurrent(); SetupDraw(frame, payload.mode, DrawSetupAspect::IndexBuffer, - payload.indexBufferView.indexType, payload.indexBufferView.indexByteOffset, payload.indexCount); + &payload.indexBufferView); MOBILEGL_ASSERT(frame.isCommandRecording, "%s: frame recording was not started", __func__); VkCommandBuffer& commandBuffer = frame.commandBuffer; vkCmdDrawIndexed(commandBuffer, - payload.indexCount, - payload.instanceCount, - payload.firstIndex, - payload.vertexOffset, - payload.firstInstance); + 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() { @@ -1639,14 +1653,15 @@ void main() { 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("Logical device created."); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h index f205a544..2feec343 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h @@ -34,34 +34,24 @@ 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 DrawBaseCmd { - GLenum mode = GL_TRIANGLES; - }; - - struct DrawCmd: public DrawBaseCmd { + struct DrawCmdParam { Uint32 vertexCount = 0; Uint32 instanceCount = 1; Uint32 firstVertex = 0; Uint32 firstInstance = 0; }; - struct IndexBufferView { - GLenum indexType = GL_UNSIGNED_SHORT; - SizeT indexByteOffset = 0; - }; - - struct DrawIndexedCmd: public DrawBaseCmd { - IndexBufferView indexBufferView; - + struct DrawIndexedCmdParam { Uint32 indexCount = 0; Uint32 instanceCount = 1; Uint32 firstIndex = 0; @@ -69,8 +59,30 @@ namespace MobileGL::MG_Backend::DirectVulkan { Int32 firstInstance = 0; }; - struct MultiDrawElementsCmd { + struct DrawCmd { + GLenum mode = GL_TRIANGLES; + DrawCmdParam params; + }; + struct IndexBufferView { + GLenum indexType = GL_UNSIGNED_SHORT; + SizeT indexByteOffset = 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 { @@ -97,7 +109,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { void Shutdown(); void SetupDraw(FrameContext::FrameData& frame, GLenum mode, Flags aspects, - GLenum indexType = 0, SizeT indexByteOffset = 0, Uint32 indexCount = 0); + const IndexBufferView* pIndexBufferView = nullptr); void ClearAttachmentsOnActiveRenderPass(VkCommandBuffer commandBuffer, const RenderPassEntry& compatibleRenderPassEntry); @@ -107,7 +119,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { GLbitfield mask, GLenum filter); void DrawArrays(const DrawCmd& payload); void DrawElements(const DrawIndexedCmd& payload); - void MultiDrawElements(const Vector& payloads); + void MultiDrawElements(const MultiDrawIndexedCmd& payloads); void Present(); const PhysicalDevice& GetPhysicalDevice() const; @@ -154,7 +166,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { 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; @@ -203,9 +215,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { Bool UploadAndBindVertexStreams(VkCommandBuffer commandBuffer, const MG_State::GLState::VertexArrayObject& vao); Bool UploadAndBindIndexBuffer(FrameContext::FrameData& frame, const MG_State::GLState::VertexArrayObject& vao, - GLenum indexType, - SizeT indexByteOffset, - Uint32 indexCount); + const IndexBufferView* pIndexBufferView = nullptr); Bool InitializeBlitResources(); void ShutdownBlitResources(); Bool TryBlitToDefaultFramebufferWithShader(FrameContext::FrameData& frame, From 5e9f0f0c7045c68102d49db48ec24f753a84436e Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Thu, 19 Mar 2026 08:59:34 +0800 Subject: [PATCH 14/31] [Chore] (MG_Backend/DirectVulkan): remove some unused stuff --- MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp | 5 ----- MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h | 2 -- 2 files changed, 7 deletions(-) diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 388159c6..6bdd7b61 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -1707,11 +1707,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; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h index 2feec343..987392cf 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h @@ -250,8 +250,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(); From b6ac6c182e8cdb10e72ac639e1d6b8804c08edfa Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Thu, 19 Mar 2026 17:23:52 +0800 Subject: [PATCH 15/31] [Feat] (MG_Backend/DirectVulkan): Buffer arena, buffer slice --- CMakeLists.txt | 1 + .../DirectVulkan/Renderer/BufferArena.cpp | 138 ++++++++++++++ .../DirectVulkan/Renderer/BufferArena.h | 56 ++++++ .../DirectVulkan/Renderer/BufferSlice.h | 23 +++ .../DirectVulkan/Renderer/VkBufferObject.cpp | 61 ++++++- .../DirectVulkan/Renderer/VkBufferObject.h | 16 ++ .../DirectVulkan/Renderer/VulkanRenderer.cpp | 172 +++++++----------- .../DirectVulkan/Renderer/VulkanRenderer.h | 14 +- 8 files changed, 356 insertions(+), 125 deletions(-) create mode 100644 MobileGL/MG_Backend/DirectVulkan/Renderer/BufferArena.cpp create mode 100644 MobileGL/MG_Backend/DirectVulkan/Renderer/BufferArena.h create mode 100644 MobileGL/MG_Backend/DirectVulkan/Renderer/BufferSlice.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 354dca10..dad53933 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -224,6 +224,7 @@ set(SOURCE_FILES 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/BufferArena.cpp MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateBuilder.cpp MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferObject.cpp 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/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/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 6bdd7b61..6204e0d2 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -334,16 +334,32 @@ namespace MobileGL::MG_Backend::DirectVulkan { 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()); + succeeded = m_vertexUploadArena.Initialize({ + .allocator = m_allocator, + .frameCount = m_frameContext.GetFrameCount(), + .usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, + .memoryUsage = VMA_MEMORY_USAGE_AUTO, + .allocationFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + .minBufferSize = 4 * 1024 * 1024, + .persistentlyMapped = false, + }); + MOBILEGL_ASSERT(succeeded, "Vertex upload arena initialization failed."); + succeeded = m_indexUploadArena.Initialize({ + .allocator = m_allocator, + .frameCount = m_frameContext.GetFrameCount(), + .usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT, + .memoryUsage = VMA_MEMORY_USAGE_AUTO, + .allocationFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + .minBufferSize = 1 * 1024 * 1024, + .persistentlyMapped = false, + }); + MOBILEGL_ASSERT(succeeded, "Index upload arena initialization failed."); // 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_vertexUploadArena.BeginFrame(m_frameContext.GetCurrentFrameIndex()); + m_indexUploadArena.BeginFrame(m_frameContext.GetCurrentFrameIndex()); MGLOG_D("VulkanRenderer initialized"); } @@ -363,17 +379,8 @@ 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_vertexUploadArena.Shutdown(); + m_indexUploadArena.Shutdown(); m_frameContext.Destroy(m_device, m_commandPool); @@ -418,58 +425,6 @@ 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( VkCommandBuffer commandBuffer, const MG_State::GLState::VertexArrayObject& vao) { auto& vertexInputState = m_vertexInputStateFactory->GetOrCreateVertexInputState(vao); @@ -498,26 +453,19 @@ 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); + MOBILEGL_ASSERT(sourceBuffer != nullptr, "UploadAndBindVertexStreams failed to resolve source buffer"); 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; - } - auto& frameUploadBuffer = m_frameVertexUploadBuffers[frameIndex]; - if (!frameUploadBuffer.Upload(sourceData->data(), static_cast(sourceSize), writeOffset)) { + BufferSlice slice{}; + if (!m_vertexUploadArena.Upload(frameIndex, sourceData->data(), static_cast(sourceSize), 16, + slice)) { 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()); @@ -551,25 +499,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { "DrawElements index range out of bounds"); const Uint32 frameIndex = m_frameContext.GetCurrentFrameIndex(); - VkDeviceSize& frameIndexHead = m_frameIndexUploadHeads[frameIndex]; const VkDeviceSize alignment = 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)) { + BufferSlice slice{}; + if (!m_indexUploadArena.Upload(frameIndex, indexData->data() + pIndexBufferView->indexByteOffset, + static_cast(indexDataSizeBytes), alignment, slice)) { MGLOG_E("DrawElements skipped: failed to prepare index upload buffer"); return false; } - - auto& frameIndexUploadBuffer = m_frameIndexUploadBuffers[frameIndex]; - if (!frameIndexUploadBuffer.Upload(indexData->data() + pIndexBufferView->indexByteOffset, - static_cast(indexDataSizeBytes), writeOffset)) { - MGLOG_E("DrawElements skipped: failed to upload index data"); - return false; - } - - frameIndexHead = writeEnd; - vkCmdBindIndexBuffer(frame.commandBuffer, frameIndexUploadBuffer.GetHandle(), writeOffset, vkIndexType); + vkCmdBindIndexBuffer(frame.commandBuffer, slice.buffer, slice.offset, vkIndexType); return true; } @@ -1346,13 +1283,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_vertexUploadArena.BeginFrame(m_frameContext.GetCurrentFrameIndex()); + m_indexUploadArena.BeginFrame(m_frameContext.GetCurrentFrameIndex()); } void VulkanRenderer::CreateInstance() { @@ -1888,12 +1820,32 @@ 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); + m_vertexUploadArena.Shutdown(); + Bool okArena = m_vertexUploadArena.Initialize({ + .allocator = m_allocator, + .frameCount = m_frameContext.GetFrameCount(), + .usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, + .memoryUsage = VMA_MEMORY_USAGE_AUTO, + .allocationFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + .minBufferSize = 4 * 1024 * 1024, + .persistentlyMapped = false, + }); + MOBILEGL_ASSERT(okArena, "RecreateSwapchain: vertex upload arena initialization failed"); + m_indexUploadArena.Shutdown(); + okArena = m_indexUploadArena.Initialize({ + .allocator = m_allocator, + .frameCount = m_frameContext.GetFrameCount(), + .usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT, + .memoryUsage = VMA_MEMORY_USAGE_AUTO, + .allocationFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + .minBufferSize = 1 * 1024 * 1024, + .persistentlyMapped = false, + }); + MOBILEGL_ASSERT(okArena, "RecreateSwapchain: index upload arena initialization failed"); + if (m_frameContext.GetFrameCount() > 0) { + m_vertexUploadArena.BeginFrame(m_frameContext.GetCurrentFrameIndex()); + m_indexUploadArena.BeginFrame(m_frameContext.GetCurrentFrameIndex()); + } } const PhysicalDevice& VulkanRenderer::GetPhysicalDevice() const { diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h index 987392cf..ec18114b 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h @@ -8,6 +8,7 @@ #pragma once #include "Config.h" +#include "BufferArena.h" #include "FrameContext.h" #include "PipelineFactory.h" #include "ProgramFactory.h" @@ -170,11 +171,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkCommandPool m_commandPool = VK_NULL_HANDLE; - Vector m_frameVertexUploadBuffers; - Vector m_frameVertexUploadHeads; - Vector m_frameIndexUploadBuffers; - Vector m_frameIndexUploadHeads; - Vector> m_deferredBufferReleases; + BufferArena m_vertexUploadArena; + BufferArena m_indexUploadArena; Uint m_imageIndexAcquired = 0; FrameContext m_frameContext; @@ -208,13 +206,9 @@ 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 UploadAndBindIndexBuffer(FrameContext::FrameData& frame, - const MG_State::GLState::VertexArrayObject& vao, + const MG_State::GLState::VertexArrayObject& vao, const IndexBufferView* pIndexBufferView = nullptr); Bool InitializeBlitResources(); void ShutdownBlitResources(); From f2ab50b84e2f356bea9dacd44b23994a1d59d856 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Thu, 19 Mar 2026 17:42:39 +0800 Subject: [PATCH 16/31] [Feat] (MG_Backend/DirectVulkan): VkBufferManager --- CMakeLists.txt | 1 + MobileGL/Defines.h | 2 +- .../DirectVulkan/Renderer/VkBufferManager.cpp | 85 +++++++++++++++++++ .../DirectVulkan/Renderer/VkBufferManager.h | 51 +++++++++++ .../DirectVulkan/Renderer/VulkanRenderer.cpp | 69 ++++----------- .../DirectVulkan/Renderer/VulkanRenderer.h | 5 +- 6 files changed, 158 insertions(+), 55 deletions(-) create mode 100644 MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp create mode 100644 MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.h diff --git a/CMakeLists.txt b/CMakeLists.txt index dad53933..c6f35a10 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -225,6 +225,7 @@ set(SOURCE_FILES MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.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 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/DirectVulkan/Renderer/VkBufferManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp new file mode 100644 index 00000000..b7ab8aa8 --- /dev/null +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp @@ -0,0 +1,85 @@ +// 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 { + 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; + return InitializeTransientArenas(); + } + + void VkBufferManager::Shutdown() { + m_vertexUploadArena.Shutdown(); + m_indexUploadArena.Shutdown(); + m_initInfo = {}; + } + + 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_vertexUploadArena.Shutdown(); + m_indexUploadArena.Shutdown(); + m_initInfo.frameCount = frameCount; + return InitializeTransientArenas(); + } + + void VkBufferManager::BeginFrame(Uint32 frameIndex) { + m_vertexUploadArena.BeginFrame(frameIndex); + m_indexUploadArena.BeginFrame(frameIndex); + } + + Bool VkBufferManager::UploadTransient(TransientBufferKind kind, Uint32 frameIndex, const void* data, + VkDeviceSize size, VkDeviceSize alignment, BufferSlice& outSlice) { + switch (kind) { + case TransientBufferKind::Vertex: + return m_vertexUploadArena.Upload(frameIndex, data, size, alignment, outSlice); + case TransientBufferKind::Index: + return m_indexUploadArena.Upload(frameIndex, data, size, alignment, outSlice); + default: + return false; + } + } + + Bool VkBufferManager::InitializeTransientArenas() { + Bool ok = m_vertexUploadArena.Initialize({ + .allocator = m_initInfo.allocator, + .frameCount = m_initInfo.frameCount, + .usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, + .memoryUsage = m_initInfo.transientMemoryUsage, + .allocationFlags = m_initInfo.transientAllocationFlags, + .minBufferSize = m_initInfo.minVertexUploadBytes, + .persistentlyMapped = m_initInfo.transientPersistentMapping, + }); + if (!ok) { + return false; + } + + ok = m_indexUploadArena.Initialize({ + .allocator = m_initInfo.allocator, + .frameCount = m_initInfo.frameCount, + .usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT, + .memoryUsage = m_initInfo.transientMemoryUsage, + .allocationFlags = m_initInfo.transientAllocationFlags, + .minBufferSize = m_initInfo.minIndexUploadBytes, + .persistentlyMapped = m_initInfo.transientPersistentMapping, + }); + if (!ok) { + m_vertexUploadArena.Shutdown(); + return false; + } + + return true; + } +} // 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..20469123 --- /dev/null +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.h @@ -0,0 +1,51 @@ +// 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 "../VkIncludes.h" +#include +#include + +namespace MobileGL::MG_Backend::DirectVulkan { + enum class TransientBufferKind : Uint8 { + Vertex, + Index, + }; + + struct VkBufferManagerInitInfo { + VmaAllocator allocator = nullptr; + Uint32 frameCount = 0; + VkDeviceSize minVertexUploadBytes = 4 * 1024 * 1024; + VkDeviceSize minIndexUploadBytes = 1 * 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(TransientBufferKind kind, Uint32 frameIndex, const void* data, VkDeviceSize size, + VkDeviceSize alignment, BufferSlice& outSlice); + + private: + Bool InitializeTransientArenas(); + + VkBufferManagerInitInfo m_initInfo{}; + BufferArena m_vertexUploadArena; + BufferArena m_indexUploadArena; + }; +} // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 6204e0d2..c32ba3e2 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -334,32 +334,21 @@ namespace MobileGL::MG_Backend::DirectVulkan { MOBILEGL_ASSERT(m_vertexInputStateFactory != nullptr, "VertexInputStateFactory creation failed."); CreateFrameContexts(); - succeeded = m_vertexUploadArena.Initialize({ + succeeded = m_bufferManager.Initialize({ .allocator = m_allocator, .frameCount = m_frameContext.GetFrameCount(), - .usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, - .memoryUsage = VMA_MEMORY_USAGE_AUTO, - .allocationFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, - .minBufferSize = 4 * 1024 * 1024, - .persistentlyMapped = false, + .minVertexUploadBytes = 4 * 1024 * 1024, + .minIndexUploadBytes = 1 * 1024 * 1024, + .transientMemoryUsage = VMA_MEMORY_USAGE_AUTO, + .transientAllocationFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + .transientPersistentMapping = false, }); - MOBILEGL_ASSERT(succeeded, "Vertex upload arena initialization failed."); - succeeded = m_indexUploadArena.Initialize({ - .allocator = m_allocator, - .frameCount = m_frameContext.GetFrameCount(), - .usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT, - .memoryUsage = VMA_MEMORY_USAGE_AUTO, - .allocationFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, - .minBufferSize = 1 * 1024 * 1024, - .persistentlyMapped = false, - }); - MOBILEGL_ASSERT(succeeded, "Index upload arena initialization failed."); + MOBILEGL_ASSERT(succeeded, "Buffer manager initialization failed."); // 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_vertexUploadArena.BeginFrame(m_frameContext.GetCurrentFrameIndex()); - m_indexUploadArena.BeginFrame(m_frameContext.GetCurrentFrameIndex()); + m_bufferManager.BeginFrame(m_frameContext.GetCurrentFrameIndex()); MGLOG_D("VulkanRenderer initialized"); } @@ -379,8 +368,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_textureManager.reset(); } m_vertexInputStateFactory.reset(); - m_vertexUploadArena.Shutdown(); - m_indexUploadArena.Shutdown(); + m_bufferManager.Shutdown(); m_frameContext.Destroy(m_device, m_commandPool); @@ -459,8 +447,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { const SizeT sourceSize = sourceBuffer->GetSize(); BufferSlice slice{}; - if (!m_vertexUploadArena.Upload(frameIndex, sourceData->data(), static_cast(sourceSize), 16, - slice)) { + if (!m_bufferManager.UploadTransient(TransientBufferKind::Vertex, frameIndex, sourceData->data(), + static_cast(sourceSize), 16, slice)) { MGLOG_E("UploadAndBindVertexStreams skipped: failed to upload binding %zu", binding); return false; } @@ -501,8 +489,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { const Uint32 frameIndex = m_frameContext.GetCurrentFrameIndex(); const VkDeviceSize alignment = indexSize; BufferSlice slice{}; - if (!m_indexUploadArena.Upload(frameIndex, indexData->data() + pIndexBufferView->indexByteOffset, - static_cast(indexDataSizeBytes), alignment, slice)) { + if (!m_bufferManager.UploadTransient(TransientBufferKind::Index, frameIndex, + indexData->data() + pIndexBufferView->indexByteOffset, + static_cast(indexDataSizeBytes), alignment, slice)) { MGLOG_E("DrawElements skipped: failed to prepare index upload buffer"); return false; } @@ -1283,8 +1272,7 @@ void main() { result = VK_SUCCESS; } VK_VERIFY(result, "Present, vkAcquireNextImageKHR"); - m_vertexUploadArena.BeginFrame(m_frameContext.GetCurrentFrameIndex()); - m_indexUploadArena.BeginFrame(m_frameContext.GetCurrentFrameIndex()); + m_bufferManager.BeginFrame(m_frameContext.GetCurrentFrameIndex()); } void VulkanRenderer::CreateInstance() { @@ -1820,31 +1808,10 @@ void main() { m_frameContext.GetCurrent().isCommandRecording = false; m_frameContext.GetCurrent().hasCommandBufferRecorded = false; } - m_vertexUploadArena.Shutdown(); - Bool okArena = m_vertexUploadArena.Initialize({ - .allocator = m_allocator, - .frameCount = m_frameContext.GetFrameCount(), - .usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, - .memoryUsage = VMA_MEMORY_USAGE_AUTO, - .allocationFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, - .minBufferSize = 4 * 1024 * 1024, - .persistentlyMapped = false, - }); - MOBILEGL_ASSERT(okArena, "RecreateSwapchain: vertex upload arena initialization failed"); - m_indexUploadArena.Shutdown(); - okArena = m_indexUploadArena.Initialize({ - .allocator = m_allocator, - .frameCount = m_frameContext.GetFrameCount(), - .usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT, - .memoryUsage = VMA_MEMORY_USAGE_AUTO, - .allocationFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, - .minBufferSize = 1 * 1024 * 1024, - .persistentlyMapped = false, - }); - MOBILEGL_ASSERT(okArena, "RecreateSwapchain: index upload arena initialization failed"); + 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_vertexUploadArena.BeginFrame(m_frameContext.GetCurrentFrameIndex()); - m_indexUploadArena.BeginFrame(m_frameContext.GetCurrentFrameIndex()); + m_bufferManager.BeginFrame(m_frameContext.GetCurrentFrameIndex()); } } diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h index ec18114b..778771e9 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h @@ -8,7 +8,6 @@ #pragma once #include "Config.h" -#include "BufferArena.h" #include "FrameContext.h" #include "PipelineFactory.h" #include "ProgramFactory.h" @@ -16,6 +15,7 @@ #include "UniformDescriptorBinder.h" #include "VertexInputStateFactory.h" #include "VkBufferObject.h" +#include "VkBufferManager.h" #include "VkClearManager.h" #include "VkRenderPassManager.h" #include "VkSamplerManager.h" @@ -171,8 +171,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkCommandPool m_commandPool = VK_NULL_HANDLE; - BufferArena m_vertexUploadArena; - BufferArena m_indexUploadArena; + VkBufferManager m_bufferManager; Uint m_imageIndexAcquired = 0; FrameContext m_frameContext; From 87c56ce3d5a889d3c2f4e63b98cf209722c7c09f Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Fri, 20 Mar 2026 09:19:50 +0800 Subject: [PATCH 17/31] [Fix] (MG_Backend/DirectVulkan): Fix VkBufferManager initialization assertion failure --- .../DirectVulkan/Renderer/VulkanRenderer.cpp | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index c32ba3e2..62f56286 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -279,9 +279,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { 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"); } @@ -293,9 +290,20 @@ namespace MobileGL::MG_Backend::DirectVulkan { CreateAllocator(); CreateCommandPool(); + CreateFrameContexts(); + auto succeeded = false; + succeeded = m_bufferManager.Initialize({ + .allocator = m_allocator, + .frameCount = m_frameContext.GetFrameCount(), + .minVertexUploadBytes = 4 * 1024 * 1024, + .minIndexUploadBytes = 1 * 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."); @@ -333,18 +341,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_vertexInputStateFactory = MakeUnique(m_config); MOBILEGL_ASSERT(m_vertexInputStateFactory != nullptr, "VertexInputStateFactory creation failed."); - CreateFrameContexts(); - succeeded = m_bufferManager.Initialize({ - .allocator = m_allocator, - .frameCount = m_frameContext.GetFrameCount(), - .minVertexUploadBytes = 4 * 1024 * 1024, - .minIndexUploadBytes = 1 * 1024 * 1024, - .transientMemoryUsage = VMA_MEMORY_USAGE_AUTO, - .transientAllocationFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, - .transientPersistentMapping = false, - }); - MOBILEGL_ASSERT(succeeded, "Buffer manager initialization failed."); - // 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"); From bfa4049ac15ee0deb458b5f5a44d9625b2335d65 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Fri, 20 Mar 2026 09:55:22 +0800 Subject: [PATCH 18/31] [Chore] (MG_Backend/DirectVulkan): unwrap frame context initialization --- .../DirectVulkan/Renderer/VulkanRenderer.cpp | 10 +++------- .../MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h | 1 - 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 62f56286..9505f98a 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -276,12 +276,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { return flags; } - void VulkanRenderer::CreateFrameContexts() { - VK_VERIFY(m_frameContext.Initialize(m_device, m_commandPool, m_config.MaxFramesInFlight), - "CreateFrameContexts"); - MGLOG_I("CreateFrameContexts completed"); - } - void VulkanRenderer::Initialize() { CreateInstance(); CreateSurface(); @@ -290,7 +284,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { CreateAllocator(); CreateCommandPool(); - CreateFrameContexts(); + 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, diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h index 778771e9..449fe822 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h @@ -197,7 +197,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { void DestroyAllocator(); void CreateSwapchain(); void CreateCommandPool(); - void CreateFrameContexts(); VkPipeline GetOrCreatePipeline( GLenum mode, From 9d1e8bd7cd7c8a32f299a400ef81690a753b0efc Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Fri, 20 Mar 2026 13:31:51 +0800 Subject: [PATCH 19/31] [Feat] (MG_Backend/DirectVulkan): VkBufferManager transient upload now includes uniform --- .../Renderer/UniformDescriptorBinder.cpp | 49 +++++-------------- .../Renderer/UniformDescriptorBinder.h | 15 ++---- .../DirectVulkan/Renderer/VkBufferManager.cpp | 20 ++++++++ .../DirectVulkan/Renderer/VkBufferManager.h | 3 ++ .../DirectVulkan/Renderer/VulkanRenderer.cpp | 5 +- 5 files changed, 42 insertions(+), 50 deletions(-) diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.cpp index 9786233d..f2650365 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.cpp @@ -174,14 +174,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { } } - Bool UniformDescriptorBinder::Initialize(VkDevice device, VmaAllocator allocator, + Bool UniformDescriptorBinder::Initialize(VkDevice device, VkBufferManager* bufferManager, VkDeviceSize minUniformBufferOffsetAlignment, Uint32 frameCount, - Uint32 maxBindings, Uint32 setsPerFrame, VkDeviceSize perFrameUploadBytes, + Uint32 maxBindings, Uint32 setsPerFrame, 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(bufferManager != nullptr, "UniformDescriptorBinder::Initialize requires valid buffer manager"); 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"); @@ -191,9 +191,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { "UniformDescriptorBinder::Initialize requires valid sampler manager"); m_device = device; - m_allocator = allocator; + m_bufferManager = bufferManager; m_minDynamicOffsetAlignment = std::max(1, minUniformBufferOffsetAlignment); - m_perFrameUploadBytes = perFrameUploadBytes; m_frameCount = frameCount; m_maxBindings = maxBindings; m_setsPerFrame = setsPerFrame; @@ -204,7 +203,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { 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; @@ -219,15 +217,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { } 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; @@ -235,7 +224,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { 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) { @@ -248,15 +236,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { frame.activeDescriptorPoolIndex = 0; frame.allocatedSetsThisFrame = 0; frame.peakAllocatedSetsThisFrame = 0; - frame.writeCursor = 0; } m_frames.clear(); DestroyProgramLayouts(); - m_allocator = nullptr; + m_bufferManager = nullptr; m_device = VK_NULL_HANDLE; m_minDynamicOffsetAlignment = 1; - m_perFrameUploadBytes = 0; m_frameCount = 0; m_maxBindings = 0; m_setsPerFrame = 0; @@ -274,7 +260,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { "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; @@ -678,16 +663,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { 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 { @@ -870,6 +845,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { 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"); + MOBILEGL_ASSERT(m_bufferManager != nullptr, "BindProgramUniformBuffers: buffer manager is null"); Vector writes; writes.reserve(m_maxBindings); @@ -911,19 +887,16 @@ namespace MobileGL::MG_Backend::DirectVulkan { } } - 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)) { + BufferSlice slice{}; + if (!m_bufferManager->UploadTransient(TransientBufferKind::Uniform, frameIndex, payload, payloadSize, + m_minDynamicOffsetAlignment, slice)) { MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: UBO upload failed on binding %u", binding); return false; } VkDescriptorBufferInfo bufferInfo{}; - bufferInfo.buffer = frame.uploadBuffer.GetHandle(); + bufferInfo.buffer = slice.buffer; bufferInfo.offset = 0; bufferInfo.range = payloadSize; bufferInfos.push_back(bufferInfo); @@ -931,7 +904,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; write.pBufferInfo = &bufferInfos.back(); writes.push_back(write); - dynamicOffsets.push_back(static_cast(payloadOffset)); + dynamicOffsets.push_back(static_cast(slice.offset)); } else { VkDescriptorImageInfo imageInfo{}; Bool hasImage = false; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.h index 64ff2310..d023988b 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.h @@ -8,12 +8,11 @@ #pragma once -#include "VkBufferObject.h" +#include "VkBufferManager.h" #include "VkSamplerManager.h" #include "VkTextureManager.h" #include "../VkIncludes.h" #include -#include namespace MobileGL::MG_State::GLState { class ITextureObject; @@ -36,9 +35,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { 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, + VkDeviceSize minUniformBufferOffsetAlignment, Uint32 frameCount, + Uint32 maxBindings = 16, Uint32 setsPerFrame = 64, VkTextureManager* textureManager = nullptr, VkSamplerManager* samplerManager = nullptr); void Shutdown(); @@ -60,12 +59,10 @@ 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 { @@ -94,7 +91,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { 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; @@ -102,12 +98,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { void DestroyProgramLayouts(); VkDevice m_device = VK_NULL_HANDLE; - VmaAllocator m_allocator = nullptr; + VkBufferManager* m_bufferManager = 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/VkBufferManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp index b7ab8aa8..f5c6d85c 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp @@ -22,6 +22,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { void VkBufferManager::Shutdown() { m_vertexUploadArena.Shutdown(); m_indexUploadArena.Shutdown(); + m_uniformUploadArena.Shutdown(); m_initInfo = {}; } @@ -31,6 +32,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_vertexUploadArena.Shutdown(); m_indexUploadArena.Shutdown(); + m_uniformUploadArena.Shutdown(); m_initInfo.frameCount = frameCount; return InitializeTransientArenas(); } @@ -38,6 +40,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { void VkBufferManager::BeginFrame(Uint32 frameIndex) { m_vertexUploadArena.BeginFrame(frameIndex); m_indexUploadArena.BeginFrame(frameIndex); + m_uniformUploadArena.BeginFrame(frameIndex); } Bool VkBufferManager::UploadTransient(TransientBufferKind kind, Uint32 frameIndex, const void* data, @@ -47,6 +50,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { return m_vertexUploadArena.Upload(frameIndex, data, size, alignment, outSlice); case TransientBufferKind::Index: return m_indexUploadArena.Upload(frameIndex, data, size, alignment, outSlice); + case TransientBufferKind::Uniform: + return m_uniformUploadArena.Upload(frameIndex, data, size, alignment, outSlice); default: return false; } @@ -80,6 +85,21 @@ namespace MobileGL::MG_Backend::DirectVulkan { return false; } + ok = m_uniformUploadArena.Initialize({ + .allocator = m_initInfo.allocator, + .frameCount = m_initInfo.frameCount, + .usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, + .memoryUsage = m_initInfo.transientMemoryUsage, + .allocationFlags = m_initInfo.transientAllocationFlags, + .minBufferSize = m_initInfo.minUniformUploadBytes, + .persistentlyMapped = m_initInfo.transientPersistentMapping, + }); + if (!ok) { + m_vertexUploadArena.Shutdown(); + m_indexUploadArena.Shutdown(); + return false; + } + return true; } } // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.h index 20469123..e0eda8ab 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.h @@ -17,6 +17,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { enum class TransientBufferKind : Uint8 { Vertex, Index, + Uniform, }; struct VkBufferManagerInitInfo { @@ -24,6 +25,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { Uint32 frameCount = 0; VkDeviceSize minVertexUploadBytes = 4 * 1024 * 1024; VkDeviceSize minIndexUploadBytes = 1 * 1024 * 1024; + VkDeviceSize minUniformUploadBytes = 4 * 1024 * 1024; VmaMemoryUsage transientMemoryUsage = VMA_MEMORY_USAGE_AUTO; VmaAllocationCreateFlags transientAllocationFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; Bool transientPersistentMapping = false; @@ -47,5 +49,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkBufferManagerInitInfo m_initInfo{}; BufferArena m_vertexUploadArena; BufferArena m_indexUploadArena; + BufferArena m_uniformUploadArena; }; } // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 9505f98a..c55db12d 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -293,6 +293,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { .frameCount = m_frameContext.GetFrameCount(), .minVertexUploadBytes = 4 * 1024 * 1024, .minIndexUploadBytes = 1 * 1024 * 1024, + .minUniformUploadBytes = 4 * 1024 * 1024, .transientMemoryUsage = VMA_MEMORY_USAGE_AUTO, .transientAllocationFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, .transientPersistentMapping = false, @@ -329,9 +330,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_uniformDescriptorBinder = MakeUnique(); MOBILEGL_ASSERT(m_uniformDescriptorBinder != nullptr, "UniformDescriptorBinder creation failed."); - succeeded = m_uniformDescriptorBinder->Initialize(m_device, m_allocator, + succeeded = m_uniformDescriptorBinder->Initialize(m_device, &m_bufferManager, m_physicalDevice.properties.limits.minUniformBufferOffsetAlignment, - m_config.MaxFramesInFlight, 16, 64, 4 * 1024 * 1024, + m_config.MaxFramesInFlight, 16, 64, m_textureManager.get(), m_samplerManager.get()); MOBILEGL_ASSERT(succeeded, "UniformDescriptorBinder initialization failed."); m_vertexInputStateFactory = MakeUnique(m_config); From 7dfc2149d88b9098cab2bed9487d2f9e5866f439 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Fri, 20 Mar 2026 13:56:12 +0800 Subject: [PATCH 20/31] [Feat] (MG_Backend/DirectVulkan): unified transient buffer arena --- .../DirectVulkan/Renderer/VkBufferManager.cpp | 65 +++---------------- .../DirectVulkan/Renderer/VkBufferManager.h | 8 +-- .../DirectVulkan/Renderer/VulkanRenderer.cpp | 4 +- 3 files changed, 12 insertions(+), 65 deletions(-) diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp index f5c6d85c..a1690978 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp @@ -20,9 +20,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { } void VkBufferManager::Shutdown() { - m_vertexUploadArena.Shutdown(); - m_indexUploadArena.Shutdown(); - m_uniformUploadArena.Shutdown(); + m_transientUploadArena.Shutdown(); m_initInfo = {}; } @@ -30,76 +28,31 @@ namespace MobileGL::MG_Backend::DirectVulkan { MOBILEGL_ASSERT(m_initInfo.allocator != nullptr, "VkBufferManager::RecreateTransientArenas requires initialized manager"); MOBILEGL_ASSERT(frameCount > 0, "VkBufferManager::RecreateTransientArenas requires non-zero frame count"); - m_vertexUploadArena.Shutdown(); - m_indexUploadArena.Shutdown(); - m_uniformUploadArena.Shutdown(); + m_transientUploadArena.Shutdown(); m_initInfo.frameCount = frameCount; return InitializeTransientArenas(); } void VkBufferManager::BeginFrame(Uint32 frameIndex) { - m_vertexUploadArena.BeginFrame(frameIndex); - m_indexUploadArena.BeginFrame(frameIndex); - m_uniformUploadArena.BeginFrame(frameIndex); + m_transientUploadArena.BeginFrame(frameIndex); } Bool VkBufferManager::UploadTransient(TransientBufferKind kind, Uint32 frameIndex, const void* data, VkDeviceSize size, VkDeviceSize alignment, BufferSlice& outSlice) { - switch (kind) { - case TransientBufferKind::Vertex: - return m_vertexUploadArena.Upload(frameIndex, data, size, alignment, outSlice); - case TransientBufferKind::Index: - return m_indexUploadArena.Upload(frameIndex, data, size, alignment, outSlice); - case TransientBufferKind::Uniform: - return m_uniformUploadArena.Upload(frameIndex, data, size, alignment, outSlice); - default: - return false; - } + (void)kind; + return m_transientUploadArena.Upload(frameIndex, data, size, alignment, outSlice); } Bool VkBufferManager::InitializeTransientArenas() { - Bool ok = m_vertexUploadArena.Initialize({ + return m_transientUploadArena.Initialize({ .allocator = m_initInfo.allocator, .frameCount = m_initInfo.frameCount, - .usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, + .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.minVertexUploadBytes, + .minBufferSize = m_initInfo.minUploadBytes, .persistentlyMapped = m_initInfo.transientPersistentMapping, }); - if (!ok) { - return false; - } - - ok = m_indexUploadArena.Initialize({ - .allocator = m_initInfo.allocator, - .frameCount = m_initInfo.frameCount, - .usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT, - .memoryUsage = m_initInfo.transientMemoryUsage, - .allocationFlags = m_initInfo.transientAllocationFlags, - .minBufferSize = m_initInfo.minIndexUploadBytes, - .persistentlyMapped = m_initInfo.transientPersistentMapping, - }); - if (!ok) { - m_vertexUploadArena.Shutdown(); - return false; - } - - ok = m_uniformUploadArena.Initialize({ - .allocator = m_initInfo.allocator, - .frameCount = m_initInfo.frameCount, - .usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, - .memoryUsage = m_initInfo.transientMemoryUsage, - .allocationFlags = m_initInfo.transientAllocationFlags, - .minBufferSize = m_initInfo.minUniformUploadBytes, - .persistentlyMapped = m_initInfo.transientPersistentMapping, - }); - if (!ok) { - m_vertexUploadArena.Shutdown(); - m_indexUploadArena.Shutdown(); - return false; - } - - return true; } } // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.h index e0eda8ab..83042ae4 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.h @@ -23,9 +23,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { struct VkBufferManagerInitInfo { VmaAllocator allocator = nullptr; Uint32 frameCount = 0; - VkDeviceSize minVertexUploadBytes = 4 * 1024 * 1024; - VkDeviceSize minIndexUploadBytes = 1 * 1024 * 1024; - VkDeviceSize minUniformUploadBytes = 4 * 1024 * 1024; + VkDeviceSize minUploadBytes = 4 * 1024 * 1024; VmaMemoryUsage transientMemoryUsage = VMA_MEMORY_USAGE_AUTO; VmaAllocationCreateFlags transientAllocationFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; Bool transientPersistentMapping = false; @@ -47,8 +45,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { Bool InitializeTransientArenas(); VkBufferManagerInitInfo m_initInfo{}; - BufferArena m_vertexUploadArena; - BufferArena m_indexUploadArena; - BufferArena m_uniformUploadArena; + BufferArena m_transientUploadArena; }; } // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index c55db12d..f18c6e53 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -291,9 +291,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { succeeded = m_bufferManager.Initialize({ .allocator = m_allocator, .frameCount = m_frameContext.GetFrameCount(), - .minVertexUploadBytes = 4 * 1024 * 1024, - .minIndexUploadBytes = 1 * 1024 * 1024, - .minUniformUploadBytes = 4 * 1024 * 1024, + .minUploadBytes = 4 * 1024 * 1024, .transientMemoryUsage = VMA_MEMORY_USAGE_AUTO, .transientAllocationFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, .transientPersistentMapping = false, From 810b4886c7c53e23d3f3107dfd41dc58c1221ff6 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Fri, 20 Mar 2026 15:27:57 +0800 Subject: [PATCH 21/31] [Feat] (MG_Backend/DirectVulkan): supports uint8 index buffer by `VK_KHR_index_type_uint8` / `VK_EXT_index_type_uint8` --- .../DirectVulkan/Renderer/VulkanRenderer.cpp | 48 ++++++++++++++++++- .../DirectVulkan/Renderer/VulkanRenderer.h | 3 +- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index f18c6e53..5eb46ba6 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -456,6 +456,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { 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; @@ -685,7 +690,7 @@ void main() { 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 = @@ -823,6 +828,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) { @@ -1560,6 +1566,45 @@ 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"); @@ -1574,6 +1619,7 @@ void main() { 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 diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h index 449fe822..5f28f0aa 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h @@ -109,7 +109,7 @@ 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); @@ -163,6 +163,7 @@ 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, From 0209c5461fd393bccd651dd67be1775323544ea6 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Fri, 20 Mar 2026 17:39:10 +0800 Subject: [PATCH 22/31] [Feat] (MG_Backend/DirectVulkan): implement naive transient/resident buffer, and heuristics to downgrade resident buffer to transient --- .../Renderer/UniformDescriptorBinder.cpp | 2 +- .../DirectVulkan/Renderer/VkBufferManager.cpp | 190 +++++++++++++++++- .../DirectVulkan/Renderer/VkBufferManager.h | 26 ++- .../DirectVulkan/Renderer/VulkanRenderer.cpp | 97 +++++++-- .../DirectVulkan/Renderer/VulkanRenderer.h | 1 + 5 files changed, 293 insertions(+), 23 deletions(-) diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.cpp index f2650365..b9cafd44 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.cpp @@ -888,7 +888,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { } BufferSlice slice{}; - if (!m_bufferManager->UploadTransient(TransientBufferKind::Uniform, frameIndex, payload, payloadSize, + if (!m_bufferManager->UploadTransient(BufferKind::Uniform, frameIndex, payload, payloadSize, m_minDynamicOffsetAlignment, slice)) { MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: UBO upload failed on binding %u", binding); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp index a1690978..69f13c68 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp @@ -9,6 +9,12 @@ #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(); @@ -16,12 +22,18 @@ namespace MobileGL::MG_Backend::DirectVulkan { 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) { @@ -30,14 +42,21 @@ namespace MobileGL::MG_Backend::DirectVulkan { 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(TransientBufferKind kind, Uint32 frameIndex, const void* data, + 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); @@ -55,4 +74,173 @@ namespace MobileGL::MG_Backend::DirectVulkan { .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 index 83042ae4..096cdb90 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.h @@ -9,12 +9,13 @@ #pragma once #include "BufferArena.h" +#include "MG_State/GLState/BufferState/BufferObject.h" #include "../VkIncludes.h" #include #include namespace MobileGL::MG_Backend::DirectVulkan { - enum class TransientBufferKind : Uint8 { + enum class BufferKind : Uint8 { Vertex, Index, Uniform, @@ -38,13 +39,34 @@ namespace MobileGL::MG_Backend::DirectVulkan { Bool RecreateTransientArenas(Uint32 frameCount); void BeginFrame(Uint32 frameIndex); - Bool UploadTransient(TransientBufferKind kind, Uint32 frameIndex, const void* data, VkDeviceSize size, + 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/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 5eb46ba6..dd549cf5 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: @@ -340,6 +363,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { 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"); } @@ -360,6 +384,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { } m_vertexInputStateFactory.reset(); m_bufferManager.Shutdown(); + m_transientVertexIndexBuffersThisFrame.clear(); m_frameContext.Destroy(m_device, m_commandPool); @@ -412,7 +437,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(); @@ -433,15 +457,32 @@ namespace MobileGL::MG_Backend::DirectVulkan { const SizeT bufferKey = vertexInputState.bindingBufferKeys[binding]; const MG_State::GLState::BufferObject* sourceBuffer = findBufferByKey(bufferKey); MOBILEGL_ASSERT(sourceBuffer != nullptr, "UploadAndBindVertexStreams failed to resolve source buffer"); - - const auto sourceData = sourceBuffer->GetDataReadOnly(); - - const SizeT sourceSize = sourceBuffer->GetSize(); + auto sourceBufferShared = MG_State::pGLContext->GetBufferObject(sourceBuffer->GetExternalIndex()); + MOBILEGL_ASSERT(sourceBufferShared != nullptr, + "UploadAndBindVertexStreams failed to resolve shared source buffer"); BufferSlice slice{}; - if (!m_bufferManager.UploadTransient(TransientBufferKind::Vertex, frameIndex, sourceData->data(), - static_cast(sourceSize), 16, slice)) { - MGLOG_E("UploadAndBindVertexStreams skipped: failed to upload binding %zu", binding); - return false; + 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)) { + MGLOG_E("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; + } } vkBuffers[binding] = slice.buffer; vkOffsets[binding] = slice.offset; @@ -474,24 +515,40 @@ namespace MobileGL::MG_Backend::DirectVulkan { const auto* indexBuffer = vao.GetIndexBufferBindingSlot().GetBoundObject().get(); MOBILEGL_ASSERT(indexBuffer != nullptr, "UploadAndBindIndexBuffer requires bound EBO"); - const auto indexData = indexBuffer->GetDataReadOnly(); - MOBILEGL_ASSERT(indexData != nullptr && !indexData->empty(), "DrawElements requires non-empty EBO data"); - 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"); - const Uint32 frameIndex = m_frameContext.GetCurrentFrameIndex(); - const VkDeviceSize alignment = indexSize; BufferSlice slice{}; - if (!m_bufferManager.UploadTransient(TransientBufferKind::Index, frameIndex, - indexData->data() + pIndexBufferView->indexByteOffset, - static_cast(indexDataSizeBytes), alignment, slice)) { - MGLOG_E("DrawElements skipped: failed to prepare index upload buffer"); + 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)) { + MGLOG_E("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, vkIndexType); + vkCmdBindIndexBuffer(frame.commandBuffer, slice.buffer, + slice.offset + static_cast(pIndexBufferView->indexByteOffset), vkIndexType); return true; } @@ -1270,6 +1327,7 @@ void main() { } VK_VERIFY(result, "Present, vkAcquireNextImageKHR"); m_bufferManager.BeginFrame(m_frameContext.GetCurrentFrameIndex()); + m_transientVertexIndexBuffersThisFrame.clear(); } void VulkanRenderer::CreateInstance() { @@ -1849,6 +1907,7 @@ void main() { MOBILEGL_ASSERT(okArena, "RecreateSwapchain: buffer manager transient arena initialization failed"); if (m_frameContext.GetFrameCount() > 0) { m_bufferManager.BeginFrame(m_frameContext.GetCurrentFrameIndex()); + m_transientVertexIndexBuffersThisFrame.clear(); } } diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h index 5f28f0aa..f36a17e3 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h @@ -173,6 +173,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkCommandPool m_commandPool = VK_NULL_HANDLE; VkBufferManager m_bufferManager; + Vector m_transientVertexIndexBuffersThisFrame; Uint m_imageIndexAcquired = 0; FrameContext m_frameContext; From ccbde0196ef24e4512b445e3c0a17b6686b2698f Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Sat, 21 Mar 2026 23:07:27 +0800 Subject: [PATCH 23/31] [Fix] (MG_Test/ProgramTest): fix wrong decomp source output --- MobileGL/MG_Test/Program/ProgramTest.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/MobileGL/MG_Test/Program/ProgramTest.cpp b/MobileGL/MG_Test/Program/ProgramTest.cpp index 1da44ad5..cc571775 100644 --- a/MobileGL/MG_Test/Program/ProgramTest.cpp +++ b/MobileGL/MG_Test/Program/ProgramTest.cpp @@ -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,8 +1208,7 @@ 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; + auto& fragSpirv = spirvs[programObject->GetShaderIndexByStage(ShaderStage::Fragment)]; MG_Util::ShaderTranspiler::SpvcSession spvcSession(fragSpirv); 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); } From 35658eb9989c113a399dcc81e9e45e520385d600 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Mon, 23 Mar 2026 16:36:17 +0800 Subject: [PATCH 24/31] [Refactor] (MG_Backend/DirectVulkan): move uniform reflection from UniformDescriptorBinder to ProgramFactory --- .../DirectVulkan/Renderer/ProgramFactory.cpp | 348 +++++++++++++- .../DirectVulkan/Renderer/ProgramFactory.h | 36 +- .../Renderer/UniformDescriptorBinder.cpp | 450 ++---------------- .../Renderer/UniformDescriptorBinder.h | 38 +- .../DirectVulkan/Renderer/VulkanRenderer.cpp | 18 +- 5 files changed, 431 insertions(+), 459 deletions(-) diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp index ad1f254b..9cbb002b 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp @@ -8,6 +8,8 @@ #include "ProgramFactory.h" +#include "MG_Util/ShaderTranspiler/Types.h" +#include #include #include #include @@ -321,7 +323,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { } } // namespace - ProgramFactory::~ProgramFactory() = default; + ProgramFactory::~ProgramFactory() { + DestroyLayoutCache(); + } VkShaderStageFlagBits ProgramFactory::ToVkStage(ShaderStage stage) { switch (stage) { @@ -355,6 +359,266 @@ namespace MobileGL::MG_Backend::DirectVulkan { return hash; } + ProgramFactory::HashType ProgramFactory::ComputeLayoutHash(const MG_State::GLState::ProgramObject& program) const { + XXHASH_VERIFY(XXH64_reset(m_hashState, m_config.CacheVersion)); + const auto& spirvs = program.GetGeneratedSpirv(); + for (const auto& spv : spirvs) { + XXHASH_VERIFY(XXH64_update(m_hashState, spv.data(), spv.size() * sizeof(Uint))); + } + + 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))); + } + return XXH64_digest(m_hashState); + } + + 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; + } + } + + Bool ProgramFactory::ReflectBindingKinds(const MG_State::GLState::ProgramObject& program, + Vector& outKinds) const { + outKinds.assign(m_maxBindings, DescriptorBindingKind::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, DescriptorBindingKind 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 == DescriptorBindingKind::CombinedImageSampler) { + outKinds[binding] = DescriptorBindingKind::CombinedImageSampler; + } else if (outKinds[binding] == DescriptorBindingKind::None) { + outKinds[binding] = kind; + } + } + }; + + applyBindings(SPVC_RESOURCE_TYPE_UNIFORM_BUFFER, DescriptorBindingKind::UniformBufferDynamic); + applyBindings(SPVC_RESOURCE_TYPE_SAMPLED_IMAGE, DescriptorBindingKind::CombinedImageSampler); + + spvc_context_destroy(context); + } + + return true; + } + + Bool ProgramFactory::ReflectSamplerBindings(const MG_State::GLState::ProgramObject& program, + VkProgramLayout& 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 ProgramFactory::ReflectGlobalUboBinding(const MG_State::GLState::ProgramObject& program, + VkProgramLayout& 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; + } + Vector& ProgramFactory::GetOrCreatePipelineShaderStages( const MG_State::GLState::ProgramObject& program, CompileOptionFlags flags) { auto hash = ComputeHash(program, flags); @@ -402,4 +666,86 @@ namespace MobileGL::MG_Backend::DirectVulkan { return entry.stages; } + + const ProgramFactory::VkProgramLayout* ProgramFactory::GetOrCreateProgramLayout( + const MG_State::GLState::ProgramObject& program) { + const HashType hash = ComputeLayoutHash(program); + auto it = m_layoutCache.find(hash); + if (it != m_layoutCache.end()) { + return &it->second; + } + + VkProgramLayout layout{}; + layout.hash = hash; + if (!ReflectBindingKinds(program, layout.bindingKinds)) { + MGLOG_E("ProgramFactory::GetOrCreateProgramLayout failed: reflection failed"); + return nullptr; + } + if (!ReflectSamplerBindings(program, layout)) { + MGLOG_E("ProgramFactory::GetOrCreateProgramLayout failed: sampler reflection failed"); + return nullptr; + } + if (!ReflectGlobalUboBinding(program, layout)) { + MGLOG_E("ProgramFactory::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 == 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; + 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), + "ProgramFactory::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), + "ProgramFactory::GetOrCreateProgramLayout, vkCreatePipelineLayout"); + + auto [insertIt, _] = m_layoutCache.emplace(hash, std::move(layout)); + return &insertIt->second; + } + + VkPipelineLayout ProgramFactory::GetOrCreatePipelineLayout(const MG_State::GLState::ProgramObject& program) { + const auto* layout = GetOrCreateProgramLayout(program); + return layout ? layout->pipelineLayout : VK_NULL_HANDLE; + } + + void ProgramFactory::DestroyLayoutCache() { + for (auto& [_, layout] : m_layoutCache) { + 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_layoutCache.clear(); + } } // 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..cc7d8635 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,6 +34,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { }; using CompileOptionFlags = Flags; using HashType = Uint64; + struct VkProgramObject { HashType hash = 0; Vector stages; @@ -70,8 +79,19 @@ namespace MobileGL::MG_Backend::DirectVulkan { } }; - explicit ProgramFactory(VkDevice device, const VulkanRendererConfig& config) - : m_device(device), m_config(config) { + struct VkProgramLayout { + HashType hash = 0; + VkDescriptorSetLayout descriptorSetLayout = VK_NULL_HANDLE; + VkPipelineLayout pipelineLayout = VK_NULL_HANDLE; + Vector bindingKinds; + Vector dynamicBindings; + Vector samplerUniformLocationByBinding; + Vector samplerTextureTargetByBinding; + Int globalUboBinding = -1; + }; + + explicit ProgramFactory(VkDevice device, const VulkanRendererConfig& config, Uint32 maxBindings = 16) + : m_device(device), m_config(config), m_maxBindings(maxBindings) { VkProgramObject::s_device = device; } ~ProgramFactory(); @@ -80,12 +100,24 @@ namespace MobileGL::MG_Backend::DirectVulkan { HashType ComputeHash(const MG_State::GLState::ProgramObject& program, CompileOptionFlags flags) const; Vector& GetOrCreatePipelineShaderStages( const MG_State::GLState::ProgramObject& program, CompileOptionFlags flags); + const VkProgramLayout* GetOrCreateProgramLayout(const MG_State::GLState::ProgramObject& program); + VkPipelineLayout GetOrCreatePipelineLayout(const MG_State::GLState::ProgramObject& program); static VkShaderStageFlagBits ToVkStage(ShaderStage stage); private: + HashType ComputeLayoutHash(const MG_State::GLState::ProgramObject& program) const; + static TextureTarget UniformTypeToTextureTarget(GLenum glType); + Bool ReflectBindingKinds(const MG_State::GLState::ProgramObject& program, + Vector& outKinds) const; + Bool ReflectSamplerBindings(const MG_State::GLState::ProgramObject& program, VkProgramLayout& layout) const; + Bool ReflectGlobalUboBinding(const MG_State::GLState::ProgramObject& program, VkProgramLayout& layout) const; + void DestroyLayoutCache(); + VkDevice m_device = VK_NULL_HANDLE; + Uint32 m_maxBindings = 0; UnorderedMap m_cache; + UnorderedMap m_layoutCache; 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 index b9cafd44..ae3e21a7 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.cpp @@ -11,7 +11,6 @@ #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 { @@ -54,127 +53,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { } } - 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, VkBufferManager* bufferManager, + ProgramFactory* programFactory, VkDeviceSize minUniformBufferOffsetAlignment, Uint32 frameCount, Uint32 maxBindings, Uint32 setsPerFrame, VkTextureManager* textureManager, VkSamplerManager* samplerManager) { @@ -182,6 +62,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { 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"); @@ -192,6 +74,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_device = device; m_bufferManager = bufferManager; + m_programFactory = programFactory; m_minDynamicOffsetAlignment = std::max(1, minUniformBufferOffsetAlignment); m_frameCount = frameCount; m_maxBindings = maxBindings; @@ -216,7 +99,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { return false; } frame.descriptorPools.push_back({initialPool, m_setsPerFrame, 0}); - MGLOG_D("UniformDescriptorBinder: frame %u descriptor pool created (maxSets=%u)", frameIndex, m_setsPerFrame); + MGLOG_D("UniformDescriptorBinder: frame %u descriptor pool created (maxSets=%u)", frameIndex, + m_setsPerFrame); } return true; @@ -238,9 +122,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { frame.peakAllocatedSetsThisFrame = 0; } m_frames.clear(); - DestroyProgramLayouts(); m_bufferManager = nullptr; + m_programFactory = nullptr; m_device = VK_NULL_HANDLE; m_minDynamicOffsetAlignment = 1; m_frameCount = 0; @@ -273,203 +157,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { } } - 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 { + const ProgramFactory::VkProgramLayout& 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"); @@ -486,12 +177,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { return false; } - const MG_State::GLState::SamplerObject* samplerToUse = samplerOverride ? samplerOverride.get() : texture->GetSamplerObject().get(); + const MG_State::GLState::SamplerObject* samplerToUse = + samplerOverride ? samplerOverride.get() : texture->GetSamplerObject().get(); if (!samplerToUse) { return false; } - VkTextureManager::TextureResource* resource = - m_textureManager->SyncTextureAndGetDescriptor(*texture); + VkTextureManager::TextureResource* resource = m_textureManager->SyncTextureAndGetDescriptor(*texture); if (resource == nullptr) { return false; } @@ -524,8 +215,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { } Bool UniformDescriptorBinder::ResolveSamplerDescriptorOverride( - const SamplerBindingOverride& samplerBindingOverride, - VkDescriptorImageInfo& outImageInfo) const { + 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) { @@ -546,7 +236,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { } Bool UniformDescriptorBinder::ResolveSamplerTexture(const MG_State::GLState::ProgramObject& program, - const ProgramLayout& layout, Uint32 binding, + const ProgramFactory::VkProgramLayout& layout, Uint32 binding, SharedPtr& outTexture) const { outTexture.reset(); if (!MG_State::pGLContext || binding >= layout.samplerUniformLocationByBinding.size()) { @@ -572,13 +262,16 @@ namespace MobileGL::MG_Backend::DirectVulkan { Bool UniformDescriptorBinder::CollectSampledTextures(const MG_State::GLState::ProgramObject& program, Vector& outTextures) { outTextures.clear(); - ProgramLayout* layout = GetOrCreateProgramLayout(program); + MOBILEGL_ASSERT(m_programFactory != nullptr, "CollectSampledTextures: program factory is null"); + const auto* layout = m_programFactory->GetOrCreateProgramLayout(program); if (layout == nullptr) { return false; } - for (Uint32 binding = 0; binding < m_maxBindings; ++binding) { - if (layout->bindingKinds[binding] != BindingKind::CombinedImageSampler) { + const Uint32 bindingCount = + std::min(m_maxBindings, static_cast(layout->bindingKinds.size())); + for (Uint32 binding = 0; binding < bindingCount; ++binding) { + if (layout->bindingKinds[binding] != ProgramFactory::DescriptorBindingKind::CombinedImageSampler) { continue; } @@ -595,74 +288,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { 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::GatherBindingPayloads(const MG_State::GLState::ProgramObject& program, Vector& outData, Vector& outSizes) const { @@ -749,7 +374,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { const VkResult result = vkCreateDescriptorPool(m_device, &poolInfo, nullptr, &outPool); if (result != VK_SUCCESS) { - MGLOG_E("UniformDescriptorBinder::CreateDescriptorPool failed: vkCreateDescriptorPool returned %d", result); + MGLOG_E("UniformDescriptorBinder::CreateDescriptorPool failed: vkCreateDescriptorPool returned %d", + result); return false; } return true; @@ -790,7 +416,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { const MG_State::GLState::ProgramObject& program, Uint32 frameIndex, const SamplerBindingOverride* samplerBindingOverride) { - ProgramLayout* layout = GetOrCreateProgramLayout(program); + MOBILEGL_ASSERT(m_programFactory != nullptr, "BindProgramUniformBuffers: program factory is null"); + const auto* layout = m_programFactory->GetOrCreateProgramLayout(program); MOBILEGL_ASSERT(layout != nullptr, "UniformDescriptorBinder::BindProgramUniformBuffers: program layout is null"); @@ -816,7 +443,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (outResult == VK_SUCCESS) { ++bucket.allocatedSets; ++frame.allocatedSetsThisFrame; - frame.peakAllocatedSetsThisFrame = std::max(frame.peakAllocatedSetsThisFrame, frame.allocatedSetsThisFrame); + frame.peakAllocatedSetsThisFrame = + std::max(frame.peakAllocatedSetsThisFrame, frame.allocatedSetsThisFrame); } }; @@ -856,9 +484,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { imageInfos.reserve(m_maxBindings); dynamicOffsets.reserve(layout->dynamicBindings.size()); - for (Uint32 binding = 0; binding < m_maxBindings; ++binding) { + const Uint32 bindingCount = + std::min(m_maxBindings, static_cast(layout->bindingKinds.size())); + for (Uint32 binding = 0; binding < bindingCount; ++binding) { const auto kind = layout->bindingKinds[binding]; - if (kind == BindingKind::None) { + if (kind == ProgramFactory::DescriptorBindingKind::None) { continue; } @@ -869,7 +499,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { write.dstArrayElement = 0; write.descriptorCount = 1; - if (kind == BindingKind::UniformBufferDynamic) { + if (kind == ProgramFactory::DescriptorBindingKind::UniformBufferDynamic) { const void* payload = bindingData[binding]; VkDeviceSize payloadSize = bindingSizes[binding]; if (payload == nullptr || payloadSize == 0) { @@ -937,22 +567,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { 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()); + 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/UniformDescriptorBinder.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.h index d023988b..5697f32d 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.h @@ -8,6 +8,7 @@ #pragma once +#include "ProgramFactory.h" #include "VkBufferManager.h" #include "VkSamplerManager.h" #include "VkTextureManager.h" @@ -23,12 +24,6 @@ namespace MobileGL::MG_State::GLState { namespace MobileGL::MG_Backend::DirectVulkan { class UniformDescriptorBinder { public: - enum class BindingKind : Uint8 { - None = 0, - UniformBufferDynamic, - CombinedImageSampler - }; - struct SamplerBindingOverride { Uint32 binding = 0; MG_State::GLState::ITextureObject* texture = nullptr; @@ -36,13 +31,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { }; 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, Vector& outTextures); Bool BindProgramUniformBuffers(VkCommandBuffer commandBuffer, @@ -65,42 +60,23 @@ namespace MobileGL::MG_Backend::DirectVulkan { Uint32 peakAllocatedSetsThisFrame = 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::VkProgramLayout& layout, Uint32 binding, + SharedPtr& outTexture) const; Bool ResolveSamplerDescriptor(VkCommandBuffer commandBuffer, const MG_State::GLState::ProgramObject& program, - const ProgramLayout& layout, Uint32 binding, + const ProgramFactory::VkProgramLayout& layout, 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 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(); VkDevice m_device = VK_NULL_HANDLE; VkBufferManager* m_bufferManager = nullptr; + ProgramFactory* m_programFactory = nullptr; Vector m_frames; - UnorderedMap m_programLayouts; VkDeviceSize m_minDynamicOffsetAlignment = 1; Uint32 m_frameCount = 0; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index dd549cf5..eb01deb3 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -98,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; @@ -339,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(); @@ -351,10 +353,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_uniformDescriptorBinder = MakeUnique(); MOBILEGL_ASSERT(m_uniformDescriptorBinder != nullptr, "UniformDescriptorBinder creation failed."); - succeeded = m_uniformDescriptorBinder->Initialize(m_device, &m_bufferManager, - m_physicalDevice.properties.limits.minUniformBufferOffsetAlignment, - m_config.MaxFramesInFlight, 16, 64, - m_textureManager.get(), m_samplerManager.get()); + succeeded = m_uniformDescriptorBinder->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."); @@ -372,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(); @@ -392,6 +393,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_uniformDescriptorBinder->Shutdown(); m_uniformDescriptorBinder.reset(); } + m_programFactory.reset(); ShutdownSwapchain(); m_renderPassManager.reset(); @@ -669,7 +671,7 @@ void main() { PipelineFactory::PipelineCreatePayload payload{ .programHash = m_programFactory->ComputeHash(*m_blitResources.program, transformFlags), .vertexInputHash = 0, - .pipelineLayout = m_uniformDescriptorBinder->GetOrCreatePipelineLayout(*m_blitResources.program), + .pipelineLayout = m_programFactory->GetOrCreatePipelineLayout(*m_blitResources.program), .renderPass = renderPassEntry.renderPass, .subpass = 0, .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, @@ -707,7 +709,7 @@ void main() { auto vertexInputHash = m_vertexInputStateFactory->ComputeHash(vao); auto& vis = m_vertexInputStateFactory->GetOrCreateVertexInputState(vao); - auto pipelineLayout = m_uniformDescriptorBinder->GetOrCreatePipelineLayout(program); + auto pipelineLayout = m_programFactory->GetOrCreatePipelineLayout(program); auto cullFaceEnabled = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::CullFace); auto depthTestEnabled = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::DepthTest); BlendFactor srcRGB = BlendFactor::One; From accfaab72018e6bf637f1af0187474e5cec4a64f Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Tue, 24 Mar 2026 13:29:18 +0800 Subject: [PATCH 25/31] [Refactor] (MG_Backend/DirectVulkan): get rid of junk, and rename some symbols --- CMakeLists.txt | 2 +- ...escriptorBinder.cpp => UniformManager.cpp} | 72 ++++++++----------- ...ormDescriptorBinder.h => UniformManager.h} | 8 +-- .../DirectVulkan/Renderer/VulkanRenderer.cpp | 32 ++++----- .../DirectVulkan/Renderer/VulkanRenderer.h | 6 +- 5 files changed, 55 insertions(+), 65 deletions(-) rename MobileGL/MG_Backend/DirectVulkan/Renderer/{UniformDescriptorBinder.cpp => UniformManager.cpp} (91%) rename MobileGL/MG_Backend/DirectVulkan/Renderer/{UniformDescriptorBinder.h => UniformManager.h} (94%) diff --git a/CMakeLists.txt b/CMakeLists.txt index c6f35a10..b1864dfa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -223,7 +223,7 @@ 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 diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp similarity index 91% rename from MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.cpp rename to MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp index ae3e21a7..85414a41 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp @@ -6,7 +6,7 @@ // SPDX-License-Identifier: LGPL-3.0-only // End of Source File Header -#include "UniformDescriptorBinder.h" +#include "UniformManager.h" #include "MG_State/GLState/Core.h" #include "MG_State/GLState/ProgramState/ProgramObject.h" @@ -53,7 +53,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { } } - Bool UniformDescriptorBinder::Initialize(VkDevice device, VkBufferManager* bufferManager, + Bool UniformManager::Initialize(VkDevice device, VkBufferManager* bufferManager, ProgramFactory* programFactory, VkDeviceSize minUniformBufferOffsetAlignment, Uint32 frameCount, Uint32 maxBindings, Uint32 setsPerFrame, @@ -106,7 +106,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { return true; } - void UniformDescriptorBinder::Shutdown() { + void UniformManager::Shutdown() { for (auto& frame : m_frames) { if (m_device != VK_NULL_HANDLE) { for (auto& bucket : frame.descriptorPools) { @@ -135,7 +135,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_samplerManager = nullptr; } - void UniformDescriptorBinder::BeginFrame(Uint32 frameIndex) { + 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) { @@ -157,7 +157,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { } } - Bool UniformDescriptorBinder::ResolveSamplerDescriptor(VkCommandBuffer commandBuffer, + Bool UniformManager::ResolveSamplerDescriptor(VkCommandBuffer commandBuffer, const MG_State::GLState::ProgramObject& program, const ProgramFactory::VkProgramLayout& layout, Uint32 binding, VkDescriptorImageInfo& outImageInfo) const { @@ -214,7 +214,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { return outImageInfo.sampler != VK_NULL_HANDLE; } - Bool UniformDescriptorBinder::ResolveSamplerDescriptorOverride( + 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"); @@ -235,7 +235,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { return outImageInfo.sampler != VK_NULL_HANDLE; } - Bool UniformDescriptorBinder::ResolveSamplerTexture(const MG_State::GLState::ProgramObject& program, + Bool UniformManager::ResolveSamplerTexture(const MG_State::GLState::ProgramObject& program, const ProgramFactory::VkProgramLayout& layout, Uint32 binding, SharedPtr& outTexture) const { outTexture.reset(); @@ -259,7 +259,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { return outTexture != nullptr; } - Bool UniformDescriptorBinder::CollectSampledTextures(const MG_State::GLState::ProgramObject& program, + Bool UniformManager::CollectSampledTextures(const MG_State::GLState::ProgramObject& program, Vector& outTextures) { outTextures.clear(); MOBILEGL_ASSERT(m_programFactory != nullptr, "CollectSampledTextures: program factory is null"); @@ -288,7 +288,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { return true; } - Bool UniformDescriptorBinder::GatherBindingPayloads(const MG_State::GLState::ProgramObject& program, + Bool UniformManager::GatherBindingPayloads(const MG_State::GLState::ProgramObject& program, Vector& outData, Vector& outSizes) const { outData.assign(m_maxBindings, nullptr); @@ -347,7 +347,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { return true; } - Bool UniformDescriptorBinder::CreateDescriptorPool(Uint32 maxSets, VkDescriptorPool& outPool) const { + 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; @@ -381,7 +381,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { return true; } - Bool UniformDescriptorBinder::GrowFrameDescriptorPool(FrameResources& frame, Uint32 frameIndex) { + Bool UniformManager::GrowFrameDescriptorPool(FrameResources& frame, Uint32 frameIndex) { if (frame.descriptorPools.empty()) { return false; } @@ -406,13 +406,26 @@ namespace MobileGL::MG_Backend::DirectVulkan { return true; } - Bool UniformDescriptorBinder::BindProgramUniformBuffers(VkCommandBuffer commandBuffer, - const MG_State::GLState::ProgramObject& program, - Uint32 frameIndex) { - return BindProgramUniformBuffers(commandBuffer, program, frameIndex, nullptr); + VkResult UniformManager::AllocateDescriptorSetsFromActivePool(Uint32 frameIndex, const ProgramFactory::VkProgramLayout& layout, 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 = &layout.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 UniformDescriptorBinder::BindProgramUniformBuffers(VkCommandBuffer commandBuffer, + Bool UniformManager::BindProgramUniformBuffers(VkCommandBuffer commandBuffer, const MG_State::GLState::ProgramObject& program, Uint32 frameIndex, const SamplerBindingOverride* samplerBindingOverride) { @@ -430,32 +443,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { 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); + VkResult allocResult = AllocateDescriptorSetsFromActivePool(frameIndex, *layout, 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; } - allocateFromActivePool(allocResult); + allocResult = AllocateDescriptorSetsFromActivePool(frameIndex, *layout, descriptorSet); } if (allocResult != VK_SUCCESS || descriptorSet == VK_NULL_HANDLE) { MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: vkAllocateDescriptorSets returned %d", @@ -470,7 +465,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { 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"); MOBILEGL_ASSERT(m_bufferManager != nullptr, "BindProgramUniformBuffers: buffer manager is null"); @@ -511,10 +505,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { payloadSize = globalUboSize; } } - if (payload == nullptr || payloadSize == 0) { - payload = kFallbackData; - payloadSize = sizeof(kFallbackData); - } } BufferSlice slice{}; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h similarity index 94% rename from MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.h rename to MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h index 5697f32d..16c9148e 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h @@ -22,7 +22,7 @@ namespace MobileGL::MG_State::GLState { } namespace MobileGL::MG_Backend::DirectVulkan { - class UniformDescriptorBinder { + class UniformManager { public: struct SamplerBindingOverride { Uint32 binding = 0; @@ -40,11 +40,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { void BeginFrame(Uint32 frameIndex); Bool CollectSampledTextures(const MG_State::GLState::ProgramObject& program, 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 SamplerBindingOverride* samplerBindingOverride = nullptr); private: struct DescriptorPoolBucket { @@ -72,6 +70,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { Vector& outSizes) const; Bool CreateDescriptorPool(Uint32 maxSets, VkDescriptorPool& outPool) const; Bool GrowFrameDescriptorPool(FrameResources& frame, Uint32 frameIndex); + VkResult AllocateDescriptorSetsFromActivePool( + Uint32 frameIndex, const ProgramFactory::VkProgramLayout& layout, VkDescriptorSet& outDescriptorSet); VkDevice m_device = VK_NULL_HANDLE; VkBufferManager* m_bufferManager = nullptr; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index eb01deb3..dc50c518 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -351,9 +351,9 @@ 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_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()); @@ -389,9 +389,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { 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(); @@ -431,7 +431,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { MGLOG_I("VulkanRenderer shut down completed"); } - Bool VulkanRenderer::UploadAndBindVertexStreams( + Bool VulkanRenderer::UploadAndBindVertexBuffers( VkCommandBuffer commandBuffer, const MG_State::GLState::VertexArrayObject& vao) { auto& vertexInputState = m_vertexInputStateFactory->GetOrCreateVertexInputState(vao); @@ -661,7 +661,7 @@ 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 @@ -760,7 +760,7 @@ void main() { // 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(); @@ -770,7 +770,7 @@ void main() { // 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, 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(), @@ -856,11 +856,11 @@ void main() { vkCmdBindPipeline(frame.commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); - m_uniformDescriptorBinder->BindProgramUniformBuffers(frame.commandBuffer, program, + m_uniformManager->BindProgramUniformBuffers(frame.commandBuffer, program, m_frameContext.GetCurrentFrameIndex()); - auto vtxUploadOk = UploadAndBindVertexStreams(frame.commandBuffer, vao); - MOBILEGL_ASSERT(vtxUploadOk, "SetupDraw skipped: failed to upload vertex streams"); + 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); @@ -1080,13 +1080,13 @@ 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( + const Bool bound = m_uniformManager->BindProgramUniformBuffers( frame.commandBuffer, *m_blitResources.program, m_frameContext.GetCurrentFrameIndex(), &samplerBindingOverride); MOBILEGL_ASSERT(bound, "TryBlitToDefaultFramebufferWithShader: BindProgramUniformBuffers failed"); @@ -1118,7 +1118,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(); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h index f36a17e3..cc829d80 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h @@ -12,7 +12,7 @@ #include "PipelineFactory.h" #include "ProgramFactory.h" #include "SwapchainObject.h" -#include "UniformDescriptorBinder.h" +#include "UniformManager.h" #include "VertexInputStateFactory.h" #include "VkBufferObject.h" #include "VkBufferManager.h" @@ -180,7 +180,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { UniquePtr m_pipelineFactory; UniquePtr m_programFactory; - UniquePtr m_uniformDescriptorBinder; + UniquePtr m_uniformManager; UniquePtr m_vertexInputStateFactory; UniquePtr m_clearManager; UniquePtr m_renderPassManager; @@ -206,7 +206,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { const MG_State::GLState::VertexArrayObject& vao, const RenderPassEntry& renderPassEntry); - 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); From a039ce0987c7ee1e9517c3c961e9c7fe171a9082 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Thu, 26 Mar 2026 09:49:05 +0800 Subject: [PATCH 26/31] [Chore] (MG_State/ProgramState): some renames --- .../GLState/ProgramState/ProgramObject.cpp | 8 ++++---- .../MG_State/GLState/ProgramState/ProgramObject.h | 15 ++++++--------- MobileGL/MG_Util/ShaderTranspiler/SpvcSession.cpp | 2 +- MobileGL/MG_Util/ShaderTranspiler/SpvcSession.h | 2 +- 4 files changed, 12 insertions(+), 15 deletions(-) diff --git a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp index 11e5ed62..877cf5cc 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp +++ b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp @@ -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_Util/ShaderTranspiler/SpvcSession.cpp b/MobileGL/MG_Util/ShaderTranspiler/SpvcSession.cpp index 6caa19ad..5b27265e 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpvcSession.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/SpvcSession.cpp @@ -112,7 +112,7 @@ namespace MobileGL { 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); + 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); diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpvcSession.h b/MobileGL/MG_Util/ShaderTranspiler/SpvcSession.h index a552b291..7696c8b2 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpvcSession.h +++ b/MobileGL/MG_Util/ShaderTranspiler/SpvcSession.h @@ -59,7 +59,7 @@ namespace MobileGL { UnorderedMap plainUniformOffsetsInUBO; UnorderedMap plainUniformMemberSizesInBytes; UnorderedMap plainUniformMemberTypes; - SizeT uboSize = 0; + SizeT globalUboSize = 0; }; class SpvcSession { From 4321c4a827d6d5f0124f2b97d702d03b1cd19383 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Sun, 29 Mar 2026 00:03:57 +0800 Subject: [PATCH 27/31] [Submodule] (3rdparty/SPIRV-Reflect): add SPIRV-Reflect as dependency --- .gitmodules | 3 +++ 3rdparty/SPIRV-Reflect | 1 + CMakeLists.txt | 9 +++++++++ 3 files changed, 13 insertions(+) create mode 160000 3rdparty/SPIRV-Reflect 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 b1864dfa..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) @@ -270,6 +278,7 @@ set(MOBILEGL_LINK_LIBRARIES xxHash::xxhash GPUOpen::VulkanMemoryAllocator Vulkan::UtilityHeaders + spirv-reflect-static ) set(MOBILEGL_COMPILE_DEF From 83d1ce177e0d855db918d2a9bc66283a6f44f985 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Wed, 1 Apr 2026 10:28:37 +0800 Subject: [PATCH 28/31] [Optimization] (MG_Util/ShaderTranspiler/SpvcSession): use SPIRV-Reflect to avoid full AST parse, speeding up reflection --- .../MG_Util/ShaderTranspiler/SpvcSession.cpp | 295 ++++++++++++++---- .../MG_Util/ShaderTranspiler/SpvcSession.h | 16 +- 2 files changed, 249 insertions(+), 62 deletions(-) diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpvcSession.cpp b/MobileGL/MG_Util/ShaderTranspiler/SpvcSession.cpp index 5b27265e..0045bab4 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpvcSession.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/SpvcSession.cpp @@ -11,65 +11,194 @@ 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; + 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 +209,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 +216,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->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.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); + 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 +317,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 7696c8b2..f30143bb 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,6 +56,11 @@ namespace MobileGL { } }; + enum class SessionUsageBit { + Reflection = 1 << 0, + Transpile = 1 << 1, + }; + struct SpvcMetadata { UnorderedMap plainUniformOffsetsInUBO; UnorderedMap plainUniformMemberSizesInBytes; @@ -66,7 +72,8 @@ namespace MobileGL { public: SpvcSession() {} - explicit SpvcSession(const Vector& spirv); + explicit SpvcSession(const Vector& spirv, + Flags usage = SessionUsageBit::Reflection | SessionUsageBit::Transpile); 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 From 1b2a7989af6ba8c006f7c9da12e557d817b568a8 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Wed, 1 Apr 2026 14:57:00 +0800 Subject: [PATCH 29/31] [Fix] (MG_Util/ShaderTranspiler/SpvcSession): use proper usage bit to select the right code path. Fix bugs along the way --- MobileGL/MG_Backend/DirectGLES/Managers.cpp | 3 ++- MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp | 2 +- MobileGL/MG_Test/Program/ProgramTest.cpp | 8 ++++---- MobileGL/MG_Test/Program/ProgramUtilTest.cpp | 6 +++--- MobileGL/MG_Util/ShaderTranspiler/SpvcSession.cpp | 2 +- MobileGL/MG_Util/ShaderTranspiler/SpvcSession.h | 2 +- 6 files changed, 12 insertions(+), 11 deletions(-) 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_State/GLState/ProgramState/ProgramObject.cpp b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp index 877cf5cc..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, " diff --git a/MobileGL/MG_Test/Program/ProgramTest.cpp b/MobileGL/MG_Test/Program/ProgramTest.cpp index cc571775..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); @@ -1209,7 +1209,7 @@ TEST_F(ProgramTest, CompileShaderWithSamplerAsVarName) { auto programObject = MG_State::pGLContext->GetCurrentProgram(); auto& spirvs = programObject->GetGeneratedSpirv(); auto& fragSpirv = spirvs[programObject->GetShaderIndexByStage(ShaderStage::Fragment)]; - MG_Util::ShaderTranspiler::SpvcSession spvcSession(fragSpirv); + MG_Util::ShaderTranspiler::SpvcSession spvcSession(fragSpirv, MG_Util::ShaderTranspiler::SessionUsageBit::Transpile); spvc_compiler_options options; spvcSession.CreateOptions(&options); 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_Util/ShaderTranspiler/SpvcSession.cpp b/MobileGL/MG_Util/ShaderTranspiler/SpvcSession.cpp index 0045bab4..81ebb237 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpvcSession.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/SpvcSession.cpp @@ -286,7 +286,7 @@ namespace MobileGL { 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->name, GLOBAL_UBO_NAME) != 0) continue; + if (strcmp(binding->type_description->type_name, GLOBAL_UBO_NAME) != 0) continue; auto& block = binding->block; metadata.globalUboSize = block.size; diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpvcSession.h b/MobileGL/MG_Util/ShaderTranspiler/SpvcSession.h index f30143bb..7bbc406b 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpvcSession.h +++ b/MobileGL/MG_Util/ShaderTranspiler/SpvcSession.h @@ -73,7 +73,7 @@ namespace MobileGL { SpvcSession() {} explicit SpvcSession(const Vector& spirv, - Flags usage = SessionUsageBit::Reflection | SessionUsageBit::Transpile); + Flags usage); SpvcSession(SpvcSession&) = delete; From 4a3a44924ab713180f54b881614a1339f3166bff Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Fri, 3 Apr 2026 16:45:11 +0800 Subject: [PATCH 30/31] [Refactor] (MG_Backend/DirectVulkan/ProgramFactory): refactor uniform reflection --- .../DirectVulkan/Renderer/ProgramFactory.cpp | 365 +++++------------- .../DirectVulkan/Renderer/ProgramFactory.h | 80 ++-- .../DirectVulkan/Renderer/UniformManager.cpp | 68 ++-- .../DirectVulkan/Renderer/UniformManager.h | 11 +- .../DirectVulkan/Renderer/VulkanRenderer.cpp | 34 +- .../MG_Util/ShaderTranspiler/SpvcSession.cpp | 9 +- 6 files changed, 208 insertions(+), 359 deletions(-) diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp index 9cbb002b..efdbffb9 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp @@ -8,6 +8,7 @@ #include "ProgramFactory.h" +#include "MG_Util/ShaderTranspiler/SpvcSession.h" #include "MG_Util/ShaderTranspiler/Types.h" #include #include @@ -23,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; @@ -323,10 +326,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { } } // namespace - ProgramFactory::~ProgramFactory() { - DestroyLayoutCache(); - } - VkShaderStageFlagBits ProgramFactory::ToVkStage(ShaderStage stage) { switch (stage) { case ShaderStage::Vertex: @@ -355,24 +354,17 @@ 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))); - HashType hash = XXH64_digest(m_hashState); - return hash; - } - - ProgramFactory::HashType ProgramFactory::ComputeLayoutHash(const MG_State::GLState::ProgramObject& program) const { - XXHASH_VERIFY(XXH64_reset(m_hashState, m_config.CacheVersion)); - const auto& spirvs = program.GetGeneratedSpirv(); - for (const auto& spv : spirvs) { - XXHASH_VERIFY(XXH64_update(m_hashState, spv.data(), spv.size() * sizeof(Uint))); - } + // 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))); } - return XXH64_digest(m_hashState); + + HashType hash = XXH64_digest(m_hashState); + return hash; } TextureTarget ProgramFactory::UniformTypeToTextureTarget(GLenum glType) { @@ -426,205 +418,117 @@ namespace MobileGL::MG_Backend::DirectVulkan { } } - Bool ProgramFactory::ReflectBindingKinds(const MG_State::GLState::ProgramObject& program, - Vector& outKinds) const { - outKinds.assign(m_maxBindings, DescriptorBindingKind::None); + 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; } - spvc_context context = nullptr; - spvc_parsed_ir ir = nullptr; - spvc_compiler compiler = nullptr; - spvc_resources resources = nullptr; + SpvcSession session(module, SessionUsageBit::Reflection); - 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, DescriptorBindingKind 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; + // 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; } - 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 == DescriptorBindingKind::CombinedImageSampler) { - outKinds[binding] = DescriptorBindingKind::CombinedImageSampler; - } else if (outKinds[binding] == DescriptorBindingKind::None) { - outKinds[binding] = kind; + 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; + } - applyBindings(SPVC_RESOURCE_TYPE_UNIFORM_BUFFER, DescriptorBindingKind::UniformBufferDynamic); - applyBindings(SPVC_RESOURCE_TYPE_SAMPLED_IMAGE, DescriptorBindingKind::CombinedImageSampler); - - spvc_context_destroy(context); + entry.samplerUniformLocationByBinding[binding] = location; + entry.samplerTextureTargetByBinding[binding] = + UniformTypeToTextureTarget(program.GetUniformType(static_cast(location))); + } } - return true; - } - - Bool ProgramFactory::ReflectSamplerBindings(const MG_State::GLState::ProgramObject& program, - VkProgramLayout& 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()) { + // 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; } - 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; + 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; } - 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); + bindings.push_back(layoutBinding); } - return true; + 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"); } - Bool ProgramFactory::ReflectGlobalUboBinding(const MG_State::GLState::ProgramObject& program, - VkProgramLayout& 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; - } - - Vector& ProgramFactory::GetOrCreatePipelineShaderStages( + 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]; @@ -664,88 +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); - const ProgramFactory::VkProgramLayout* ProgramFactory::GetOrCreateProgramLayout( - const MG_State::GLState::ProgramObject& program) { - const HashType hash = ComputeLayoutHash(program); - auto it = m_layoutCache.find(hash); - if (it != m_layoutCache.end()) { - return &it->second; - } - - VkProgramLayout layout{}; - layout.hash = hash; - if (!ReflectBindingKinds(program, layout.bindingKinds)) { - MGLOG_E("ProgramFactory::GetOrCreateProgramLayout failed: reflection failed"); - return nullptr; - } - if (!ReflectSamplerBindings(program, layout)) { - MGLOG_E("ProgramFactory::GetOrCreateProgramLayout failed: sampler reflection failed"); - return nullptr; - } - if (!ReflectGlobalUboBinding(program, layout)) { - MGLOG_E("ProgramFactory::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 == 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; - 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), - "ProgramFactory::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), - "ProgramFactory::GetOrCreateProgramLayout, vkCreatePipelineLayout"); - - auto [insertIt, _] = m_layoutCache.emplace(hash, std::move(layout)); - return &insertIt->second; - } - - VkPipelineLayout ProgramFactory::GetOrCreatePipelineLayout(const MG_State::GLState::ProgramObject& program) { - const auto* layout = GetOrCreateProgramLayout(program); - return layout ? layout->pipelineLayout : VK_NULL_HANDLE; - } - - void ProgramFactory::DestroyLayoutCache() { - for (auto& [_, layout] : m_layoutCache) { - 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_layoutCache.clear(); + 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 cc7d8635..06fe897c 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.h @@ -39,6 +39,16 @@ namespace MobileGL::MG_Backend::DirectVulkan { 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; @@ -48,76 +58,86 @@ 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(); } }; - struct VkProgramLayout { - HashType hash = 0; - VkDescriptorSetLayout descriptorSetLayout = VK_NULL_HANDLE; - VkPipelineLayout pipelineLayout = VK_NULL_HANDLE; - Vector bindingKinds; - Vector dynamicBindings; - Vector samplerUniformLocationByBinding; - Vector samplerTextureTargetByBinding; - Int globalUboBinding = -1; - }; - 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); - const VkProgramLayout* GetOrCreateProgramLayout(const MG_State::GLState::ProgramObject& program); - VkPipelineLayout GetOrCreatePipelineLayout(const MG_State::GLState::ProgramObject& program); static VkShaderStageFlagBits ToVkStage(ShaderStage stage); private: - HashType ComputeLayoutHash(const MG_State::GLState::ProgramObject& program) const; static TextureTarget UniformTypeToTextureTarget(GLenum glType); - Bool ReflectBindingKinds(const MG_State::GLState::ProgramObject& program, - Vector& outKinds) const; - Bool ReflectSamplerBindings(const MG_State::GLState::ProgramObject& program, VkProgramLayout& layout) const; - Bool ReflectGlobalUboBinding(const MG_State::GLState::ProgramObject& program, VkProgramLayout& layout) const; - void DestroyLayoutCache(); + void ReflectLayout(const MG_State::GLState::ProgramObject& program, VkProgramObject& entry) const; VkDevice m_device = VK_NULL_HANDLE; Uint32 m_maxBindings = 0; UnorderedMap m_cache; - UnorderedMap m_layoutCache; const VulkanRendererConfig& m_config; static inline XXH64_state_t* m_hashState = XXH64_createState(); }; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp index 85414a41..7f16d652 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp @@ -158,18 +158,18 @@ namespace MobileGL::MG_Backend::DirectVulkan { } Bool UniformManager::ResolveSamplerDescriptor(VkCommandBuffer commandBuffer, - const MG_State::GLState::ProgramObject& program, - const ProgramFactory::VkProgramLayout& layout, - Uint32 binding, VkDescriptorImageInfo& outImageInfo) const { + 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, layout, binding, texture)) { + if (!ResolveSamplerTexture(program, programObj, binding, texture)) { return false; } - const Int location = layout.samplerUniformLocationByBinding[binding]; + 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(); @@ -236,14 +236,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { } Bool UniformManager::ResolveSamplerTexture(const MG_State::GLState::ProgramObject& program, - const ProgramFactory::VkProgramLayout& layout, Uint32 binding, - SharedPtr& outTexture) const { + const ProgramFactory::VkProgramObject& programObj, Uint32 binding, + SharedPtr& outTexture) const { outTexture.reset(); - if (!MG_State::pGLContext || binding >= layout.samplerUniformLocationByBinding.size()) { + if (!MG_State::pGLContext || binding >= programObj.samplerUniformLocationByBinding.size()) { return false; } - const Int location = layout.samplerUniformLocationByBinding[binding]; + const Int location = programObj.samplerUniformLocationByBinding[binding]; if (location < 0) { return false; } @@ -254,29 +254,25 @@ namespace MobileGL::MG_Backend::DirectVulkan { } auto& textureUnit = MG_State::pGLContext->GetTextureUnitObject(unit); - const TextureTarget preferredTarget = layout.samplerTextureTargetByBinding[binding]; + const TextureTarget preferredTarget = programObj.samplerTextureTargetByBinding[binding]; outTexture = textureUnit.GetBindingSlot(preferredTarget).GetBoundObject(); return outTexture != nullptr; } Bool UniformManager::CollectSampledTextures(const MG_State::GLState::ProgramObject& program, - Vector& outTextures) { + const ProgramFactory::VkProgramObject& programObj, + Vector& outTextures) { outTextures.clear(); - MOBILEGL_ASSERT(m_programFactory != nullptr, "CollectSampledTextures: program factory is null"); - const auto* layout = m_programFactory->GetOrCreateProgramLayout(program); - if (layout == nullptr) { - return false; - } const Uint32 bindingCount = - std::min(m_maxBindings, static_cast(layout->bindingKinds.size())); + std::min(m_maxBindings, static_cast(programObj.bindingKinds.size())); for (Uint32 binding = 0; binding < bindingCount; ++binding) { - if (layout->bindingKinds[binding] != ProgramFactory::DescriptorBindingKind::CombinedImageSampler) { + if (programObj.bindingKinds[binding] != ProgramFactory::DescriptorBindingKind::CombinedImageSampler) { continue; } SharedPtr texture; - if (!ResolveSamplerTexture(program, *layout, binding, texture) || !texture) { + if (!ResolveSamplerTexture(program, programObj, binding, texture) || !texture) { continue; } @@ -406,13 +402,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { return true; } - VkResult UniformManager::AllocateDescriptorSetsFromActivePool(Uint32 frameIndex, const ProgramFactory::VkProgramLayout& layout, VkDescriptorSet& outDescriptorSet) { + 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 = &layout.descriptorSetLayout; + allocInfo.pSetLayouts = &programObj.descriptorSetLayout; allocInfo.descriptorPool = bucket.handle; VkResult result = vkAllocateDescriptorSets(m_device, &allocInfo, &outDescriptorSet); @@ -426,14 +422,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { } Bool UniformManager::BindProgramUniformBuffers(VkCommandBuffer commandBuffer, - const MG_State::GLState::ProgramObject& program, - Uint32 frameIndex, - const SamplerBindingOverride* samplerBindingOverride) { - MOBILEGL_ASSERT(m_programFactory != nullptr, "BindProgramUniformBuffers: program factory is null"); - const auto* layout = m_programFactory->GetOrCreateProgramLayout(program); - MOBILEGL_ASSERT(layout != nullptr, - "UniformDescriptorBinder::BindProgramUniformBuffers: program layout is null"); - + 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"); @@ -444,13 +436,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { } VkDescriptorSet descriptorSet = VK_NULL_HANDLE; - VkResult allocResult = AllocateDescriptorSetsFromActivePool(frameIndex, *layout, descriptorSet); + 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, *layout, descriptorSet); + allocResult = AllocateDescriptorSetsFromActivePool(frameIndex, programObj, descriptorSet); } if (allocResult != VK_SUCCESS || descriptorSet == VK_NULL_HANDLE) { MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: vkAllocateDescriptorSets returned %d", @@ -476,12 +468,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { Vector dynamicOffsets; bufferInfos.reserve(m_maxBindings); imageInfos.reserve(m_maxBindings); - dynamicOffsets.reserve(layout->dynamicBindings.size()); + dynamicOffsets.reserve(programObj.dynamicBindings.size()); const Uint32 bindingCount = - std::min(m_maxBindings, static_cast(layout->bindingKinds.size())); + std::min(m_maxBindings, static_cast(programObj.bindingKinds.size())); for (Uint32 binding = 0; binding < bindingCount; ++binding) { - const auto kind = layout->bindingKinds[binding]; + const auto kind = programObj.bindingKinds[binding]; if (kind == ProgramFactory::DescriptorBindingKind::None) { continue; } @@ -497,7 +489,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { const void* payload = bindingData[binding]; VkDeviceSize payloadSize = bindingSizes[binding]; if (payload == nullptr || payloadSize == 0) { - if (layout->globalUboBinding == static_cast(binding)) { + if (programObj.globalUboBinding == static_cast(binding)) { const void* globalUboData = program.GetUBOData(); const VkDeviceSize globalUboSize = static_cast(program.GetUBOSize()); if (globalUboData != nullptr && globalUboSize > 0) { @@ -510,7 +502,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { BufferSlice slice{}; if (!m_bufferManager->UploadTransient(BufferKind::Uniform, frameIndex, payload, payloadSize, m_minDynamicOffsetAlignment, slice)) { - MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: UBO upload failed on binding %u", + MOBILEGL_ASSERT(false, "UniformDescriptorBinder::BindProgramUniformBuffers failed: UBO upload failed on binding %u", binding); return false; } @@ -534,7 +526,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { samplerBindingOverride->sampler != nullptr) { hasImage = ResolveSamplerDescriptorOverride(*samplerBindingOverride, imageInfo); } else { - hasImage = ResolveSamplerDescriptor(commandBuffer, program, *layout, binding, imageInfo); + hasImage = ResolveSamplerDescriptor(commandBuffer, program, programObj, binding, imageInfo); } if (!hasImage) { MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: sampler binding %u has no valid texture descriptor", @@ -557,7 +549,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { vkUpdateDescriptorSets(m_device, static_cast(writes.size()), writes.data(), 0, nullptr); } - vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, layout->pipelineLayout, 0, 1, + vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, programObj.pipelineLayout, 0, 1, &descriptorSet, static_cast(dynamicOffsets.size()), dynamicOffsets.data()); return true; } diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h index 16c9148e..36d43997 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h @@ -39,9 +39,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { void BeginFrame(Uint32 frameIndex); 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, + const MG_State::GLState::ProgramObject& program, + const ProgramFactory::VkProgramObject& programObj, + Uint32 frameIndex, const SamplerBindingOverride* samplerBindingOverride = nullptr); private: @@ -59,10 +62,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { }; Bool ResolveSamplerTexture(const MG_State::GLState::ProgramObject& program, - const ProgramFactory::VkProgramLayout& layout, Uint32 binding, + const ProgramFactory::VkProgramObject& programObj, Uint32 binding, SharedPtr& outTexture) const; Bool ResolveSamplerDescriptor(VkCommandBuffer commandBuffer, const MG_State::GLState::ProgramObject& program, - const ProgramFactory::VkProgramLayout& layout, Uint32 binding, + const ProgramFactory::VkProgramObject& programObj, Uint32 binding, VkDescriptorImageInfo& outImageInfo) const; Bool ResolveSamplerDescriptorOverride(const SamplerBindingOverride& samplerBindingOverride, VkDescriptorImageInfo& outImageInfo) const; @@ -71,7 +74,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { Bool CreateDescriptorPool(Uint32 maxSets, VkDescriptorPool& outPool) const; Bool GrowFrameDescriptorPool(FrameResources& frame, Uint32 frameIndex); VkResult AllocateDescriptorSetsFromActivePool( - Uint32 frameIndex, const ProgramFactory::VkProgramLayout& layout, VkDescriptorSet& outDescriptorSet); + Uint32 frameIndex, const ProgramFactory::VkProgramObject& programObj, VkDescriptorSet& outDescriptorSet); VkDevice m_device = VK_NULL_HANDLE; VkBufferManager* m_bufferManager = nullptr; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index dc50c518..d4cd3101 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -472,7 +472,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (!m_bufferManager.UploadTransient(BufferKind::Vertex, m_frameContext.GetCurrentFrameIndex(), sourceData->data(), static_cast(sourceSize), 16, slice)) { - MGLOG_E("UploadAndBindVertexStreams skipped: failed to upload transient binding %zu", binding); + MOBILEGL_ASSERT(false, "UploadAndBindVertexStreams skipped: failed to upload transient binding %zu", binding); return false; } if (!transientThisFrame) { @@ -534,7 +534,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (!m_bufferManager.UploadTransient(BufferKind::Index, m_frameContext.GetCurrentFrameIndex(), indexData->data() + pIndexBufferView->indexByteOffset, static_cast(indexDataSizeBytes), indexSize, slice)) { - MGLOG_E("DrawElements skipped: failed to prepare transient index buffer"); + MOBILEGL_ASSERT(false, "DrawElements skipped: failed to prepare transient index buffer"); return false; } if (!transientThisFrame) { @@ -667,11 +667,11 @@ void main() { 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_programFactory->GetOrCreatePipelineLayout(*m_blitResources.program), + .pipelineLayout = programObj.pipelineLayout, .renderPass = renderPassEntry.renderPass, .subpass = 0, .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, @@ -687,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); @@ -700,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_programFactory->GetOrCreatePipelineLayout(program); auto cullFaceEnabled = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::CullFace); auto depthTestEnabled = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::DepthTest); BlendFactor srcRGB = BlendFactor::One; @@ -720,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), @@ -743,7 +741,7 @@ 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); @@ -756,6 +754,8 @@ void main() { 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) { @@ -770,7 +770,7 @@ void main() { // without draws in between to give it a chance to materialize such clear. // Deal with this situation here. Vector sampledTextures; - Bool hasSampledTextures = m_uniformManager->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(), @@ -856,7 +856,7 @@ void main() { vkCmdBindPipeline(frame.commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); - m_uniformManager->BindProgramUniformBuffers(frame.commandBuffer, program, + m_uniformManager->BindProgramUniformBuffers(frame.commandBuffer, program, programObj, m_frameContext.GetCurrentFrameIndex()); auto vtxUploadOk = UploadAndBindVertexBuffers(frame.commandBuffer, vao); @@ -1086,8 +1086,10 @@ void main() { .sampler = (filter == GL_LINEAR ? m_blitResources.linearSampler.get() : m_blitResources.nearestSampler.get()), }; + 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, m_frameContext.GetCurrentFrameIndex(), + frame.commandBuffer, *m_blitResources.program, blitProgramObj, m_frameContext.GetCurrentFrameIndex(), &samplerBindingOverride); MOBILEGL_ASSERT(bound, "TryBlitToDefaultFramebufferWithShader: BindProgramUniformBuffers failed"); vkCmdDraw(frame.commandBuffer, 3, 1, 0, 0); diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpvcSession.cpp b/MobileGL/MG_Util/ShaderTranspiler/SpvcSession.cpp index 81ebb237..7c31ecb0 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpvcSession.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/SpvcSession.cpp @@ -179,7 +179,14 @@ namespace MobileGL { for (uint32_t i = 0; i < count; ++i) { if (bindings[i]->descriptor_type == SPV_REFLECT_DESCRIPTOR_TYPE_UNIFORM_BUFFER) { InterfaceVariable var; - var.name = bindings[i]->name; + // 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); } From af58e2c7b45c602d839462c3edeb93fe70eb1be6 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Sun, 26 Apr 2026 19:43:27 +0800 Subject: [PATCH 31/31] [Fix] (MG_Backend/DirectVulkan/VertexInputStateFactory): don't use SSCALED formats --- .../Renderer/VertexInputStateFactory.cpp | 48 +++++++------------ 1 file changed, 16 insertions(+), 32 deletions(-) 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: