diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index dc013e58..b4567eb2 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -3435,90 +3435,6 @@ namespace MobileGL::MG_Backend::DirectGLES { return componentType != 0 ? static_cast(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(packParams.RowLength > 0 ? packParams.RowLength : width); - const SizeT dstRowStride = AlignPixelRow(rowPixels * dstPixelBytes, packParams.Alignment); - const SizeT imageRows = - applyPackImageParams && packParams.ImageHeight > 0 - ? static_cast(packParams.ImageHeight) - : static_cast(sliceHeight); - const SizeT dstImageStride = imageRows * dstRowStride; - const SizeT skipImages = - applyPackImageParams ? static_cast(std::max(packParams.SkipImages, 0)) : SizeT{0}; - const SizeT dstSkipOffset = skipImages * dstImageStride + - static_cast(std::max(packParams.SkipRows, 0)) * dstRowStride + - static_cast(std::max(packParams.SkipPixels, 0)) * dstPixelBytes; - const SizeT dstRowBytes = static_cast(width) * dstPixelBytes; - - const SizeT pboBaseOffset = reinterpret_cast(pixels); // with a PBO, `pixels` is an offset - if (pixelPackBufferObject) { - const SizeT requiredSize = pboBaseOffset + dstSkipOffset + - static_cast(sliceCount - 1) * dstImageStride + - static_cast(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 convertedRow(dstRowBytes); - - for (GLsizei slice = 0; slice < sliceCount; ++slice) { - for (GLsizei row = 0; row < sliceHeight; ++row) { - const SizeT flatRow = static_cast(slice) * static_cast(sliceHeight) + - static_cast(row); - const Uint8* srcRow = wide + flatRow * static_cast(width) * srcPixelBytes; - ReadbackImpl::ConvertWideReadbackRow(srcRow, convertedRow.data(), static_cast(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(slice) * dstImageStride + - static_cast(row) * dstRowStride; - if (pixelPackBufferObject) { - pixelPackBufferObject->WritebackFromBackend({convertedRow.data(), dstRowBytes}, - pboBaseOffset + dstOffset); - } else { - Memcpy(static_cast(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(width) * static_cast(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; } diff --git a/MobileGL/MG_Backend/DirectGLES/Utils.cpp b/MobileGL/MG_Backend/DirectGLES/Utils.cpp index 17f33db9..0a3b7e1c 100644 --- a/MobileGL/MG_Backend/DirectGLES/Utils.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Utils.cpp @@ -764,5 +764,95 @@ namespace MobileGL::MG_Backend::DirectGLES { } } } + + static SizeT AlignReadbackRow(SizeT rowBytes, Int alignment) { + const SizeT align = alignment > 0 ? static_cast(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(packParams.RowLength > 0 ? packParams.RowLength : width); + const SizeT dstRowStride = AlignReadbackRow(rowPixels * dstPixelBytes, packParams.Alignment); + const SizeT imageRows = + applyPackImageParams && packParams.ImageHeight > 0 + ? static_cast(packParams.ImageHeight) + : static_cast(sliceHeight); + const SizeT dstImageStride = imageRows * dstRowStride; + const SizeT skipImages = + applyPackImageParams ? static_cast(std::max(packParams.SkipImages, 0)) : SizeT{0}; + const SizeT dstSkipOffset = skipImages * dstImageStride + + static_cast(std::max(packParams.SkipRows, 0)) * dstRowStride + + static_cast(std::max(packParams.SkipPixels, 0)) * dstPixelBytes; + const SizeT dstRowBytes = static_cast(width) * dstPixelBytes; + + const SizeT pboBaseOffset = reinterpret_cast(pixels); // with a PBO, `pixels` is an offset + if (pixelPackBufferObject) { + const SizeT requiredSize = pboBaseOffset + dstSkipOffset + + static_cast(sliceCount - 1) * dstImageStride + + static_cast(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 convertedRow(dstRowBytes); + + for (GLsizei slice = 0; slice < sliceCount; ++slice) { + for (GLsizei row = 0; row < sliceHeight; ++row) { + const SizeT flatRow = static_cast(slice) * static_cast(sliceHeight) + + static_cast(row); + const Uint8* srcRow = wide + flatRow * static_cast(width) * srcPixelBytes; + ConvertWideReadbackRow(srcRow, convertedRow.data(), static_cast(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(slice) * dstImageStride + + static_cast(row) * dstRowStride; + if (pixelPackBufferObject) { + pixelPackBufferObject->WritebackFromBackend({convertedRow.data(), dstRowBytes}, + pboBaseOffset + dstOffset); + } else { + Memcpy(static_cast(pixels) + dstOffset, convertedRow.data(), dstRowBytes); + } + } + } + return true; + } } // namespace ReadbackImpl } // namespace MobileGL::MG_Backend::DirectGLES diff --git a/MobileGL/MG_Backend/DirectGLES/Utils.h b/MobileGL/MG_Backend/DirectGLES/Utils.h index 1068d0cd..f9d20c93 100644 --- a/MobileGL/MG_Backend/DirectGLES/Utils.h +++ b/MobileGL/MG_Backend/DirectGLES/Utils.h @@ -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 { diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index e5606d0a..e4288192 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -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(mantissa) / 64.0f * std::pow(2.0f, -14.0f); + } + if (exponent == 31) { + return mantissa == 0 ? std::numeric_limits::infinity() + : std::numeric_limits::quiet_NaN(); + } + return (1.0f + static_cast(mantissa) / 64.0f) * + std::pow(2.0f, static_cast(static_cast(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(mantissa) / 32.0f * std::pow(2.0f, -14.0f); + } + if (exponent == 31) { + return mantissa == 0 ? std::numeric_limits::infinity() + : std::numeric_limits::quiet_NaN(); + } + return (1.0f + static_cast(mantissa) / 32.0f) * + std::pow(2.0f, static_cast(static_cast(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(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(channels) * sizeof(Float)); + return; + } + case VK_FORMAT_A2B10G10R10_UNORM_PACK32: { + Uint32 word = 0; + Memcpy(&word, source, sizeof(word)); + rgba[0] = static_cast(word & 0x3FFu) / 1023.0f; + rgba[1] = static_cast((word >> 10) & 0x3FFu) / 1023.0f; + rgba[2] = static_cast((word >> 20) & 0x3FFu) / 1023.0f; + rgba[3] = static_cast((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((word >> 27) & 0x1Fu) - 15 - 9; + const Float scale = std::pow(2.0f, static_cast(exponent)); + rgba[0] = static_cast(word & 0x1FFu) * scale; + rgba[1] = static_cast((word >> 9) & 0x1FFu) * scale; + rgba[2] = static_cast((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((word >> 11) & 0x1Fu) / 31.0f; + const Float c1 = static_cast((word >> 5) & 0x3Fu) / 63.0f; + const Float c2 = static_cast(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((word >> 15) & 0x1u); + rgba[0] = static_cast((word >> 10) & 0x1Fu) / 31.0f; + rgba[1] = static_cast((word >> 5) & 0x1Fu) / 31.0f; + rgba[2] = static_cast(word & 0x1Fu) / 31.0f; + return; + } + case VK_FORMAT_R5G5B5A1_UNORM_PACK16: { + Uint16 word = 0; + Memcpy(&word, source, sizeof(word)); + rgba[0] = static_cast((word >> 11) & 0x1Fu) / 31.0f; + rgba[1] = static_cast((word >> 6) & 0x1Fu) / 31.0f; + rgba[2] = static_cast((word >> 1) & 0x1Fu) / 31.0f; + rgba[3] = static_cast(word & 0x1u); + return; + } + case VK_FORMAT_B5G5R5A1_UNORM_PACK16: { + Uint16 word = 0; + Memcpy(&word, source, sizeof(word)); + rgba[2] = static_cast((word >> 11) & 0x1Fu) / 31.0f; + rgba[1] = static_cast((word >> 6) & 0x1Fu) / 31.0f; + rgba[0] = static_cast((word >> 1) & 0x1Fu) / 31.0f; + rgba[3] = static_cast(word & 0x1u); + return; + } + case VK_FORMAT_R4G4B4A4_UNORM_PACK16: { + Uint16 word = 0; + Memcpy(&word, source, sizeof(word)); + rgba[0] = static_cast((word >> 12) & 0xFu) / 15.0f; + rgba[1] = static_cast((word >> 8) & 0xFu) / 15.0f; + rgba[2] = static_cast((word >> 4) & 0xFu) / 15.0f; + rgba[3] = static_cast(word & 0xFu) / 15.0f; + return; + } + case VK_FORMAT_B4G4R4A4_UNORM_PACK16: { + Uint16 word = 0; + Memcpy(&word, source, sizeof(word)); + rgba[2] = static_cast((word >> 12) & 0xFu) / 15.0f; + rgba[1] = static_cast((word >> 8) & 0xFu) / 15.0f; + rgba[0] = static_cast((word >> 4) & 0xFu) / 15.0f; + rgba[3] = static_cast(word & 0xFu) / 15.0f; + return; + } + default: + return; + } + } + + static Bool DecodeReadbackRowsToWide(const Uint8* srcPixels, VkFormat srcFormat, GLsizei width, + GLsizei height, Vector& 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(width) * static_cast(height); + outWide.assign(pixelCount * 4 * sizeof(Uint32), 0); + + if (desc.sourceClass == ReadbackSourceClass::Float) { + outWideType = GL_FLOAT; + Float* wide = reinterpret_cast(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(raw) / 127.0f, -1.0f); + } else { + value = static_cast(source[c]) / 255.0f; + } + } else { // 16 + if (desc.isSnorm) { + Int16 raw = 0; + Memcpy(&raw, source + static_cast(c) * 2, sizeof(raw)); + value = std::max(static_cast(raw) / 32767.0f, -1.0f); + } else { + Uint16 raw = 0; + Memcpy(&raw, source + static_cast(c) * 2, sizeof(raw)); + value = static_cast(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(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(static_cast(raw)); + } else { + rgba[c] = source[c]; + } + } else if (desc.componentBits == 16) { + if (desc.sourceClass == ReadbackSourceClass::SignedInt) { + Int16 raw = 0; + Memcpy(&raw, source + static_cast(c) * 2, sizeof(raw)); + rgba[c] = static_cast(static_cast(raw)); + } else { + Uint16 raw = 0; + Memcpy(&raw, source + static_cast(c) * 2, sizeof(raw)); + rgba[c] = raw; + } + } else { + Memcpy(&rgba[c], source + static_cast(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(packParams.RowLength > 0 ? packParams.RowLength : width); - const SizeT dstRowStride = AlignPixelRow(rowPixels * static_cast(dstChannels) * dstComponentBytes, - packParams.Alignment); - const SizeT dstOffset = static_cast(std::max(packParams.SkipRows, 0)) * dstRowStride + - static_cast(std::max(packParams.SkipPixels, 0)) * - static_cast(dstChannels) * dstComponentBytes; - const SizeT packedSize = dstOffset + - (static_cast(height - 1) * dstRowStride) + - (static_cast(width) * static_cast(dstChannels) * dstComponentBytes); - Vector packed(packedSize, 0); - - if (!VulkanRenderer::ConvertReadbackPixels(srcPixels, srcFormat, width, height, format, type, - dstRowStride, packed.data() + dstOffset)) { + Vector wide; + GLenum wideType = GL_FLOAT; + if (!DecodeReadbackRowsToWide(srcPixels, srcFormat, width, height, wide, wideType)) { MGLOG_E("DirectVulkan readback skipped: unsupported source format=%d", static_cast(srcFormat)); return false; } - const auto& pixelPackBufferObject = - MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject(); - if (pixelPackBufferObject) { - const SizeT pboOffset = reinterpret_cast(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(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