[Magma] preserve packed texture storage and read unbacked sparse levels from owned bytes

This commit is contained in:
2026-09-20 12:48:34 -04:00
parent 40c5443fc0
commit 616f3c90d2
3 changed files with 114 additions and 7 deletions
@@ -1992,6 +1992,46 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return true;
}
Bool VkTextureManager::ReadUnbackedWireLevel(MG_Pipe::MGPipeHandle handle, TextureUploadTarget target,
Uint32 level, const IntVec3& extent, VkFormat& format,
Vector<Uint8>& bytes) {
const auto& records = MG_Pipe::MGPipeApplier().TextureResources;
if (!handle.Slot || handle.Slot >= records.size()) return false;
const auto& record = records[handle.Slot];
if (!record.Live || record.Gen != handle.Gen || !MG_Pipe::MGPipeHandleIsNull(record.Desc.ViewOf)) return false;
auto& store = MG_Remote::Server::ServerStagedTexture();
const Uint64 key = MG_Remote::Server::StagedTextureStore::KeyForHandle(handle);
const auto upload = static_cast<Uint16>(target);
const auto native = m_wireTextureResources.find(key);
if (native != m_wireTextureResources.end() && native->second.image && level < native->second.mipLevels) {
const auto& image = native->second;
const IntVec3 nativeExtent = ToVulkanLevelExtent(WireTextureTargetOfPipeTarget(record.Desc.Target), extent);
const Bool volume = image.viewType == VK_IMAGE_VIEW_TYPE_3D;
const Uint64 firstLayer = ResolveUploadArrayLayer(target);
if (nativeExtent.x() == static_cast<Int>(std::max(image.extent.width >> level, 1u)) &&
nativeExtent.y() == static_cast<Int>(std::max(image.extent.height >> level, 1u)) &&
(volume ? nativeExtent.z() == static_cast<Int>(std::max(image.depth >> level, 1u))
: firstLayer + static_cast<Uint64>(nativeExtent.z()) <= image.arrayLayers))
return false; // a failed sync cannot substitute CPU bytes for this native image
}
if (!store.IsCovered(key, upload, level) || store.IsLevelGpuDirty(key, upload, level) ||
store.LevelExtentOrUndefined(key, upload, level) != extent) return false;
const auto logical = static_cast<TextureInternalFormat>(record.Desc.InternalFormat);
const auto formatInfo = ResolveTextureFormatInfo(logical);
format = formatInfo.format;
if (format == VK_FORMAT_UNDEFINED) return false;
const void* source = store.RequireLevelBytes(key, upload, level, "unbacked texture level readback");
const void* converted = nullptr;
SizeT convertedSize = 0;
Vector<Uint8> owned;
if (!ConvertWireLevelBytes(source, store.LevelByteSize(key, upload, level),
ToVulkanLevelExtent(WireTextureTargetOfPipeTarget(record.Desc.Target), extent), format,
GetAspectMaskForFormat(format), logical, formatInfo, owned, converted, convertedSize)) return false;
const auto* begin = static_cast<const Uint8*>(converted);
bytes.assign(begin, begin + convertedSize);
return true;
}
// D-D5's consume, Magma-local: the level's entry leaves the applier's pending set ONLY
// here, where its bytes were actually staged. (Espryt's twin is Managers.cpp's
// ConsumePipeTextureUpload; the decode rule is the shared one.)
@@ -2100,7 +2140,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkImageUsageFlags desiredUsage =
VK_IMAGE_USAGE_SAMPLED_BIT | (supportsStorageImage ? VK_IMAGE_USAGE_STORAGE_BIT : 0) |
((aspect & VK_IMAGE_ASPECT_COLOR_BIT) ? VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT : 0) |
((aspect & VK_IMAGE_ASPECT_COLOR_BIT) &&
(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT)
? VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT : 0) |
(((aspect & VK_IMAGE_ASPECT_DEPTH_BIT) || (aspect & VK_IMAGE_ASPECT_STENCIL_BIT))
? VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT
: 0);
@@ -2328,7 +2370,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
MGLOG_E_ONCE("Magma wire texture {slot=%u, gen=%u}: pending upload for level %u exceeds the "
"image's %u levels; left pending",
handle.Slot, handle.Gen, static_cast<Uint32>(level), resource.mipLevels);
incomplete = true;
continue;
}
const IntVec3 glExtent = store.LevelExtentOrUndefined(key, uploadTarget, level);
@@ -2341,6 +2382,16 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// Vulkan geometry, like the image this stages into: a 1D array's layers move out of
// the shadow's height into z.
const IntVec3 texelSize = ToVulkanLevelExtent(target, glExtent);
// GL permits independently defined mutable mip images. A Vulkan
// image can only hold the canonical halving chain. Keep an unusual
// level in owned staging instead of copying outside a VkImage mip.
const Bool volume = resource.viewType == VK_IMAGE_VIEW_TYPE_3D;
if (texelSize.x() != static_cast<Int>(std::max(resource.extent.width >> level, 1u)) ||
texelSize.y() != static_cast<Int>(std::max(resource.extent.height >> level, 1u)) ||
(volume && texelSize.z() != static_cast<Int>(std::max(resource.depth >> level, 1u))) ||
(!volume && Uint64(ResolveUploadArrayLayer(static_cast<TextureUploadTarget>(uploadTarget))) +
static_cast<Uint64>(texelSize.z()) > resource.arrayLayers))
continue;
if (texelSize.x() <= 0 || texelSize.y() <= 0 || byteSize == 0) {
incomplete = true;
continue;
@@ -559,6 +559,8 @@ public:
// a NAMESPACE THE FRONTEND-KEYED m_textureResources NEVER MEETS, so a recycled {slot, gen}
// can never inherit its predecessor's image. Texture views resolve the storage owner from
// Desc.ViewOf and the window from ViewCso; buffer-backed textures remain P7's buffer arm.
Bool ReadUnbackedWireLevel(MG_Pipe::MGPipeHandle handle, TextureUploadTarget target, Uint32 level,
const IntVec3& extent, VkFormat& format, Vector<Uint8>& bytes);
TextureResource* SyncTextureResourceByHandle(MG_Pipe::MGPipeHandle handle, Bool renderbuffer = false,
Bool requireStorage = false);
// Map a texture/view-relative subresource to its live storage owner. layerCount, when
@@ -67,6 +67,47 @@
return true;
}
static Bool PackWireColorTight(const Uint8* source, SizeT sourceBytes, VkFormat format,
TextureInternalFormat logicalFormat, const MG_Pipe::MGPReadbackInfo& info,
Vector<Uint8>& result) {
using namespace DirectGLES::ReadbackImpl;
ReadbackChannelMapping mapping{};
if (!GetReadbackChannelMapping(info.Format, mapping)) return false;
const SizeT sourcePixelSize = VulkanRenderer::GetReadbackTexelSize(format);
const SizeT pixelBytes = GetReadbackDstPixelSize(mapping, info.Type);
const Uint64 rows64 = Uint64(info.Box.H) * info.Box.D;
if (!info.Box.W || !rows64 || rows64 > std::numeric_limits<GLsizei>::max() ||
rows64 > std::numeric_limits<SizeT>::max() / 16 / info.Box.W) return false;
const SizeT pixels = static_cast<SizeT>(info.Box.W) * static_cast<SizeT>(rows64);
if (!sourcePixelSize || !pixelBytes || sourceBytes != pixels * sourcePixelSize ||
info.DstSize != pixels * pixelBytes) return false;
if (MG_Util::PixelStoreProcessor::IsRawPackedPixelTransfer(GetPackedReadbackInternalFormat(format),
MG_Util::ConvertGLEnumToTextureInputFormat(info.Format),
MG_Util::ConvertGLEnumToTexturePixelDataType(info.Type))) {
if (pixelBytes != sourcePixelSize) return false;
result.assign(source, source + sourceBytes);
return true;
}
Vector<Uint8> wide;
GLenum wideType = GL_NONE;
const GLsizei rows = static_cast<GLsizei>(Uint64(info.Box.H) * info.Box.D);
if (!DecodeReadbackRowsToWide(source, format, info.Box.W, rows, wide, wideType) ||
mapping.isInteger != (wideType == GL_INT || wideType == GL_UNSIGNED_INT)) return false;
if (ResolveTextureFormatInfo(logicalFormat).expandRgbToRgba) {
const Float floatOne = 1.0f;
const Uint32 integerOne = 1;
const void* alpha = wideType == GL_FLOAT ? static_cast<const void*>(&floatOne) : &integerOne;
for (SizeT i = 0; i < pixels; ++i)
Memcpy(wide.data() + (i * 4 + 3) * sizeof(Uint32), alpha, sizeof(Uint32));
}
result.resize(info.DstSize);
for (GLsizei row = 0; row < rows; ++row)
ConvertWideReadbackRow(wide.data() + row * static_cast<SizeT>(info.Box.W) * 16,
result.data() + row * static_cast<SizeT>(info.Box.W) * pixelBytes,
info.Box.W, wideType, mapping, info.Type);
return true;
}
Bool VulkanRenderer::ReadTextureImageWire(const MG_Pipe::MGPReadbackInfo& info,
Vector<Uint8>& ownedBytes) {
ownedBytes.clear();
@@ -112,18 +153,33 @@
VkFormat viewFormat = VK_FORMAT_UNDEFINED;
const auto storage = m_textureManager->ResolveWireTextureStorage(info.Res, level, layer, &viewFormat, &availableLayers);
if (MG_Pipe::MGPipeHandleIsNull(storage)) return false;
const Uint64 pixels64 = Uint64(box.W) * box.H * box.D;
if (pixels64 > std::numeric_limits<SizeT>::max() / 16) return false;
const auto readUnbacked = [&]() {
// Only a level the VkImage cannot represent may use its owned,
// non-GPU-dirty staging bytes. A failed GPU read never falls back.
if (storage != info.Res || info.Target == GL_TEXTURE_CUBE_MAP) return false;
Vector<Uint8> raw;
VkFormat nativeFormat = VK_FORMAT_UNDEFINED;
if (!m_textureManager->ReadUnbackedWireLevel(storage,
MG_Util::ConvertGLEnumToTextureUploadTarget(info.Target), info.Level,
IntVec3{static_cast<Int>(box.W), static_cast<Int>(box.H), static_cast<Int>(box.D)},
nativeFormat, raw)) return false;
return PackWireColorTight(raw.data(), raw.size(), nativeFormat,
static_cast<TextureInternalFormat>(record.Desc.InternalFormat), info, ownedBytes);
};
RetireWireDrawPass();
MakeXfbWritesVisible();
auto* resource = m_textureManager->SyncTextureResourceByHandle(storage);
if (!resource || !resource->image || level >= resource->mipLevels ||
resource->sampleCount != VK_SAMPLE_COUNT_1_BIT) return false;
if (!resource || !resource->image || level >= resource->mipLevels) return readUnbacked();
if (resource->sampleCount != VK_SAMPLE_COUNT_1_BIT) return false;
m_textureManager->FlushPendingUploads();
const Bool nativeVolume = resource->viewType == VK_IMAGE_VIEW_TYPE_3D;
const Uint32 nativeDepth = nativeVolume ? std::max(resource->depth >> level, 1u) : resource->arrayLayers;
if (extent.width != std::max(resource->extent.width >> level, 1u) ||
extent.height != std::max(resource->extent.height >> level, 1u) ||
layer >= nativeDepth || (nativeVolume ? extent.depth : layerCount) > nativeDepth - layer ||
(!nativeVolume && layerCount > availableLayers)) return false;
(!nativeVolume && layerCount > availableLayers)) return readUnbacked();
// A view can reinterpret the same native words. Decode using the outer
// view format, while Vulkan copies the storage allocation's bytes.
const VkFormat format = viewFormat == VK_FORMAT_UNDEFINED ? resource->format : viewFormat;
@@ -133,8 +189,6 @@
const VkImageAspectFlags aspects = depthStencil ?
((depth ? VK_IMAGE_ASPECT_DEPTH_BIT : 0) | (stencil ? VK_IMAGE_ASPECT_STENCIL_BIT : 0)) : VK_IMAGE_ASPECT_COLOR_BIT;
if ((resource->aspect & aspects) != aspects) return false;
const Uint64 pixels64 = Uint64(box.W) * box.H * box.D;
if (pixels64 > std::numeric_limits<SizeT>::max() / 16) return false;
const SizeT pixels = static_cast<SizeT>(pixels64);
const SizeT destinationPixelSize = MG_Util::GetInputBytesPerPixel(
MG_Util::ConvertGLEnumToTextureInputFormat(info.Format), MG_Util::ConvertGLEnumToTexturePixelDataType(info.Type));