From a701c896f0da348610a045e62ceb737e8803decd Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Sun, 7 Jun 2026 18:05:48 +0800 Subject: [PATCH] [Feat] (MG_Backend/DirectVulkan): wire up client-side buffer --- .../DirectVulkan/Renderer/UniformManager.cpp | 7 +- .../Renderer/VertexInputStateFactory.cpp | 10 +- .../Renderer/VertexInputStateFactory.h | 4 +- .../DirectVulkan/Renderer/VulkanRenderer.cpp | 180 +++++++++++++++++- .../DirectVulkan/Renderer/VulkanRenderer.h | 4 +- .../GLState/ProgramState/ProgramObject.cpp | 9 +- 6 files changed, 200 insertions(+), 14 deletions(-) diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp index 5457da79..fef6a2ba 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp @@ -558,8 +558,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (programObj.globalUboBinding == static_cast(binding)) { outData = program.GetUBOData(); outSize = static_cast(program.GetUBOSize()); - MOBILEGL_ASSERT(outData != nullptr, "ResolveUniformBufferPayload: global UBO data is null"); - MOBILEGL_ASSERT(outSize > 0, "ResolveUniformBufferPayload: global UBO size is zero"); + static const Array emptyGlobalUbo{}; + if (outData == nullptr || outSize == 0) { + outData = emptyGlobalUbo.data(); + outSize = static_cast(emptyGlobalUbo.size()); + } return outData != nullptr && outSize > 0; } diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp index c9b0b563..5c2b398b 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp @@ -54,10 +54,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { VertexInputStateBuilder builder; Vector bindingBufferKeys; Vector bindingBaseOffsets; + Vector bindingAttributeLocations; + Vector bindingUsesClientMemory; for (Uint32 location = 0; location < MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS; ++location) { const auto& attr = vao.GetAttribute(location); - if (!attr.Enabled || !attr.Buffer) { + if (!attr.Enabled) { continue; } @@ -84,7 +86,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { const SizeT bufferKey = reinterpret_cast(attr.Buffer.get()); const Uint32 binding = static_cast(bindingBufferKeys.size()); bindingBufferKeys.push_back(bufferKey); - bindingBaseOffsets.push_back(attr.Offset); + bindingBaseOffsets.push_back(attr.Buffer ? attr.Offset : 0); + bindingAttributeLocations.push_back(location); + bindingUsesClientMemory.push_back(attr.Buffer == nullptr); builder.AddBinding(binding, stride, inputRate); builder.AddAttribute(location, binding, vkFormat, 0); } @@ -97,6 +101,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { entry.attributes = builder.GetAttributes(); entry.bindingBufferKeys = std::move(bindingBufferKeys); entry.bindingBaseOffsets = std::move(bindingBaseOffsets); + entry.bindingAttributeLocations = std::move(bindingAttributeLocations); + entry.bindingUsesClientMemory = std::move(bindingUsesClientMemory); entry.state = state; entry.state.pVertexBindingDescriptions = entry.bindings.empty() ? nullptr : entry.bindings.data(); entry.state.pVertexAttributeDescriptions = entry.attributes.empty() ? nullptr : entry.attributes.data(); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.h index b42d6c9a..54bceb92 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.h @@ -25,6 +25,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { Vector attributes; Vector bindingBufferKeys; Vector bindingBaseOffsets; + Vector bindingAttributeLocations; + Vector bindingUsesClientMemory; VkPipelineVertexInputStateCreateInfo state{ VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO }; @@ -39,10 +41,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { const BackendVertexInputState& GetOrCreateVertexInputState( const MG_State::GLState::VertexArrayObject& vao, HashType hash); const BackendVertexInputState& GetOrCreateVertexInputState(const MG_State::GLState::VertexArrayObject& vao); + static SizeT GetComponentSize(DataType type); private: static VkFormat ToVkVertexFormat(DataType type, Int size, Bool normalized, Bool isInteger); - static SizeT GetComponentSize(DataType type); const VulkanRendererConfig& m_config; UnorderedMap m_cache; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 7e35920a..aae87450 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -1130,6 +1130,11 @@ void main() { break; } } + + static Bool PresentStatsEnabled() { + const char* value = std::getenv("MOBILEGL_PRESENT_STATS"); + return value != nullptr && value[0] == '1' && value[1] == '\0'; + } } // namespace VkBool32 VulkanRenderer::DebugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, @@ -1360,7 +1365,7 @@ void main() { } Bool VulkanRenderer::UploadAndBindVertexBuffers( - VkCommandBuffer commandBuffer, const MG_State::GLState::VertexArrayObject& vao) { + VkCommandBuffer commandBuffer, const MG_State::GLState::VertexArrayObject& vao, const DrawCmdParam& drawParams) { auto& vertexInputState = m_vertexInputStateFactory->GetOrCreateVertexInputState(vao); const auto& program = *MG_State::pGLContext->GetCurrentProgram(); const auto transformFlags = GetShaderTransformFlags(m_swapchainObject.GetPreTransform()); @@ -1393,6 +1398,43 @@ void main() { if (binding >= vertexInputState.bindings.size()) { break; } + const Bool usesClientMemory = binding < vertexInputState.bindingUsesClientMemory.size() && + vertexInputState.bindingUsesClientMemory[binding]; + if (usesClientMemory) { + const Uint32 location = binding < vertexInputState.bindingAttributeLocations.size() + ? vertexInputState.bindingAttributeLocations[binding] + : static_cast(MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS); + MOBILEGL_ASSERT(location < MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS, + "UploadAndBindVertexStreams failed to resolve client attribute location"); + + const auto& attr = vao.GetAttribute(location); + const SizeT componentSize = VertexInputStateFactory::GetComponentSize(attr.Type); + const SizeT elementSize = componentSize * static_cast(attr.Size); + const SizeT stride = attr.Stride > 0 ? static_cast(attr.Stride) : elementSize; + const auto* clientData = reinterpret_cast(attr.Offset); + if (!clientData || componentSize == 0 || elementSize == 0 || stride == 0) { + MGLOG_E("UploadAndBindVertexStreams skipped: invalid client vertex attribute at location %u", location); + return false; + } + + const Uint32 lastVertex = drawParams.vertexCount > 0 + ? drawParams.firstVertex + drawParams.vertexCount - 1 + : drawParams.firstVertex; + const SizeT uploadSize = static_cast(lastVertex) * stride + elementSize; + BufferSlice slice{}; + if (!m_bufferManager.UploadTransient(BufferKind::Vertex, m_frameContext.GetCurrentFrameIndex(), + clientData, static_cast(uploadSize), 16, slice)) { + MOBILEGL_ASSERT(false, + "UploadAndBindVertexStreams skipped: failed to upload client attribute binding %zu", + binding); + return false; + } + + vkBuffers[binding] = slice.buffer; + vkOffsets[binding] = slice.offset; + continue; + } + const SizeT bufferKey = vertexInputState.bindingBufferKeys[binding]; const MG_State::GLState::BufferObject* sourceBuffer = findBufferByKey(bufferKey); MOBILEGL_ASSERT(sourceBuffer != nullptr, "UploadAndBindVertexStreams failed to resolve source buffer"); @@ -2412,6 +2454,7 @@ void main() { } Bool VulkanRenderer::SetupDraw(FrameContext::FrameData& frame, GLenum mode, Flags aspects, + const DrawCmdParam& drawParams, const IndexBufferView* pIndexBufferView) { m_textureManager->CollectGarbage(); const auto& drawFbo = @@ -2539,7 +2582,7 @@ void main() { return false; } - auto vtxUploadOk = UploadAndBindVertexBuffers(frame.commandBuffer, vao); + auto vtxUploadOk = UploadAndBindVertexBuffers(frame.commandBuffer, vao, drawParams); MOBILEGL_ASSERT(vtxUploadOk, "SetupDraw skipped: failed to upload vertex buffers"); if (aspects & DrawSetupAspect::IndexBuffer) { @@ -3228,6 +3271,11 @@ void main() { VkImageLayout dstLayout = drawIsDefaultFbo ? m_swapchainObject.GetImageLayout(m_imageIndexAcquired) : *dstBinding.trackedLayout; + const VkImageLayout srcOriginalLayout = srcLayout; + const VkImageLayout dstOriginalLayout = dstLayout; + const VkImageLayout dstRestoreLayout = dstOriginalLayout == VK_IMAGE_LAYOUT_UNDEFINED + ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL + : dstOriginalLayout; if (readIsDefaultFbo && srcLayout == VK_IMAGE_LAYOUT_UNDEFINED) { MGLOG_E("BlitFramebuffer skipped: swapchain source image layout is undefined"); @@ -3295,6 +3343,36 @@ void main() { srcBinding.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dstBinding.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &blitRegion, filter == GL_LINEAR ? VK_FILTER_LINEAR : VK_FILTER_NEAREST); + + VkPipelineStageFlags srcRestoreStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + VkAccessFlags srcRestoreAccessMask = 0; + GetImageTransitionDestinationState(srcOriginalLayout, srcRestoreStageMask, srcRestoreAccessMask); + if (readIsDefaultFbo) { + VkImageLayout srcTrackedLayout = m_swapchainObject.GetImageLayout(m_imageIndexAcquired); + Bool ok = VkTextureManager::TransitionImageLayout( + frame.commandBuffer, srcBinding.image, srcTrackedLayout, srcOriginalLayout, + VK_PIPELINE_STAGE_TRANSFER_BIT, srcRestoreStageMask, + VK_ACCESS_TRANSFER_READ_BIT, srcRestoreAccessMask, srcBinding.aspectMask); + MOBILEGL_ASSERT(ok, "%s: failed to restore swapchain source image layout", __func__); + m_swapchainObject.SetImageLayout(m_imageIndexAcquired, srcTrackedLayout); + } else { + Bool ok = VkTextureManager::TransitionImageLayout( + frame.commandBuffer, srcBinding.image, *srcBinding.trackedLayout, srcOriginalLayout, + VK_PIPELINE_STAGE_TRANSFER_BIT, srcRestoreStageMask, + VK_ACCESS_TRANSFER_READ_BIT, srcRestoreAccessMask, srcBinding.aspectMask, 0, srcBinding.mipLevelCount); + MOBILEGL_ASSERT(ok, "%s: failed to restore source image layout", __func__); + } + + if (!drawIsDefaultFbo) { + VkPipelineStageFlags dstRestoreStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + VkAccessFlags dstRestoreAccessMask = 0; + GetImageTransitionDestinationState(dstRestoreLayout, dstRestoreStageMask, dstRestoreAccessMask); + Bool ok = VkTextureManager::TransitionImageLayout( + frame.commandBuffer, dstBinding.image, *dstBinding.trackedLayout, dstRestoreLayout, + VK_PIPELINE_STAGE_TRANSFER_BIT, dstRestoreStageMask, + VK_ACCESS_TRANSFER_WRITE_BIT, dstRestoreAccessMask, dstBinding.aspectMask, 0, dstBinding.mipLevelCount); + MOBILEGL_ASSERT(ok, "%s: failed to restore destination image layout", __func__); + } } void VulkanRenderer::CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, @@ -3678,7 +3756,7 @@ void main() { void VulkanRenderer::DrawArrays(const DrawCmd& payload) { auto& frame = m_frameContext.GetCurrent(); - if (!SetupDraw(frame, payload.mode, 0)) { + if (!SetupDraw(frame, payload.mode, 0, payload.params)) { return; } @@ -3696,7 +3774,15 @@ void main() { void VulkanRenderer::DrawElements(const DrawIndexedCmd& payload) { auto& frame = m_frameContext.GetCurrent(); - if (!SetupDraw(frame, payload.mode, DrawSetupAspect::IndexBuffer, + DrawCmdParam vertexRange{}; + vertexRange.vertexCount = payload.params.indexCount + (payload.params.vertexOffset > 0 + ? static_cast(payload.params.vertexOffset) + : 0); + vertexRange.instanceCount = payload.params.instanceCount; + vertexRange.firstVertex = 0; + vertexRange.firstInstance = static_cast(payload.params.firstInstance); + + if (!SetupDraw(frame, payload.mode, DrawSetupAspect::IndexBuffer, vertexRange, &payload.indexBufferView)) { return; } @@ -3716,7 +3802,15 @@ void main() { void VulkanRenderer::MultiDrawElements(const MultiDrawIndexedCmd& payload) { auto& frame = m_frameContext.GetCurrent(); - if (!SetupDraw(frame, payload.mode, DrawSetupAspect::IndexBuffer, + DrawCmdParam vertexRange{}; + for (Uint32 idraw = 0; idraw < payload.drawCount; ++idraw) { + vertexRange.vertexCount = std::max(vertexRange.vertexCount, payload.pParams[idraw].indexCount); + vertexRange.instanceCount = std::max(vertexRange.instanceCount, payload.pParams[idraw].instanceCount); + vertexRange.firstInstance = std::max(vertexRange.firstInstance, + static_cast(payload.pParams[idraw].firstInstance)); + } + + if (!SetupDraw(frame, payload.mode, DrawSetupAspect::IndexBuffer, vertexRange, &payload.indexBufferView)) { return; } @@ -3742,6 +3836,59 @@ void main() { auto* activeRenderPass = VkRenderPassManager::GetActiveRenderPass(); if (activeRenderPass) VkRenderPassManager::EndRenderPass(frame.commandBuffer); + + VkBufferObject presentStatsReadback; + VkDeviceSize presentStatsReadbackSize = 0; + const VkExtent2D presentStatsExtent = m_swapchainObject.GetExtent(); + const Bool collectPresentStats = PresentStatsEnabled() && frame.isCommandRecording && + presentStatsExtent.width > 0 && presentStatsExtent.height > 0; + if (collectPresentStats) { + presentStatsReadbackSize = static_cast(presentStatsExtent.width) * + static_cast(presentStatsExtent.height) * 4; + const Bool readbackCreated = presentStatsReadback.Create({ + .allocator = m_allocator, + .size = presentStatsReadbackSize, + .usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT, + .memoryUsage = VMA_MEMORY_USAGE_AUTO, + .allocationFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT, + }); + MOBILEGL_ASSERT(readbackCreated, "Present stats: failed to create readback buffer"); + + VkImageLayout statsOriginalLayout = m_swapchainObject.GetImageLayout(m_imageIndexAcquired); + Bool toTransferSrc = VkTextureManager::TransitionImageLayout( + frame.commandBuffer, m_swapchainObject.GetImage(m_imageIndexAcquired), + statsOriginalLayout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, + VK_ACCESS_TRANSFER_READ_BIT, VK_IMAGE_ASPECT_COLOR_BIT); + MOBILEGL_ASSERT(toTransferSrc, "Present stats: failed to transition swapchain for readback"); + + VkBufferImageCopy copyRegion{}; + copyRegion.bufferOffset = 0; + copyRegion.bufferRowLength = 0; + copyRegion.bufferImageHeight = 0; + copyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + copyRegion.imageSubresource.mipLevel = 0; + copyRegion.imageSubresource.baseArrayLayer = 0; + copyRegion.imageSubresource.layerCount = 1; + copyRegion.imageOffset = {0, 0, 0}; + copyRegion.imageExtent = {presentStatsExtent.width, presentStatsExtent.height, 1}; + vkCmdCopyImageToBuffer(frame.commandBuffer, + m_swapchainObject.GetImage(m_imageIndexAcquired), + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + presentStatsReadback.GetHandle(), + 1, + ©Region); + + Bool restoreLayout = VkTextureManager::TransitionImageLayout( + frame.commandBuffer, m_swapchainObject.GetImage(m_imageIndexAcquired), + statsOriginalLayout, m_swapchainObject.GetImageLayout(m_imageIndexAcquired), + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, + VK_ACCESS_TRANSFER_READ_BIT, + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, + VK_IMAGE_ASPECT_COLOR_BIT); + MOBILEGL_ASSERT(restoreLayout, "Present stats: failed to restore swapchain layout after readback"); + } if (frame.isCommandRecording) { m_frameContext.EndCommandRecording(); frame.hasCommandBufferRecorded = true; @@ -3755,6 +3902,29 @@ void main() { // 1) Submit current frame work. auto submitPacket = m_frameContext.GetSubmitInfo(shouldSubmitCommandBuffer, m_imageIndexAcquired); VK_VERIFY(vkQueueSubmit(m_graphicsQueue, 1, &submitPacket.submitInfo, frame.imageInFlightFence)); + if (collectPresentStats) { + VK_VERIFY(vkWaitForFences(m_device, 1, &frame.imageInFlightFence, VK_TRUE, UINT64_MAX), + "Present stats, vkWaitForFences"); + const auto* pixels = static_cast(presentStatsReadback.Map()); + MOBILEGL_ASSERT(pixels != nullptr, "Present stats: failed to map readback buffer"); + SizeT nonBlack = 0; + SizeT nonTransparent = 0; + const SizeT pixelCount = static_cast(presentStatsExtent.width) * + static_cast(presentStatsExtent.height); + for (SizeT i = 0; i < pixelCount; ++i) { + const Uint8* p = pixels + i * 4; + if (p[0] != 0 || p[1] != 0 || p[2] != 0) { + ++nonBlack; + } + if (p[3] != 0) { + ++nonTransparent; + } + } + std::fprintf(stderr, + "MOBILEGL_PRESENT_STATS nonBlack=%zu/%zu alpha=%zu/%zu size=%ux%u\n", + nonBlack, pixelCount, nonTransparent, pixelCount, + presentStatsExtent.width, presentStatsExtent.height); + } frame.isCommandRecording = false; frame.hasCommandBufferRecorded = false; m_swapchainObject.SetImageLayout(m_imageIndexAcquired, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h index 7bbe39d0..39627a3b 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h @@ -110,6 +110,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { void Shutdown(); Bool SetupDraw(FrameContext::FrameData& frame, GLenum mode, Flags aspects, + const DrawCmdParam& drawParams, const IndexBufferView* pIndexBufferView = nullptr); void ClearAttachmentsOnActiveRenderPass(VkCommandBuffer commandBuffer, const RenderPassEntry& compatibleRenderPassEntry); @@ -250,7 +251,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkPipeline GetOrCreateComputePipeline(const ProgramFactory::VkProgramObject& programObj); void DestroyComputePipelines(); - Bool UploadAndBindVertexBuffers(VkCommandBuffer commandBuffer, const MG_State::GLState::VertexArrayObject& vao); + Bool UploadAndBindVertexBuffers(VkCommandBuffer commandBuffer, const MG_State::GLState::VertexArrayObject& vao, + const DrawCmdParam& drawParams); Bool UploadAndBindIndexBuffer(FrameContext::FrameData& frame, const MG_State::GLState::VertexArrayObject& vao, const IndexBufferView* pIndexBufferView = nullptr); diff --git a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp index 0bf21d36..e75fb9e1 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp +++ b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp @@ -449,6 +449,9 @@ namespace MobileGL::MG_State::GLState { } // 4. Do reflection (find global UBO etc.) + m_uniformSizesInBytes.clear(); + m_uniformOffsets.clear(); + m_globalUboScratch.clear(); for (SizeT i = 0; i < m_generatedSpirv.size(); i++) { auto& spv = m_generatedSpirv[i]; @@ -463,9 +466,6 @@ namespace MobileGL::MG_State::GLState { "err = %d%s", m_externalIndex, i, result, (result == SPVC_ERROR_INVALID_SPIRV ? ". Probably no global UBO?" : "")); - m_uniformSizesInBytes.clear(); - m_uniformOffsets.clear(); - m_globalUboScratch.clear(); continue; } else { auto& meta = session.GetMetadata(); @@ -474,6 +474,9 @@ namespace MobileGL::MG_State::GLState { "plainUniformOffsets=%zu", m_externalIndex, meta.globalUboSize, meta.plainUniformMemberSizesInBytes.size(), meta.plainUniformOffsetsInUBO.size()); + if (size == 0) { + continue; + } m_globalUboScratch.resize(size); m_uniformOffsets.resize(m_maxUniformLocation + 1); for (const auto& [name, offset] : meta.plainUniformOffsetsInUBO) {