Files
MobileGL/MobileGL/MG_Backend/DirectVulkan/Renderer/WireTextureReadback.inc
T

240 lines
15 KiB
C++

// Server-owned texture readback. Included after WireFramebuffer.inc so image
// layout tracking and frame retirement are the same as every other wire verb.
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();
const auto& box = info.Box;
if (box.X || box.Y || box.Z || !box.W || !box.H || !box.D ||
box.W > static_cast<Uint32>(std::numeric_limits<GLsizei>::max()) ||
Uint64(box.H) * box.D > static_cast<Uint64>(std::numeric_limits<GLsizei>::max())) return false;
const auto& state = MG_Pipe::MGPipeApplier();
if (MG_Pipe::MGPipeHandleIsNull(info.Res) || info.Res.Slot >= state.TextureResources.size()) return false;
const auto& record = state.TextureResources[info.Res.Slot];
if (!record.Live || record.Gen != info.Res.Gen) return false;
using Target = MG_Pipe::MGPipeResourceTarget;
const auto target = static_cast<Target>(record.Desc.Target);
const Bool oneDArray = target == Target::Tex1DArray;
const Bool volume = target == Target::Tex3D;
Uint32 level = info.Level, layer = 0, layerCount = 1, availableLayers = 1;
VkExtent3D extent{box.W, box.H, 1};
if (oneDArray) {
if (info.Target != GL_TEXTURE_1D_ARRAY || box.D != 1) return false;
layerCount = box.H; extent.height = 1;
} else if (volume) {
if (info.Target != GL_TEXTURE_3D) return false;
extent.depth = box.D;
} else if (target == Target::Tex2DArray || target == Target::TexCubeArray) {
if (info.Target != (target == Target::Tex2DArray ? GL_TEXTURE_2D_ARRAY : GL_TEXTURE_CUBE_MAP_ARRAY)) return false;
layerCount = box.D;
} else if (target == Target::TexCube) {
if (info.Target == GL_TEXTURE_CUBE_MAP) {
if (box.D != 6) return false;
layerCount = 6;
} else if (info.Target >= GL_TEXTURE_CUBE_MAP_POSITIVE_X && info.Target <= GL_TEXTURE_CUBE_MAP_NEGATIVE_Z) {
if (box.D != 1) return false;
layer = info.Target - GL_TEXTURE_CUBE_MAP_POSITIVE_X;
} else return false;
} else {
if (box.D != 1) return false;
const GLenum expected = target == Target::Tex1D ? GL_TEXTURE_1D :
target == Target::Tex2D ? GL_TEXTURE_2D :
target == Target::TexRect ? GL_TEXTURE_RECTANGLE : GL_NONE;
if (!expected || info.Target != expected || (target == Target::Tex1D && box.H != 1)) return false;
}
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) 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 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;
const Bool depth = info.Format == GL_DEPTH_COMPONENT || info.Format == GL_DEPTH_STENCIL;
const Bool stencil = info.Format == GL_STENCIL_INDEX || info.Format == GL_DEPTH_STENCIL;
const Bool depthStencil = depth || stencil;
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 SizeT pixels = static_cast<SizeT>(pixels64);
const SizeT destinationPixelSize = MG_Util::GetInputBytesPerPixel(
MG_Util::ConvertGLEnumToTextureInputFormat(info.Format), MG_Util::ConvertGLEnumToTexturePixelDataType(info.Type));
if (!destinationPixelSize || pixels64 * destinationPixelSize != info.DstSize) return false;
SizeT sourcePixelSize = GetReadbackTexelSize(resource->format);
if (depthStencil) {
switch (resource->format) {
case VK_FORMAT_D16_UNORM: sourcePixelSize = 2; break;
case VK_FORMAT_D24_UNORM_S8_UINT: case VK_FORMAT_X8_D24_UNORM_PACK32:
case VK_FORMAT_D32_SFLOAT: case VK_FORMAT_D32_SFLOAT_S8_UINT: sourcePixelSize = 4; break;
case VK_FORMAT_S8_UINT: sourcePixelSize = 0; break;
default: return false;
}
} else if (!sourcePixelSize || sourcePixelSize != GetReadbackTexelSize(format)) return false;
const VkDeviceSize firstBytes = pixels * (depthStencil ? (depth ? sourcePixelSize : 0) : sourcePixelSize);
const VkDeviceSize stencilOffset = (firstBytes + 3) & ~VkDeviceSize{3};
const VkDeviceSize copyBytes = depthStencil ? stencilOffset + (stencil ? pixels : 0) : firstBytes;
if (!copyBytes) return false;
VkBufferObject readback;
if (!readback.Create({.allocator = m_allocator, .size = copyBytes, .usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT,
.memoryUsage = VMA_MEMORY_USAGE_AUTO, .allocationFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT})) return false;
WireImage image{};
image.image = resource->image; image.format = resource->format; image.aspect = resource->aspect;
image.layout = resource->layout; image.trackedLayout = &resource->layout; image.levels = resource->mipLevels;
const auto restore = image.layout == VK_IMAGE_LAYOUT_UNDEFINED ? VK_IMAGE_LAYOUT_GENERAL : image.layout;
auto& frame = m_frameContext.GetCurrent();
VkBufferImageCopy regions[2]{};
Uint32 regionCount = 0;
const auto addCopy = [&](VkImageAspectFlags aspect, VkDeviceSize offset) {
auto& copy = regions[regionCount++];
copy.bufferOffset = offset;
copy.imageSubresource = {aspect, level, nativeVolume ? 0u : layer, nativeVolume ? 1u : layerCount};
copy.imageOffset = {0, 0, nativeVolume ? static_cast<Int32>(layer) : 0};
copy.imageExtent = extent;
};
if (!depthStencil) addCopy(VK_IMAGE_ASPECT_COLOR_BIT, 0);
if (depth) addCopy(VK_IMAGE_ASPECT_DEPTH_BIT, 0);
if (stencil) addCopy(VK_IMAGE_ASPECT_STENCIL_BIT, stencilOffset);
// E5B9G9R9 on a device whose transfer reads canonicalize it: the words leave through a
// shader into a twin (WireE5RawCopy.h), which is what the buffer copy then reads.
VkImage copySource = image.image;
if (!depthStencil && m_textureManager->WireE5RawReadApplies(*resource)) {
TransitionWireImage(image, VK_IMAGE_LAYOUT_GENERAL);
WireE5RawCopyRegion region;
region.source = image.image;
region.volume = nativeVolume;
region.level = level;
region.baseLayer = nativeVolume ? 0u : layer;
region.layerCount = nativeVolume ? 1u : layerCount;
region.offset = regions[0].imageOffset;
region.extent = extent;
auto twin = MakeUnique<VkTextureManager::TextureResource>();
WireE5RawCopyObjects objects;
if (m_textureManager->RecordWireE5RawTwin(frame.commandBuffer, region, VK_IMAGE_LAYOUT_GENERAL, *twin,
objects)) {
copySource = twin->image;
regions[0].imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, region.layerCount};
regions[0].imageOffset = {0, 0, 0};
WireRetiredObjects retired{};
retired.textures.push_back(Move(twin));
retired.imageViews = {objects.sourceView, objects.twinView};
retired.descriptorPools = {objects.descriptorPool};
RetireWireObjects(Move(retired));
}
}
if (copySource == image.image) TransitionWireImage(image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
vkCmdCopyImageToBuffer(frame.commandBuffer, copySource, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
readback.GetHandle(), regionCount, regions);
TransitionWireImage(image, restore);
VkMemoryBarrier host{VK_STRUCTURE_TYPE_MEMORY_BARRIER};
host.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; host.dstAccessMask = VK_ACCESS_HOST_READ_BIT;
vkCmdPipelineBarrier(frame.commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT,
0, 1, &host, 0, nullptr, 0, nullptr);
if (!SubmitReadbackCommandsAndWait(frame)) return false;
const auto* mapped = static_cast<const Uint8*>(readback.Map());
if (!mapped || !readback.Invalidate(copyBytes)) return false;
Vector<Uint8> result;
if (depthStencil) {
if (!PackDepthStencilTight(depth ? mapped : nullptr, stencil ? mapped + stencilOffset : nullptr,
resource->format, pixels, info.Format, info.Type, result)) return false;
} else {
using namespace DirectGLES::ReadbackImpl;
ReadbackChannelMapping mapping{};
if (!GetReadbackChannelMapping(info.Format, mapping) ||
GetReadbackDstPixelSize(mapping, info.Type) != destinationPixelSize) return false;
if (MG_Util::PixelStoreProcessor::IsRawPackedPixelTransfer(GetPackedReadbackInternalFormat(format),
MG_Util::ConvertGLEnumToTextureInputFormat(info.Format),
MG_Util::ConvertGLEnumToTexturePixelDataType(info.Type))) {
if (destinationPixelSize != sourcePixelSize) return false;
result.assign(mapped, mapped + firstBytes);
} else {
Vector<Uint8> wide;
GLenum wideType = GL_NONE;
const GLsizei rows = static_cast<GLsizei>(Uint64(box.H) * box.D);
if (!DecodeReadbackRowsToWide(mapped, format, box.W, rows, wide, wideType) ||
mapping.isInteger != (wideType == GL_INT || wideType == GL_UNSIGNED_INT)) return false;
if (ResolveTextureFormatInfo(static_cast<TextureInternalFormat>(record.Desc.InternalFormat)).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(pixels * destinationPixelSize);
const SizeT srcRow = static_cast<SizeT>(box.W) * 4 * sizeof(Uint32);
const SizeT dstRow = static_cast<SizeT>(box.W) * destinationPixelSize;
for (GLsizei row = 0; row < rows; ++row)
ConvertWideReadbackRow(wide.data() + row * srcRow, result.data() + row * dstRow,
box.W, wideType, mapping, info.Type);
}
}
if (result.size() != info.DstSize) return false;
ownedBytes = Move(result);
return true;
}