mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-07 19:58:32 +09:00
[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:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user