diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index 01121a85..ce65d146 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -14136,16 +14136,23 @@ namespace MobileGL::MG_Backend::DirectGLES { } // GL_DEPTH_COMPONENT readback into the client's layout, honouring the PACK pixel-store - // parameters. GL 4.6 core 18.2.8: the normalized depth is written as-is for GL_FLOAT and - // scaled into the full range of whichever integer width the client asked for otherwise. + // parameters. GL 4.6 core 18.2.8 table 8.6: the normalized depth is written as-is for the + // floating-point client types and scaled into the full range of whichever integer width the + // client asked for otherwise - 2^n - 1 for an unsigned width, 2^(n-1) - 1 for a signed one. + // Sharing one denominator across the two signednesses would put a 0.5 depth at 32768, which + // as a GLshort is -32768, so GL_BYTE/GL_SHORT/GL_INT each keep their own. static Bool ReadPixelsDepthComponent(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type, void* pixels) { SizeT dstPixelBytes = 0; switch (type) { + case GL_BYTE: case GL_UNSIGNED_BYTE: dstPixelBytes = sizeof(Uint8); break; - case GL_UNSIGNED_SHORT: dstPixelBytes = sizeof(Uint16); break; - case GL_UNSIGNED_INT: dstPixelBytes = sizeof(Uint32); break; - case GL_FLOAT: dstPixelBytes = sizeof(GLfloat); break; + case GL_SHORT: + case GL_UNSIGNED_SHORT: + case GL_HALF_FLOAT: dstPixelBytes = sizeof(Uint16); break; + case GL_INT: + case GL_UNSIGNED_INT: + case GL_FLOAT: dstPixelBytes = sizeof(Uint32); break; default: return false; } if (width <= 0 || height <= 0) { @@ -14162,21 +14169,38 @@ namespace MobileGL::MG_Backend::DirectGLES { const Float* srcRow = depth.data() + static_cast(row) * static_cast(width); for (GLsizei col = 0; col < width; ++col) { + const Float depthValue = srcRow[col]; switch (type) { + case GL_BYTE: + reinterpret_cast(dst)[col] = static_cast( + NormalizedDepthToUnsigned(depthValue, 127.0)); + break; case GL_UNSIGNED_BYTE: dst[col] = static_cast( - NormalizedDepthToUnsigned(srcRow[col], 255.0)); + NormalizedDepthToUnsigned(depthValue, 255.0)); + break; + case GL_SHORT: + reinterpret_cast(dst)[col] = static_cast( + NormalizedDepthToUnsigned(depthValue, 32767.0)); break; case GL_UNSIGNED_SHORT: reinterpret_cast(dst)[col] = static_cast( - NormalizedDepthToUnsigned(srcRow[col], 65535.0)); + NormalizedDepthToUnsigned(depthValue, 65535.0)); + break; + case GL_INT: + reinterpret_cast(dst)[col] = static_cast( + NormalizedDepthToUnsigned(depthValue, 2147483647.0)); break; case GL_UNSIGNED_INT: reinterpret_cast(dst)[col] = - NormalizedDepthToUnsigned(srcRow[col], 4294967295.0); + NormalizedDepthToUnsigned(depthValue, 4294967295.0); + break; + case GL_HALF_FLOAT: + reinterpret_cast(dst)[col] = + MG_Util::EncodeFloatToHalfBits(depthValue); break; default: - reinterpret_cast(dst)[col] = srcRow[col]; + reinterpret_cast(dst)[col] = depthValue; break; } } @@ -14245,16 +14269,18 @@ namespace MobileGL::MG_Backend::DirectGLES { static Bool ReadPixelsStencilViaNative(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type, void* pixels) { // GL 4.6 core 18.2.8: a stencil index is written unconverted into whichever integer width - // the client asked for, and converted to a float value for GL_FLOAT. The signed widths are - // as legal as the unsigned ones - the CTS reads stencil with GL_INT - and rejecting them - // here used to let the call fall through to a native ES read the driver refuses, after - // which nothing was written at all and the caller kept its zeros. + // the client asked for, and converted to a float value for the floating-point widths - both + // GL_FLOAT and GL_HALF_FLOAT. The signed widths are as legal as the unsigned ones - the CTS + // reads stencil with GL_INT - and rejecting them here used to let the call fall through to a + // native ES read the driver refuses, after which nothing was written at all and the caller + // kept its zeros. SizeT dstPixelBytes = 0; switch (type) { case GL_UNSIGNED_BYTE: case GL_BYTE: dstPixelBytes = sizeof(Uint8); break; case GL_UNSIGNED_SHORT: - case GL_SHORT: dstPixelBytes = sizeof(Uint16); break; + case GL_SHORT: + case GL_HALF_FLOAT: dstPixelBytes = sizeof(Uint16); break; case GL_UNSIGNED_INT: case GL_INT: dstPixelBytes = sizeof(Uint32); break; case GL_FLOAT: dstPixelBytes = sizeof(GLfloat); break; @@ -14286,6 +14312,10 @@ namespace MobileGL::MG_Backend::DirectGLES { case GL_FLOAT: reinterpret_cast(dst)[col] = static_cast(srcRow[col]); break; + case GL_HALF_FLOAT: + reinterpret_cast(dst)[col] = + MG_Util::EncodeFloatToHalfBits(static_cast(srcRow[col])); + break; default: reinterpret_cast(dst)[col] = srcRow[col]; break; @@ -15001,14 +15031,18 @@ namespace MobileGL::MG_Backend::DirectGLES { // covered by the colour tables above - GetReadbackChannelMapping has no entry for any // depth or stencil format, so without this gate a read the helpers CAN serve (a // GL_UNSIGNED_SHORT depth, a GL_SHORT stencil) is turned away before it reaches them. + // The signed widths and GL_HALF_FLOAT belong in it for the same reason: the helpers scale a + // depth into 127 / 32767 / 2147483647 for the signed ones and hand GL_HALF_FLOAT the value + // as a half word, so a pair listed here is one they answer in full. static Bool IsSupportedDepthStencilReadPixelsPair(GLenum format, GLenum type) { switch (format) { case GL_DEPTH_COMPONENT: - return type == GL_UNSIGNED_BYTE || type == GL_UNSIGNED_SHORT || type == GL_UNSIGNED_INT || - type == GL_FLOAT; + return type == GL_BYTE || type == GL_UNSIGNED_BYTE || type == GL_SHORT || + type == GL_UNSIGNED_SHORT || type == GL_INT || type == GL_UNSIGNED_INT || + type == GL_FLOAT || type == GL_HALF_FLOAT; case GL_STENCIL_INDEX: return type == GL_UNSIGNED_BYTE || type == GL_BYTE || type == GL_UNSIGNED_SHORT || type == GL_SHORT || - type == GL_UNSIGNED_INT || type == GL_INT || type == GL_FLOAT; + type == GL_UNSIGNED_INT || type == GL_INT || type == GL_FLOAT || type == GL_HALF_FLOAT; case GL_DEPTH_STENCIL: return type == GL_UNSIGNED_INT_24_8 || type == GL_FLOAT_32_UNSIGNED_INT_24_8_REV; default: @@ -15181,7 +15215,7 @@ namespace MobileGL::MG_Backend::DirectGLES { if (format == GL_RGBA_INTEGER) { return type == GL_INT || type == GL_UNSIGNED_INT || type == GL_UNSIGNED_INT_2_10_10_10_REV; } - if (format == GL_DEPTH_STENCIL || format == GL_DEPTH_COMPONENT) { + if (format == GL_DEPTH_STENCIL || format == GL_DEPTH_COMPONENT || format == GL_STENCIL_INDEX) { return IsSupportedDepthStencilReadPixelsPair(format, type); } return false; @@ -15524,10 +15558,17 @@ namespace MobileGL::MG_Backend::DirectGLES { // where the driver has it, shader sampling where it does not. ES accepts neither // spelling natively, which is why glGetTexImage(GL_DEPTH_STENCIL) used to leave // packed_depth_stencil.verify_get_tex_image reading its own zero-filled buffer. + // GL_STENCIL_INDEX is the third spelling of the same read and needs the same hand: + // it is not a colour format, so it has no conversion table entry either, and used to + // fall through to the native glReadPixels below, which ES refuses. if (format == GL_DEPTH_COMPONENT && ReadPixelsDepthComponent(0, 0, size.x(), size.y(), type, pixels)) { MGLOG_D("GetTexImage: finished via depth readback helper"); return; } + if (format == GL_STENCIL_INDEX && ReadPixelsStencilViaNative(0, 0, size.x(), size.y(), type, pixels)) { + MGLOG_D("GetTexImage: finished via stencil readback helper"); + return; + } if (format == GL_DEPTH_STENCIL && ReadPixelsDepthStencilPacked(0, 0, size.x(), size.y(), type, pixels)) { MGLOG_D("GetTexImage: finished via packed depth/stencil readback helper"); return; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index da028bda..43dd1a06 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -7797,6 +7797,80 @@ void main() { return ScissoredClearPrep::Ready; } + // A tightly packed depth/stencil readback, one aspect or both, in the client's own width. + // The two arms that read depth or stencil off a Vulkan image - the monolith + // ReadDepthStencilImageToClient and the wire ReadTextureImageWire - copy their aspect into a + // host-visible buffer and both owe the application the same bytes, so they share this one + // encoding rather than each carrying a copy of it. A copy is exactly the kind of pair where + // a fix landed on one side only: the signed ranges and GL_HALF_FLOAT were added to the wire + // caller and the monolith caller kept scaling a GL_SHORT depth into 65535, which put a 0.5 + // depth at 0x8000 - a negative index. + static Bool PackDepthStencilTight(const Uint8* depth, const Uint8* stencil, VkFormat sourceFormat, SizeT count, + GLenum format, GLenum type, Vector& result) { + const Bool stencilOnly = format == GL_STENCIL_INDEX; + const auto readDepth = [&](SizeT i) -> Float { + if (sourceFormat == VK_FORMAT_D16_UNORM) { + Uint16 word{}; Memcpy(&word, depth + i * 2, sizeof(word)); + return static_cast(word) / 65535.0f; + } + if (sourceFormat == VK_FORMAT_X8_D24_UNORM_PACK32 || sourceFormat == VK_FORMAT_D24_UNORM_S8_UINT) { + Uint32 word{}; Memcpy(&word, depth + i * 4, sizeof(word)); + return static_cast(word & 0xffffffu) / 16777215.0f; + } + Float value{}; Memcpy(&value, depth + i * sizeof(value), sizeof(value)); + return value; + }; + SizeT size = 0; + switch (type) { + case GL_BYTE: case GL_UNSIGNED_BYTE: size = 1; break; + case GL_SHORT: case GL_UNSIGNED_SHORT: case GL_HALF_FLOAT: size = 2; break; + case GL_INT: case GL_UNSIGNED_INT: case GL_FLOAT: size = 4; break; + case GL_UNSIGNED_INT_24_8: size = 4; break; + case GL_FLOAT_32_UNSIGNED_INT_24_8_REV: size = 8; break; + default: return false; + } + const Bool packed = type == GL_UNSIGNED_INT_24_8 || type == GL_FLOAT_32_UNSIGNED_INT_24_8_REV; + if ((format == GL_DEPTH_STENCIL) != packed) return false; + result.resize(count * size); + for (SizeT i = 0; i < count; ++i) { + Uint8* dst = result.data() + i * size; + const Uint32 s = stencil ? stencil[i] : 0; + const Float d = stencilOnly ? 0.0f : readDepth(i); + switch (type) { + case GL_FLOAT: { + const Float value = stencilOnly ? static_cast(s) : d; + Memcpy(dst, &value, sizeof(value)); break; + } + case GL_HALF_FLOAT: { + const Uint16 value = MG_Util::EncodeFloatToHalfBits(stencilOnly ? static_cast(s) : d); + Memcpy(dst, &value, sizeof(value)); break; + } + case GL_UNSIGNED_INT_24_8: { + Uint32 depth24 = 0; + if (sourceFormat == VK_FORMAT_D24_UNORM_S8_UINT) { + Memcpy(&depth24, depth + i * 4, sizeof(depth24)); + depth24 &= 0xffffffu; + } else { + depth24 = static_cast(std::llround(std::clamp(d, 0.0, 1.0) * 16777215.0)); + } + const Uint32 value = (depth24 << 8) | s; + Memcpy(dst, &value, sizeof(value)); break; + } + case GL_FLOAT_32_UNSIGNED_INT_24_8_REV: + Memcpy(dst, &d, sizeof(d)); Memcpy(dst + 4, &s, sizeof(s)); break; + default: { + const Bool signedType = type == GL_BYTE || type == GL_SHORT || type == GL_INT; + const Uint64 maximum = signedType ? ((Uint64{1} << (size * 8 - 1)) - 1) + : ((Uint64{1} << (size * 8)) - 1); + const Uint32 value = stencilOnly ? s : static_cast( + std::llround(std::clamp(d, 0.0, 1.0) * static_cast(maximum))); + Memcpy(dst, &value, size); break; + } + } + } + return true; + } + #include "WireFramebuffer.inc" #include "WireTextureReadback.inc" #include "WireColorBlit.inc" @@ -9166,6 +9240,58 @@ void main() { return true; } + // A single-aspect depth/stencil copy between two images of the same format. An image copy + // moves the format's own native representation, and for a packed depth-stencil format that + // representation is one word holding both aspects: a region that names only the depth aspect + // still carries the source's stencil byte into the destination, and the other way round, so a + // depth-only resolve silently replaces the destination's stencil (and a stencil-only resolve + // its depth). Staging the requested aspect through a buffer makes the region's aspectMask + // authoritative - a buffer copy names one aspect and moves exactly its texels - which is the + // same reason the wire resolve (WireFramebuffer.inc, ResolveWireDepthStencil) copies its + // resolved aspect through a buffer instead of patching the image copy. + // + // Both images are in a TRANSFER layout when this is called, so no transition is needed here. + Bool VulkanRenderer::CopyDepthStencilAspectThroughBuffer( + FrameContext::FrameData& frame, VkImage srcImage, VkImage dstImage, VkFormat format, Uint32 srcMipLevel, + Uint32 srcBaseArrayLayer, Uint32 dstMipLevel, Uint32 dstBaseArrayLayer, GLint srcX, GLint srcY, GLint dstX, + GLint dstY, GLsizei width, GLsizei height, VkImageAspectFlagBits aspect) { + // The aspect's own tightly defined copy layout: a stencil texel is one byte whatever the + // format, and the depth half of a packed format copies on its own at 2 or 4 bytes. + const VkDeviceSize texelSize = aspect == VK_IMAGE_ASPECT_STENCIL_BIT + ? 1 + : (format == VK_FORMAT_D16_UNORM || format == VK_FORMAT_D16_UNORM_S8_UINT ? 2 : 4); + const VkDeviceSize bytes = static_cast(width) * static_cast(height) * texelSize; + VkBufferObject staging; + if (!staging.Create({.allocator = m_allocator, .size = bytes, + .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + .memoryUsage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE})) { + MGLOG_E_ONCE("CopyDepthStencilAspectThroughBuffer: failed to create the aspect staging buffer"); + return false; + } + VkBufferImageCopy region{}; + region.imageSubresource = {aspect, srcMipLevel, srcBaseArrayLayer, 1}; + region.imageOffset = {srcX, srcY, 0}; + region.imageExtent = {static_cast(width), static_cast(height), 1}; + vkCmdCopyImageToBuffer(frame.commandBuffer, srcImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + staging.GetHandle(), 1, ®ion); + VkBufferMemoryBarrier barrier{VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER}; + barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + barrier.srcQueueFamilyIndex = barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.buffer = staging.GetHandle(); + barrier.size = bytes; + vkCmdPipelineBarrier(frame.commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, 0, nullptr, 1, &barrier, 0, nullptr); + region.imageSubresource = {aspect, dstMipLevel, dstBaseArrayLayer, 1}; + region.imageOffset = {dstX, dstY, 0}; + vkCmdCopyBufferToImage(frame.commandBuffer, staging.GetHandle(), dstImage, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); + // The commands above still name this buffer and this frame's submission has not happened + // yet, so the buffer retires with the frame rather than with this scope. + m_bufferManager.DeferRelease(Move(staging)); + return true; + } + void VulkanRenderer::BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter) { @@ -9471,6 +9597,20 @@ void main() { srcBinding.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dstBinding.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &blitRegion, VK_FILTER_NEAREST); + } else if (isDepthBlit != isStencilBlit && srcBinding.layerCount == 1 && dstBinding.layerCount == 1 && + (GetDepthStencilAspectMaskForFormat(srcBinding.format) & + ~static_cast(depthStencilAspect)) != 0) { + // Exactly one aspect of a packed depth-stencil format is being copied into a + // destination that carries the other one as well. A whole-image copy moves the + // packed native word and would replace that other aspect with the source's; see + // CopyDepthStencilAspectThroughBuffer. Same format, same size, one layer - the + // shapes the buffer round trip can express. A scaled or display-oriented copy + // takes the blit above, and a multi-layer one keeps the image copy. + (void)CopyDepthStencilAspectThroughBuffer(frame, srcBinding.image, dstBinding.image, + srcBinding.format, srcBinding.mipLevel, + srcBinding.baseArrayLayer, dstBinding.mipLevel, + dstBinding.baseArrayLayer, srcX0, srcY0, dstX0, dstY0, + srcWidth, srcHeight, depthStencilAspect); } else { VkImageCopy copyRegion{}; copyRegion.srcSubresource.aspectMask = srcBinding.aspectMask; @@ -11282,106 +11422,17 @@ void main() { } } - const auto depthValueAt = [&](SizeT i) -> Float { - switch (vkFormat) { - case VK_FORMAT_D16_UNORM: { - Uint16 raw = 0; - Memcpy(&raw, depthSrc + i * 2, sizeof(raw)); - return static_cast(raw) / 65535.0f; - } - case VK_FORMAT_X8_D24_UNORM_PACK32: - case VK_FORMAT_D24_UNORM_S8_UINT: { - Uint32 raw = 0; - Memcpy(&raw, depthSrc + i * 4, sizeof(raw)); - return static_cast(raw & 0xFFFFFFu) / static_cast(0xFFFFFFu); - } - default: { // D32_SFLOAT / D32_SFLOAT_S8_UINT - Float raw = 0.0f; - Memcpy(&raw, depthSrc + i * 4, sizeof(raw)); - return raw; - } - } - }; - - SizeT dstPixelBytes = 0; - switch (type) { - case GL_FLOAT: - case GL_UNSIGNED_INT: - case GL_INT: - case GL_UNSIGNED_INT_24_8: - dstPixelBytes = 4; - break; - case GL_UNSIGNED_SHORT: - case GL_SHORT: - dstPixelBytes = 2; - break; - case GL_UNSIGNED_BYTE: - case GL_BYTE: - dstPixelBytes = 1; - break; - case GL_FLOAT_32_UNSIGNED_INT_24_8_REV: - dstPixelBytes = 8; - break; - default: + // The packing is the SHARED PackDepthStencilTight, so this arm and the wire arm cannot + // disagree about the encoding again: one aspect or both, in the client's own width, with + // the signed integer widths scaled into their own signed range and GL_HALF_FLOAT written + // as the half word the caller asked for. + Vector packed; + if (!PackDepthStencilTight(wantDepth ? depthSrc : nullptr, wantStencil ? stencilSrc : nullptr, vkFormat, + pixelCount, format, type, packed)) { MGLOG_E_ONCE("DirectVulkan::ReadDepthStencilPixels skipped: unsupported type=0x%x", type); return; } - - // GL 4.6 core 18.2.8: a GL_STENCIL_INDEX read reports the index itself, unconverted, in - // whatever width the client asked for. Only the packed types mix depth in. Deciding this - // once - rather than per type, where GL_FLOAT and GL_UNSIGNED_SHORT used to emit a depth - // value that is meaningless for a stencil-only image - is what makes the CTS's - // (GL_STENCIL_INDEX, GL_INT) read return 7 instead of nothing. - const Bool stencilOnly = format == GL_STENCIL_INDEX; - - Vector packed(pixelCount * dstPixelBytes); - for (SizeT i = 0; i < pixelCount; ++i) { - Uint8* dst = packed.data() + i * dstPixelBytes; - switch (type) { - case GL_FLOAT: { - const Float value = stencilOnly ? static_cast(stencilSrc[i]) : depthValueAt(i); - Memcpy(dst, &value, sizeof(value)); - break; - } - case GL_UNSIGNED_SHORT: - case GL_SHORT: { - const Uint16 value = stencilOnly - ? static_cast(stencilSrc[i]) - : static_cast(std::lround(static_cast(depthValueAt(i)) * 65535.0)); - Memcpy(dst, &value, sizeof(value)); - break; - } - case GL_UNSIGNED_INT: - case GL_INT: { - const Uint32 value = stencilOnly - ? stencilSrc[i] - : static_cast(static_cast(depthValueAt(i)) * 4294967295.0); - Memcpy(dst, &value, sizeof(value)); - break; - } - case GL_UNSIGNED_BYTE: - case GL_BYTE: { - dst[0] = stencilSrc[i]; - break; - } - case GL_UNSIGNED_INT_24_8: { - const Uint32 depth24 = - static_cast(std::lround(static_cast(depthValueAt(i)) * 16777215.0)) & 0xFFFFFFu; - const Uint32 value = (depth24 << 8) | stencilSrc[i]; - Memcpy(dst, &value, sizeof(value)); - break; - } - case GL_FLOAT_32_UNSIGNED_INT_24_8_REV: { - const Float depthValue = depthValueAt(i); - const Uint32 stencilValue = stencilSrc[i]; - Memcpy(dst, &depthValue, sizeof(depthValue)); - Memcpy(dst + 4, &stencilValue, sizeof(stencilValue)); - break; - } - default: - break; - } - } + const SizeT dstPixelBytes = packed.size() / pixelCount; // Store honoring the client pack state (single slice). #if MOBILEGL_BUILD_DISAGGREGATED diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h index 19172bd1..359fae5c 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h @@ -256,6 +256,16 @@ namespace MobileGL::MG_Backend::DirectVulkan { Uint32 dstMipLevel, Uint32 dstBaseArrayLayer, GLint srcX, GLint srcY, GLint dstX, GLint dstY, GLint width, GLint height, VkImageLayout srcRestoreLayout, VkImageLayout dstRestoreLayout, Bool stencilAspect); + // One depth/stencil copy whose source and destination are the same format and whose region + // is a same-size, same-layer rectangle: the aspect named by `aspect` is staged through a + // device-local buffer so the destination's other aspect cannot be overwritten by the + // packed native word a whole-image copy moves. + Bool CopyDepthStencilAspectThroughBuffer(FrameContext::FrameData& frame, VkImage srcImage, + VkImage dstImage, VkFormat format, Uint32 srcMipLevel, + Uint32 srcBaseArrayLayer, Uint32 dstMipLevel, + Uint32 dstBaseArrayLayer, GLint srcX, GLint srcY, GLint dstX, + GLint dstY, GLsizei width, GLsizei height, + VkImageAspectFlagBits aspect); static SizeT GetReadbackTexelSize(VkFormat sourceFormat); // Map a GL bottom-left-origin rectangle into the display-oriented swapchain image. // Quarter-turn surface transforms swap the copy extent's axes. diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireTextureReadback.inc b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireTextureReadback.inc index d4742aa5..44e60a09 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireTextureReadback.inc +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireTextureReadback.inc @@ -1,72 +1,6 @@ // Server-owned texture readback. Included after WireFramebuffer.inc so image // layout tracking and frame retirement are the same as every other wire verb. #if MOBILEGL_BUILD_DISAGGREGATED - static Bool PackWireDepthStencilTight(const Uint8* depth, const Uint8* stencil, - VkFormat sourceFormat, SizeT count, GLenum format, GLenum type, Vector& result) { - const Bool stencilOnly = format == GL_STENCIL_INDEX; - const auto readDepth = [&](SizeT i) -> Float { - if (sourceFormat == VK_FORMAT_D16_UNORM) { - Uint16 word{}; Memcpy(&word, depth + i * 2, sizeof(word)); - return static_cast(word) / 65535.0f; - } - if (sourceFormat == VK_FORMAT_X8_D24_UNORM_PACK32 || sourceFormat == VK_FORMAT_D24_UNORM_S8_UINT) { - Uint32 word{}; Memcpy(&word, depth + i * 4, sizeof(word)); - return static_cast(word & 0xffffffu) / 16777215.0f; - } - Float value{}; Memcpy(&value, depth + i * sizeof(value), sizeof(value)); - return value; - }; - SizeT size = 0; - switch (type) { - case GL_BYTE: case GL_UNSIGNED_BYTE: size = 1; break; - case GL_SHORT: case GL_UNSIGNED_SHORT: case GL_HALF_FLOAT: size = 2; break; - case GL_INT: case GL_UNSIGNED_INT: case GL_FLOAT: size = 4; break; - case GL_UNSIGNED_INT_24_8: size = 4; break; - case GL_FLOAT_32_UNSIGNED_INT_24_8_REV: size = 8; break; - default: return false; - } - const Bool packed = type == GL_UNSIGNED_INT_24_8 || type == GL_FLOAT_32_UNSIGNED_INT_24_8_REV; - if ((format == GL_DEPTH_STENCIL) != packed) return false; - result.resize(count * size); - for (SizeT i = 0; i < count; ++i) { - Uint8* dst = result.data() + i * size; - const Uint32 s = stencil ? stencil[i] : 0; - const Float d = stencilOnly ? 0.0f : readDepth(i); - switch (type) { - case GL_FLOAT: { - const Float value = stencilOnly ? static_cast(s) : d; - Memcpy(dst, &value, sizeof(value)); break; - } - case GL_HALF_FLOAT: { - const Uint16 value = MG_Util::EncodeFloatToHalfBits(stencilOnly ? static_cast(s) : d); - Memcpy(dst, &value, sizeof(value)); break; - } - case GL_UNSIGNED_INT_24_8: { - Uint32 depth24 = 0; - if (sourceFormat == VK_FORMAT_D24_UNORM_S8_UINT) { - Memcpy(&depth24, depth + i * 4, sizeof(depth24)); - depth24 &= 0xffffffu; - } else { - depth24 = static_cast(std::llround(std::clamp(d, 0.0, 1.0) * 16777215.0)); - } - const Uint32 value = (depth24 << 8) | s; - Memcpy(dst, &value, sizeof(value)); break; - } - case GL_FLOAT_32_UNSIGNED_INT_24_8_REV: - Memcpy(dst, &d, sizeof(d)); Memcpy(dst + 4, &s, sizeof(s)); break; - default: { - const Bool signedType = type == GL_BYTE || type == GL_SHORT || type == GL_INT; - const Uint64 maximum = signedType ? ((Uint64{1} << (size * 8 - 1)) - 1) - : ((Uint64{1} << (size * 8)) - 1); - const Uint32 value = stencilOnly ? s : static_cast( - std::llround(std::clamp(d, 0.0, 1.0) * static_cast(maximum))); - Memcpy(dst, &value, size); break; - } - } - } - return true; - } - static Bool PackWireColorTight(const Uint8* source, SizeT sourceBytes, VkFormat format, TextureInternalFormat logicalFormat, const MG_Pipe::MGPReadbackInfo& info, Vector& result) { @@ -240,7 +174,7 @@ if (!mapped || !readback.Invalidate(copyBytes)) return false; Vector result; if (depthStencil) { - if (!PackWireDepthStencilTight(depth ? mapped : nullptr, stencil ? mapped + stencilOffset : nullptr, + if (!PackDepthStencilTight(depth ? mapped : nullptr, stencil ? mapped + stencilOffset : nullptr, resource->format, pixels, info.Format, info.Type, result)) return false; } else { using namespace DirectGLES::ReadbackImpl;