From 29b5e0640e06e42c59e12e659600c61e176e497d Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Sun, 20 Sep 2026 11:44:00 -0400 Subject: [PATCH] [MG_Impl, Espryt] read split texture pixels from server-owned GPU storage --- MobileGL/MG_Backend/DirectGLES/DirectGLES.h | 1 + .../DirectGLES/WireTextureReadback.inc | 96 ++++++++++++++++++- .../MG_Impl/GLImpl/Texture/GL_Texture.cpp | 23 ++++- .../MG_Remote/Client/TextureReadbackEmit.inc | 70 ++++++++++++++ 4 files changed, 188 insertions(+), 2 deletions(-) create mode 100644 MobileGL/MG_Remote/Client/TextureReadbackEmit.inc diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.h b/MobileGL/MG_Backend/DirectGLES/DirectGLES.h index b4e00fbb..9f67cb79 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.h +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.h @@ -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& 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 diff --git a/MobileGL/MG_Backend/DirectGLES/WireTextureReadback.inc b/MobileGL/MG_Backend/DirectGLES/WireTextureReadback.inc index c12be206..f394cbc5 100644 --- a/MobileGL/MG_Backend/DirectGLES/WireTextureReadback.inc +++ b/MobileGL/MG_Backend/DirectGLES/WireTextureReadback.inc @@ -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& 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(extent.x()) * extent.y(); + Vector 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 depth; + Vector 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(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(value); + else if (pixelBytes == 2) { const Uint16 v = static_cast(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& 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& bytes) { + const auto* record = PipeTextureRecordForHandle(info.Res); + if (!record) return false; + TextureTarget target = TextureTarget::Unknown; + for (Int i = 0; i < static_cast(TextureTarget::TextureTargetCount); ++i) + if (MG_Pipe::MGPipeResourceTargetForTextureTarget(static_cast(i)) == record->Desc.Target) + target = static_cast(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(info.Box.W), static_cast(info.Box.H), + wholeCube ? 1 : static_cast(info.Box.D)}; + Vector result; + for (Int face = 0; face < (wholeCube ? 6 : 1); ++face) { + const auto uploadTarget = wholeCube + ? static_cast(static_cast(TextureUploadTarget::CubeMapPositiveX) + face) + : MG_Util::ConvertGLEnumToTextureUploadTarget(info.Target); + Vector slice; + if (!ReadTextureLevelTight(twin->GetBackendTextureId(), target, uploadTarget, + static_cast(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; + } diff --git a/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp b/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp index b46c8074..7de26bfe 100644 --- a/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp +++ b/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp @@ -6541,6 +6541,14 @@ namespace MobileGL::MG_Impl::GLImpl { static void GetTextureImageForUploadTarget(const SharedPtr& 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; diff --git a/MobileGL/MG_Remote/Client/TextureReadbackEmit.inc b/MobileGL/MG_Remote/Client/TextureReadbackEmit.inc new file mode 100644 index 00000000..7fc55736 --- /dev/null +++ b/MobileGL/MG_Remote/Client/TextureReadbackEmit.inc @@ -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& texture, + TextureUploadTarget uploadTarget, GLint level, GLenum format, + GLenum type, GLsizei, void* pixels) { + if (!texture || texture->GetStorageType() != TextureStorageType::Mipmap) return; + auto* mipmap = static_cast(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(size.z()); + const Uint64 tight = static_cast(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(size.x()), static_cast(size.y()), + static_cast(depth)}; + info.Format = format; + info.Type = type; + info.Target = wholeCube ? GL_TEXTURE_CUBE_MAP : MG_Util::ConvertTextureUploadTargetToGLEnum(uploadTarget); + info.Level = static_cast(level); + info.DstSize = tight; + session.RequireReadPixelsReplyFits(info.Box.W, info.Box.H, format, type, tight); + Vector bytes(static_cast(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(size.x()) * bpp; + const Uint64 alignment = std::max(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(pack.SkipRows) * rowStride + + static_cast(pack.SkipPixels) * bpp + + (imagePack ? static_cast(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(rowBytes)}, + reinterpret_cast(pixels) + offset); + else if (pixels) Memcpy(static_cast(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); + }