From 99b753be9a96ee1fba637f5fd4438fb4fb917d07 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Tue, 5 May 2026 12:02:33 +0800 Subject: [PATCH] [Feat] (MG_Backend/DirectVulkan): implement backend func for glCopyTex(Sub)Image2D --- .../MG_Backend/DirectVulkan/DirectVulkan.cpp | 39 +- .../DirectVulkan/Renderer/VkClearManager.cpp | 26 +- .../DirectVulkan/Renderer/VkClearManager.h | 10 +- .../Renderer/VkRenderPassManager.cpp | 54 ++- .../DirectVulkan/Renderer/VulkanRenderer.cpp | 418 +++++++++++++++++- .../DirectVulkan/Renderer/VulkanRenderer.h | 8 + 6 files changed, 504 insertions(+), 51 deletions(-) diff --git a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp index 61e0aa8c..684ff3ac 100644 --- a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp @@ -13,10 +13,29 @@ namespace MobileGL::MG_Backend::DirectVulkan { UniquePtr pVulkanRenderer = nullptr; - void ClearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil) {} - 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 ClearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil) { + MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::ClearBufferfi called with null VulkanRenderer"); + MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::ClearBufferfi called with null GL context"); + pVulkanRenderer->ClearBufferfi(buffer, drawbuffer, depth, stencil); + } + + void ClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat* value) { + MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::ClearBufferfv called with null VulkanRenderer"); + MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::ClearBufferfv called with null GL context"); + pVulkanRenderer->ClearBufferfv(buffer, drawbuffer, value); + } + + void ClearBufferuiv(GLenum buffer, GLint drawbuffer, const GLuint* value) { + MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::ClearBufferuiv called with null VulkanRenderer"); + MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::ClearBufferuiv called with null GL context"); + pVulkanRenderer->ClearBufferuiv(buffer, drawbuffer, value); + } + + void ClearBufferiv(GLenum buffer, GLint drawbuffer, const GLint* value) { + MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::ClearBufferiv called with null VulkanRenderer"); + MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::ClearBufferiv called with null GL context"); + pVulkanRenderer->ClearBufferiv(buffer, drawbuffer, value); + } void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride) {} void MultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride) {} @@ -36,9 +55,17 @@ namespace MobileGL::MG_Backend::DirectVulkan { void DrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei instancecount) {} void DrawArraysIndirect(GLenum mode, const void* indirect) {} void CopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, - GLsizei height, GLint border) {} + GLsizei height, GLint border) { + MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::CopyTexImage2D called with null VulkanRenderer"); + MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::CopyTexImage2D called with null GL context"); + pVulkanRenderer->CopyTexSubImage2D(target, level, 0, 0, x, y, width, height); + } void CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, - GLsizei height) {} + GLsizei height) { + MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::CopyTexSubImage2D called with null VulkanRenderer"); + MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::CopyTexSubImage2D called with null GL context"); + pVulkanRenderer->CopyTexSubImage2D(target, level, xoffset, yoffset, x, y, width, height); + } void GenerateMipmap(GLenum target) {} 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) {} diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkClearManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkClearManager.cpp index 83e5af54..3baaa5ca 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkClearManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkClearManager.cpp @@ -31,8 +31,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { continue; QueueClear({ - .color = clearPayload.color, - .attachmentType = drawbuf + .mask = GL_COLOR_BUFFER_BIT, + .color = clearPayload.color }, drawFbo.GetAttachment(drawbuf).GetTexture()); MGLOG_D("%s: %s (texture %d) - color = (%.2f, %.2f, %.2f, %.2f)", __func__, MG_Util::ConvertFramebufferAttachmentTypeToString(drawbuf).c_str(), @@ -44,8 +44,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (mask & GL_DEPTH_BUFFER_BIT && !drawFbo.GetAttachment(FramebufferAttachmentType::Depth).IsRenderbuffer()) { QueueClear({ + .mask = GL_DEPTH_BUFFER_BIT, .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); @@ -54,8 +54,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (mask & GL_STENCIL_BUFFER_BIT && !drawFbo.GetAttachment(FramebufferAttachmentType::Stencil).IsRenderbuffer()) { QueueClear({ + .mask = GL_STENCIL_BUFFER_BIT, .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); @@ -64,12 +64,25 @@ namespace MobileGL::MG_Backend::DirectVulkan { void VkClearManager::QueueClear(const ClearAttachmentPayload& clearPayload, const SharedPtr& texture) { + if (clearPayload.mask == 0) { + return; + } WeakPtr weakTexturePtr = texture; if (weakTexturePtr.expired()) return; auto* pTexture = weakTexturePtr.lock().get(); m_aliveObjects[pTexture] = weakTexturePtr; - m_pendingClears[pTexture] = clearPayload; + auto& pending = m_pendingClears[pTexture]; + pending.mask |= clearPayload.mask; + if (clearPayload.mask & GL_COLOR_BUFFER_BIT) { + pending.color = clearPayload.color; + } + if (clearPayload.mask & GL_DEPTH_BUFFER_BIT) { + pending.depth = clearPayload.depth; + } + if (clearPayload.mask & GL_STENCIL_BUFFER_BIT) { + pending.stencil = clearPayload.stencil; + } } Bool VkClearManager::HasPendingClear(MG_State::GLState::ITextureObject* texture) { @@ -84,9 +97,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { } 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__, + MGLOG_D("%s: Got pending clear for texture %d (%s), mask=0x%x clear value: color = (%.2f, %.2f, %.2f, %.2f), depth = (%.2f), stencil = (%u)", __func__, texture->GetExternalIndex(), MG_Util::ConvertTextureInternalFormatToString(texture->GetFormat()).c_str(), + static_cast(outPayload.mask), outPayload.color[0], outPayload.color[1], outPayload.color[2], outPayload.color[3], outPayload.depth, outPayload.stencil); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkClearManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkClearManager.h index a46506c8..9bfb1d75 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkClearManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkClearManager.h @@ -23,12 +23,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { }; struct ClearAttachmentPayload { - union { - FloatVec4 color; - Float depth{}; - Uint32 stencil; - }; - FramebufferAttachmentType attachmentType = FramebufferAttachmentType::Color0; + GLbitfield mask = 0; + FloatVec4 color = FloatVec4(0.0f, 0.0f, 0.0f, 0.0f); + Float depth = 1.0f; + Uint32 stencil = 0; }; class VkClearManager { diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp index a635502f..83fd44d4 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp @@ -80,8 +80,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { Bool hasPayload = m_clearManager.GetPendingClear(texture, clearPayload); XXHASH_VERIFY(XXH64_update(m_hashState, &hasPayload, sizeof(hasPayload))); if (hasPayload) { - XXHASH_VERIFY(XXH64_update( - m_hashState, &clearPayload.attachmentType, sizeof(clearPayload.attachmentType))); + XXHASH_VERIFY(XXH64_update(m_hashState, &clearPayload.mask, sizeof(clearPayload.mask))); } } @@ -172,14 +171,22 @@ namespace MobileGL::MG_Backend::DirectVulkan { Int width = 0; Int height = 0; - Vector attachmentDescriptions(validDrawBufCount); + Vector attachmentDescriptions; + attachmentDescriptions.reserve(validDrawBufCount + 1); Vector colorAttachmentRefs(validDrawBufCount); + for (auto& attachmentRef : colorAttachmentRefs) { + attachmentRef.attachment = VK_ATTACHMENT_UNUSED; + attachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + } Vector pendingClearAttachments; + pendingClearAttachments.reserve(validDrawBufCount + 1); Vector trackedAttachmentLayouts; + trackedAttachmentLayouts.reserve(validDrawBufCount + 1); auto& textureResources = RenderPassEntry::s_textureResourcesScratch; textureResources.clear(); - textureResources.resize(validDrawBufCount, nullptr); - Vector attachmentViews(validDrawBufCount, VK_NULL_HANDLE); + textureResources.reserve(validDrawBufCount + 1); + Vector attachmentViews; + attachmentViews.reserve(validDrawBufCount + 1); // This should automatically work on default & offscreen FBO // assuming default FBO has the right param for (Int i = 0; i < validDrawBufCount; ++i) { @@ -192,9 +199,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { auto* texture = att.GetTexture().get(); const Uint32 attachmentMipLevel = static_cast(std::max(att.GetTextureLevel(), 0)); const auto textureTarget = texture->GetTarget(); + const Uint32 attachmentIndex = static_cast(attachmentDescriptions.size()); + attachmentDescriptions.emplace_back(); // Color attachment description - VkAttachmentDescription& desc = attachmentDescriptions[i]; + VkAttachmentDescription& desc = attachmentDescriptions.back(); switch (textureTarget) { case TextureTarget::Texture2D: { auto* texture2d = @@ -219,7 +228,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; if (hasClear) { pendingClearAttachments.emplace_back(PendingClearAttachmentInfo { - .attachmentIndex = static_cast(i), + .attachmentIndex = attachmentIndex, .texture = texture }); } @@ -238,20 +247,22 @@ namespace MobileGL::MG_Backend::DirectVulkan { .swapchainImageIndex = swapchainImageIndex, .finalLayout = desc.finalLayout, }); - attachmentViews[i] = swapchainViews[swapchainImageIndex]; + textureResources.emplace_back(nullptr); + attachmentViews.emplace_back(swapchainViews[swapchainImageIndex]); } else { - textureResources[i] = m_textureManager.SyncTextureAndGetDescriptor(*texture); - MOBILEGL_ASSERT(textureResources[i], + auto* textureResource = m_textureManager.SyncTextureAndGetDescriptor(*texture); + MOBILEGL_ASSERT(textureResource, "GetOrCreateRenderPass: SyncTextureAndGetDescriptor failed at color attachment %d", i); - desc.format = textureResources[i]->format; - trackedColorLayout = textureResources[i]->layout; + textureResources.emplace_back(textureResource); + desc.format = textureResource->format; + trackedColorLayout = textureResource->layout; trackedAttachmentLayouts.emplace_back(TrackedAttachmentLayoutInfo { .target = TrackedAttachmentTarget::Texture, .texture = texture, .finalLayout = desc.finalLayout, }); - attachmentViews[i] = m_textureManager.GetOrCreateViewAtMipLevel(*texture, attachmentMipLevel); - MOBILEGL_ASSERT(attachmentViews[i] != VK_NULL_HANDLE, + attachmentViews.emplace_back(m_textureManager.GetOrCreateViewAtMipLevel(*texture, attachmentMipLevel)); + MOBILEGL_ASSERT(attachmentViews.back() != VK_NULL_HANDLE, "GetOrCreateRenderPass: GetOrCreateAttachmentView failed at color attachment %d", i); } @@ -273,7 +284,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { // Attachment reference VkAttachmentReference& attachmentRef = colorAttachmentRefs[i]; - attachmentRef.attachment = i; + attachmentRef.attachment = attachmentIndex; attachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; } @@ -290,8 +301,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { const Uint32 depthAttachmentIndex = static_cast(attachmentDescriptions.size()); ClearAttachmentPayload clearPayload{}; Bool hasClear = m_clearManager.GetPendingClear(&texture, clearPayload); - Bool clearDepth = hasClear && clearPayload.attachmentType == FramebufferAttachmentType::Depth; - Bool clearStencil = hasClear && clearPayload.attachmentType == FramebufferAttachmentType::Stencil; + Bool clearDepth = hasClear && (clearPayload.mask & GL_DEPTH_BUFFER_BIT) != 0; + Bool clearStencil = hasClear && (clearPayload.mask & GL_STENCIL_BUFFER_BIT) != 0; VkImageLayout trackedDepthLayout = isDefaultFbo ? m_swapchainObject.GetDepthStencilImageLayout(swapchainImageIndex) : VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; @@ -453,17 +464,18 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (!s_clearManager->GetPendingClear(pending.texture, clearPayload)) { continue; } - if (clearPayload.attachmentType >= FramebufferAttachmentType::Color0 && - clearPayload.attachmentType <= FramebufferAttachmentType::Color31) { + if ((clearPayload.mask & GL_COLOR_BUFFER_BIT) != 0) { clearValues[pending.attachmentIndex].color = { clearPayload.color.x(), clearPayload.color.y(), clearPayload.color.z(), clearPayload.color.w() }; - } else if (clearPayload.attachmentType == FramebufferAttachmentType::Depth) { + } + if ((clearPayload.mask & GL_DEPTH_BUFFER_BIT) != 0) { clearValues[pending.attachmentIndex].depthStencil.depth = clearPayload.depth; - } else if (clearPayload.attachmentType == FramebufferAttachmentType::Stencil) { + } + if ((clearPayload.mask & GL_STENCIL_BUFFER_BIT) != 0) { clearValues[pending.attachmentIndex].depthStencil.stencil = clearPayload.stencil; } } diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 1683b901..38dfccbf 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -15,6 +15,7 @@ #include "MG_State/GLState/ProgramState/ShaderObject.h" #include "MG_State/GLState/SamplerState/SamplerObject.h" #include "MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h" +#include "MG_Util/Converters/GLToMG/TextureEnumConverter.h" #include "MG_Util/Converters/MGToVk/RenderStateEnumConverter.h" #include @@ -84,6 +85,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { return false; } + static void RecordClearBufferError(const char* func, ErrorCode code, const char* message) { + MG_State::pGLContext->RecordError(code, MakeUnique("DirectVulkan", func, message)); + } + + static void RecordTextureCopyError(const char* func, ErrorCode code, const char* message) { + MG_State::pGLContext->RecordError(code, MakeUnique("DirectVulkan", func, message)); + } + static Bool IsValidSampledImageLayout(VkImageLayout layout) { switch (layout) { case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: @@ -154,6 +163,49 @@ namespace MobileGL::MG_Backend::DirectVulkan { } } + static void GetImageTransitionDestinationState(VkImageLayout newLayout, VkPipelineStageFlags& outDstStageMask, + VkAccessFlags& outDstAccessMask) { + switch (newLayout) { + case VK_IMAGE_LAYOUT_UNDEFINED: + outDstStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + outDstAccessMask = 0; + break; + case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL: + outDstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + outDstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + break; + case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL: + outDstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | + VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; + outDstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + break; + case VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL: + case VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL: + case VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL: + case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: + outDstStageMask = VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT; + outDstAccessMask = VK_ACCESS_SHADER_READ_BIT; + break; + case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL: + outDstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT; + outDstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + break; + case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL: + outDstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT; + outDstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + break; + case VK_IMAGE_LAYOUT_PRESENT_SRC_KHR: + outDstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; + outDstAccessMask = VK_ACCESS_MEMORY_READ_BIT; + break; + default: + outDstStageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; + outDstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT; + break; + } + } + static Bool ResolveColorBlitBinding(MG_State::GLState::FramebufferObject& fbo, Bool isReadFramebuffer, Uint32 swapchainImageIndex, SwapchainObject& swapchainObject, VkTextureManager& textureManager, BlitImageBinding& outBinding) { @@ -218,6 +270,37 @@ namespace MobileGL::MG_Backend::DirectVulkan { return true; } + static Bool ResolveTextureCopyDestinationBinding(MG_State::GLState::ITextureObject& texture, Uint32 mipLevel, + VkTextureManager& textureManager, BlitImageBinding& outBinding) { + auto* resource = textureManager.SyncTextureAndGetDescriptor(texture); + if (resource == nullptr) { + MGLOG_E("CopyTexSubImage2D skipped: failed to sync destination textureId=%d", + texture.GetExternalIndex()); + return false; + } + if ((resource->aspect & VK_IMAGE_ASPECT_COLOR_BIT) == 0) { + MGLOG_E("CopyTexSubImage2D skipped: destination textureId=%d is not a color image", + texture.GetExternalIndex()); + return false; + } + if (mipLevel >= resource->mipLevels) { + MGLOG_E("CopyTexSubImage2D skipped: destination textureId=%d mip=%u out of range (mips=%u)", + texture.GetExternalIndex(), mipLevel, resource->mipLevels); + return false; + } + + outBinding.image = resource->image; + outBinding.trackedLayout = &resource->layout; + outBinding.aspectMask = resource->aspect; + outBinding.extent = { + static_cast(std::max(1u, resource->extent.width >> mipLevel)), + static_cast(std::max(1u, resource->extent.height >> mipLevel))}; + outBinding.mipLevel = mipLevel; + outBinding.mipLevelCount = 1; + outBinding.label = "destination texture"; + return true; + } + static BlitSurfaceTransform ToBlitSurfaceTransform(VkSurfaceTransformFlagBitsKHR preTransform) { switch (preTransform) { case VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR: @@ -925,6 +1008,135 @@ void main() { m_clearManager->QueueClear(mask, payload, *fbo); } + void VulkanRenderer::QueueClearBufferPayload(GLenum buffer, GLint drawbuffer, + const ClearAttachmentPayload& clearPayload) { + m_clearManager->CollectGarbage(); + auto* fbo = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject().get(); + MOBILEGL_ASSERT(fbo, "VulkanRenderer::QueueClearBufferPayload: draw framebuffer not found"); + + auto queueAttachmentClear = [&](FramebufferAttachmentType attachmentType) { + if (attachmentType == FramebufferAttachmentType::None) { + return; + } + const auto& attachment = fbo->GetAttachment(attachmentType); + if (!attachment.IsTexture() || attachment.IsRenderbuffer()) { + return; + } + auto texture = attachment.GetTexture(); + if (!texture) { + return; + } + m_clearManager->QueueClear(clearPayload, texture); + }; + + switch (buffer) { + case GL_COLOR: { + if (drawbuffer < 0 || + drawbuffer >= static_cast(MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS)) { + RecordClearBufferError(__func__, ErrorCode::InvalidValue, "color drawbuffer index is out of range"); + return; + } + queueAttachmentClear(fbo->GetDrawBuffers()[drawbuffer]); + return; + } + case GL_DEPTH: + if (drawbuffer != 0) { + RecordClearBufferError(__func__, ErrorCode::InvalidValue, "depth clear requires drawbuffer 0"); + return; + } + queueAttachmentClear(FramebufferAttachmentType::Depth); + return; + case GL_STENCIL: + if (drawbuffer != 0) { + RecordClearBufferError(__func__, ErrorCode::InvalidValue, "stencil clear requires drawbuffer 0"); + return; + } + queueAttachmentClear(FramebufferAttachmentType::Stencil); + return; + case GL_DEPTH_STENCIL: + if (drawbuffer != 0) { + RecordClearBufferError(__func__, ErrorCode::InvalidValue, "depth/stencil clear requires drawbuffer 0"); + return; + } + queueAttachmentClear(FramebufferAttachmentType::Depth); + queueAttachmentClear(FramebufferAttachmentType::Stencil); + return; + default: + RecordClearBufferError(__func__, ErrorCode::InvalidEnum, "unsupported clear buffer target"); + return; + } + } + + void VulkanRenderer::ClearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil) { + ClearAttachmentPayload payload{}; + payload.mask = GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT; + payload.depth = depth; + payload.stencil = static_cast(stencil); + QueueClearBufferPayload(buffer, drawbuffer, payload); + } + + void VulkanRenderer::ClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat* value) { + if (value == nullptr) { + return; + } + ClearAttachmentPayload payload{}; + switch (buffer) { + case GL_COLOR: + payload.mask = GL_COLOR_BUFFER_BIT; + payload.color = FloatVec4(value[0], value[1], value[2], value[3]); + break; + case GL_DEPTH: + payload.mask = GL_DEPTH_BUFFER_BIT; + payload.depth = value[0]; + break; + default: + break; + } + QueueClearBufferPayload(buffer, drawbuffer, payload); + } + + void VulkanRenderer::ClearBufferuiv(GLenum buffer, GLint drawbuffer, const GLuint* value) { + if (value == nullptr) { + return; + } + ClearAttachmentPayload payload{}; + switch (buffer) { + case GL_COLOR: + payload.mask = GL_COLOR_BUFFER_BIT; + payload.color = FloatVec4(static_cast(value[0]), static_cast(value[1]), + static_cast(value[2]), static_cast(value[3])); + break; + case GL_STENCIL: + payload.mask = GL_STENCIL_BUFFER_BIT; + payload.stencil = value[0]; + break; + default: + break; + } + QueueClearBufferPayload(buffer, drawbuffer, payload); + } + + void VulkanRenderer::ClearBufferiv(GLenum buffer, GLint drawbuffer, const GLint* value) { + if (value == nullptr) { + return; + } + ClearAttachmentPayload payload{}; + switch (buffer) { + case GL_COLOR: + payload.mask = GL_COLOR_BUFFER_BIT; + payload.color = FloatVec4(static_cast(value[0]), static_cast(value[1]), + static_cast(value[2]), static_cast(value[3])); + break; + case GL_STENCIL: + payload.mask = GL_STENCIL_BUFFER_BIT; + payload.stencil = static_cast(std::max(value[0], 0)); + break; + default: + break; + } + QueueClearBufferPayload(buffer, drawbuffer, payload); + } + Bool VulkanRenderer::MaterializePendingClearForTexture(VkCommandBuffer commandBuffer, MG_State::GLState::ITextureObject& texture) { ClearAttachmentPayload clearPayload{}; @@ -952,7 +1164,6 @@ void main() { texture.GetExternalIndex()); VkImageSubresourceRange subresourceRange{}; - subresourceRange.aspectMask = resource->aspect; subresourceRange.baseMipLevel = 0; subresourceRange.levelCount = 1; subresourceRange.baseArrayLayer = 0; @@ -960,6 +1171,7 @@ void main() { VkImageLayout sampledLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; if ((resource->aspect & VK_IMAGE_ASPECT_COLOR_BIT) != 0) { + subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; VkClearColorValue clearValue{}; clearValue.float32[0] = clearPayload.color.x(); clearValue.float32[1] = clearPayload.color.y(); @@ -968,6 +1180,19 @@ void main() { vkCmdClearColorImage(commandBuffer, resource->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &clearValue, 1, &subresourceRange); } else { + VkImageAspectFlags clearAspectMask = 0; + if ((resource->aspect & VK_IMAGE_ASPECT_DEPTH_BIT) != 0 && + (clearPayload.mask & GL_DEPTH_BUFFER_BIT) != 0) { + clearAspectMask |= VK_IMAGE_ASPECT_DEPTH_BIT; + } + if ((resource->aspect & VK_IMAGE_ASPECT_STENCIL_BIT) != 0 && + (clearPayload.mask & GL_STENCIL_BUFFER_BIT) != 0) { + clearAspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT; + } + MOBILEGL_ASSERT(clearAspectMask != 0, + "MaterializePendingClearForTexture: textureId=%d has no matching depth/stencil clear mask", + texture.GetExternalIndex()); + subresourceRange.aspectMask = clearAspectMask; VkClearDepthStencilValue clearValue{}; clearValue.depth = clearPayload.depth; clearValue.stencil = clearPayload.stencil; @@ -1212,7 +1437,7 @@ void main() { srcStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT, srcAccessMask, VK_ACCESS_TRANSFER_READ_BIT, srcBinding.aspectMask); MOBILEGL_ASSERT(ok, "%s: failed to transition swapchain source image", __func__); - m_swapchainObject.SetImageLayout(m_imageIndexAcquired, srcLayout); + m_swapchainObject.SetImageLayout(m_imageIndexAcquired, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); } else { Bool ok = VkTextureManager::TransitionImageLayout( frame.commandBuffer, srcBinding.image, *srcBinding.trackedLayout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, @@ -1230,7 +1455,7 @@ void main() { dstStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT, dstAccessMask, VK_ACCESS_TRANSFER_WRITE_BIT, dstBinding.aspectMask); MOBILEGL_ASSERT(ok, "%s: failed to transition swapchain destination image", __func__); - m_swapchainObject.SetImageLayout(m_imageIndexAcquired, dstLayout); + m_swapchainObject.SetImageLayout(m_imageIndexAcquired, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); } else { Bool ok = VkTextureManager::TransitionImageLayout( frame.commandBuffer, dstBinding.image, *dstBinding.trackedLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, @@ -1259,6 +1484,172 @@ void main() { 1, &blitRegion, filter == GL_LINEAR ? VK_FILTER_LINEAR : VK_FILTER_NEAREST); } + void VulkanRenderer::CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, + GLint x, GLint y, GLsizei width, GLsizei height) { + if (width <= 0 || height <= 0) { + return; + } + + const auto textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target); + if (textureTarget != TextureTarget::Texture2D) { + RecordTextureCopyError(__func__, ErrorCode::InvalidOperation, + "CopyTexSubImage2D currently only supports GL_TEXTURE_2D destinations."); + return; + } + if (level < 0) { + RecordTextureCopyError(__func__, ErrorCode::InvalidValue, + "CopyTexSubImage2D level must be non-negative."); + return; + } + + auto& textureUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit()); + auto destinationTexture = textureUnit.GetBindingSlot(textureTarget).GetBoundObject(); + if (destinationTexture == nullptr) { + RecordTextureCopyError(__func__, ErrorCode::InvalidOperation, + "CopyTexSubImage2D requires a bound destination texture."); + return; + } + + auto readFbo = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Read).GetBoundObject(); + if (readFbo == nullptr) { + RecordTextureCopyError(__func__, ErrorCode::InvalidOperation, + "CopyTexSubImage2D requires a framebuffer bound to GL_READ_FRAMEBUFFER."); + return; + } + + auto& frame = m_frameContext.GetCurrent(); + if (!frame.isCommandRecording) { + m_frameContext.BeginCommandRecording(); + m_uniformManager->BeginFrame(m_frameContext.GetCurrentFrameIndex()); + } + + if (VkRenderPassManager::GetActiveRenderPass() != nullptr) { + VkRenderPassManager::EndRenderPass(frame.commandBuffer); + } + + const Bool readIsDefaultFbo = + (readFbo == MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO); + + BlitImageBinding srcBinding{}; + if (!ResolveColorBlitBinding(*readFbo, true, m_imageIndexAcquired, m_swapchainObject, *m_textureManager, + srcBinding)) { + RecordTextureCopyError(__func__, ErrorCode::InvalidOperation, + "CopyTexSubImage2D requires a complete color read buffer."); + return; + } + + BlitImageBinding dstBinding{}; + if (!ResolveTextureCopyDestinationBinding(*destinationTexture, static_cast(level), *m_textureManager, + dstBinding)) { + RecordTextureCopyError(__func__, ErrorCode::InvalidOperation, + "CopyTexSubImage2D failed to resolve the destination texture."); + return; + } + + if (!readIsDefaultFbo) { + const auto& sourceAttachment = readFbo->GetAttachment(readFbo->GetReadBuffer()); + auto sourceTexture = sourceAttachment.GetTexture(); + MOBILEGL_ASSERT(sourceTexture != nullptr, "CopyTexSubImage2D: source texture attachment is null"); + const Bool clearReady = MaterializePendingClearForTexture(frame.commandBuffer, *sourceTexture); + MOBILEGL_ASSERT(clearReady, + "CopyTexSubImage2D: failed to materialize pending clear for source textureId=%d", + sourceTexture->GetExternalIndex()); + } + + const VkImageLayout srcOriginalLayout = readIsDefaultFbo + ? m_swapchainObject.GetImageLayout(m_imageIndexAcquired) + : *srcBinding.trackedLayout; + if (srcOriginalLayout == VK_IMAGE_LAYOUT_UNDEFINED) { + RecordTextureCopyError(__func__, ErrorCode::InvalidOperation, + "CopyTexSubImage2D source image has undefined layout."); + return; + } + + const VkImageLayout dstOriginalLayout = *dstBinding.trackedLayout; + const VkImageLayout dstRestoreLayout = dstOriginalLayout == VK_IMAGE_LAYOUT_UNDEFINED + ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL + : dstOriginalLayout; + + VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + VkAccessFlags srcAccessMask = 0; + GetImageTransitionSourceState(srcOriginalLayout, srcStageMask, srcAccessMask); + if (readIsDefaultFbo) { + VkImageLayout srcTrackedLayout = srcOriginalLayout; + Bool ok = VkTextureManager::TransitionImageLayout( + frame.commandBuffer, srcBinding.image, srcTrackedLayout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + srcStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT, + srcAccessMask, VK_ACCESS_TRANSFER_READ_BIT, srcBinding.aspectMask, + srcBinding.mipLevel, srcBinding.mipLevelCount); + MOBILEGL_ASSERT(ok, "%s: failed to transition swapchain source image", __func__); + m_swapchainObject.SetImageLayout(m_imageIndexAcquired, srcTrackedLayout); + } else { + 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, + srcBinding.mipLevel, srcBinding.mipLevelCount); + MOBILEGL_ASSERT(ok, "%s: failed to transition source image", __func__); + } + + VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + VkAccessFlags dstAccessMask = 0; + GetImageTransitionSourceState(dstOriginalLayout, dstStageMask, dstAccessMask); + Bool dstReady = 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, + dstBinding.mipLevel, dstBinding.mipLevelCount); + MOBILEGL_ASSERT(dstReady, "%s: failed to transition destination image", __func__); + + VkImageCopy copyRegion{}; + copyRegion.srcSubresource.aspectMask = srcBinding.aspectMask; + copyRegion.srcSubresource.mipLevel = srcBinding.mipLevel; + copyRegion.srcSubresource.baseArrayLayer = 0; + copyRegion.srcSubresource.layerCount = 1; + copyRegion.srcOffset = {x, y, 0}; + copyRegion.dstSubresource.aspectMask = dstBinding.aspectMask; + copyRegion.dstSubresource.mipLevel = dstBinding.mipLevel; + copyRegion.dstSubresource.baseArrayLayer = 0; + copyRegion.dstSubresource.layerCount = 1; + copyRegion.dstOffset = {xoffset, yoffset, 0}; + copyRegion.extent = {static_cast(width), static_cast(height), 1}; + vkCmdCopyImage(frame.commandBuffer, + srcBinding.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + dstBinding.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, ©Region); + + 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, + srcBinding.mipLevel, srcBinding.mipLevelCount); + 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, + srcBinding.mipLevel, srcBinding.mipLevelCount); + MOBILEGL_ASSERT(ok, "%s: failed to restore source image layout", __func__); + } + + VkPipelineStageFlags dstRestoreStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + VkAccessFlags dstRestoreAccessMask = 0; + GetImageTransitionDestinationState(dstRestoreLayout, dstRestoreStageMask, dstRestoreAccessMask); + Bool dstRestored = VkTextureManager::TransitionImageLayout( + frame.commandBuffer, dstBinding.image, *dstBinding.trackedLayout, dstRestoreLayout, + VK_PIPELINE_STAGE_TRANSFER_BIT, dstRestoreStageMask, + VK_ACCESS_TRANSFER_WRITE_BIT, dstRestoreAccessMask, dstBinding.aspectMask, + dstBinding.mipLevel, dstBinding.mipLevelCount); + MOBILEGL_ASSERT(dstRestored, "%s: failed to restore destination image layout", __func__); + } + void VulkanRenderer::DrawArrays(const DrawCmd& payload) { auto& frame = m_frameContext.GetCurrent(); @@ -1976,8 +2367,7 @@ void main() { VkClearAttachment clearAttachment{}; clearAttachment.clearValue.depthStencil = {1.0f, 0}; - if (clearPayload.attachmentType >= FramebufferAttachmentType::Color0 && - clearPayload.attachmentType <= FramebufferAttachmentType::Color31) { + if ((clearPayload.mask & GL_COLOR_BUFFER_BIT) != 0) { clearAttachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; clearAttachment.colorAttachment = pending.attachmentIndex; clearAttachment.clearValue.color = { @@ -1986,14 +2376,18 @@ void main() { clearPayload.color.z(), clearPayload.color.w() }; - } else if (clearPayload.attachmentType == FramebufferAttachmentType::Depth) { - clearAttachment.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; - clearAttachment.clearValue.depthStencil.depth = clearPayload.depth; - } else if (clearPayload.attachmentType == FramebufferAttachmentType::Stencil) { - clearAttachment.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT; - clearAttachment.clearValue.depthStencil.stencil = clearPayload.stencil; } else { - continue; + if ((clearPayload.mask & GL_DEPTH_BUFFER_BIT) != 0) { + clearAttachment.aspectMask |= VK_IMAGE_ASPECT_DEPTH_BIT; + clearAttachment.clearValue.depthStencil.depth = clearPayload.depth; + } + if ((clearPayload.mask & GL_STENCIL_BUFFER_BIT) != 0) { + clearAttachment.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT; + clearAttachment.clearValue.depthStencil.stencil = clearPayload.stencil; + } + if (clearAttachment.aspectMask == 0) { + continue; + } } vkCmdClearAttachments(commandBuffer, 1, &clearAttachment, 1, &clearRect); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h index cc829d80..29102504 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h @@ -115,9 +115,15 @@ namespace MobileGL::MG_Backend::DirectVulkan { const RenderPassEntry& compatibleRenderPassEntry); void Clear(GLbitfield mask); + void ClearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil); + 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 BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); + void CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, + GLint x, GLint y, GLsizei width, GLsizei height); void DrawArrays(const DrawCmd& payload); void DrawElements(const DrawIndexedCmd& payload); void MultiDrawElements(const MultiDrawIndexedCmd& payloads); @@ -146,6 +152,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { Uint32 samplerBinding = 0; }; + void QueueClearBufferPayload(GLenum buffer, GLint drawbuffer, const ClearAttachmentPayload& clearPayload); + NativeWindowType m_window = 0; VulkanRendererConfig m_config;