[MG_Impl, Espryt] read split texture pixels from server-owned GPU storage

This commit is contained in:
2026-09-20 11:44:00 -04:00
parent 7875e28cd8
commit 29b5e0640e
4 changed files with 188 additions and 2 deletions
@@ -24,6 +24,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// texture synchronization.
void BindCurrentTextures();
#if MOBILEGL_BUILD_DISAGGREGATED
Bool ReadTextureImageWire(const MG_Pipe::MGPReadbackInfo& info, Vector<Uint8>& bytes);
// Native/server-only color level readback. Returns tightly packed, owned
// bytes in the requested pair; never consults a frontend texture or PACK/PBO.
// The caller resolves identity/extent from its resource record and supplies
@@ -88,14 +88,80 @@
return true;
}
static Bool ReadWireDepthStencilLevelTight(GLuint texture, TextureTarget target,
TextureUploadTarget uploadTarget, GLint level,
const IntVec3& extent, GLenum format, GLenum type,
Vector<Uint8>& bytes) {
const SizeT pixelBytes = MG_Util::GetInputBytesPerPixel(
MG_Util::ConvertGLEnumToTextureInputFormat(format), MG_Util::ConvertGLEnumToTexturePixelDataType(type));
if (!pixelBytes) return false;
const Bool needDepth = format == GL_DEPTH_COMPONENT || format == GL_DEPTH_STENCIL;
const Bool needStencil = format == GL_STENCIL_INDEX || format == GL_DEPTH_STENCIL;
ScopedFramebufferBinding bindings(true, false);
GLuint fbo = 0;
g_GLESFuncs.glGenFramebuffers(1, &fbo);
struct DeleteFbo { GLuint id; ~DeleteFbo() { g_GLESFuncs.glDeleteFramebuffers(1, &id); } } cleanup{fbo};
FramebufferImpl::BindFramebufferId(GL_READ_FRAMEBUFFER, fbo);
ScopedPixelPackBuffer packBuffer(0);
ScopedPackState packState(PixelStoreImpl::PackState{1, 0, 0, 0});
const GLenum nativeTarget = TextureImpl::ConvertTextureTargetToBackendGLEnum(target);
const GLenum point = needDepth ? (needStencil ? GL_DEPTH_STENCIL_ATTACHMENT : GL_DEPTH_ATTACHMENT)
: GL_STENCIL_ATTACHMENT;
const SizeT count = static_cast<SizeT>(extent.x()) * extent.y();
Vector<Uint8> result(count * extent.z() * pixelBytes);
if (g_GLESFuncs.glMemoryBarrier) g_GLESFuncs.glMemoryBarrier(GL_ALL_BARRIER_BITS);
g_GLESFuncs.glFinish();
for (GLint z = 0; z < extent.z(); ++z) {
if (nativeTarget == GL_TEXTURE_3D || nativeTarget == GL_TEXTURE_2D_ARRAY ||
nativeTarget == GL_TEXTURE_CUBE_MAP_ARRAY)
g_GLESFuncs.glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, point, texture, level, z);
else
g_GLESFuncs.glFramebufferTexture2D(GL_READ_FRAMEBUFFER, point,
TextureImpl::ConvertTextureUploadTargetToBackendGLEnum(uploadTarget), texture, level);
g_GLESFuncs.glReadBuffer(GL_NONE);
if (g_GLESFuncs.glCheckFramebufferStatus(GL_READ_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) return false;
Vector<Float> depth;
Vector<Uint8> stencil;
if (needDepth && !ReadDepthValuesNative(0, 0, extent.x(), extent.y(), depth)) return false;
if (needStencil && !ReadStencilBytesNative(0, 0, extent.x(), extent.y(), stencil)) return false;
auto* dst = result.data() + count * z * pixelBytes;
for (SizeT i = 0; i < count; ++i) {
if (format == GL_DEPTH_STENCIL) {
const Uint32 s = stencil[i];
if (type == GL_UNSIGNED_INT_24_8) {
const Uint32 packed = (NormalizedDepthToUnsigned(depth[i], 16777215.0) << 8) | s;
Memcpy(dst + i * pixelBytes, &packed, sizeof(packed));
} else if (type == GL_FLOAT_32_UNSIGNED_INT_24_8_REV) {
Memcpy(dst + i * pixelBytes, &depth[i], sizeof(Float));
Memcpy(dst + i * pixelBytes + sizeof(Float), &s, sizeof(s));
} else return false;
} else if (type == GL_FLOAT) {
const Float value = needDepth ? depth[i] : static_cast<Float>(stencil[i]);
Memcpy(dst + i * pixelBytes, &value, sizeof(value));
} else {
const Uint32 value = needDepth ? NormalizedDepthToUnsigned(depth[i],
pixelBytes == 1 ? 255.0 : pixelBytes == 2 ? 65535.0 : 4294967295.0) : stencil[i];
if (pixelBytes == 1) dst[i] = static_cast<Uint8>(value);
else if (pixelBytes == 2) { const Uint16 v = static_cast<Uint16>(value); Memcpy(dst + i * 2, &v, 2); }
else if (pixelBytes == 4) Memcpy(dst + i * 4, &value, 4);
else return false;
}
}
}
bytes = Move(result);
return true;
}
Bool ReadTextureLevelTight(GLuint texture, TextureTarget target, TextureUploadTarget uploadTarget,
TextureInternalFormat logicalFormat, GLint level, const IntVec3& logicalExtent,
Bool sourceUsesImageCarrier, GLenum format, GLenum type, Vector<Uint8>& bytes) {
if (!texture || level < 0 || TextureImpl::IsMultisampleTextureTarget(target)) return false;
const IntVec3 extent = TextureImpl::GetBackendUploadSize(target, logicalExtent);
if (format == GL_DEPTH_COMPONENT || format == GL_DEPTH_STENCIL || format == GL_STENCIL_INDEX)
return ReadWireDepthStencilLevelTight(texture, target, uploadTarget, level, extent, format, type, bytes);
ReadbackChannelMapping mapping{};
if (!GetReadbackChannelMapping(format, mapping)) return false;
const SizeT pixelBytes = GetReadbackDstPixelSize(mapping, type);
const IntVec3 extent = TextureImpl::GetBackendUploadSize(target, logicalExtent);
if (!pixelBytes || extent.x() <= 0 || extent.y() <= 0 || extent.z() <= 0) return false;
const GLenum nativeTarget = TextureImpl::ConvertTextureTargetToBackendGLEnum(target);
const GLenum faceTarget = TextureImpl::ConvertTextureUploadTargetToBackendGLEnum(uploadTarget);
@@ -145,3 +211,31 @@
bytes = Move(result);
return true;
}
Bool ReadTextureImageWire(const MG_Pipe::MGPReadbackInfo& info, Vector<Uint8>& bytes) {
const auto* record = PipeTextureRecordForHandle(info.Res);
if (!record) return false;
TextureTarget target = TextureTarget::Unknown;
for (Int i = 0; i < static_cast<Int>(TextureTarget::TextureTargetCount); ++i)
if (MG_Pipe::MGPipeResourceTargetForTextureTarget(static_cast<TextureTarget>(i)) == record->Desc.Target)
target = static_cast<TextureTarget>(i);
if (target == TextureTarget::Unknown) return false;
const auto twin = TextureImpl::SyncTextureToBackendByHandle(info.Res);
if (!twin) return false;
const Bool wholeCube = info.Target == GL_TEXTURE_CUBE_MAP;
const IntVec3 extent{static_cast<Int>(info.Box.W), static_cast<Int>(info.Box.H),
wholeCube ? 1 : static_cast<Int>(info.Box.D)};
Vector<Uint8> result;
for (Int face = 0; face < (wholeCube ? 6 : 1); ++face) {
const auto uploadTarget = wholeCube
? static_cast<TextureUploadTarget>(static_cast<Int>(TextureUploadTarget::CubeMapPositiveX) + face)
: MG_Util::ConvertGLEnumToTextureUploadTarget(info.Target);
Vector<Uint8> slice;
if (!ReadTextureLevelTight(twin->GetBackendTextureId(), target, uploadTarget,
static_cast<TextureInternalFormat>(record->Desc.InternalFormat), info.Level, extent,
twin->RequiresImageBindableStorage(), info.Format, info.Type, slice)) return false;
result.insert(result.end(), slice.begin(), slice.end());
}
bytes = Move(result);
return true;
}
+22 -1
View File
@@ -6541,6 +6541,14 @@ namespace MobileGL::MG_Impl::GLImpl {
static void GetTextureImageForUploadTarget(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject,
TextureUploadTarget uploadTarget, GLint level, GLenum format,
GLenum type, GLsizei bufSize, void* pixels, const char* caller) {
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
MGP_FILL(GetTextureImage);
MG_Backend::gBackendFunctionsTable.GL.GetTextureImage(textureObject, uploadTarget, level, format, type,
bufSize, pixels);
return;
}
#endif
if (MG_Backend::pActiveBackendObject != nullptr &&
MG_Backend::pActiveBackendObject->GetBackendType() == BackendType::DirectVulkan &&
MGL_BACKEND_SLOT_LOCAL(GetTextureImage)) {
@@ -6560,7 +6568,14 @@ namespace MobileGL::MG_Impl::GLImpl {
__func__)) {
return;
}
GetTextureImageForUploadTarget(textureObject, GetPrimaryUploadTarget(textureObject), level, format, type,
auto uploadTarget = GetPrimaryUploadTarget(textureObject);
#if MOBILEGL_BUILD_DISAGGREGATED
// Unlike glGetTexImage's face target, this DSA call reads all six faces.
if (MG_Config::Transport != MG_Config::TransportMode::Monolith &&
textureObject->GetTarget() == TextureTarget::TextureCubeMap)
uploadTarget = TextureUploadTarget::Unknown;
#endif
GetTextureImageForUploadTarget(textureObject, uploadTarget, level, format, type,
bufSize, pixels, __func__);
}
@@ -6815,6 +6830,12 @@ namespace MobileGL::MG_Impl::GLImpl {
void GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels) {
if (!GetTexImage_State(target, level, format, type, pixels)) return;
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
GetTexImage_Backend(target, level, format, type, pixels);
return;
}
#endif
if (MGL_BACKEND_SLOT_LOCAL(GetTexImage)) {
GetTexImage_Backend(target, level, format, type, pixels);
return;
@@ -0,0 +1,70 @@
// Included in EmitTables.cpp's client-only anonymous namespace.
// The server returns owned tight pixels. Only the client interprets PACK state
// and application pointers; PBO rows are ordinary buffer uploads after the reply.
void EmitGetTextureImage(const SharedPtr<MG_State::GLState::ITextureObject>& texture,
TextureUploadTarget uploadTarget, GLint level, GLenum format,
GLenum type, GLsizei, void* pixels) {
if (!texture || texture->GetStorageType() != TextureStorageType::Mipmap) return;
auto* mipmap = static_cast<MG_State::GLState::TextureObjectMipmap*>(texture.get());
const Bool wholeCube = texture->GetTarget() == TextureTarget::TextureCubeMap &&
uploadTarget == TextureUploadTarget::Unknown;
const auto firstTarget = wholeCube ? TextureUploadTarget::CubeMapPositiveX : uploadTarget;
const IntVec3 size = mipmap->GetMipmapTexelSize(firstTarget, level);
if (size.x() <= 0 || size.y() <= 0 || size.z() <= 0) return;
ClientSession& session = RequireSession("GetTextureImage");
BeforeReadOnlyVerb();
// A by-name texture need not occur in any currently bound sampler.
MG_Pipe::MGPipeTextureEmitterInstance().DrainTextureSubData(*MG_State::pGLContext);
const Uint64 bpp = ReadbackBytesPerPixel(format, type);
const Uint64 depth = wholeCube ? 6 : static_cast<Uint64>(size.z());
const Uint64 tight = static_cast<Uint64>(size.x()) * size.y() * depth * bpp;
MG_Pipe::MGPReadbackInfo info{};
info.Res = MG_Pipe::MGPipeSlots().FindByLifetimeId(MG_Pipe::MGPipeKind::Texture,
texture->GetLifetimeId());
info.Box = {0, 0, 0, static_cast<Uint32>(size.x()), static_cast<Uint32>(size.y()),
static_cast<Uint32>(depth)};
info.Format = format;
info.Type = type;
info.Target = wholeCube ? GL_TEXTURE_CUBE_MAP : MG_Util::ConvertTextureUploadTargetToGLEnum(uploadTarget);
info.Level = static_cast<Uint16>(level);
info.DstSize = tight;
session.RequireReadPixelsReplyFits(info.Box.W, info.Box.H, format, type, tight);
Vector<Uint8> bytes(static_cast<SizeT>(tight));
Int32 status = Wire::ReplySink::kStatusError;
Uint64 received = 0;
session.EmitAndWait(MG_Pipe::MGPWireOp::GetTextureImage, &info, sizeof(info), nullptr, 0,
bytes.data(), tight, &status, &received);
RequireReadbackReplyComplete(status, received, tight);
const auto pack = MG_State::pGLContext->GetPixelStoreParameters(false);
ApplyReadbackByteSwap(bytes.data(), tight, type, pack);
const Uint64 rowBytes = static_cast<Uint64>(size.x()) * bpp;
const Uint64 alignment = std::max<Int>(pack.Alignment, 1);
const Uint64 rowPixels = pack.RowLength > 0 ? pack.RowLength : size.x();
const Uint64 rowStride = (rowPixels * bpp + alignment - 1) / alignment * alignment;
const auto target = texture->GetTarget();
const Bool imagePack = wholeCube || target == TextureTarget::Texture3D ||
target == TextureTarget::Texture2DArray || target == TextureTarget::TextureCubeMapArray;
const Uint64 imageRows = imagePack && pack.ImageHeight > 0 ? pack.ImageHeight : size.y();
const Uint64 imageStride = imageRows * rowStride;
const Uint64 start = static_cast<Uint64>(pack.SkipRows) * rowStride +
static_cast<Uint64>(pack.SkipPixels) * bpp +
(imagePack ? static_cast<Uint64>(pack.SkipImages) * imageStride : 0);
const auto& pbo = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject();
for (Uint64 z = 0; z < depth; ++z) {
for (Int y = 0; y < size.y(); ++y) {
const Uint8* source = bytes.data() + (z * size.y() + y) * rowBytes;
const Uint64 offset = start + z * imageStride + y * rowStride;
if (pbo) pbo->UploadSubData({source, static_cast<SizeT>(rowBytes)},
reinterpret_cast<SizeT>(pixels) + offset);
else if (pixels) Memcpy(static_cast<Uint8*>(pixels) + offset, source, rowBytes);
}
}
}
void EmitGetTexImage(GLenum target, GLint level, GLenum format, GLenum type, void* pixels) {
const auto textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
const auto& unit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
const auto& texture = unit.GetBindingSlot(textureTarget).GetBoundObject();
EmitGetTextureImage(texture, MG_Util::ConvertGLEnumToTextureUploadTarget(target),
level, format, type, -1, pixels);
}