[Fix] (MG_Backend/DirectGLES, MG_Impl/GLImpl, MG_Util): GL CTS packed_pixels + texture_swizzle readback overhaul - canonical shadow layouts for legacy sized/unsized/packed internal formats (RGB5->RGB565, RGB10/12->RGB16, RGBA2->RGBA4, RGB10_A2(UI)/RGB9_E5/R11F_G11F_B10F packed-word shadows with per-texel encode/decode incl. 5_9_9_9_REV and 10F_11F_11F_REV client types), GL_UNSIGNED_INT_10_10_10_2 pixel type mapping, conversion-first GetTexImage with CPU-shadow fallback for non-attachable formats and stale-temp-FBO detach, narrow implementation read pairs + SNORM read candidates + 2_10_10_10_REV wide-read decode with RGBA expansion, PACK image/skip and SWAP_BYTES honored on the CPU repack (never in ES), state-reset conformance (default-texture TexParameter/TexImage/TexBuffer no-ops, renderbuffer 0 unbind, vertex attrib 0 current value, ActiveTexture up to combined units, UBO binding count clamp), FramebufferTexture3D/TextureLayer slice attachments via glFramebufferTextureLayer, capability-driven FBO UNSUPPORTED for non-renderable colors, ReadPixels integer-ness mismatch error, single-value texture swizzle validation, and DirectGLES 1D/1D-array/2D-array texture emulation (2D/2D-array backend targets matching SPIRV-Cross ES 1D-as-2D shaders)

This commit is contained in:
2026-07-16 22:41:54 -04:00
parent 3ca57068a8
commit 870d882fef
21 changed files with 1683 additions and 419 deletions
@@ -308,6 +308,8 @@ namespace MobileGL {
return TexturePixelDataType::UnsignedInt8888;
case GL_UNSIGNED_INT_8_8_8_8_REV:
return TexturePixelDataType::UnsignedInt8888Rev;
case GL_UNSIGNED_INT_10_10_10_2:
return TexturePixelDataType::UnsignedInt1010102;
case GL_UNSIGNED_INT_10F_11F_11F_REV:
return TexturePixelDataType::UnsignedInt101111Rev;
case GL_UNSIGNED_INT_2_10_10_10_REV:
+116
View File
@@ -0,0 +1,116 @@
// MobileGL - MobileGL/MG_Util/Math/SmallFloat.h
// Copyright (c) 2025-2026 MobileGL-Dev
// Licensed under the GNU Lesser General Public License v3.0:
// https://www.gnu.org/licenses/gpl-3.0.txt
// https://www.gnu.org/licenses/lgpl-3.0.txt
// SPDX-License-Identifier: LGPL-3.0-only
// End of Source File Header
#pragma once
#include <Includes.h>
#include <bit>
#include <cmath>
#include <limits>
namespace MobileGL::MG_Util {
// Encodes an unsigned small float with a 5-bit exponent (bias 15) and mantissaBits mantissa
// bits, per the EXT_packed_float conversion rules: negatives (including -Inf) go to zero,
// +Inf stays +Inf, NaN stays NaN, and finite values above the largest representable value
// clamp to it. The mantissa is truncated (rounding mode is implementation-defined).
inline Uint32 EncodeFloatToUnsignedSmallFloat(Float value, Int mantissaBits) {
const Uint32 bits = std::bit_cast<Uint32>(value);
const Bool negative = (bits & 0x80000000u) != 0;
const Uint32 exponent = (bits >> 23) & 0xFFu;
const Uint32 mantissa = bits & 0x7FFFFFu;
const Uint32 exponentMask = 0x1Fu << mantissaBits;
if (exponent == 0xFFu) {
if (mantissa != 0) {
return exponentMask | 1u; // NaN keeps NaN
}
return negative ? 0u : exponentMask; // -Inf -> 0, +Inf -> +Inf
}
if (negative) {
return 0u;
}
const Int32 smallExponent = static_cast<Int32>(exponent) - 127 + 15;
if (smallExponent >= 31) { // above the largest finite value -> clamp to it
return ((31u - 1u) << mantissaBits) | ((1u << mantissaBits) - 1u);
}
if (smallExponent <= 0) { // subnormal range: renormalize, flushing tiny values to zero
const Uint32 fullMantissa = mantissa | 0x800000u;
const Int32 shift = (23 - mantissaBits) + 1 - smallExponent;
return shift > 23 ? 0u : fullMantissa >> shift;
}
return (static_cast<Uint32>(smallExponent) << mantissaBits) |
(mantissa >> (23u - static_cast<Uint32>(mantissaBits)));
}
inline Uint32 EncodeFloatToUnsignedF11(Float value) { return EncodeFloatToUnsignedSmallFloat(value, 6); }
inline Uint32 EncodeFloatToUnsignedF10(Float value) { return EncodeFloatToUnsignedSmallFloat(value, 5); }
// Decodes an unsigned small float (5-bit exponent, bias 15, mantissaBits mantissa bits).
inline Float DecodeUnsignedSmallFloatToFloat(Uint32 field, Int mantissaBits) {
const Uint32 exponent = (field >> mantissaBits) & 0x1Fu;
const Uint32 mantissa = field & ((1u << mantissaBits) - 1u);
const Float mantissaScale = 1.0f / static_cast<Float>(1u << mantissaBits);
if (exponent == 0) {
return std::exp2(-14.0f) * static_cast<Float>(mantissa) * mantissaScale;
}
if (exponent == 31) {
return mantissa == 0 ? std::numeric_limits<Float>::infinity()
: std::numeric_limits<Float>::quiet_NaN();
}
return std::exp2(static_cast<Float>(exponent) - 15.0f) *
(1.0f + static_cast<Float>(mantissa) * mantissaScale);
}
inline Float DecodeUnsignedF11ToFloat(Uint32 field) { return DecodeUnsignedSmallFloatToFloat(field, 6); }
inline Float DecodeUnsignedF10ToFloat(Uint32 field) { return DecodeUnsignedSmallFloatToFloat(field, 5); }
// RGB9E5 shared-exponent encode, following the EXT_texture_shared_exponent spec algorithm
// (N = 9 mantissa bits, B = 15 exponent bias, Emax = 31).
inline Uint32 EncodeSharedExponentRGB9E5(const Float rgb[3]) {
constexpr Int kMantissaBits = 9;
constexpr Int kExponentBias = 15;
constexpr Float kSharedExpMax = 511.0f / 512.0f * 65536.0f; // (2^N-1)/2^N * 2^(Emax-B)
Float clamped[3];
for (Int i = 0; i < 3; ++i) {
const Float v = rgb[i];
clamped[i] = (std::isnan(v) || v < 0.0f) ? 0.0f : std::min(v, kSharedExpMax);
}
const Float maxComponent = std::max(clamped[0], std::max(clamped[1], clamped[2]));
Int sharedExponent = 0; // all-zero input keeps the all-zero word
if (maxComponent > 0.0f) {
sharedExponent = std::max(-kExponentBias - 1, static_cast<Int>(std::floor(std::log2(maxComponent)))) +
1 + kExponentBias;
const Float maxScaled = std::floor(
maxComponent / std::exp2(static_cast<Float>(sharedExponent - kExponentBias - kMantissaBits)) +
0.5f);
if (maxScaled >= 512.0f) { // rounded up to 2^N: bump the shared exponent instead
++sharedExponent;
}
}
const Float scale = std::exp2(static_cast<Float>(sharedExponent - kExponentBias - kMantissaBits));
Uint32 word = static_cast<Uint32>(sharedExponent) << 27;
for (Int i = 0; i < 3; ++i) {
const auto field = static_cast<Uint32>(std::floor(clamped[i] / scale + 0.5f));
word |= std::min(field, 511u) << (i * kMantissaBits);
}
return word;
}
// RGB9E5 shared-exponent decode.
inline void DecodeSharedExponentRGB9E5(Uint32 word, Float outRgb[3]) {
constexpr Int kMantissaBits = 9;
constexpr Int kExponentBias = 15;
const Int exponent = static_cast<Int>(word >> 27) - kExponentBias - kMantissaBits;
const Float scale = std::exp2(static_cast<Float>(exponent));
for (Int i = 0; i < 3; ++i) {
outRgb[i] = static_cast<Float>((word >> (i * kMantissaBits)) & 0x1FFu) * scale;
}
}
} // namespace MobileGL::MG_Util
+7 -3
View File
@@ -15,10 +15,10 @@ namespace MobileGL {
SizeT GetSizedInternalFormatSizeInBytes(TextureInternalFormat internal) {
switch (internal) {
case TextureInternalFormat::R8:
case TextureInternalFormat::Red: // UNorm8 shadow layout
case TextureInternalFormat::R8Snorm:
case TextureInternalFormat::R8I:
case TextureInternalFormat::R8UI:
case TextureInternalFormat::R3G3B2:
return 1;
case TextureInternalFormat::R16:
@@ -27,14 +27,17 @@ namespace MobileGL {
case TextureInternalFormat::R16UI:
case TextureInternalFormat::R16F:
case TextureInternalFormat::RG8:
case TextureInternalFormat::RG: // UNorm8x2 shadow layout
case TextureInternalFormat::RG8Snorm:
case TextureInternalFormat::RG8I:
case TextureInternalFormat::RG8UI:
case TextureInternalFormat::DepthComponent16:
return 2;
case TextureInternalFormat::R3G3B2: // UNorm8x3 shadow layout
case TextureInternalFormat::RGB4:
case TextureInternalFormat::RGB5:
case TextureInternalFormat::RGB: // UNorm8x3 shadow layout
case TextureInternalFormat::RGB8:
case TextureInternalFormat::RGB8Snorm:
case TextureInternalFormat::SRGB8:
@@ -43,11 +46,10 @@ namespace MobileGL {
case TextureInternalFormat::DepthComponent24:
return 3;
case TextureInternalFormat::RGB10:
case TextureInternalFormat::RGB12:
case TextureInternalFormat::RGBA2:
case TextureInternalFormat::RGBA4:
case TextureInternalFormat::RGB5A1:
case TextureInternalFormat::RGBA: // UNorm8x4 shadow layout
case TextureInternalFormat::RGBA8:
case TextureInternalFormat::RGBA8Snorm:
case TextureInternalFormat::RGBA8I:
@@ -72,6 +74,8 @@ namespace MobileGL {
return 4;
case TextureInternalFormat::RGB16:
case TextureInternalFormat::RGB10: // UNorm16x3 shadow layout
case TextureInternalFormat::RGB12: // UNorm16x3 shadow layout
case TextureInternalFormat::RGB16Snorm:
case TextureInternalFormat::RGB16F:
case TextureInternalFormat::RGB16I:
+341 -15
View File
@@ -8,6 +8,7 @@
#include "PixelStoreProcessor.h"
#include "MG_Util/Math/HalfFloat.h"
#include "MG_Util/Math/SmallFloat.h"
#include <cmath>
namespace MobileGL::MG_Util::PixelStoreProcessor {
@@ -122,13 +123,30 @@ namespace MobileGL::MG_Util::PixelStoreProcessor {
Bool GetInternalShadowLayout(TextureInternalFormat internal, InternalShadowLayout& out) {
switch (internal) {
case TextureInternalFormat::R8: out = {1, ShadowComponent::UNorm8, false}; return true;
case TextureInternalFormat::RG8: out = {2, ShadowComponent::UNorm8, false}; return true;
case TextureInternalFormat::R8:
case TextureInternalFormat::Red: out = {1, ShadowComponent::UNorm8, false}; return true;
case TextureInternalFormat::RG8:
case TextureInternalFormat::RG: out = {2, ShadowComponent::UNorm8, false}; return true;
case TextureInternalFormat::RGB8:
case TextureInternalFormat::RGB:
case TextureInternalFormat::SRGB8: out = {3, ShadowComponent::UNorm8, false}; return true;
case TextureInternalFormat::RGBA8:
case TextureInternalFormat::RGBA:
case TextureInternalFormat::SRGB8Alpha8: out = {4, ShadowComponent::UNorm8, false}; return true;
// Legacy desktop-GL sized normalized formats are stored in the closest ES-legal layout
// (see TextureFormatProcessor::NormalizePixelFormat): 8-bit unorm for <=8-bit channels,
// 16-bit unorm for 10/12-bit channels.
case TextureInternalFormat::R3G3B2:
case TextureInternalFormat::RGB4:
case TextureInternalFormat::RGB5: out = {3, ShadowComponent::UNorm8, false}; return true;
case TextureInternalFormat::RGBA2:
case TextureInternalFormat::RGBA4:
case TextureInternalFormat::RGB5A1: out = {4, ShadowComponent::UNorm8, false}; return true;
case TextureInternalFormat::RGB10:
case TextureInternalFormat::RGB12: out = {3, ShadowComponent::UNorm16, false}; return true;
case TextureInternalFormat::RGBA12: out = {4, ShadowComponent::UNorm16, false}; return true;
case TextureInternalFormat::R8Snorm: out = {1, ShadowComponent::SNorm8, false}; return true;
case TextureInternalFormat::RG8Snorm: out = {2, ShadowComponent::SNorm8, false}; return true;
case TextureInternalFormat::RGB8Snorm: out = {3, ShadowComponent::SNorm8, false}; return true;
@@ -185,12 +203,76 @@ namespace MobileGL::MG_Util::PixelStoreProcessor {
case TextureInternalFormat::RGBA32I: out = {4, ShadowComponent::Int32, true}; return true;
default:
// Packed internal layouts (RGB5A1, RGB10A2, RGB9E5, ...), depth/stencil and unsized formats
// keep the legacy copy path.
// Packed internal layouts (RGB10A2, RGB9E5, ...), depth/stencil and unsized formats
// have no component-array shadow layout (packed ones are handled below).
return false;
}
}
// Packed internal formats whose shadow bytes hold the ES upload word directly
// (GL_UNSIGNED_INT_2_10_10_10_REV / 5_9_9_9_REV / 10F_11F_11F_REV encoding, 4 bytes/texel).
enum class PackedInternalKind {
UNorm2101010Rev, // GL_RGB10_A2
UInt2101010Rev, // GL_RGB10_A2UI
FloatR11G11B10, // GL_R11F_G11F_B10F
FloatRGB9E5, // GL_RGB9_E5
};
struct InternalPackedLayout {
PackedInternalKind kind;
Int channelCount;
Bool isInteger;
};
Bool GetInternalPackedLayout(TextureInternalFormat internal, InternalPackedLayout& out) {
switch (internal) {
case TextureInternalFormat::RGB10A2:
out = {PackedInternalKind::UNorm2101010Rev, 4, false};
return true;
case TextureInternalFormat::RGB10A2UI:
out = {PackedInternalKind::UInt2101010Rev, 4, true};
return true;
case TextureInternalFormat::R11FG11FB10F:
out = {PackedInternalKind::FloatR11G11B10, 3, false};
return true;
case TextureInternalFormat::RGB9E5:
out = {PackedInternalKind::FloatRGB9E5, 3, false};
return true;
default:
return false;
}
}
Uint32 EncodePackedInternalWordFloat(PackedInternalKind kind, const Float rgba[4]) {
switch (kind) {
case PackedInternalKind::UNorm2101010Rev: {
const auto field = [](Float v, Uint32 maxValue) {
return static_cast<Uint32>(std::llround(std::clamp(v, 0.0f, 1.0f) * static_cast<Float>(maxValue)));
};
return field(rgba[0], 1023u) | (field(rgba[1], 1023u) << 10) | (field(rgba[2], 1023u) << 20) |
(field(rgba[3], 3u) << 30);
}
case PackedInternalKind::FloatR11G11B10:
return EncodeFloatToUnsignedF11(rgba[0]) | (EncodeFloatToUnsignedF11(rgba[1]) << 11) |
(EncodeFloatToUnsignedF10(rgba[2]) << 22);
case PackedInternalKind::FloatRGB9E5:
return EncodeSharedExponentRGB9E5(rgba);
default:
return 0;
}
}
Uint32 EncodePackedInternalWordInt(PackedInternalKind kind, const Int64 rgba[4]) {
if (kind != PackedInternalKind::UInt2101010Rev) {
return 0;
}
const auto field = [](Int64 v, Int64 maxValue) {
return static_cast<Uint32>(std::clamp<Int64>(v, 0, maxValue));
};
return field(rgba[0], 1023) | (field(rgba[1], 1023) << 10) | (field(rgba[2], 1023) << 20) |
(field(rgba[3], 3) << 30);
}
struct UnpackChannelMapping {
Int formatPosition[4]; // position of R,G,B,A within the input format's component list; -1 = missing
Int channelCount;
@@ -301,6 +383,8 @@ namespace MobileGL::MG_Util::PixelStoreProcessor {
SizeT inputPixelSize;
SizeT swapGroupSize; // UNPACK_SWAP_BYTES group: packed word size, or the component size
SizeT internalPixelSize;
Bool internalIsPacked;
InternalPackedLayout internalPacked;
};
// Returns true when the (format, type) -> internal-format upload needs a per-texel conversion;
@@ -309,10 +393,16 @@ namespace MobileGL::MG_Util::PixelStoreProcessor {
Bool GetUnpackConversionSpec(TextureInternalFormat internal, TextureInputFormat format,
TexturePixelDataType type, UnpackConversionSpec& out) {
InternalShadowLayout layout{};
if (!GetInternalShadowLayout(internal, layout)) return false;
InternalPackedLayout packedInternal{};
const Bool hasComponentLayout = GetInternalShadowLayout(internal, layout);
const Bool hasPackedInternal = !hasComponentLayout && GetInternalPackedLayout(internal, packedInternal);
if (!hasComponentLayout && !hasPackedInternal) return false;
const Bool internalIsInteger = hasComponentLayout ? layout.isInteger : packedInternal.isInteger;
const Int internalChannelCount = hasComponentLayout ? layout.channelCount : packedInternal.channelCount;
UnpackChannelMapping mapping{};
if (!GetUnpackChannelMapping(format, mapping)) return false;
if (mapping.isInteger != layout.isInteger) return false; // rejected upstream; stay safe
if (mapping.isInteger != internalIsInteger) return false; // rejected upstream; stay safe
PackedTypeLayout packed{};
const Bool isPacked = GetPackedTypeLayout(type, packed);
@@ -323,6 +413,13 @@ namespace MobileGL::MG_Util::PixelStoreProcessor {
type == TexturePixelDataType::UnsignedInt8888Rev) {
return false;
}
// The client word already equals the packed internal word (memcpy fast path).
if (hasPackedInternal && type == TexturePixelDataType::UnsignedInt2101010Rev &&
(format == TextureInputFormat::RGBA || format == TextureInputFormat::RGBAInteger) &&
(packedInternal.kind == PackedInternalKind::UNorm2101010Rev ||
packedInternal.kind == PackedInternalKind::UInt2101010Rev)) {
return false;
}
} else {
ShadowComponent direct{};
const Bool hasDirect = GetDirectShadowComponentForType(type, mapping.isInteger, direct);
@@ -338,25 +435,46 @@ namespace MobileGL::MG_Util::PixelStoreProcessor {
case TexturePixelDataType::HalfFloat:
if (mapping.isInteger) return false; // rejected upstream
break;
case TexturePixelDataType::UnsignedInt5999Rev:
case TexturePixelDataType::UnsignedInt101111Rev:
// Packed-float RGB source words (decoded in ConvertUnpackRow); only pair with
// GL_RGB, which the state layer already enforces.
if (mapping.isInteger || mapping.channelCount != 3) return false;
// The client word already equals the packed internal word.
if (hasPackedInternal &&
((packedInternal.kind == PackedInternalKind::FloatRGB9E5 &&
type == TexturePixelDataType::UnsignedInt5999Rev) ||
(packedInternal.kind == PackedInternalKind::FloatR11G11B10 &&
type == TexturePixelDataType::UnsignedInt101111Rev))) {
return false;
}
break;
default:
return false;
}
if (hasDirect && direct == layout.component && mapping.channelCount == layout.channelCount &&
IsIdentityChannelOrder(mapping)) {
if (hasComponentLayout && hasDirect && direct == layout.component &&
mapping.channelCount == layout.channelCount && IsIdentityChannelOrder(mapping)) {
return false; // input already matches the shadow layout
}
}
out.mapping = mapping;
out.internal = layout;
out.internal = hasComponentLayout
? layout
: InternalShadowLayout{internalChannelCount, ShadowComponent::UNorm8, internalIsInteger};
out.packed = packed;
out.isPacked = isPacked;
out.type = type;
out.inputPixelSize = GetInputBytesPerPixel(format, type);
const Bool isPackedFloatWord = type == TexturePixelDataType::UnsignedInt5999Rev ||
type == TexturePixelDataType::UnsignedInt101111Rev;
out.swapGroupSize = isPacked ? static_cast<SizeT>(packed.totalBits / 8)
: GetBaseTexturePixelDataTypeSize(type);
: (isPackedFloatWord ? 4 : GetBaseTexturePixelDataTypeSize(type));
out.internalIsPacked = hasPackedInternal;
out.internalPacked = packedInternal;
out.internalPixelSize =
static_cast<SizeT>(layout.channelCount) * GetShadowComponentSize(layout.component);
hasPackedInternal ? 4
: static_cast<SizeT>(layout.channelCount) * GetShadowComponentSize(layout.component);
return true;
}
@@ -564,13 +682,36 @@ namespace MobileGL::MG_Util::PixelStoreProcessor {
rgba[ch] = DecodeComponentToInt(s + static_cast<SizeT>(pos) * srcComponentSize, conv.type);
}
}
if (conv.internalIsPacked) {
const Uint32 word = EncodePackedInternalWordInt(conv.internalPacked.kind, rgba);
Memcpy(d, &word, sizeof(word));
continue;
}
for (Int ch = 0; ch < conv.internal.channelCount; ++ch) {
EncodeShadowComponentInt(d + static_cast<SizeT>(ch) * dstComponentSize,
conv.internal.component, rgba[ch]);
}
} else {
Float rgba[4] = {0.0f, 0.0f, 0.0f, 1.0f};
if (conv.isPacked) {
if (conv.type == TexturePixelDataType::UnsignedInt5999Rev ||
conv.type == TexturePixelDataType::UnsignedInt101111Rev) {
// Packed-float RGB source word: decode the shared-exponent / small-float fields.
Uint32 word;
Memcpy(&word, s, sizeof(word));
Float comps[3];
if (conv.type == TexturePixelDataType::UnsignedInt5999Rev) {
DecodeSharedExponentRGB9E5(word, comps);
} else {
comps[0] = DecodeUnsignedF11ToFloat(word & 0x7FFu);
comps[1] = DecodeUnsignedF11ToFloat((word >> 11) & 0x7FFu);
comps[2] = DecodeUnsignedF10ToFloat((word >> 22) & 0x3FFu);
}
for (Int ch = 0; ch < 4; ++ch) {
const Int pos = conv.mapping.formatPosition[ch];
if (pos < 0 || pos >= 3) continue;
rgba[ch] = comps[pos];
}
} else if (conv.isPacked) {
const Uint32 word = ReadPackedWord(s, conv.packed.totalBits);
for (Int ch = 0; ch < 4; ++ch) {
const Int pos = conv.mapping.formatPosition[ch];
@@ -587,6 +728,11 @@ namespace MobileGL::MG_Util::PixelStoreProcessor {
DecodeComponentToFloat(s + static_cast<SizeT>(pos) * srcComponentSize, conv.type);
}
}
if (conv.internalIsPacked) {
const Uint32 word = EncodePackedInternalWordFloat(conv.internalPacked.kind, rgba);
Memcpy(d, &word, sizeof(word));
continue;
}
for (Int ch = 0; ch < conv.internal.channelCount; ++ch) {
EncodeShadowComponentFloat(d + static_cast<SizeT>(ch) * dstComponentSize,
conv.internal.component, rgba[ch]);
@@ -687,9 +833,16 @@ namespace MobileGL::MG_Util::PixelStoreProcessor {
} else {
Memcpy(layerDst, layerSrc, static_cast<SizeT>(copyWidth) * pixelSize);
if (params.SwapBytes && pixelSize > 1 && !isByteType) {
MGLOG_D("%s: SwapBytes", __func__);
SwapBytes(layerDst, pixelSize, static_cast<SizeT>(copyWidth));
if (params.SwapBytes && !isByteType) {
// GL_UNPACK_SWAP_BYTES swaps within each element (component or packed
// word), never across a whole multi-component pixel.
SizeT swapGroup = GetSizedTexturePixelDataTypeSize(inputDataType);
if (swapGroup == 0) swapGroup = GetBaseTexturePixelDataTypeSize(inputDataType);
if (swapGroup > 1) {
MGLOG_D("%s: SwapBytes (group %d)", __func__, static_cast<Int>(swapGroup));
SwapBytes(layerDst, swapGroup,
static_cast<SizeT>(copyWidth) * pixelSize / swapGroup);
}
}
if (params.LSBFirst && isBitmap) {
@@ -788,4 +941,177 @@ namespace MobileGL::MG_Util::PixelStoreProcessor {
return outputPixels;
}
namespace {
Float DecodeShadowComponentToFloat(const Uint8* p, ShadowComponent component) {
switch (component) {
case ShadowComponent::UNorm8:
return static_cast<Float>(*p) / 255.0f;
case ShadowComponent::SNorm8: {
Int8 v;
Memcpy(&v, p, sizeof(v));
return std::max(static_cast<Float>(v) / 127.0f, -1.0f);
}
case ShadowComponent::UNorm16: {
Uint16 v;
Memcpy(&v, p, sizeof(v));
return static_cast<Float>(v) / 65535.0f;
}
case ShadowComponent::SNorm16: {
Int16 v;
Memcpy(&v, p, sizeof(v));
return std::max(static_cast<Float>(v) / 32767.0f, -1.0f);
}
case ShadowComponent::Half: {
Uint16 v;
Memcpy(&v, p, sizeof(v));
return DecodeHalfBitsToFloat(v);
}
case ShadowComponent::Float32: {
Float v;
Memcpy(&v, p, sizeof(v));
return v;
}
default:
return 0.0f;
}
}
Int64 DecodeShadowComponentToInt(const Uint8* p, ShadowComponent component) {
switch (component) {
case ShadowComponent::UInt8:
return *p;
case ShadowComponent::Int8: {
Int8 v;
Memcpy(&v, p, sizeof(v));
return v;
}
case ShadowComponent::UInt16: {
Uint16 v;
Memcpy(&v, p, sizeof(v));
return v;
}
case ShadowComponent::Int16: {
Int16 v;
Memcpy(&v, p, sizeof(v));
return v;
}
case ShadowComponent::UInt32: {
Uint32 v;
Memcpy(&v, p, sizeof(v));
return v;
}
case ShadowComponent::Int32: {
Int32 v;
Memcpy(&v, p, sizeof(v));
return v;
}
default:
return 0;
}
}
} // namespace
Bool DecodeShadowDataToWideRGBA(TextureInternalFormat internalFormat, const void* src, SizeT pixelCount,
Vector<Uint8>& outWide, Bool& outIsInteger, Bool& outIsSigned) {
if (!src) return false;
const Uint8* srcBytes = static_cast<const Uint8*>(src);
InternalShadowLayout layout{};
if (GetInternalShadowLayout(internalFormat, layout)) {
const SizeT componentSize = GetShadowComponentSize(layout.component);
const SizeT srcPixelSize = static_cast<SizeT>(layout.channelCount) * componentSize;
outIsInteger = layout.isInteger;
outIsSigned = layout.component == ShadowComponent::Int8 || layout.component == ShadowComponent::Int16 ||
layout.component == ShadowComponent::Int32;
outWide.resize(pixelCount * 16);
if (layout.isInteger) {
auto* dst = reinterpret_cast<Uint32*>(outWide.data());
for (SizeT i = 0; i < pixelCount; ++i) {
const Uint8* s = srcBytes + i * srcPixelSize;
for (Int ch = 0; ch < 4; ++ch) {
Int64 v = ch == 3 ? 1 : 0;
if (ch < layout.channelCount) {
v = DecodeShadowComponentToInt(s + static_cast<SizeT>(ch) * componentSize,
layout.component);
}
if (outIsSigned) {
const auto out = static_cast<Int32>(v);
Memcpy(&dst[i * 4 + ch], &out, sizeof(out));
} else {
dst[i * 4 + ch] = static_cast<Uint32>(v);
}
}
}
} else {
auto* dst = reinterpret_cast<Float*>(outWide.data());
for (SizeT i = 0; i < pixelCount; ++i) {
const Uint8* s = srcBytes + i * srcPixelSize;
for (Int ch = 0; ch < 4; ++ch) {
Float v = ch == 3 ? 1.0f : 0.0f;
if (ch < layout.channelCount) {
v = DecodeShadowComponentToFloat(s + static_cast<SizeT>(ch) * componentSize,
layout.component);
}
dst[i * 4 + ch] = v;
}
}
}
return true;
}
InternalPackedLayout packedInternal{};
if (GetInternalPackedLayout(internalFormat, packedInternal)) {
outIsInteger = packedInternal.isInteger;
outIsSigned = false;
outWide.resize(pixelCount * 16);
if (packedInternal.isInteger) {
auto* dst = reinterpret_cast<Uint32*>(outWide.data());
for (SizeT i = 0; i < pixelCount; ++i) {
Uint32 word;
Memcpy(&word, srcBytes + i * 4, sizeof(word));
dst[i * 4 + 0] = word & 0x3FFu;
dst[i * 4 + 1] = (word >> 10) & 0x3FFu;
dst[i * 4 + 2] = (word >> 20) & 0x3FFu;
dst[i * 4 + 3] = (word >> 30) & 0x3u;
}
} else {
auto* dst = reinterpret_cast<Float*>(outWide.data());
for (SizeT i = 0; i < pixelCount; ++i) {
Uint32 word;
Memcpy(&word, srcBytes + i * 4, sizeof(word));
switch (packedInternal.kind) {
case PackedInternalKind::UNorm2101010Rev:
dst[i * 4 + 0] = static_cast<Float>(word & 0x3FFu) / 1023.0f;
dst[i * 4 + 1] = static_cast<Float>((word >> 10) & 0x3FFu) / 1023.0f;
dst[i * 4 + 2] = static_cast<Float>((word >> 20) & 0x3FFu) / 1023.0f;
dst[i * 4 + 3] = static_cast<Float>((word >> 30) & 0x3u) / 3.0f;
break;
case PackedInternalKind::FloatR11G11B10:
dst[i * 4 + 0] = DecodeUnsignedF11ToFloat(word & 0x7FFu);
dst[i * 4 + 1] = DecodeUnsignedF11ToFloat((word >> 11) & 0x7FFu);
dst[i * 4 + 2] = DecodeUnsignedF10ToFloat((word >> 22) & 0x3FFu);
dst[i * 4 + 3] = 1.0f;
break;
case PackedInternalKind::FloatRGB9E5: {
Float rgb[3];
DecodeSharedExponentRGB9E5(word, rgb);
dst[i * 4 + 0] = rgb[0];
dst[i * 4 + 1] = rgb[1];
dst[i * 4 + 2] = rgb[2];
dst[i * 4 + 3] = 1.0f;
break;
}
default:
dst[i * 4 + 0] = dst[i * 4 + 1] = dst[i * 4 + 2] = 0.0f;
dst[i * 4 + 3] = 1.0f;
break;
}
}
}
return true;
}
return false;
}
} // namespace MobileGL::MG_Util::PixelStoreProcessor
@@ -22,4 +22,12 @@ namespace MobileGL::MG_Util::PixelStoreProcessor {
TextureInputFormat dstInputFormat, TexturePixelDataType dstDataType,
IntVec3 dimension, Bool isBitmap, SizeT& outSize);
void ProcessColorSwizzle(void* data, SizeT pixelCount, const Vector<TextureSwizzleParam>& swizzle);
// Decodes the canonical shadow-mip storage of `internalFormat` into wide RGBA texels for CPU
// readback (GetTexImage of non-renderable formats). Non-integer formats fill outWide with
// 4 Floats per texel; integer formats fill it with 4 Uint32/Int32 per texel and set
// outIsInteger (outIsSigned tells signed from unsigned). Missing channels read 0 (G/B) and
// 1 / 1.0f (A). Returns false when the format has no canonical shadow layout.
Bool DecodeShadowDataToWideRGBA(TextureInternalFormat internalFormat, const void* src, SizeT pixelCount,
Vector<Uint8>& outWide, Bool& outIsInteger, Bool& outIsSigned);
} // namespace MobileGL::MG_Util::PixelStoreProcessor
@@ -19,11 +19,14 @@ namespace MobileGL::MG_Util::TextureFormatProcessor {
applicableOptions |= options & PixelFormatNormalizeOptionBit::NoDepthComponent32;
break;
case GL_RGBA16:
case GL_RGBA12: // stored as RGBA16 (see NormalizePixelFormat)
case GL_RG16:
case GL_R16:
applicableOptions |= options & PixelFormatNormalizeOptionBit::NoNorm16;
break;
case GL_RGB16:
case GL_RGB10: // stored as RGB16 (see NormalizePixelFormat)
case GL_RGB12: // stored as RGB16 (see NormalizePixelFormat)
applicableOptions |= options & PixelFormatNormalizeOptionBit::NoNorm16;
applicableOptions |= options & PixelFormatNormalizeOptionBit::NoRgb16;
break;
@@ -159,6 +162,30 @@ namespace MobileGL::MG_Util::TextureFormatProcessor {
}
*outInternalFormat = internalFormat;
break;
// Legacy desktop-GL sized normalized formats (GL CTS packed_pixels): ES drivers reject them
// as internal formats, so store them in the closest ES-legal format with at least the same
// per-channel precision (extra precision stays inside the CTS comparison epsilon, which is
// derived from the requested format's bit widths). The upload (format, type) below matches
// the canonical shadow layout in PixelStoreProcessor (UNorm8 / UNorm16 component arrays).
case GL_R3_G3_B2:
case GL_RGB4:
case GL_RGB5:
*outInternalFormat = GL_RGB565;
break;
case GL_RGB10:
case GL_RGB12:
*outInternalFormat = (options & PixelFormatNormalizeOptionBit::NoNorm16) ||
(options & PixelFormatNormalizeOptionBit::NoRgb16)
? GL_RGB32F
: GL_RGB16;
break;
case GL_RGBA2:
*outInternalFormat = GL_RGBA4;
break;
case GL_RGBA12:
*outInternalFormat =
(options & PixelFormatNormalizeOptionBit::NoNorm16) ? GL_RGBA32F : GL_RGBA16;
break;
default:
*outInternalFormat = internalFormat;
break;
@@ -276,6 +303,7 @@ namespace MobileGL::MG_Util::TextureFormatProcessor {
*outFormat = GL_RGB;
break;
case GL_SRGB8_ALPHA8:
case GL_SRGB_ALPHA:
*outFormat = GL_RGBA;
break;
@@ -288,6 +316,24 @@ namespace MobileGL::MG_Util::TextureFormatProcessor {
case GL_RGB5_A1:
*outFormat = GL_RGBA;
break;
case GL_RGB10_A2UI:
*outFormat = GL_RGBA_INTEGER;
break;
// Legacy desktop-GL sized normalized formats
case GL_R3_G3_B2:
case GL_RGB4:
case GL_RGB5:
case GL_RGB565:
case GL_RGB10:
case GL_RGB12:
*outFormat = GL_RGB;
break;
case GL_RGBA2:
case GL_RGBA4:
case GL_RGBA12:
*outFormat = GL_RGBA;
break;
// Depth
case GL_DEPTH_COMPONENT16:
@@ -459,10 +505,44 @@ namespace MobileGL::MG_Util::TextureFormatProcessor {
*outType = GL_UNSIGNED_INT_10F_11F_11F_REV;
break;
case GL_RGB10_A2:
case GL_RGB10_A2UI:
*outType = GL_UNSIGNED_INT_2_10_10_10_REV;
break;
case GL_RGB5_A1:
*outType = GL_UNSIGNED_SHORT_5_5_5_1;
// The shadow stores RGB5_A1 as UNorm8x4 (see PixelStoreProcessor); ES accepts
// GL_RGBA/GL_UNSIGNED_BYTE uploads for this internal format.
*outType = GL_UNSIGNED_BYTE;
break;
// Legacy desktop-GL sized normalized formats: the upload type matches the canonical
// shadow layout (UNorm8 for <=8-bit channels, UNorm16 for 10/12-bit channels).
case GL_R3_G3_B2:
case GL_RGB4:
case GL_RGB5:
case GL_RGB565:
case GL_RGBA2:
case GL_RGBA4:
*outType = GL_UNSIGNED_BYTE;
break;
case GL_RGB10:
case GL_RGB12:
*outType = (options & PixelFormatNormalizeOptionBit::NoNorm16) ||
(options & PixelFormatNormalizeOptionBit::NoRgb16)
? GL_FLOAT
: GL_UNSIGNED_SHORT;
break;
case GL_RGBA12:
*outType = (options & PixelFormatNormalizeOptionBit::NoNorm16) ? GL_FLOAT : GL_UNSIGNED_SHORT;
break;
// Unsized color formats keep their byte-per-channel client layout.
case GL_RGBA:
case GL_RGB:
case GL_RG:
case GL_RED:
case GL_SRGB:
case GL_SRGB_ALPHA:
*outType = GL_UNSIGNED_BYTE;
break;
// Depth