From 5310ff62bbb532b8e70c99e0dbcfef2459e6892d Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Sun, 20 Sep 2026 12:51:19 -0400 Subject: [PATCH] [MG_Backend] (Disaggregated): generate packed float mips with native shader blits --- .../DirectVulkan/Renderer/VulkanRenderer.h | 3 + .../DirectVulkan/Renderer/WireColorBlit.inc | 54 ++++++++--- .../DirectVulkan/Renderer/WireFramebuffer.inc | 95 +++++++++++++------ .../Scenarios/F1WireScenario.cpp | 19 ++++ 4 files changed, 129 insertions(+), 42 deletions(-) diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h index 4fe7d092..6cdf8d85 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h @@ -478,6 +478,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { Bool BlitWireColorToDefault(WireImage source, WireImage destination, GLint sx0, GLint sy0, GLint sx1, GLint sy1, GLint dx0, GLint dy0, GLint dx1, GLint dy1, GLenum filter); + Bool BlitWireColorImage(WireImage source, WireImage destination, + GLint sx0, GLint sy0, GLint sx1, GLint sy1, + GLint dx0, GLint dy0, GLint dx1, GLint dy1, GLenum filter, Bool mipmap); struct WireColorBlitResources; WireColorBlitResources* m_wireColorBlitResources = nullptr; void DestroyWireColorBlitResources(); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireColorBlit.inc b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireColorBlit.inc index a4930ed2..866c8ccc 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireColorBlit.inc +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireColorBlit.inc @@ -6,6 +6,7 @@ struct VulkanRenderer::WireColorBlitResources { struct Target { VkFormat format = VK_FORMAT_UNDEFINED; + VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED; VkRenderPass renderPass = VK_NULL_HANDLE; VkPipeline pipeline = VK_NULL_HANDLE; }; @@ -38,15 +39,25 @@ Bool VulkanRenderer::BlitWireColorToDefault(WireImage source, WireImage destination, GLint sx0, GLint sy0, GLint sx1, GLint sy1, GLint dx0, GLint dy0, GLint dx1, GLint dy1, GLenum filter) { - if (!destination.isDefault || source.isDefault || source.image == destination.image || + if (!destination.isDefault) return false; + return BlitWireColorImage(source, destination, sx0, sy0, sx1, sy1, dx0, dy0, dx1, dy1, filter, false); + } + + Bool VulkanRenderer::BlitWireColorImage(WireImage source, WireImage destination, + GLint sx0, GLint sy0, GLint sx1, GLint sy1, + GLint dx0, GLint dy0, GLint dx1, GLint dy1, GLenum filter, Bool mipmap) { + const Bool sameImage = source.image == destination.image; + if (source.isDefault || (sameImage && source.level == destination.level && source.layer == destination.layer) || source.samples != VK_SAMPLE_COUNT_1_BIT || destination.samples != VK_SAMPLE_COUNT_1_BIT || - source.is3D || (source.viewType != VK_IMAGE_VIEW_TYPE_2D && + source.is3D || destination.is3D || (source.viewType != VK_IMAGE_VIEW_TYPE_2D && source.viewType != VK_IMAGE_VIEW_TYPE_2D_ARRAY) || + (destination.viewType != VK_IMAGE_VIEW_TYPE_2D && destination.viewType != VK_IMAGE_VIEW_TYPE_2D_ARRAY) || !vkuFormatIsSampledFloat(source.format)) return false; if (sx0 == sx1 || sy0 == sy1 || dx0 == dx1 || dy0 == dy1) return true; if (!source.extent.width || !source.extent.height || !destination.extent.width || !destination.extent.height) return false; - const auto transform = m_swapchainObject.GetPreTransform(); + const auto transform = destination.isDefault ? m_swapchainObject.GetPreTransform() : + VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; if (transform != VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR && transform != VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR && transform != VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR && @@ -56,6 +67,15 @@ const VkFormatFeatureFlags required = VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | (filter == GL_LINEAR ? VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT : 0); if ((sourceProperties.optimalTilingFeatures & required) != required) return false; + // Mip generation samples and writes the texture's own color space; + // GL_FRAMEBUFFER_SRGB only controls framebuffer operations. + if (!mipmap) destination.format = ResolveSrgbAttachmentWriteFormat(destination.format, + MGB_CTX->IsCapabilityEnabled(CapabilityInput::FramebufferSrgb)); + VkFormatProperties destinationProperties{}; + vkGetPhysicalDeviceFormatProperties(m_physicalDevice.handle, destination.format, &destinationProperties); + if (!(destinationProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT)) return false; + const VkImageLayout sourceLayout = sameImage ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + const VkImageLayout destinationLayout = sameImage ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; struct Constants { float srcRect[4]; @@ -97,17 +117,15 @@ VK_VERIFY(vkCreateSampler(m_device, &sampler, nullptr, &cache.linear)); } auto& cache = *m_wireColorBlitResources; - // Honor the same sRGB write policy as wire draw attachments. - destination.format = ResolveSrgbAttachmentWriteFormat(destination.format, - MGB_CTX->IsCapabilityEnabled(CapabilityInput::FramebufferSrgb)); WireColorBlitResources::Target* target = nullptr; for (auto& entry : cache.targets) { - if (entry.format == destination.format) { target = &entry; break; } + if (entry.format == destination.format && entry.layout == destinationLayout) { target = &entry; break; } } if (!target) { cache.targets.emplace_back(); target = &cache.targets.back(); target->format = destination.format; + target->layout = destinationLayout; VkAttachmentDescription attachment{}; attachment.format = destination.format; attachment.samples = VK_SAMPLE_COUNT_1_BIT; @@ -115,8 +133,8 @@ attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - attachment.initialLayout = attachment.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - const VkAttachmentReference color{0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL}; + attachment.initialLayout = attachment.finalLayout = destinationLayout; + const VkAttachmentReference color{0, destinationLayout}; VkSubpassDescription subpass{}; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpass.colorAttachmentCount = 1; @@ -174,8 +192,12 @@ } m_textureManager->FlushPendingUploads(); - TransitionWireImage(source, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); - TransitionWireImage(destination, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + // Distinct mip/layer views of the same image are legal shader input and + // output. GENERAL keeps the whole-image layout tracker truthful; the + // transition also orders the previous mip's attachment write. + TransitionWireImage(source, sourceLayout); + if (sameImage) destination.layout = source.layout; + else TransitionWireImage(destination, destinationLayout); const auto makeView = [&](const WireImage& image) { VkImageViewCreateInfo view{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; view.image = image.image; @@ -213,7 +235,7 @@ alloc.pSetLayouts = &cache.descriptorLayout; VK_VERIFY(vkAllocateDescriptorSets(m_device, &alloc, &descriptorSet)); const VkDescriptorImageInfo sampled{filter == GL_LINEAR ? cache.linear : cache.nearest, - sourceView, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL}; + sourceView, sourceLayout}; VkWriteDescriptorSet write{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET}; write.dstSet = descriptorSet; write.dstBinding = 0; @@ -227,6 +249,12 @@ transform == VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR; const float dw = static_cast(quarterTurn ? destination.extent.height : destination.extent.width); const float dh = static_cast(quarterTurn ? destination.extent.width : destination.extent.height); + // The shared vertex shader flips window Y. Offscreen images keep the + // source row order, so undo that flip in the destination rectangle. + if (!destination.isDefault) { + dy0 = static_cast(destination.extent.height) - dy0; + dy1 = static_cast(destination.extent.height) - dy1; + } const Constants constants{{sx0 / sw, sy0 / sh, (static_cast(sx1) - sx0) / sw, (static_cast(sy1) - sy0) / sh}, {dx0 / dw, dy0 / dh, (static_cast(dx1) - dx0) / dw, @@ -260,7 +288,7 @@ retired.imageViews = {sourceView, destinationView}; retired.descriptorPools.push_back(descriptorPool); RetireWireObjects(Move(retired)); - m_swapchainObject.SetImageContentDefined(m_imageIndexAcquired, true); + if (destination.isDefault) m_swapchainObject.SetImageContentDefined(m_imageIndexAcquired, true); return true; } #endif diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireFramebuffer.inc b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireFramebuffer.inc index 07f1e427..54026930 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireFramebuffer.inc +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireFramebuffer.inc @@ -661,8 +661,9 @@ RetireWireDrawPass(); auto& state = MG_Pipe::MGPipeApplier(); Uint32 base = state.VerbMipBaseLevel, baseLayer = 0, layerCount = 1; + VkFormat viewFormat = VK_FORMAT_UNDEFINED; const auto handle = m_textureManager->ResolveWireTextureStorage(state.VerbMipRes, base, baseLayer, - nullptr, &layerCount); + &viewFormat, &layerCount); auto* resource = m_textureManager->SyncTextureResourceByHandle(handle); if (!resource) MagmaWireFatal("mipmap-resource"); m_textureManager->FlushPendingUploads(); @@ -676,38 +677,74 @@ if (resource->aspect != VK_IMAGE_ASPECT_COLOR_BIT && resource->aspect != VK_IMAGE_ASPECT_DEPTH_BIT) MagmaWireFatal("depth-stencil-mipmap@P7"); VkFormatProperties props{}; - vkGetPhysicalDeviceFormatProperties(m_physicalDevice.handle,resource->format,&props); + const VkFormat format = viewFormat == VK_FORMAT_UNDEFINED ? resource->format : viewFormat; + vkGetPhysicalDeviceFormatProperties(m_physicalDevice.handle,format,&props); const auto required = VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT; - if ((props.optimalTilingFeatures & required) != required) MagmaWireFatal("mipmap-native-format@P7"); + const Bool nativeBlit = format == resource->format && (props.optimalTilingFeatures & required) == required; const VkFilter filter = props.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT ? VK_FILTER_LINEAR : VK_FILTER_NEAREST; - VkPipelineStageFlags stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - VkAccessFlags access = 0; - GetImageTransitionSourceState(resource->layout,stage,access); - if (!VkTextureManager::TransitionImageLayout(frame.commandBuffer,resource->image,resource->layout, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,stage,VK_PIPELINE_STAGE_TRANSFER_BIT,access, - VK_ACCESS_TRANSFER_WRITE_BIT,resource->aspect,0,resource->mipLevels)) MagmaWireFatal("mipmap-transition"); - for (Uint32 level=base+1;levelimage,previous, - VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,VK_PIPELINE_STAGE_TRANSFER_BIT,VK_PIPELINE_STAGE_TRANSFER_BIT, - VK_ACCESS_TRANSFER_WRITE_BIT,VK_ACCESS_TRANSFER_READ_BIT,resource->aspect,level-1,1); - VkImageBlit blit{}; - blit.srcSubresource = {resource->aspect,level-1,baseLayer,layerCount}; - blit.dstSubresource = {resource->aspect,level,baseLayer,layerCount}; - blit.srcOffsets[1] = {static_cast(std::max(resource->extent.width>>(level-1),1u)), - static_cast(std::max(resource->extent.height>>(level-1),1u)), - static_cast(std::max(resource->depth>>(level-1),1u))}; - blit.dstOffsets[1] = {static_cast(std::max(resource->extent.width>>level,1u)), - static_cast(std::max(resource->extent.height>>level,1u)), - static_cast(std::max(resource->depth>>level,1u))}; - vkCmdBlitImage(frame.commandBuffer,resource->image,VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - resource->image,VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,1,&blit,filter); - VkTextureManager::TransitionImageLayout(frame.commandBuffer,resource->image,previous, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,VK_PIPELINE_STAGE_TRANSFER_BIT,VK_PIPELINE_STAGE_TRANSFER_BIT, - VK_ACCESS_TRANSFER_READ_BIT,VK_ACCESS_TRANSFER_WRITE_BIT,resource->aspect,level-1,1); + if (!nativeBlit) { + // Packed floating-point color formats may support sampling and + // rendering without BLIT_DST. Generate from actual GPU mip contents + // through the native shader pass; no client or staged texels enter. + if (resource->aspect != VK_IMAGE_ASPECT_COLOR_BIT || + resource->viewType == VK_IMAGE_VIEW_TYPE_1D || resource->viewType == VK_IMAGE_VIEW_TYPE_1D_ARRAY || + resource->viewType == VK_IMAGE_VIEW_TYPE_3D || + !(resource->usageFlags & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) || + baseLayer >= resource->arrayLayers || layerCount > resource->arrayLayers - baseLayer) + MagmaWireFatal("mipmap-shader-format-or-shape"); + for (Uint32 level = base + 1; level < base + count; ++level) { + for (Uint32 layer = baseLayer; layer < baseLayer + layerCount; ++layer) { + WireImage source{}; + source.image = resource->image; + source.format = format; + source.aspect = VK_IMAGE_ASPECT_COLOR_BIT; + source.layout = resource->layout; + source.trackedLayout = &resource->layout; + source.levels = resource->mipLevels; + source.level = level - 1; + source.layer = layer; + source.extent = {std::max(resource->extent.width >> (level - 1), 1u), + std::max(resource->extent.height >> (level - 1), 1u)}; + WireImage destination = source; + destination.level = level; + destination.extent = {std::max(resource->extent.width >> level, 1u), + std::max(resource->extent.height >> level, 1u)}; + if (!BlitWireColorImage(source, destination, 0, 0, source.extent.width, source.extent.height, + 0, 0, destination.extent.width, destination.extent.height, + filter == VK_FILTER_LINEAR ? GL_LINEAR : GL_NEAREST, true)) + MagmaWireFatal("mipmap-shader-blit"); + } + } + } else { + VkPipelineStageFlags stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + VkAccessFlags access = 0; + GetImageTransitionSourceState(resource->layout,stage,access); + if (!VkTextureManager::TransitionImageLayout(frame.commandBuffer,resource->image,resource->layout, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,stage,VK_PIPELINE_STAGE_TRANSFER_BIT,access, + VK_ACCESS_TRANSFER_WRITE_BIT,resource->aspect,0,resource->mipLevels)) MagmaWireFatal("mipmap-transition"); + for (Uint32 level=base+1;levelimage,previous, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,VK_PIPELINE_STAGE_TRANSFER_BIT,VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_ACCESS_TRANSFER_WRITE_BIT,VK_ACCESS_TRANSFER_READ_BIT,resource->aspect,level-1,1); + VkImageBlit blit{}; + blit.srcSubresource = {resource->aspect,level-1,baseLayer,layerCount}; + blit.dstSubresource = {resource->aspect,level,baseLayer,layerCount}; + blit.srcOffsets[1] = {static_cast(std::max(resource->extent.width>>(level-1),1u)), + static_cast(std::max(resource->extent.height>>(level-1),1u)), + static_cast(std::max(resource->depth>>(level-1),1u))}; + blit.dstOffsets[1] = {static_cast(std::max(resource->extent.width>>level,1u)), + static_cast(std::max(resource->extent.height>>level,1u)), + static_cast(std::max(resource->depth>>level,1u))}; + vkCmdBlitImage(frame.commandBuffer,resource->image,VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + resource->image,VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,1,&blit,filter); + VkTextureManager::TransitionImageLayout(frame.commandBuffer,resource->image,previous, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,VK_PIPELINE_STAGE_TRANSFER_BIT,VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_ACCESS_TRANSFER_READ_BIT,VK_ACCESS_TRANSFER_WRITE_BIT,resource->aspect,level-1,1); + } + resource->layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; } - resource->layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; auto& record = state.TextureResources[handle.Slot]; record.Desc.Levels = std::max(record.Desc.Levels,static_cast(base+count)); resource->syncedWireSerial = ++record.Serial; diff --git a/MobileGL/MG_IntegrationTest/Scenarios/F1WireScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/F1WireScenario.cpp index ab6e0d55..a9570d9e 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/F1WireScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/F1WireScenario.cpp @@ -460,6 +460,25 @@ TEST_F(F1WireScenario, GenerateMipmapPackedFloatPixels) { ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); const GLfloat expected[4] = {0.25f, 0.5f, 0.75f, 1.0f}; for (int i = 0; i < 4; ++i) EXPECT_NEAR(pixel[i], expected[i], 0.01f); + + // Change the base on the GPU after its client upload. The regenerated mip + // must read those new pixels, and offscreen shader blits must keep row order. + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); + glDisable(GL_SCISSOR_TEST); + glClearColor(0.875f, 0.375f, 0.125f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + glEnable(GL_SCISSOR_TEST); + glScissor(0, 4, 8, 4); + glClearColor(0.125f, 0.75f, 0.5f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + glDisable(GL_SCISSOR_TEST); + glGenerateMipmap(GL_TEXTURE_2D); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 2); + GLfloat rows[8]{}; + glReadPixels(1, 0, 1, 2, GL_RGBA, GL_FLOAT, rows); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); + const GLfloat generated[8] = {0.875f, 0.375f, 0.125f, 1.0f, 0.125f, 0.75f, 0.5f, 1.0f}; + for (int i = 0; i < 8; ++i) EXPECT_NEAR(rows[i], generated[i], 0.01f); } TEST_F(F1WireScenario, GenerateMipmapDepthPixels) {