mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[MG_Backend] (DirectGLES/DirectVulkan): the monolith readback and resolve arms get the depth/stencil fixes the wire arm already had
f5234b9etaught the WIRE depth/stencil readback to scale a depth into the signed integer widths' own range and to answer GL_HALF_FLOAT;7b926429taught the WIRE depth/stencil resolve to copy only the requested aspect. Both landings stopped at the wire entry points, so with MOBILEGL_TRANSPORT=monolith the same seven DepthStencilReadbackMatrixScenario cases still failed on the same assertions. DirectGLES: IsSupportedDepthStencilReadPixelsPair turned GL_BYTE/GL_SHORT/GL_INT/ GL_HALF_FLOAT depth and GL_HALF_FLOAT stencil away before the helper that can serve them, and glGetTexImage had no GL_STENCIL_INDEX branch at all, so the read fell through to a native ES glReadPixels the driver refuses and nothing was written. ReadPixelsDepthComponent and ReadPixelsStencilViaNative now answer the signed widths out of their own ranges and encode the half-float widths. DirectVulkan: ReadDepthStencilImageToClient carried its own copy of the packing and had drifted - GL_SHORT was scaled into 65535 (a 0.5 depth came back as 0x8000, i.e. -32768), GL_BYTE/GL_UNSIGNED_BYTE emitted the stencil byte for a DEPTH_COMPONENT read, and GL_HALF_FLOAT was rejected outright. The packing moves into one PackDepthStencilTight that both arms call, so they cannot disagree again. DirectVulkan blit: a single-aspect depth/stencil copy of a packed format went out as a whole-image vkCmdCopyImage, which moves the packed native word and therefore replaced the destination's other aspect with the source's - a depth-only multisample resolve overwrote the destination's stencil. Same-format, same-size, single-layer single-aspect copies now stage that one aspect through a device-local buffer, exactly as ResolveWireDepthStencil does.
This commit is contained in:
@@ -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<SizeT>(row) * static_cast<SizeT>(width);
|
||||
for (GLsizei col = 0; col < width; ++col) {
|
||||
const Float depthValue = srcRow[col];
|
||||
switch (type) {
|
||||
case GL_BYTE:
|
||||
reinterpret_cast<Int8*>(dst)[col] = static_cast<Int8>(
|
||||
NormalizedDepthToUnsigned(depthValue, 127.0));
|
||||
break;
|
||||
case GL_UNSIGNED_BYTE:
|
||||
dst[col] = static_cast<Uint8>(
|
||||
NormalizedDepthToUnsigned(srcRow[col], 255.0));
|
||||
NormalizedDepthToUnsigned(depthValue, 255.0));
|
||||
break;
|
||||
case GL_SHORT:
|
||||
reinterpret_cast<Int16*>(dst)[col] = static_cast<Int16>(
|
||||
NormalizedDepthToUnsigned(depthValue, 32767.0));
|
||||
break;
|
||||
case GL_UNSIGNED_SHORT:
|
||||
reinterpret_cast<Uint16*>(dst)[col] = static_cast<Uint16>(
|
||||
NormalizedDepthToUnsigned(srcRow[col], 65535.0));
|
||||
NormalizedDepthToUnsigned(depthValue, 65535.0));
|
||||
break;
|
||||
case GL_INT:
|
||||
reinterpret_cast<Int32*>(dst)[col] = static_cast<Int32>(
|
||||
NormalizedDepthToUnsigned(depthValue, 2147483647.0));
|
||||
break;
|
||||
case GL_UNSIGNED_INT:
|
||||
reinterpret_cast<Uint32*>(dst)[col] =
|
||||
NormalizedDepthToUnsigned(srcRow[col], 4294967295.0);
|
||||
NormalizedDepthToUnsigned(depthValue, 4294967295.0);
|
||||
break;
|
||||
case GL_HALF_FLOAT:
|
||||
reinterpret_cast<Uint16*>(dst)[col] =
|
||||
MG_Util::EncodeFloatToHalfBits(depthValue);
|
||||
break;
|
||||
default:
|
||||
reinterpret_cast<GLfloat*>(dst)[col] = srcRow[col];
|
||||
reinterpret_cast<GLfloat*>(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<GLfloat*>(dst)[col] = static_cast<GLfloat>(srcRow[col]);
|
||||
break;
|
||||
case GL_HALF_FLOAT:
|
||||
reinterpret_cast<Uint16*>(dst)[col] =
|
||||
MG_Util::EncodeFloatToHalfBits(static_cast<Float>(srcRow[col]));
|
||||
break;
|
||||
default:
|
||||
reinterpret_cast<Uint32*>(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;
|
||||
|
||||
@@ -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<Uint8>& 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<Float>(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<Float>(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<Float>(s) : d;
|
||||
Memcpy(dst, &value, sizeof(value)); break;
|
||||
}
|
||||
case GL_HALF_FLOAT: {
|
||||
const Uint16 value = MG_Util::EncodeFloatToHalfBits(stencilOnly ? static_cast<Float>(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<Uint32>(std::llround(std::clamp<double>(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<Uint32>(
|
||||
std::llround(std::clamp<double>(d, 0.0, 1.0) * static_cast<double>(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<VkDeviceSize>(width) * static_cast<VkDeviceSize>(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<Uint32>(width), static_cast<Uint32>(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<VkImageAspectFlags>(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<Float>(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<Float>(raw & 0xFFFFFFu) / static_cast<Float>(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<Uint8> 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<Uint8> 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<Float>(stencilSrc[i]) : depthValueAt(i);
|
||||
Memcpy(dst, &value, sizeof(value));
|
||||
break;
|
||||
}
|
||||
case GL_UNSIGNED_SHORT:
|
||||
case GL_SHORT: {
|
||||
const Uint16 value = stencilOnly
|
||||
? static_cast<Uint16>(stencilSrc[i])
|
||||
: static_cast<Uint16>(std::lround(static_cast<double>(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<Uint32>(static_cast<double>(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<Uint32>(std::lround(static_cast<double>(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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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<Uint8>& 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<Float>(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<Float>(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<Float>(s) : d;
|
||||
Memcpy(dst, &value, sizeof(value)); break;
|
||||
}
|
||||
case GL_HALF_FLOAT: {
|
||||
const Uint16 value = MG_Util::EncodeFloatToHalfBits(stencilOnly ? static_cast<Float>(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<Uint32>(std::llround(std::clamp<double>(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<Uint32>(
|
||||
std::llround(std::clamp<double>(d, 0.0, 1.0) * static_cast<double>(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<Uint8>& result) {
|
||||
@@ -240,7 +174,7 @@
|
||||
if (!mapped || !readback.Invalidate(copyBytes)) return false;
|
||||
Vector<Uint8> 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;
|
||||
|
||||
Reference in New Issue
Block a user