[Fix] (MG_Backend): read a multi-slice glGetTexImage off the GPU instead of the CPU shadow

DirectGLES served every multi-slice glGetTexImage from the CPU shadow copy, on
the grounds that its scratch FBO can only expose one layer at a time. But the
shadow only holds what was uploaded, so any slice that was rendered to rather
than written by glTexSubImage came back stale - and a layered framebuffer
produces exactly that.

The scratch FBO can expose one layer at a time repeatedly. The read now attaches
each layer in turn and takes the slice off the GPU, walking the destination over
GL_PACK_SKIP_IMAGES / GL_PACK_IMAGE_HEIGHT itself so each per-slice call packs a
plain 2D image with the same layout StoreWideRowsToClient computes for the whole
stack. The shadow stays as the fallback for the formats a colour attachment
cannot represent at all, and for any slice whose attachment comes back
incomplete.

Takes all 27 remaining direct_state_access.textures_storage_multisample_3d_*
cases from failing to passing on Espryt - they render into a
TEXTURE_2D_MULTISAMPLE_ARRAY one layer per colour attachment and then read the
whole array back. DirectVulkan is untouched.
This commit is contained in:
BZLZHH
2026-08-05 03:55:30 -04:00
parent 088f263495
commit e80a23eae6
+50 -2
View File
@@ -5345,10 +5345,58 @@ namespace MobileGL::MG_Backend::DirectGLES {
const Bool applyPackImageParams = backendAttachTarget == GL_TEXTURE_3D ||
backendAttachTarget == GL_TEXTURE_2D_ARRAY ||
backendAttachTarget == GL_TEXTURE_CUBE_MAP_ARRAY;
// 3D/array images read back every slice, but the FBO path can only read one layer:
// multi-slice reads are served from the CPU shadow (slice-major, tight layout).
const GLsizei sliceCount = std::max(size.z(), 1);
const Bool multiSlice = size.z() > 1;
// A multi-slice read used to go to the CPU shadow outright, on the grounds that the
// scratch FBO can only expose one layer at a time. But the shadow only holds what was
// uploaded, so every slice that was rendered to came back stale - which is exactly what
// a layered framebuffer produces, and what textures_storage_multisample_3d_* checks.
// Attach the layers one at a time instead and read each off the GPU, keeping the shadow
// for the formats the FBO cannot represent at all.
if (multiSlice && tempFBOComplete &&
(backendAttachTarget == GL_TEXTURE_3D || backendAttachTarget == GL_TEXTURE_2D_ARRAY)) {
// Each slice is packed as its own 2D image, so the per-slice call must not apply
// GL_PACK_SKIP_IMAGES / GL_PACK_IMAGE_HEIGHT itself - this walks the destination
// over them, using the same layout StoreWideRowsToClient computes.
const auto packParams = MG_State::pGLContext->GetPixelStoreParameters(false);
const SizeT dstPixelBytes = GetReadbackDstPixelSize(conversionMapping, type);
const SizeT rowPixels =
static_cast<SizeT>(packParams.RowLength > 0 ? packParams.RowLength : size.x());
const SizeT alignment =
packParams.Alignment > 0 ? static_cast<SizeT>(packParams.Alignment) : SizeT{1};
const SizeT dstRowStride = (rowPixels * dstPixelBytes + alignment - 1) / alignment * alignment;
const SizeT imageRows = applyPackImageParams && packParams.ImageHeight > 0
? static_cast<SizeT>(packParams.ImageHeight)
: static_cast<SizeT>(size.y());
const SizeT dstImageStride = imageRows * dstRowStride;
const SizeT skipImages =
applyPackImageParams ? static_cast<SizeT>(std::max(packParams.SkipImages, 0)) : SizeT{0};
Bool allSlicesRead = true;
for (GLsizei slice = 0; slice < sliceCount; ++slice) {
ScratchFBOImpl::EnsureColorAttachmentLayer(tempFB, GL_READ_FRAMEBUFFER, backendTexId, level,
slice);
if (g_GLESFuncs.glCheckFramebufferStatus(GL_READ_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
allSlicesRead = false;
break;
}
const SizeT sliceOffset = (skipImages + static_cast<SizeT>(slice)) * dstImageStride;
void* sliceDst = static_cast<Uint8*>(pixels) + sliceOffset;
if (!ReadPixelsViaFormatConversion(0, 0, size.x(), size.y(), format, type, sliceDst,
/*honorPackImageParams=*/false,
/*applyFixedPointReadClamp=*/false)) {
allSlicesRead = false;
break;
}
}
// Leave the scratch FBO on layer 0 so the single-slice paths below see what they
// set up.
ScratchFBOImpl::EnsureColorAttachmentLayer(tempFB, GL_READ_FRAMEBUFFER, backendTexId, level, 0);
if (allSlicesRead) {
MGLOG_D("GetTexImage: finished %d slices via per-layer readback", sliceCount);
return;
}
}
if (multiSlice &&
GetTexImageViaShadowConversion(textureMipmapObject,
MG_Util::ConvertGLEnumToTextureUploadTarget(target), level, size.x(),