mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[Magma] preserve packed texture storage and read unbacked sparse levels from owned bytes
This commit is contained in:
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user