[Fix] (DirectVulkan): general (format,type) readback conversion - hoist the CTS-verified StoreWideRowsToClient into shared ReadbackImpl and decode any color VkFormat to wide RGBA rows; readback previously supported only RGB/BGR/RGBA/BGRA x UNSIGNED_BYTE/FLOAT and silently returned zeros for everything else

This commit is contained in:
2026-07-26 13:28:30 -04:00
parent cc3dcfd80e
commit db01bfa3e8
4 changed files with 450 additions and 123 deletions
+2 -86
View File
@@ -3435,90 +3435,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
return componentType != 0 ? static_cast<GLenum>(componentType) : GL_UNSIGNED_NORMALIZED;
}
// Repacks wide RGBA(_INTEGER) rows into the client's (format, type) layout, honoring the
// client-side PACK parameters and the bound pixel-pack buffer. `wide` holds
// `sliceHeight * sliceCount` rows of `width` texels (slice-major, tightly stacked),
// 4 components x GetReadbackComponentSize(wideType) bytes each.
// applyPackImageParams: GL_PACK_IMAGE_HEIGHT / GL_PACK_SKIP_IMAGES apply only to GetTexImage
// of 3D/array images; ReadPixels and 2D GetTexImage ignore them (GL 3.3 sections 4.3.1, 6.1.4).
// Per the GL addressing rules, slice k row j lands at
// SKIP_IMAGES*imageStride + SKIP_ROWS*rowStride + SKIP_PIXELS*pixelBytes
// + k*imageStride + j*rowStride, with imageStride = max(IMAGE_HEIGHT, sliceHeight)*rowStride.
static Bool StoreWideRowsToClient(const Uint8* wide, GLenum wideType, GLsizei width, GLsizei sliceHeight,
GLsizei sliceCount, const ReadbackChannelMapping& mapping, GLenum type,
void* pixels, Bool applyPackImageParams) {
const SizeT dstPixelBytes = GetReadbackDstPixelSize(mapping, type);
if (dstPixelBytes == 0) {
return false;
}
ReadbackImpl::PackedReadbackLayout packedLayout{};
const Bool isPackedType = ReadbackImpl::GetPackedReadbackLayout(type, packedLayout);
const SizeT dstComponentSize = GetReadbackComponentSize(type);
const auto& pixelPackBufferObject =
MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject();
// Destination layout is computed from the client-side PACK parameters; only the actual pixel
// rows are written so skip regions of the destination stay untouched.
const auto packParams = MG_State::pGLContext->GetPixelStoreParameters(false);
const SizeT rowPixels = static_cast<SizeT>(packParams.RowLength > 0 ? packParams.RowLength : width);
const SizeT dstRowStride = AlignPixelRow(rowPixels * dstPixelBytes, packParams.Alignment);
const SizeT imageRows =
applyPackImageParams && packParams.ImageHeight > 0
? static_cast<SizeT>(packParams.ImageHeight)
: static_cast<SizeT>(sliceHeight);
const SizeT dstImageStride = imageRows * dstRowStride;
const SizeT skipImages =
applyPackImageParams ? static_cast<SizeT>(std::max(packParams.SkipImages, 0)) : SizeT{0};
const SizeT dstSkipOffset = skipImages * dstImageStride +
static_cast<SizeT>(std::max(packParams.SkipRows, 0)) * dstRowStride +
static_cast<SizeT>(std::max(packParams.SkipPixels, 0)) * dstPixelBytes;
const SizeT dstRowBytes = static_cast<SizeT>(width) * dstPixelBytes;
const SizeT pboBaseOffset = reinterpret_cast<SizeT>(pixels); // with a PBO, `pixels` is an offset
if (pixelPackBufferObject) {
const SizeT requiredSize = pboBaseOffset + dstSkipOffset +
static_cast<SizeT>(sliceCount - 1) * dstImageStride +
static_cast<SizeT>(sliceHeight - 1) * dstRowStride + dstRowBytes;
if (requiredSize > pixelPackBufferObject->GetSize()) {
MGLOG_E("Readback conversion: pixel pack buffer is too small");
return true;
}
}
const SizeT srcComponentSize = GetReadbackComponentSize(wideType);
const SizeT srcPixelBytes = 4 * srcComponentSize;
Vector<Uint8> convertedRow(dstRowBytes);
for (GLsizei slice = 0; slice < sliceCount; ++slice) {
for (GLsizei row = 0; row < sliceHeight; ++row) {
const SizeT flatRow = static_cast<SizeT>(slice) * static_cast<SizeT>(sliceHeight) +
static_cast<SizeT>(row);
const Uint8* srcRow = wide + flatRow * static_cast<SizeT>(width) * srcPixelBytes;
ReadbackImpl::ConvertWideReadbackRow(srcRow, convertedRow.data(), static_cast<SizeT>(width), wideType,
mapping, type);
if (packParams.SwapBytes) {
const SizeT groupSize = isPackedType ? packedLayout.byteSize : dstComponentSize;
if (groupSize > 1) {
for (SizeT offset = 0; offset + groupSize <= dstRowBytes; offset += groupSize) {
std::reverse(convertedRow.data() + offset, convertedRow.data() + offset + groupSize);
}
}
}
const SizeT dstOffset = dstSkipOffset + static_cast<SizeT>(slice) * dstImageStride +
static_cast<SizeT>(row) * dstRowStride;
if (pixelPackBufferObject) {
pixelPackBufferObject->WritebackFromBackend({convertedRow.data(), dstRowBytes},
pboBaseOffset + dstOffset);
} else {
Memcpy(static_cast<Uint8*>(pixels) + dstOffset, convertedRow.data(), dstRowBytes);
}
}
}
return true;
}
// Reads the current READ framebuffer as wide RGBA(_INTEGER) and repacks the pixels into the client's
// (format, type) layout. Returns false when the combination is not convertible (the caller keeps its
@@ -3651,7 +3567,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
ExpandNarrowWideRead(wide, static_cast<SizeT>(width) * static_cast<SizeT>(height), readChannels, wideType);
}
if (!StoreWideRowsToClient(wide.data(), wideType, width, height, /*sliceCount=*/1, mapping, type, pixels,
if (!ReadbackImpl::StoreWideRowsToClient(wide.data(), wideType, width, height, /*sliceCount=*/1, mapping, type, pixels,
honorPackImageParams)) {
return false;
}
@@ -3705,7 +3621,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
return false;
}
const GLenum wideType = isInteger ? (isSigned ? GL_INT : GL_UNSIGNED_INT) : GL_FLOAT;
if (!StoreWideRowsToClient(wide.data(), wideType, width, sliceHeight, sliceCount, mapping, type, pixels,
if (!ReadbackImpl::StoreWideRowsToClient(wide.data(), wideType, width, sliceHeight, sliceCount, mapping, type, pixels,
applyPackImageParams)) {
return false;
}
+90
View File
@@ -764,5 +764,95 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
}
}
static SizeT AlignReadbackRow(SizeT rowBytes, Int alignment) {
const SizeT align = alignment > 0 ? static_cast<SizeT>(alignment) : 1;
return (rowBytes + align - 1) / align * align;
}
// Repacks wide RGBA(_INTEGER) rows into the client's (format, type) layout, honoring the
// client-side PACK parameters and the bound pixel-pack buffer. `wide` holds
// `sliceHeight * sliceCount` rows of `width` texels (slice-major, tightly stacked),
// 4 components x GetReadbackComponentSize(wideType) bytes each.
// applyPackImageParams: GL_PACK_IMAGE_HEIGHT / GL_PACK_SKIP_IMAGES apply only to GetTexImage
// of 3D/array images; ReadPixels and 2D GetTexImage ignore them (GL 3.3 sections 4.3.1, 6.1.4).
// Per the GL addressing rules, slice k row j lands at
// SKIP_IMAGES*imageStride + SKIP_ROWS*rowStride + SKIP_PIXELS*pixelBytes
// + k*imageStride + j*rowStride, with imageStride = max(IMAGE_HEIGHT, sliceHeight)*rowStride.
Bool StoreWideRowsToClient(const Uint8* wide, GLenum wideType, GLsizei width, GLsizei sliceHeight,
GLsizei sliceCount, const ReadbackChannelMapping& mapping, GLenum type,
void* pixels, Bool applyPackImageParams) {
const SizeT dstPixelBytes = GetReadbackDstPixelSize(mapping, type);
if (dstPixelBytes == 0) {
return false;
}
PackedReadbackLayout packedLayout{};
const Bool isPackedType = GetPackedReadbackLayout(type, packedLayout);
const SizeT dstComponentSize = GetReadbackComponentSize(type);
const auto& pixelPackBufferObject =
MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject();
// Destination layout is computed from the client-side PACK parameters; only the actual pixel
// rows are written so skip regions of the destination stay untouched.
const auto packParams = MG_State::pGLContext->GetPixelStoreParameters(false);
const SizeT rowPixels = static_cast<SizeT>(packParams.RowLength > 0 ? packParams.RowLength : width);
const SizeT dstRowStride = AlignReadbackRow(rowPixels * dstPixelBytes, packParams.Alignment);
const SizeT imageRows =
applyPackImageParams && packParams.ImageHeight > 0
? static_cast<SizeT>(packParams.ImageHeight)
: static_cast<SizeT>(sliceHeight);
const SizeT dstImageStride = imageRows * dstRowStride;
const SizeT skipImages =
applyPackImageParams ? static_cast<SizeT>(std::max(packParams.SkipImages, 0)) : SizeT{0};
const SizeT dstSkipOffset = skipImages * dstImageStride +
static_cast<SizeT>(std::max(packParams.SkipRows, 0)) * dstRowStride +
static_cast<SizeT>(std::max(packParams.SkipPixels, 0)) * dstPixelBytes;
const SizeT dstRowBytes = static_cast<SizeT>(width) * dstPixelBytes;
const SizeT pboBaseOffset = reinterpret_cast<SizeT>(pixels); // with a PBO, `pixels` is an offset
if (pixelPackBufferObject) {
const SizeT requiredSize = pboBaseOffset + dstSkipOffset +
static_cast<SizeT>(sliceCount - 1) * dstImageStride +
static_cast<SizeT>(sliceHeight - 1) * dstRowStride + dstRowBytes;
if (requiredSize > pixelPackBufferObject->GetSize()) {
MGLOG_E("Readback conversion: pixel pack buffer is too small");
return true;
}
}
const SizeT srcComponentSize = GetReadbackComponentSize(wideType);
const SizeT srcPixelBytes = 4 * srcComponentSize;
Vector<Uint8> convertedRow(dstRowBytes);
for (GLsizei slice = 0; slice < sliceCount; ++slice) {
for (GLsizei row = 0; row < sliceHeight; ++row) {
const SizeT flatRow = static_cast<SizeT>(slice) * static_cast<SizeT>(sliceHeight) +
static_cast<SizeT>(row);
const Uint8* srcRow = wide + flatRow * static_cast<SizeT>(width) * srcPixelBytes;
ConvertWideReadbackRow(srcRow, convertedRow.data(), static_cast<SizeT>(width), wideType,
mapping, type);
if (packParams.SwapBytes) {
const SizeT groupSize = isPackedType ? packedLayout.byteSize : dstComponentSize;
if (groupSize > 1) {
for (SizeT offset = 0; offset + groupSize <= dstRowBytes; offset += groupSize) {
std::reverse(convertedRow.data() + offset, convertedRow.data() + offset + groupSize);
}
}
}
const SizeT dstOffset = dstSkipOffset + static_cast<SizeT>(slice) * dstImageStride +
static_cast<SizeT>(row) * dstRowStride;
if (pixelPackBufferObject) {
pixelPackBufferObject->WritebackFromBackend({convertedRow.data(), dstRowBytes},
pboBaseOffset + dstOffset);
} else {
Memcpy(static_cast<Uint8*>(pixels) + dstOffset, convertedRow.data(), dstRowBytes);
}
}
}
return true;
}
} // namespace ReadbackImpl
} // namespace MobileGL::MG_Backend::DirectGLES
+8
View File
@@ -88,6 +88,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
// bytes, dst receives width * GetReadbackDstPixelSize(mapping, type) bytes.
void ConvertWideReadbackRow(const Uint8* src, Uint8* dst, SizeT width, GLenum wideType,
const ReadbackChannelMapping& mapping, GLenum type);
// Stores wide RGBA(_INTEGER) rows into the client pointer or the bound PACK pixel buffer,
// honoring the client-side PACK pixel-store parameters (row length, alignment, skips,
// swap-bytes, and - when applyPackImageParams - image height/skip images). Shared by the
// DirectGLES and DirectVulkan readback conversion paths.
Bool StoreWideRowsToClient(const Uint8* wide, GLenum wideType, GLsizei width, GLsizei sliceHeight,
GLsizei sliceCount, const ReadbackChannelMapping& mapping, GLenum type,
void* pixels, Bool applyPackImageParams);
} // namespace ReadbackImpl
namespace PrgramImpl {
@@ -7,6 +7,8 @@
// End of Source File Header
#include "VulkanRenderer.h"
#include "MG_Backend/DirectGLES/Utils.h"
#include "VertexInputStateFactory.h"
#include "VertexInputStateBuilder.h"
@@ -1881,58 +1883,369 @@ void main() {
}
}
// Generic VkFormat texel decode into the wide RGBA row layouts the shared readback
// store expects: GL_FLOAT rows for normalized/float sources, GL_INT / GL_UNSIGNED_INT
// rows for integer sources. Missing channels take GL defaults (0,0,0,1).
enum class ReadbackSourceClass : Uint8 { Unsupported, Float, SignedInt, UnsignedInt };
struct ReadbackSourceDesc {
ReadbackSourceClass sourceClass = ReadbackSourceClass::Unsupported;
Int channels = 0; // component count stored per texel
Int componentBits = 0; // per-component bits for regular formats; 0 for special packed
Bool isSnorm = false;
Bool isSrgb = false;
Bool bgraSwizzle = false;
VkFormat special = VK_FORMAT_UNDEFINED; // set for packed/special formats
};
static Bool GetReadbackSourceDesc(VkFormat format, ReadbackSourceDesc& out) {
out = ReadbackSourceDesc{};
switch (format) {
// --- regular UNORM ---
case VK_FORMAT_R8_UNORM: out = {ReadbackSourceClass::Float, 1, 8}; return true;
case VK_FORMAT_R8G8_UNORM: out = {ReadbackSourceClass::Float, 2, 8}; return true;
case VK_FORMAT_R8G8B8A8_UNORM: out = {ReadbackSourceClass::Float, 4, 8}; return true;
case VK_FORMAT_B8G8R8A8_UNORM: out = {ReadbackSourceClass::Float, 4, 8, false, false, true}; return true;
case VK_FORMAT_R16_UNORM: out = {ReadbackSourceClass::Float, 1, 16}; return true;
case VK_FORMAT_R16G16_UNORM: out = {ReadbackSourceClass::Float, 2, 16}; return true;
case VK_FORMAT_R16G16B16A16_UNORM: out = {ReadbackSourceClass::Float, 4, 16}; return true;
// --- SRGB (decode to linear like GL readback of sRGB textures) ---
case VK_FORMAT_R8G8B8A8_SRGB: out = {ReadbackSourceClass::Float, 4, 8, false, true}; return true;
case VK_FORMAT_B8G8R8A8_SRGB: out = {ReadbackSourceClass::Float, 4, 8, false, true, true}; return true;
// --- SNORM ---
case VK_FORMAT_R8_SNORM: out = {ReadbackSourceClass::Float, 1, 8, true}; return true;
case VK_FORMAT_R8G8_SNORM: out = {ReadbackSourceClass::Float, 2, 8, true}; return true;
case VK_FORMAT_R8G8B8A8_SNORM: out = {ReadbackSourceClass::Float, 4, 8, true}; return true;
case VK_FORMAT_R16_SNORM: out = {ReadbackSourceClass::Float, 1, 16, true}; return true;
case VK_FORMAT_R16G16_SNORM: out = {ReadbackSourceClass::Float, 2, 16, true}; return true;
case VK_FORMAT_R16G16B16A16_SNORM: out = {ReadbackSourceClass::Float, 4, 16, true}; return true;
// --- SFLOAT ---
case VK_FORMAT_R16_SFLOAT: out = {ReadbackSourceClass::Float, 1, 16}; out.special = format; return true;
case VK_FORMAT_R16G16_SFLOAT: out = {ReadbackSourceClass::Float, 2, 16}; out.special = format; return true;
case VK_FORMAT_R16G16B16A16_SFLOAT: out = {ReadbackSourceClass::Float, 4, 16}; out.special = format; return true;
case VK_FORMAT_R32_SFLOAT: out = {ReadbackSourceClass::Float, 1, 32}; out.special = format; return true;
case VK_FORMAT_R32G32_SFLOAT: out = {ReadbackSourceClass::Float, 2, 32}; out.special = format; return true;
case VK_FORMAT_R32G32B32A32_SFLOAT: out = {ReadbackSourceClass::Float, 4, 32}; out.special = format; return true;
// --- UINT ---
case VK_FORMAT_R8_UINT: out = {ReadbackSourceClass::UnsignedInt, 1, 8}; return true;
case VK_FORMAT_R8G8_UINT: out = {ReadbackSourceClass::UnsignedInt, 2, 8}; return true;
case VK_FORMAT_R8G8B8A8_UINT: out = {ReadbackSourceClass::UnsignedInt, 4, 8}; return true;
case VK_FORMAT_R16_UINT: out = {ReadbackSourceClass::UnsignedInt, 1, 16}; return true;
case VK_FORMAT_R16G16_UINT: out = {ReadbackSourceClass::UnsignedInt, 2, 16}; return true;
case VK_FORMAT_R16G16B16A16_UINT: out = {ReadbackSourceClass::UnsignedInt, 4, 16}; return true;
case VK_FORMAT_R32_UINT: out = {ReadbackSourceClass::UnsignedInt, 1, 32}; return true;
case VK_FORMAT_R32G32_UINT: out = {ReadbackSourceClass::UnsignedInt, 2, 32}; return true;
case VK_FORMAT_R32G32B32A32_UINT: out = {ReadbackSourceClass::UnsignedInt, 4, 32}; return true;
// --- SINT ---
case VK_FORMAT_R8_SINT: out = {ReadbackSourceClass::SignedInt, 1, 8}; return true;
case VK_FORMAT_R8G8_SINT: out = {ReadbackSourceClass::SignedInt, 2, 8}; return true;
case VK_FORMAT_R8G8B8A8_SINT: out = {ReadbackSourceClass::SignedInt, 4, 8}; return true;
case VK_FORMAT_R16_SINT: out = {ReadbackSourceClass::SignedInt, 1, 16}; return true;
case VK_FORMAT_R16G16_SINT: out = {ReadbackSourceClass::SignedInt, 2, 16}; return true;
case VK_FORMAT_R16G16B16A16_SINT: out = {ReadbackSourceClass::SignedInt, 4, 16}; return true;
case VK_FORMAT_R32_SINT: out = {ReadbackSourceClass::SignedInt, 1, 32}; return true;
case VK_FORMAT_R32G32_SINT: out = {ReadbackSourceClass::SignedInt, 2, 32}; return true;
case VK_FORMAT_R32G32B32A32_SINT: out = {ReadbackSourceClass::SignedInt, 4, 32}; return true;
// --- packed / special ---
case VK_FORMAT_A2B10G10R10_UNORM_PACK32:
case VK_FORMAT_A2B10G10R10_UINT_PACK32:
case VK_FORMAT_B10G11R11_UFLOAT_PACK32:
case VK_FORMAT_E5B9G9R9_UFLOAT_PACK32:
case VK_FORMAT_R5G6B5_UNORM_PACK16:
case VK_FORMAT_B5G6R5_UNORM_PACK16:
case VK_FORMAT_A1R5G5B5_UNORM_PACK16:
case VK_FORMAT_R5G5B5A1_UNORM_PACK16:
case VK_FORMAT_B5G5R5A1_UNORM_PACK16:
case VK_FORMAT_R4G4B4A4_UNORM_PACK16:
case VK_FORMAT_B4G4R4A4_UNORM_PACK16:
out.sourceClass = format == VK_FORMAT_A2B10G10R10_UINT_PACK32 ?
ReadbackSourceClass::UnsignedInt : ReadbackSourceClass::Float;
out.special = format;
return true;
default:
return false;
}
}
static Float SrgbToLinear(Float value) {
if (value <= 0.04045f) {
return value / 12.92f;
}
return std::pow((value + 0.055f) / 1.055f, 2.4f);
}
static Float DecodeUnsignedF11(Uint32 bits) {
const Uint32 exponent = (bits >> 6) & 0x1F;
const Uint32 mantissa = bits & 0x3F;
if (exponent == 0) {
return static_cast<Float>(mantissa) / 64.0f * std::pow(2.0f, -14.0f);
}
if (exponent == 31) {
return mantissa == 0 ? std::numeric_limits<Float>::infinity()
: std::numeric_limits<Float>::quiet_NaN();
}
return (1.0f + static_cast<Float>(mantissa) / 64.0f) *
std::pow(2.0f, static_cast<Float>(static_cast<Int>(exponent)) - 15.0f);
}
static Float DecodeUnsignedF10(Uint32 bits) {
const Uint32 exponent = (bits >> 5) & 0x1F;
const Uint32 mantissa = bits & 0x1F;
if (exponent == 0) {
return static_cast<Float>(mantissa) / 32.0f * std::pow(2.0f, -14.0f);
}
if (exponent == 31) {
return mantissa == 0 ? std::numeric_limits<Float>::infinity()
: std::numeric_limits<Float>::quiet_NaN();
}
return (1.0f + static_cast<Float>(mantissa) / 32.0f) *
std::pow(2.0f, static_cast<Float>(static_cast<Int>(exponent)) - 15.0f);
}
static void DecodeReadbackTexelSpecialFloat(const Uint8* source, VkFormat format, Float* rgba) {
rgba[0] = 0.0f; rgba[1] = 0.0f; rgba[2] = 0.0f; rgba[3] = 1.0f;
switch (format) {
case VK_FORMAT_R16_SFLOAT:
case VK_FORMAT_R16G16_SFLOAT:
case VK_FORMAT_R16G16B16A16_SFLOAT: {
const Int channels = format == VK_FORMAT_R16_SFLOAT ? 1 :
(format == VK_FORMAT_R16G16_SFLOAT ? 2 : 4);
for (Int c = 0; c < channels; ++c) {
Uint16 bits = 0;
Memcpy(&bits, source + static_cast<SizeT>(c) * sizeof(bits), sizeof(bits));
rgba[c] = MG_Util::DecodeHalfBitsToFloat(bits);
}
return;
}
case VK_FORMAT_R32_SFLOAT:
case VK_FORMAT_R32G32_SFLOAT:
case VK_FORMAT_R32G32B32A32_SFLOAT: {
const Int channels = format == VK_FORMAT_R32_SFLOAT ? 1 :
(format == VK_FORMAT_R32G32_SFLOAT ? 2 : 4);
Memcpy(rgba, source, static_cast<SizeT>(channels) * sizeof(Float));
return;
}
case VK_FORMAT_A2B10G10R10_UNORM_PACK32: {
Uint32 word = 0;
Memcpy(&word, source, sizeof(word));
rgba[0] = static_cast<Float>(word & 0x3FFu) / 1023.0f;
rgba[1] = static_cast<Float>((word >> 10) & 0x3FFu) / 1023.0f;
rgba[2] = static_cast<Float>((word >> 20) & 0x3FFu) / 1023.0f;
rgba[3] = static_cast<Float>((word >> 30) & 0x3u) / 3.0f;
return;
}
case VK_FORMAT_B10G11R11_UFLOAT_PACK32: {
Uint32 word = 0;
Memcpy(&word, source, sizeof(word));
rgba[0] = DecodeUnsignedF11(word & 0x7FFu);
rgba[1] = DecodeUnsignedF11((word >> 11) & 0x7FFu);
rgba[2] = DecodeUnsignedF10((word >> 22) & 0x3FFu);
return;
}
case VK_FORMAT_E5B9G9R9_UFLOAT_PACK32: {
Uint32 word = 0;
Memcpy(&word, source, sizeof(word));
const Int exponent = static_cast<Int>((word >> 27) & 0x1Fu) - 15 - 9;
const Float scale = std::pow(2.0f, static_cast<Float>(exponent));
rgba[0] = static_cast<Float>(word & 0x1FFu) * scale;
rgba[1] = static_cast<Float>((word >> 9) & 0x1FFu) * scale;
rgba[2] = static_cast<Float>((word >> 18) & 0x1FFu) * scale;
return;
}
case VK_FORMAT_R5G6B5_UNORM_PACK16:
case VK_FORMAT_B5G6R5_UNORM_PACK16: {
Uint16 word = 0;
Memcpy(&word, source, sizeof(word));
const Float c0 = static_cast<Float>((word >> 11) & 0x1Fu) / 31.0f;
const Float c1 = static_cast<Float>((word >> 5) & 0x3Fu) / 63.0f;
const Float c2 = static_cast<Float>(word & 0x1Fu) / 31.0f;
const Bool bgr = format == VK_FORMAT_B5G6R5_UNORM_PACK16;
rgba[0] = bgr ? c2 : c0;
rgba[1] = c1;
rgba[2] = bgr ? c0 : c2;
return;
}
case VK_FORMAT_A1R5G5B5_UNORM_PACK16: {
Uint16 word = 0;
Memcpy(&word, source, sizeof(word));
rgba[3] = static_cast<Float>((word >> 15) & 0x1u);
rgba[0] = static_cast<Float>((word >> 10) & 0x1Fu) / 31.0f;
rgba[1] = static_cast<Float>((word >> 5) & 0x1Fu) / 31.0f;
rgba[2] = static_cast<Float>(word & 0x1Fu) / 31.0f;
return;
}
case VK_FORMAT_R5G5B5A1_UNORM_PACK16: {
Uint16 word = 0;
Memcpy(&word, source, sizeof(word));
rgba[0] = static_cast<Float>((word >> 11) & 0x1Fu) / 31.0f;
rgba[1] = static_cast<Float>((word >> 6) & 0x1Fu) / 31.0f;
rgba[2] = static_cast<Float>((word >> 1) & 0x1Fu) / 31.0f;
rgba[3] = static_cast<Float>(word & 0x1u);
return;
}
case VK_FORMAT_B5G5R5A1_UNORM_PACK16: {
Uint16 word = 0;
Memcpy(&word, source, sizeof(word));
rgba[2] = static_cast<Float>((word >> 11) & 0x1Fu) / 31.0f;
rgba[1] = static_cast<Float>((word >> 6) & 0x1Fu) / 31.0f;
rgba[0] = static_cast<Float>((word >> 1) & 0x1Fu) / 31.0f;
rgba[3] = static_cast<Float>(word & 0x1u);
return;
}
case VK_FORMAT_R4G4B4A4_UNORM_PACK16: {
Uint16 word = 0;
Memcpy(&word, source, sizeof(word));
rgba[0] = static_cast<Float>((word >> 12) & 0xFu) / 15.0f;
rgba[1] = static_cast<Float>((word >> 8) & 0xFu) / 15.0f;
rgba[2] = static_cast<Float>((word >> 4) & 0xFu) / 15.0f;
rgba[3] = static_cast<Float>(word & 0xFu) / 15.0f;
return;
}
case VK_FORMAT_B4G4R4A4_UNORM_PACK16: {
Uint16 word = 0;
Memcpy(&word, source, sizeof(word));
rgba[2] = static_cast<Float>((word >> 12) & 0xFu) / 15.0f;
rgba[1] = static_cast<Float>((word >> 8) & 0xFu) / 15.0f;
rgba[0] = static_cast<Float>((word >> 4) & 0xFu) / 15.0f;
rgba[3] = static_cast<Float>(word & 0xFu) / 15.0f;
return;
}
default:
return;
}
}
static Bool DecodeReadbackRowsToWide(const Uint8* srcPixels, VkFormat srcFormat, GLsizei width,
GLsizei height, Vector<Uint8>& outWide, GLenum& outWideType) {
ReadbackSourceDesc desc{};
if (!GetReadbackSourceDesc(srcFormat, desc)) {
return false;
}
const SizeT texelSize = VulkanRenderer::GetReadbackTexelSize(srcFormat);
if (texelSize == 0) {
return false;
}
const SizeT pixelCount = static_cast<SizeT>(width) * static_cast<SizeT>(height);
outWide.assign(pixelCount * 4 * sizeof(Uint32), 0);
if (desc.sourceClass == ReadbackSourceClass::Float) {
outWideType = GL_FLOAT;
Float* wide = reinterpret_cast<Float*>(outWide.data());
for (SizeT i = 0; i < pixelCount; ++i) {
const Uint8* source = srcPixels + i * texelSize;
Float rgba[4] = {0.0f, 0.0f, 0.0f, 1.0f};
if (desc.special != VK_FORMAT_UNDEFINED) {
DecodeReadbackTexelSpecialFloat(source, desc.special, rgba);
} else {
for (Int c = 0; c < desc.channels; ++c) {
Float value = 0.0f;
if (desc.componentBits == 8) {
if (desc.isSnorm) {
Int8 raw = 0;
Memcpy(&raw, source + c, sizeof(raw));
value = std::max(static_cast<Float>(raw) / 127.0f, -1.0f);
} else {
value = static_cast<Float>(source[c]) / 255.0f;
}
} else { // 16
if (desc.isSnorm) {
Int16 raw = 0;
Memcpy(&raw, source + static_cast<SizeT>(c) * 2, sizeof(raw));
value = std::max(static_cast<Float>(raw) / 32767.0f, -1.0f);
} else {
Uint16 raw = 0;
Memcpy(&raw, source + static_cast<SizeT>(c) * 2, sizeof(raw));
value = static_cast<Float>(raw) / 65535.0f;
}
}
if (desc.isSrgb && c < 3) {
value = SrgbToLinear(value);
}
rgba[c] = value;
}
if (desc.bgraSwizzle) {
std::swap(rgba[0], rgba[2]);
}
}
Memcpy(wide + i * 4, rgba, sizeof(rgba));
}
return true;
}
// Integer classes: decode to 4 x (U)Int32 per texel; missing alpha reads 1.
outWideType = desc.sourceClass == ReadbackSourceClass::SignedInt ? GL_INT : GL_UNSIGNED_INT;
Uint32* wide = reinterpret_cast<Uint32*>(outWide.data());
for (SizeT i = 0; i < pixelCount; ++i) {
const Uint8* source = srcPixels + i * texelSize;
Uint32 rgba[4] = {0, 0, 0, 1};
if (srcFormat == VK_FORMAT_A2B10G10R10_UINT_PACK32) {
Uint32 word = 0;
Memcpy(&word, source, sizeof(word));
rgba[0] = word & 0x3FFu;
rgba[1] = (word >> 10) & 0x3FFu;
rgba[2] = (word >> 20) & 0x3FFu;
rgba[3] = (word >> 30) & 0x3u;
} else {
for (Int c = 0; c < desc.channels; ++c) {
if (desc.componentBits == 8) {
if (desc.sourceClass == ReadbackSourceClass::SignedInt) {
Int8 raw = 0;
Memcpy(&raw, source + c, sizeof(raw));
rgba[c] = static_cast<Uint32>(static_cast<Int32>(raw));
} else {
rgba[c] = source[c];
}
} else if (desc.componentBits == 16) {
if (desc.sourceClass == ReadbackSourceClass::SignedInt) {
Int16 raw = 0;
Memcpy(&raw, source + static_cast<SizeT>(c) * 2, sizeof(raw));
rgba[c] = static_cast<Uint32>(static_cast<Int32>(raw));
} else {
Uint16 raw = 0;
Memcpy(&raw, source + static_cast<SizeT>(c) * 2, sizeof(raw));
rgba[c] = raw;
}
} else {
Memcpy(&rgba[c], source + static_cast<SizeT>(c) * 4, sizeof(Uint32));
}
}
}
Memcpy(wide + i * 4, rgba, sizeof(rgba));
}
return true;
}
static Bool PackReadbackToClientOrPbo(const Uint8* srcPixels, VkFormat srcFormat, GLsizei width,
GLsizei height, GLenum format, GLenum type, void* pixels) {
if (width <= 0 || height <= 0) {
return true;
}
if (type != GL_UNSIGNED_BYTE && type != GL_FLOAT) {
MGLOG_E("DirectVulkan readback skipped: unsupported type=0x%x", type);
DirectGLES::ReadbackImpl::ReadbackChannelMapping mapping{};
if (!DirectGLES::ReadbackImpl::GetReadbackChannelMapping(format, mapping) ||
DirectGLES::ReadbackImpl::GetReadbackDstPixelSize(mapping, type) == 0) {
MGLOG_E("DirectVulkan readback skipped: unsupported format=0x%x type=0x%x", format, type);
return false;
}
const Int dstChannels = GetReadbackChannelCount(format);
if (dstChannels == 0) {
MGLOG_E("DirectVulkan readback skipped: unsupported format=0x%x", format);
return false;
}
const auto packParams = MG_State::pGLContext->GetPixelStoreParameters(false);
const SizeT dstComponentBytes = type == GL_FLOAT ? sizeof(Float) : sizeof(Uint8);
const SizeT rowPixels = static_cast<SizeT>(packParams.RowLength > 0 ? packParams.RowLength : width);
const SizeT dstRowStride = AlignPixelRow(rowPixels * static_cast<SizeT>(dstChannels) * dstComponentBytes,
packParams.Alignment);
const SizeT dstOffset = static_cast<SizeT>(std::max(packParams.SkipRows, 0)) * dstRowStride +
static_cast<SizeT>(std::max(packParams.SkipPixels, 0)) *
static_cast<SizeT>(dstChannels) * dstComponentBytes;
const SizeT packedSize = dstOffset +
(static_cast<SizeT>(height - 1) * dstRowStride) +
(static_cast<SizeT>(width) * static_cast<SizeT>(dstChannels) * dstComponentBytes);
Vector<Uint8> packed(packedSize, 0);
if (!VulkanRenderer::ConvertReadbackPixels(srcPixels, srcFormat, width, height, format, type,
dstRowStride, packed.data() + dstOffset)) {
Vector<Uint8> wide;
GLenum wideType = GL_FLOAT;
if (!DecodeReadbackRowsToWide(srcPixels, srcFormat, width, height, wide, wideType)) {
MGLOG_E("DirectVulkan readback skipped: unsupported source format=%d",
static_cast<Int>(srcFormat));
return false;
}
const auto& pixelPackBufferObject =
MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject();
if (pixelPackBufferObject) {
const SizeT pboOffset = reinterpret_cast<SizeT>(pixels);
if (pboOffset + packed.size() > pixelPackBufferObject->GetSize()) {
MGLOG_E("DirectVulkan readback skipped: pixel pack buffer is too small");
return false;
}
pixelPackBufferObject->WritebackFromBackend({packed.data(), packed.size()}, pboOffset);
return true;
const Bool sourceIsInteger = wideType == GL_INT || wideType == GL_UNSIGNED_INT;
if (sourceIsInteger != mapping.isInteger) {
MGLOG_E("DirectVulkan readback skipped: integerness mismatch (format=0x%x source=%d)",
format, static_cast<Int>(srcFormat));
return false;
}
if (pixels != nullptr && !packed.empty()) {
Memcpy(pixels, packed.data(), packed.size());
}
return true;
return DirectGLES::ReadbackImpl::StoreWideRowsToClient(wide.data(), wideType, width, height,
/*sliceCount=*/1, mapping, type, pixels,
/*applyPackImageParams=*/false);
}
} // namespace