mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-09 04:38:30 +09:00
[Fix, Test] (DirectGLES, TextureUtil): return RGB9_E5 glGetTexImage from the stored words
This commit is contained in:
@@ -5805,6 +5805,92 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return endpoint.Texture ? endpoint.Texture->GetFormat() : TextureInternalFormat::Unknown;
|
||||
}
|
||||
|
||||
// Whether this endpoint's CPU shadow can be addressed texel-exactly by the mirror below: one
|
||||
// upload target (so not a cube map, whose six chains the z axis selects between) and layers on
|
||||
// the z axis (GL_TEXTURE_1D_ARRAY carries them on y).
|
||||
static Bool CanMirrorCopyImageShadow(const SharedPtr<MG_State::GLState::ITextureObject>& texture) {
|
||||
if (!texture) return false;
|
||||
if (texture->GetTarget() == TextureTarget::Texture1DArray) return false;
|
||||
return texture->GetUploadTargets().size() == 1;
|
||||
}
|
||||
|
||||
// glCopyImageSubData is defined as a raw texel-block move, so for a destination whose CPU
|
||||
// shadow has to stay authoritative - a packed format with redundant encodings, where a GPU
|
||||
// readback can only answer with RE-ENCODED words (see the verbatim branch in GetTexImage) -
|
||||
// the same move is replayed on the shadow. Nothing is marked dirty: the driver copy already
|
||||
// put these texels on the GPU, and flagging the level would only schedule a redundant upload
|
||||
// back over them.
|
||||
//
|
||||
// Declined, leaving the shadow exactly as it was, for every shape whose bytes this cannot
|
||||
// address exactly - a renderbuffer (no shadow at all), a cube or 1D-array endpoint, a level
|
||||
// whose shadow is missing or not a plain texel grid, a region outside either level, or a
|
||||
// self-copy within one level, where the row copies could overlap.
|
||||
static void MirrorCopyImageIntoDestinationShadow(const CopyImageEndpoint& srcEndpoint, GLint srcLevel, GLint srcX,
|
||||
GLint srcY, GLint srcZ, const CopyImageEndpoint& dstEndpoint,
|
||||
GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ,
|
||||
GLsizei width, GLsizei height, GLsizei depth) {
|
||||
if (!CanMirrorCopyImageShadow(srcEndpoint.Texture) || !CanMirrorCopyImageShadow(dstEndpoint.Texture)) return;
|
||||
if (srcEndpoint.Texture == dstEndpoint.Texture && srcLevel == dstLevel) return;
|
||||
if (width <= 0 || height <= 0 || depth <= 0) return;
|
||||
if (srcLevel < 0 || dstLevel < 0 || srcX < 0 || srcY < 0 || srcZ < 0 || dstX < 0 || dstY < 0 || dstZ < 0) {
|
||||
return;
|
||||
}
|
||||
auto* srcMipmap = MG_State::GLState::AsMipmapTexture(srcEndpoint.Texture.get());
|
||||
auto* dstMipmap = MG_State::GLState::AsMipmapTexture(dstEndpoint.Texture.get());
|
||||
if (!srcMipmap || !dstMipmap) return;
|
||||
|
||||
const auto srcUploadTarget = srcEndpoint.Texture->GetUploadTargets()[0];
|
||||
const auto dstUploadTarget = dstEndpoint.Texture->GetUploadTargets()[0];
|
||||
const IntVec3 srcSize = srcMipmap->GetMipmapTexelSize(srcUploadTarget, static_cast<Uint>(srcLevel));
|
||||
const IntVec3 dstSize = dstMipmap->GetMipmapTexelSize(dstUploadTarget, static_cast<Uint>(dstLevel));
|
||||
const SizeT srcSlices = static_cast<SizeT>(std::max(srcSize.z(), 1));
|
||||
const SizeT dstSlices = static_cast<SizeT>(std::max(dstSize.z(), 1));
|
||||
if (srcSize.x() <= 0 || srcSize.y() <= 0 || dstSize.x() <= 0 || dstSize.y() <= 0) return;
|
||||
const SizeT srcTexels = static_cast<SizeT>(srcSize.x()) * static_cast<SizeT>(srcSize.y()) * srcSlices;
|
||||
const SizeT dstTexels = static_cast<SizeT>(dstSize.x()) * static_cast<SizeT>(dstSize.y()) * dstSlices;
|
||||
const SizeT srcBytes = srcMipmap->GetMipmapByteSize(srcUploadTarget, static_cast<Uint>(srcLevel));
|
||||
const SizeT dstBytes = dstMipmap->GetMipmapByteSize(dstUploadTarget, static_cast<Uint>(dstLevel));
|
||||
// A shadow that is not exactly texels x texelSize bytes is one this cannot index (a
|
||||
// compressed blob, or a level whose allocation disagrees with its recorded extent).
|
||||
const SizeT texelBytes = srcTexels == 0 ? 0 : srcBytes / srcTexels;
|
||||
if (texelBytes == 0 || srcBytes != srcTexels * texelBytes || dstTexels == 0 ||
|
||||
dstBytes != dstTexels * texelBytes) {
|
||||
return;
|
||||
}
|
||||
if (static_cast<SizeT>(srcX) + width > static_cast<SizeT>(srcSize.x()) ||
|
||||
static_cast<SizeT>(srcY) + height > static_cast<SizeT>(srcSize.y()) ||
|
||||
static_cast<SizeT>(srcZ) + depth > srcSlices ||
|
||||
static_cast<SizeT>(dstX) + width > static_cast<SizeT>(dstSize.x()) ||
|
||||
static_cast<SizeT>(dstY) + height > static_cast<SizeT>(dstSize.y()) ||
|
||||
static_cast<SizeT>(dstZ) + depth > dstSlices) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto* srcBase = static_cast<const Uint8*>(
|
||||
srcMipmap->MapMipmapData(srcUploadTarget, static_cast<Uint>(srcLevel)));
|
||||
auto* dstBase = static_cast<Uint8*>(dstMipmap->MapMipmapData(dstUploadTarget, static_cast<Uint>(dstLevel)));
|
||||
if (!srcBase || !dstBase) return;
|
||||
|
||||
const SizeT rowBytes = static_cast<SizeT>(width) * texelBytes;
|
||||
for (GLsizei slice = 0; slice < depth; ++slice) {
|
||||
for (GLsizei row = 0; row < height; ++row) {
|
||||
const SizeT srcOffset = ((static_cast<SizeT>(srcZ + slice) * static_cast<SizeT>(srcSize.y()) +
|
||||
static_cast<SizeT>(srcY + row)) *
|
||||
static_cast<SizeT>(srcSize.x()) +
|
||||
static_cast<SizeT>(srcX)) *
|
||||
texelBytes;
|
||||
const SizeT dstOffset = ((static_cast<SizeT>(dstZ + slice) * static_cast<SizeT>(dstSize.y()) +
|
||||
static_cast<SizeT>(dstY + row)) *
|
||||
static_cast<SizeT>(dstSize.x()) +
|
||||
static_cast<SizeT>(dstX)) *
|
||||
texelBytes;
|
||||
Memcpy(dstBase + dstOffset, srcBase + srcOffset, rowBytes);
|
||||
}
|
||||
}
|
||||
MGLOG_D("CopyImageSubData: mirrored %dx%dx%d texels into the destination's CPU shadow", width, height,
|
||||
depth);
|
||||
}
|
||||
|
||||
void CopyImageSubData(const CopyImageEndpoint& srcEndpoint,
|
||||
GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ,
|
||||
const CopyImageEndpoint& dstEndpoint,
|
||||
@@ -5895,6 +5981,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
MG_Util::ConvertGLEnumToString(dst.target).c_str(),
|
||||
MG_Util::ConvertGLEnumToString(dstTarget).c_str());
|
||||
MOBILEGL_ASSERT(false, "glCopyImageSubData failed after frontend validation accepted the request.");
|
||||
return;
|
||||
}
|
||||
// The copy landed on the GPU. For a destination whose readback cannot be bit-exact the
|
||||
// CPU shadow is what glGetTexImage answers from, so it has to follow the same move -
|
||||
// otherwise it hands back whatever the level held before this copy.
|
||||
if (MG_Util::PixelStoreProcessor::HasRedundantPackedEncoding(dstFormat)) {
|
||||
MirrorCopyImageIntoDestinationShadow(srcEndpoint, srcLevel, srcX, srcY, srcZ, dstEndpoint, dstLevel,
|
||||
dstX, dstY, dstZ, srcWidth, srcHeight, srcDepth);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7818,6 +7912,30 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
backendAttachTarget == GL_TEXTURE_CUBE_MAP_ARRAY;
|
||||
const GLsizei sliceCount = std::max(size.z(), 1);
|
||||
const Bool multiSlice = size.z() > 1;
|
||||
// glGetTexImage answers with the STORED texels, and for a packed format whose encoding
|
||||
// is not unique the GPU route below cannot: it reads GL_RGBA/GL_FLOAT and re-encodes,
|
||||
// which canonicalizes an RGB9_E5 shared exponent (0xf8fc0000 -> 0xe7e00000 - the same
|
||||
// value 8064, different words), and the conformance suite compares the words
|
||||
// ("CopyImageSubData modified contents of source image"). The scratch FBO does NOT
|
||||
// decide this for us: Adreno reports an RGB9_E5 colour attachment complete, so the
|
||||
// shadow branch further down was unreachable. Serve the verbatim-word pairs from the
|
||||
// shadow first and keep the GPU attempts as the fallback for a level the shadow never
|
||||
// received. Every other format still prefers the GPU, so a rendered-into texture is
|
||||
// unaffected; RGB9_E5 is not colour-renderable, so its shadow stays authoritative -
|
||||
// and the one path that GPU-writes it, CopyImageSubData, mirrors itself into the
|
||||
// shadow for exactly this reason.
|
||||
const Bool verbatimPackedShadowRead =
|
||||
MG_Util::PixelStoreProcessor::HasRedundantPackedEncoding(textureObject->GetFormat()) &&
|
||||
MG_Util::PixelStoreProcessor::IsRawPackedPixelTransfer(
|
||||
textureObject->GetFormat(), MG_Util::ConvertGLEnumToTextureInputFormat(format),
|
||||
MG_Util::ConvertGLEnumToTexturePixelDataType(type));
|
||||
if (verbatimPackedShadowRead &&
|
||||
GetTexImageViaShadowConversion(textureMipmapObject,
|
||||
MG_Util::ConvertGLEnumToTextureUploadTarget(target), level, size.x(),
|
||||
size.y(), sliceCount, format, type, pixels, applyPackImageParams)) {
|
||||
MGLOG_D("GetTexImage: finished via shadow conversion (verbatim packed words)");
|
||||
return;
|
||||
}
|
||||
// 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
|
||||
|
||||
Reference in New Issue
Block a user