mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-07 19:58:32 +09:00
Merge branch 'worktree-agent-af155789372f0003b' into dev
This commit is contained in:
@@ -3269,69 +3269,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// GL_RGBA/GL_FLOAT for float buffers plus one implementation-defined pair, while desktop GL clients read
|
||||
// back narrower layouts (RED, RG, RGB, BGR, byte-order packed types, ...). For those we read a guaranteed
|
||||
// wide RGBA format into scratch memory and repack into the caller's (format, type) layout on the CPU,
|
||||
// honoring the client-side PACK pixel-store parameters.
|
||||
// honoring the client-side PACK pixel-store parameters. The pure repacking helpers live in
|
||||
// ReadbackImpl (Utils.cpp) so unit tests can assert the exact packed words.
|
||||
|
||||
using MG_Util::DecodeHalfBitsToFloat;
|
||||
using MG_Util::EncodeFloatToHalfBits;
|
||||
|
||||
struct ReadbackChannelMapping {
|
||||
Int sourceChannel[4]; // RGBA source channel feeding each destination channel
|
||||
Int channelCount; // destination channel count
|
||||
Bool isInteger;
|
||||
};
|
||||
|
||||
static Bool GetReadbackChannelMapping(GLenum format, ReadbackChannelMapping& outMapping) {
|
||||
switch (format) {
|
||||
case GL_RED: outMapping = {{0, 0, 0, 0}, 1, false}; return true;
|
||||
case GL_RED_INTEGER: outMapping = {{0, 0, 0, 0}, 1, true}; return true;
|
||||
// Desktop-GL single-channel client formats (GL CTS packed_pixels rgba8_format_green/blue):
|
||||
// the destination holds one component sourced from the named channel of the wide RGBA read.
|
||||
// GL_ALPHA is mapped here from the raw enum because the state layer folds it into Red for the
|
||||
// legacy alpha-texture upload hack.
|
||||
case GL_GREEN: outMapping = {{1, 0, 0, 0}, 1, false}; return true;
|
||||
case GL_GREEN_INTEGER: outMapping = {{1, 0, 0, 0}, 1, true}; return true;
|
||||
case GL_BLUE: outMapping = {{2, 0, 0, 0}, 1, false}; return true;
|
||||
case GL_BLUE_INTEGER: outMapping = {{2, 0, 0, 0}, 1, true}; return true;
|
||||
case GL_ALPHA: outMapping = {{3, 0, 0, 0}, 1, false}; return true;
|
||||
case GL_ALPHA_INTEGER: outMapping = {{3, 0, 0, 0}, 1, true}; return true;
|
||||
case GL_RG: outMapping = {{0, 1, 0, 0}, 2, false}; return true;
|
||||
case GL_RG_INTEGER: outMapping = {{0, 1, 0, 0}, 2, true}; return true;
|
||||
case GL_RGB: outMapping = {{0, 1, 2, 0}, 3, false}; return true;
|
||||
case GL_RGB_INTEGER: outMapping = {{0, 1, 2, 0}, 3, true}; return true;
|
||||
case GL_BGR: outMapping = {{2, 1, 0, 0}, 3, false}; return true;
|
||||
case GL_BGR_INTEGER: outMapping = {{2, 1, 0, 0}, 3, true}; return true;
|
||||
case GL_RGBA: outMapping = {{0, 1, 2, 3}, 4, false}; return true;
|
||||
case GL_RGBA_INTEGER: outMapping = {{0, 1, 2, 3}, 4, true}; return true;
|
||||
case GL_BGRA: outMapping = {{2, 1, 0, 3}, 4, false}; return true;
|
||||
case GL_BGRA_INTEGER: outMapping = {{2, 1, 0, 3}, 4, true}; return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static Bool IsPackedReadback8888Type(GLenum type) {
|
||||
return type == GL_UNSIGNED_INT_8_8_8_8 || type == GL_UNSIGNED_INT_8_8_8_8_REV;
|
||||
}
|
||||
|
||||
static SizeT GetReadbackComponentSize(GLenum type) {
|
||||
switch (type) {
|
||||
case GL_UNSIGNED_BYTE:
|
||||
case GL_BYTE:
|
||||
return 1;
|
||||
case GL_UNSIGNED_SHORT:
|
||||
case GL_SHORT:
|
||||
case GL_HALF_FLOAT:
|
||||
return 2;
|
||||
case GL_UNSIGNED_INT:
|
||||
case GL_INT:
|
||||
case GL_FLOAT:
|
||||
case GL_UNSIGNED_INT_8_8_8_8:
|
||||
case GL_UNSIGNED_INT_8_8_8_8_REV:
|
||||
return 4;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
using ReadbackImpl::GetReadbackChannelMapping;
|
||||
using ReadbackImpl::GetReadbackComponentSize;
|
||||
using ReadbackImpl::GetReadbackDstPixelSize;
|
||||
using ReadbackImpl::ReadbackChannelMapping;
|
||||
|
||||
static Bool CanDecodeWideSourceType(GLenum type) {
|
||||
switch (type) {
|
||||
@@ -3379,17 +3323,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
if (!GetReadbackChannelMapping(format, mapping)) {
|
||||
return false;
|
||||
}
|
||||
const Bool packed8888 = IsPackedReadback8888Type(type);
|
||||
if (packed8888 && mapping.channelCount != 4) {
|
||||
return false;
|
||||
}
|
||||
if (mapping.isInteger && (type == GL_FLOAT || type == GL_HALF_FLOAT)) {
|
||||
// Covers unknown types, packed field-count/format mismatches and float types on integer formats.
|
||||
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);
|
||||
if (dstComponentSize == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (width <= 0 || height <= 0) {
|
||||
return true;
|
||||
@@ -3473,8 +3414,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// 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 dstPixelBytes =
|
||||
packed8888 ? sizeof(Uint32) : static_cast<SizeT>(mapping.channelCount) * dstComponentSize;
|
||||
const SizeT rowPixels = static_cast<SizeT>(packParams.RowLength > 0 ? packParams.RowLength : width);
|
||||
const SizeT dstRowStride = AlignPixelRow(rowPixels * dstPixelBytes, packParams.Alignment);
|
||||
const SizeT dstSkipOffset = static_cast<SizeT>(std::max(packParams.SkipRows, 0)) * dstRowStride +
|
||||
@@ -3497,164 +3436,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
|
||||
for (GLsizei row = 0; row < height; ++row) {
|
||||
const Uint8* srcRow = wide.data() + static_cast<SizeT>(row) * static_cast<SizeT>(width) * srcPixelBytes;
|
||||
for (GLsizei col = 0; col < width; ++col) {
|
||||
const Uint8* srcPixel = srcRow + static_cast<SizeT>(col) * srcPixelBytes;
|
||||
Uint8* dstPixel = convertedRow.data() + static_cast<SizeT>(col) * dstPixelBytes;
|
||||
if (mapping.isInteger) {
|
||||
Int64 src[4];
|
||||
for (Int c = 0; c < 4; ++c) {
|
||||
src[c] = wideType == GL_INT
|
||||
? static_cast<Int64>(reinterpret_cast<const Int32*>(srcPixel)[c])
|
||||
: static_cast<Int64>(reinterpret_cast<const Uint32*>(srcPixel)[c]);
|
||||
}
|
||||
if (packed8888) {
|
||||
Uint32 word = 0;
|
||||
for (Int ch = 0; ch < 4; ++ch) {
|
||||
const auto v =
|
||||
static_cast<Uint32>(std::clamp<Int64>(src[mapping.sourceChannel[ch]], 0, 255));
|
||||
word |= type == GL_UNSIGNED_INT_8_8_8_8 ? v << (24 - ch * 8) : v << (ch * 8);
|
||||
}
|
||||
Memcpy(dstPixel, &word, sizeof(word));
|
||||
} else {
|
||||
for (Int ch = 0; ch < mapping.channelCount; ++ch) {
|
||||
const Int64 v = src[mapping.sourceChannel[ch]];
|
||||
Uint8* dstComponent = dstPixel + static_cast<SizeT>(ch) * dstComponentSize;
|
||||
switch (type) {
|
||||
case GL_UNSIGNED_BYTE:
|
||||
*dstComponent = static_cast<Uint8>(std::clamp<Int64>(v, 0, 255));
|
||||
break;
|
||||
case GL_BYTE: {
|
||||
const auto out = static_cast<Int8>(std::clamp<Int64>(v, -128, 127));
|
||||
Memcpy(dstComponent, &out, sizeof(out));
|
||||
break;
|
||||
}
|
||||
case GL_UNSIGNED_SHORT: {
|
||||
const auto out = static_cast<Uint16>(std::clamp<Int64>(v, 0, 65535));
|
||||
Memcpy(dstComponent, &out, sizeof(out));
|
||||
break;
|
||||
}
|
||||
case GL_SHORT: {
|
||||
const auto out = static_cast<Int16>(std::clamp<Int64>(v, -32768, 32767));
|
||||
Memcpy(dstComponent, &out, sizeof(out));
|
||||
break;
|
||||
}
|
||||
case GL_UNSIGNED_INT: {
|
||||
const auto out = static_cast<Uint32>(std::clamp<Int64>(v, 0, 4294967295LL));
|
||||
Memcpy(dstComponent, &out, sizeof(out));
|
||||
break;
|
||||
}
|
||||
case GL_INT: {
|
||||
const auto out =
|
||||
static_cast<Int32>(std::clamp<Int64>(v, -2147483648LL, 2147483647LL));
|
||||
Memcpy(dstComponent, &out, sizeof(out));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Float src[4];
|
||||
switch (wideType) {
|
||||
case GL_UNSIGNED_BYTE:
|
||||
for (Int c = 0; c < 4; ++c) {
|
||||
src[c] = static_cast<Float>(srcPixel[c]) / 255.0f;
|
||||
}
|
||||
break;
|
||||
case GL_BYTE:
|
||||
for (Int c = 0; c < 4; ++c) {
|
||||
src[c] = std::max(
|
||||
static_cast<Float>(reinterpret_cast<const Int8*>(srcPixel)[c]) / 127.0f, -1.0f);
|
||||
}
|
||||
break;
|
||||
case GL_UNSIGNED_SHORT:
|
||||
for (Int c = 0; c < 4; ++c) {
|
||||
src[c] = static_cast<Float>(reinterpret_cast<const Uint16*>(srcPixel)[c]) / 65535.0f;
|
||||
}
|
||||
break;
|
||||
case GL_SHORT:
|
||||
for (Int c = 0; c < 4; ++c) {
|
||||
src[c] = std::max(
|
||||
static_cast<Float>(reinterpret_cast<const Int16*>(srcPixel)[c]) / 32767.0f, -1.0f);
|
||||
}
|
||||
break;
|
||||
case GL_HALF_FLOAT:
|
||||
for (Int c = 0; c < 4; ++c) {
|
||||
src[c] = DecodeHalfBitsToFloat(reinterpret_cast<const Uint16*>(srcPixel)[c]);
|
||||
}
|
||||
break;
|
||||
default: // GL_FLOAT
|
||||
for (Int c = 0; c < 4; ++c) {
|
||||
src[c] = reinterpret_cast<const Float*>(srcPixel)[c];
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (packed8888) {
|
||||
Uint32 word = 0;
|
||||
for (Int ch = 0; ch < 4; ++ch) {
|
||||
const auto v = static_cast<Uint32>(
|
||||
std::llround(std::clamp(src[mapping.sourceChannel[ch]], 0.0f, 1.0f) * 255.0));
|
||||
word |= type == GL_UNSIGNED_INT_8_8_8_8 ? v << (24 - ch * 8) : v << (ch * 8);
|
||||
}
|
||||
Memcpy(dstPixel, &word, sizeof(word));
|
||||
} else {
|
||||
for (Int ch = 0; ch < mapping.channelCount; ++ch) {
|
||||
const Float v = src[mapping.sourceChannel[ch]];
|
||||
Uint8* dstComponent = dstPixel + static_cast<SizeT>(ch) * dstComponentSize;
|
||||
switch (type) {
|
||||
case GL_UNSIGNED_BYTE:
|
||||
*dstComponent =
|
||||
static_cast<Uint8>(std::llround(std::clamp(v, 0.0f, 1.0f) * 255.0));
|
||||
break;
|
||||
case GL_BYTE: {
|
||||
const auto out =
|
||||
static_cast<Int8>(std::llround(std::clamp(v, -1.0f, 1.0f) * 127.0));
|
||||
Memcpy(dstComponent, &out, sizeof(out));
|
||||
break;
|
||||
}
|
||||
case GL_UNSIGNED_SHORT: {
|
||||
const auto out =
|
||||
static_cast<Uint16>(std::llround(std::clamp(v, 0.0f, 1.0f) * 65535.0));
|
||||
Memcpy(dstComponent, &out, sizeof(out));
|
||||
break;
|
||||
}
|
||||
case GL_SHORT: {
|
||||
const auto out =
|
||||
static_cast<Int16>(std::llround(std::clamp(v, -1.0f, 1.0f) * 32767.0));
|
||||
Memcpy(dstComponent, &out, sizeof(out));
|
||||
break;
|
||||
}
|
||||
case GL_UNSIGNED_INT: {
|
||||
const auto out = static_cast<Uint32>(
|
||||
std::llround(static_cast<Double>(std::clamp(v, 0.0f, 1.0f)) * 4294967295.0));
|
||||
Memcpy(dstComponent, &out, sizeof(out));
|
||||
break;
|
||||
}
|
||||
case GL_INT: {
|
||||
const auto out = static_cast<Int32>(
|
||||
std::llround(static_cast<Double>(std::clamp(v, -1.0f, 1.0f)) * 2147483647.0));
|
||||
Memcpy(dstComponent, &out, sizeof(out));
|
||||
break;
|
||||
}
|
||||
case GL_FLOAT:
|
||||
Memcpy(dstComponent, &v, sizeof(v));
|
||||
break;
|
||||
case GL_HALF_FLOAT: {
|
||||
const Uint16 out = EncodeFloatToHalfBits(v);
|
||||
Memcpy(dstComponent, &out, sizeof(out));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ReadbackImpl::ConvertWideReadbackRow(srcRow, convertedRow.data(), static_cast<SizeT>(width), wideType,
|
||||
mapping, type);
|
||||
|
||||
if (packParams.SwapBytes) {
|
||||
const SizeT groupSize = packed8888 ? sizeof(Uint32) : dstComponentSize;
|
||||
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);
|
||||
@@ -3696,8 +3482,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// of killing the process; spec-invalid combinations are already rejected with GL errors at the state layer.
|
||||
const Bool useNativeReadback = IsLegacyNativeReadPixelsFormat(format) && IsLegacyNativeReadPixelsType(type);
|
||||
ReadbackChannelMapping conversionMapping{};
|
||||
const Bool convertible =
|
||||
GetReadbackChannelMapping(format, conversionMapping) && GetReadbackComponentSize(type) != 0;
|
||||
const Bool convertible = GetReadbackChannelMapping(format, conversionMapping) &&
|
||||
GetReadbackDstPixelSize(conversionMapping, type) != 0;
|
||||
if (!useNativeReadback && !convertible) {
|
||||
MGLOG_E("ReadPixels: format %s with type %s is not implemented yet, skipping readback",
|
||||
MG_Util::ConvertGLEnumToString(format).c_str(), MG_Util::ConvertGLEnumToString(type).c_str());
|
||||
@@ -3838,8 +3624,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// spec-invalid combinations are already rejected with GL errors at the state layer.
|
||||
const Bool useNativeReadback = IsNativeGetTexImagePair(format, type);
|
||||
ReadbackChannelMapping conversionMapping{};
|
||||
const Bool convertible =
|
||||
GetReadbackChannelMapping(format, conversionMapping) && GetReadbackComponentSize(type) != 0;
|
||||
const Bool convertible = GetReadbackChannelMapping(format, conversionMapping) &&
|
||||
GetReadbackDstPixelSize(conversionMapping, type) != 0;
|
||||
if (!useNativeReadback && !convertible) {
|
||||
MGLOG_E("GetTexImage: format %s with type %s is not implemented yet, skipping readback",
|
||||
MG_Util::ConvertGLEnumToString(format).c_str(), MG_Util::ConvertGLEnumToString(type).c_str());
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
#include <MG_Util/Converters/GLToStr/GLEnumConverter.h>
|
||||
#include <MG_Util/Converters/MGToGL/TextureEnumConverter.h>
|
||||
#include <MG_Util/Converters/MGToGL/FramebufferEnumConverter.h>
|
||||
#include <MG_Util/Math/HalfFloat.h>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectGLES {
|
||||
namespace {
|
||||
@@ -448,4 +451,381 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
}
|
||||
} // namespace Utils
|
||||
|
||||
// ---- Client-format readback conversion helpers -------------------------------------------------
|
||||
// ReadPixels/GetTexImage read a guaranteed wide RGBA(_INTEGER) layout from the ES driver and repack
|
||||
// it on the CPU into the client's (format, type) layout. Everything here is pure byte shuffling so
|
||||
// unit tests can assert the exact packed words; field positions follow GL 3.3 table 3.6 and mirror
|
||||
// the GL CTS packed_pixels oracle (glcPackedPixelsTests.cpp pack_UNSIGNED_* helpers).
|
||||
namespace ReadbackImpl {
|
||||
using MG_Util::DecodeHalfBitsToFloat;
|
||||
using MG_Util::EncodeFloatToHalfBits;
|
||||
|
||||
Bool GetReadbackChannelMapping(GLenum format, ReadbackChannelMapping& outMapping) {
|
||||
switch (format) {
|
||||
case GL_RED: outMapping = {{0, 0, 0, 0}, 1, false}; return true;
|
||||
case GL_RED_INTEGER: outMapping = {{0, 0, 0, 0}, 1, true}; return true;
|
||||
// Desktop-GL single-channel client formats (GL CTS packed_pixels rgba8_format_green/blue):
|
||||
// the destination holds one component sourced from the named channel of the wide RGBA read.
|
||||
// GL_ALPHA is mapped here from the raw enum because the state layer folds it into Red for the
|
||||
// legacy alpha-texture upload hack.
|
||||
case GL_GREEN: outMapping = {{1, 0, 0, 0}, 1, false}; return true;
|
||||
case GL_GREEN_INTEGER: outMapping = {{1, 0, 0, 0}, 1, true}; return true;
|
||||
case GL_BLUE: outMapping = {{2, 0, 0, 0}, 1, false}; return true;
|
||||
case GL_BLUE_INTEGER: outMapping = {{2, 0, 0, 0}, 1, true}; return true;
|
||||
case GL_ALPHA: outMapping = {{3, 0, 0, 0}, 1, false}; return true;
|
||||
case GL_ALPHA_INTEGER: outMapping = {{3, 0, 0, 0}, 1, true}; return true;
|
||||
case GL_RG: outMapping = {{0, 1, 0, 0}, 2, false}; return true;
|
||||
case GL_RG_INTEGER: outMapping = {{0, 1, 0, 0}, 2, true}; return true;
|
||||
case GL_RGB: outMapping = {{0, 1, 2, 0}, 3, false}; return true;
|
||||
case GL_RGB_INTEGER: outMapping = {{0, 1, 2, 0}, 3, true}; return true;
|
||||
case GL_BGR: outMapping = {{2, 1, 0, 0}, 3, false}; return true;
|
||||
case GL_BGR_INTEGER: outMapping = {{2, 1, 0, 0}, 3, true}; return true;
|
||||
case GL_RGBA: outMapping = {{0, 1, 2, 3}, 4, false}; return true;
|
||||
case GL_RGBA_INTEGER: outMapping = {{0, 1, 2, 3}, 4, true}; return true;
|
||||
case GL_BGRA: outMapping = {{2, 1, 0, 3}, 4, false}; return true;
|
||||
case GL_BGRA_INTEGER: outMapping = {{2, 1, 0, 3}, 4, true}; return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Bool GetPackedReadbackLayout(GLenum type, PackedReadbackLayout& out) {
|
||||
switch (type) {
|
||||
// Non-REV types pack the first format component starting at the most significant bit,
|
||||
// *_REV types starting at the least significant bit (GL CTS pack_UNSIGNED_SHORT_5_6_5:
|
||||
// R bits 15-11; pack_UNSIGNED_SHORT_1_5_5_5_REV: R bits 4-0, A bit 15).
|
||||
case GL_UNSIGNED_BYTE_3_3_2: out = {3, {3, 3, 2, 0}, {5, 2, 0, 0}, 1, false}; return true;
|
||||
case GL_UNSIGNED_BYTE_2_3_3_REV: out = {3, {3, 3, 2, 0}, {0, 3, 6, 0}, 1, false}; return true;
|
||||
case GL_UNSIGNED_SHORT_5_6_5: out = {3, {5, 6, 5, 0}, {11, 5, 0, 0}, 2, false}; return true;
|
||||
case GL_UNSIGNED_SHORT_5_6_5_REV: out = {3, {5, 6, 5, 0}, {0, 5, 11, 0}, 2, false}; return true;
|
||||
case GL_UNSIGNED_SHORT_4_4_4_4: out = {4, {4, 4, 4, 4}, {12, 8, 4, 0}, 2, false}; return true;
|
||||
case GL_UNSIGNED_SHORT_4_4_4_4_REV: out = {4, {4, 4, 4, 4}, {0, 4, 8, 12}, 2, false}; return true;
|
||||
case GL_UNSIGNED_SHORT_5_5_5_1: out = {4, {5, 5, 5, 1}, {11, 6, 1, 0}, 2, false}; return true;
|
||||
case GL_UNSIGNED_SHORT_1_5_5_5_REV: out = {4, {5, 5, 5, 1}, {0, 5, 10, 15}, 2, false}; return true;
|
||||
case GL_UNSIGNED_INT_8_8_8_8: out = {4, {8, 8, 8, 8}, {24, 16, 8, 0}, 4, false}; return true;
|
||||
case GL_UNSIGNED_INT_8_8_8_8_REV: out = {4, {8, 8, 8, 8}, {0, 8, 16, 24}, 4, false}; return true;
|
||||
case GL_UNSIGNED_INT_10_10_10_2: out = {4, {10, 10, 10, 2}, {22, 12, 2, 0}, 4, false}; return true;
|
||||
case GL_UNSIGNED_INT_2_10_10_10_REV: out = {4, {10, 10, 10, 2}, {0, 10, 20, 30}, 4, false}; return true;
|
||||
// Packed-float RGB types: fields hold unsigned small floats; 5_9_9_9_REV's shared 5-bit
|
||||
// exponent (bits 31-27) is emitted by EncodeSharedExponentRGB9E5, not a component field.
|
||||
case GL_UNSIGNED_INT_10F_11F_11F_REV: out = {3, {11, 11, 10, 0}, {0, 11, 22, 0}, 4, true}; return true;
|
||||
case GL_UNSIGNED_INT_5_9_9_9_REV: out = {3, {9, 9, 9, 0}, {0, 9, 18, 0}, 4, true}; return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
SizeT GetReadbackComponentSize(GLenum type) {
|
||||
PackedReadbackLayout packedLayout{};
|
||||
if (GetPackedReadbackLayout(type, packedLayout)) {
|
||||
return packedLayout.byteSize;
|
||||
}
|
||||
switch (type) {
|
||||
case GL_UNSIGNED_BYTE:
|
||||
case GL_BYTE:
|
||||
return 1;
|
||||
case GL_UNSIGNED_SHORT:
|
||||
case GL_SHORT:
|
||||
case GL_HALF_FLOAT:
|
||||
return 2;
|
||||
case GL_UNSIGNED_INT:
|
||||
case GL_INT:
|
||||
case GL_FLOAT:
|
||||
return 4;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
SizeT GetReadbackDstPixelSize(const ReadbackChannelMapping& mapping, GLenum type) {
|
||||
PackedReadbackLayout packedLayout{};
|
||||
if (GetPackedReadbackLayout(type, packedLayout)) {
|
||||
if (packedLayout.fieldCount != mapping.channelCount) {
|
||||
return 0; // 3-field packed types pair with 3-component formats only, 4 with 4
|
||||
}
|
||||
if (mapping.isInteger && packedLayout.isFloatPacked) {
|
||||
return 0; // packed-float RGB types never pair with integer formats
|
||||
}
|
||||
return packedLayout.byteSize;
|
||||
}
|
||||
if (mapping.isInteger && (type == GL_FLOAT || type == GL_HALF_FLOAT)) {
|
||||
return 0;
|
||||
}
|
||||
const SizeT componentSize = GetReadbackComponentSize(type);
|
||||
return componentSize == 0 ? 0 : static_cast<SizeT>(mapping.channelCount) * componentSize;
|
||||
}
|
||||
|
||||
namespace {
|
||||
// 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).
|
||||
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)));
|
||||
}
|
||||
|
||||
void WritePackedReadbackWord(Uint8* dst, Uint32 word, SizeT byteSize) {
|
||||
switch (byteSize) {
|
||||
case 1: {
|
||||
const auto out = static_cast<Uint8>(word);
|
||||
Memcpy(dst, &out, sizeof(out));
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
const auto out = static_cast<Uint16>(word);
|
||||
Memcpy(dst, &out, sizeof(out));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Memcpy(dst, &word, sizeof(word));
|
||||
break;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Uint32 EncodeFloatToUnsignedF11(Float value) { return EncodeFloatToUnsignedSmallFloat(value, 6); }
|
||||
Uint32 EncodeFloatToUnsignedF10(Float value) { return EncodeFloatToUnsignedSmallFloat(value, 5); }
|
||||
|
||||
// RGB9E5 shared-exponent encode, following the EXT_texture_shared_exponent spec algorithm
|
||||
// (N = 9 mantissa bits, B = 15 exponent bias, Emax = 31).
|
||||
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;
|
||||
}
|
||||
|
||||
void ConvertWideReadbackRow(const Uint8* src, Uint8* dst, SizeT width, GLenum wideType,
|
||||
const ReadbackChannelMapping& mapping, GLenum type) {
|
||||
PackedReadbackLayout packedLayout{};
|
||||
const Bool isPacked = GetPackedReadbackLayout(type, packedLayout);
|
||||
const SizeT dstComponentSize = GetReadbackComponentSize(type);
|
||||
const SizeT dstPixelBytes = GetReadbackDstPixelSize(mapping, type);
|
||||
const SizeT srcPixelBytes = 4 * GetReadbackComponentSize(wideType);
|
||||
|
||||
for (SizeT col = 0; col < width; ++col) {
|
||||
const Uint8* srcPixel = src + col * srcPixelBytes;
|
||||
Uint8* dstPixel = dst + col * dstPixelBytes;
|
||||
if (mapping.isInteger) {
|
||||
Int64 srcValues[4];
|
||||
for (Int c = 0; c < 4; ++c) {
|
||||
srcValues[c] = wideType == GL_INT
|
||||
? static_cast<Int64>(reinterpret_cast<const Int32*>(srcPixel)[c])
|
||||
: static_cast<Int64>(reinterpret_cast<const Uint32*>(srcPixel)[c]);
|
||||
}
|
||||
if (isPacked) {
|
||||
// Integer sources clamp each component to the unsigned range of its field
|
||||
// (GL 3.3 section 4.3.1 final conversion).
|
||||
Uint32 word = 0;
|
||||
for (Int ch = 0; ch < packedLayout.fieldCount; ++ch) {
|
||||
const Int64 fieldMax = (Int64{1} << packedLayout.width[ch]) - 1;
|
||||
const auto v = static_cast<Uint32>(
|
||||
std::clamp<Int64>(srcValues[mapping.sourceChannel[ch]], 0, fieldMax));
|
||||
word |= v << packedLayout.shift[ch];
|
||||
}
|
||||
WritePackedReadbackWord(dstPixel, word, packedLayout.byteSize);
|
||||
} else {
|
||||
for (Int ch = 0; ch < mapping.channelCount; ++ch) {
|
||||
const Int64 v = srcValues[mapping.sourceChannel[ch]];
|
||||
Uint8* dstComponent = dstPixel + static_cast<SizeT>(ch) * dstComponentSize;
|
||||
switch (type) {
|
||||
case GL_UNSIGNED_BYTE:
|
||||
*dstComponent = static_cast<Uint8>(std::clamp<Int64>(v, 0, 255));
|
||||
break;
|
||||
case GL_BYTE: {
|
||||
const auto out = static_cast<Int8>(std::clamp<Int64>(v, -128, 127));
|
||||
Memcpy(dstComponent, &out, sizeof(out));
|
||||
break;
|
||||
}
|
||||
case GL_UNSIGNED_SHORT: {
|
||||
const auto out = static_cast<Uint16>(std::clamp<Int64>(v, 0, 65535));
|
||||
Memcpy(dstComponent, &out, sizeof(out));
|
||||
break;
|
||||
}
|
||||
case GL_SHORT: {
|
||||
const auto out = static_cast<Int16>(std::clamp<Int64>(v, -32768, 32767));
|
||||
Memcpy(dstComponent, &out, sizeof(out));
|
||||
break;
|
||||
}
|
||||
case GL_UNSIGNED_INT: {
|
||||
const auto out = static_cast<Uint32>(std::clamp<Int64>(v, 0, 4294967295LL));
|
||||
Memcpy(dstComponent, &out, sizeof(out));
|
||||
break;
|
||||
}
|
||||
case GL_INT: {
|
||||
const auto out =
|
||||
static_cast<Int32>(std::clamp<Int64>(v, -2147483648LL, 2147483647LL));
|
||||
Memcpy(dstComponent, &out, sizeof(out));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Float srcValues[4];
|
||||
switch (wideType) {
|
||||
case GL_UNSIGNED_BYTE:
|
||||
for (Int c = 0; c < 4; ++c) {
|
||||
srcValues[c] = static_cast<Float>(srcPixel[c]) / 255.0f;
|
||||
}
|
||||
break;
|
||||
case GL_BYTE:
|
||||
for (Int c = 0; c < 4; ++c) {
|
||||
srcValues[c] = std::max(
|
||||
static_cast<Float>(reinterpret_cast<const Int8*>(srcPixel)[c]) / 127.0f, -1.0f);
|
||||
}
|
||||
break;
|
||||
case GL_UNSIGNED_SHORT:
|
||||
for (Int c = 0; c < 4; ++c) {
|
||||
srcValues[c] =
|
||||
static_cast<Float>(reinterpret_cast<const Uint16*>(srcPixel)[c]) / 65535.0f;
|
||||
}
|
||||
break;
|
||||
case GL_SHORT:
|
||||
for (Int c = 0; c < 4; ++c) {
|
||||
srcValues[c] = std::max(
|
||||
static_cast<Float>(reinterpret_cast<const Int16*>(srcPixel)[c]) / 32767.0f, -1.0f);
|
||||
}
|
||||
break;
|
||||
case GL_HALF_FLOAT:
|
||||
for (Int c = 0; c < 4; ++c) {
|
||||
srcValues[c] = DecodeHalfBitsToFloat(reinterpret_cast<const Uint16*>(srcPixel)[c]);
|
||||
}
|
||||
break;
|
||||
default: // GL_FLOAT
|
||||
for (Int c = 0; c < 4; ++c) {
|
||||
srcValues[c] = reinterpret_cast<const Float*>(srcPixel)[c];
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (isPacked) {
|
||||
Uint32 word = 0;
|
||||
if (packedLayout.isFloatPacked) {
|
||||
const Float fields[3] = {srcValues[mapping.sourceChannel[0]],
|
||||
srcValues[mapping.sourceChannel[1]],
|
||||
srcValues[mapping.sourceChannel[2]]};
|
||||
word = type == GL_UNSIGNED_INT_5_9_9_9_REV
|
||||
? EncodeSharedExponentRGB9E5(fields)
|
||||
: (EncodeFloatToUnsignedF11(fields[0]) << packedLayout.shift[0]) |
|
||||
(EncodeFloatToUnsignedF11(fields[1]) << packedLayout.shift[1]) |
|
||||
(EncodeFloatToUnsignedF10(fields[2]) << packedLayout.shift[2]);
|
||||
} else {
|
||||
// Normalized encode: round(clamp(v, 0, 1) * (2^bits - 1)) into each field.
|
||||
for (Int ch = 0; ch < packedLayout.fieldCount; ++ch) {
|
||||
const auto fieldMax = static_cast<Float>((1u << packedLayout.width[ch]) - 1u);
|
||||
const auto v = static_cast<Uint32>(std::llround(
|
||||
std::clamp(srcValues[mapping.sourceChannel[ch]], 0.0f, 1.0f) * fieldMax));
|
||||
word |= v << packedLayout.shift[ch];
|
||||
}
|
||||
}
|
||||
WritePackedReadbackWord(dstPixel, word, packedLayout.byteSize);
|
||||
} else {
|
||||
for (Int ch = 0; ch < mapping.channelCount; ++ch) {
|
||||
const Float v = srcValues[mapping.sourceChannel[ch]];
|
||||
Uint8* dstComponent = dstPixel + static_cast<SizeT>(ch) * dstComponentSize;
|
||||
switch (type) {
|
||||
case GL_UNSIGNED_BYTE:
|
||||
*dstComponent =
|
||||
static_cast<Uint8>(std::llround(std::clamp(v, 0.0f, 1.0f) * 255.0));
|
||||
break;
|
||||
case GL_BYTE: {
|
||||
const auto out =
|
||||
static_cast<Int8>(std::llround(std::clamp(v, -1.0f, 1.0f) * 127.0));
|
||||
Memcpy(dstComponent, &out, sizeof(out));
|
||||
break;
|
||||
}
|
||||
case GL_UNSIGNED_SHORT: {
|
||||
const auto out =
|
||||
static_cast<Uint16>(std::llround(std::clamp(v, 0.0f, 1.0f) * 65535.0));
|
||||
Memcpy(dstComponent, &out, sizeof(out));
|
||||
break;
|
||||
}
|
||||
case GL_SHORT: {
|
||||
const auto out =
|
||||
static_cast<Int16>(std::llround(std::clamp(v, -1.0f, 1.0f) * 32767.0));
|
||||
Memcpy(dstComponent, &out, sizeof(out));
|
||||
break;
|
||||
}
|
||||
case GL_UNSIGNED_INT: {
|
||||
const auto out = static_cast<Uint32>(
|
||||
std::llround(static_cast<Double>(std::clamp(v, 0.0f, 1.0f)) * 4294967295.0));
|
||||
Memcpy(dstComponent, &out, sizeof(out));
|
||||
break;
|
||||
}
|
||||
case GL_INT: {
|
||||
const auto out = static_cast<Int32>(
|
||||
std::llround(static_cast<Double>(std::clamp(v, -1.0f, 1.0f)) * 2147483647.0));
|
||||
Memcpy(dstComponent, &out, sizeof(out));
|
||||
break;
|
||||
}
|
||||
case GL_FLOAT:
|
||||
Memcpy(dstComponent, &v, sizeof(v));
|
||||
break;
|
||||
case GL_HALF_FLOAT: {
|
||||
const Uint16 out = EncodeFloatToHalfBits(v);
|
||||
Memcpy(dstComponent, &out, sizeof(out));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace ReadbackImpl
|
||||
} // namespace MobileGL::MG_Backend::DirectGLES
|
||||
|
||||
@@ -45,6 +45,51 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
|
||||
namespace FramebufferImpl {} // namespace FramebufferImpl
|
||||
|
||||
// Pure CPU helpers of the client-format readback conversion (ReadPixels/GetTexImage repack a wide
|
||||
// RGBA(_INTEGER) read into the caller's (format, type) layout). Kept context-free so unit tests can
|
||||
// exercise the exact packing the GL CTS packed_pixels oracle compares against.
|
||||
namespace ReadbackImpl {
|
||||
struct ReadbackChannelMapping {
|
||||
Int sourceChannel[4]; // RGBA source channel feeding each destination component
|
||||
Int channelCount; // destination component count
|
||||
Bool isInteger;
|
||||
};
|
||||
Bool GetReadbackChannelMapping(GLenum format, ReadbackChannelMapping& outMapping);
|
||||
|
||||
// Byte size of one destination component of `type`; packed types report the packed word size.
|
||||
// 0 = type not supported by the conversion path.
|
||||
SizeT GetReadbackComponentSize(GLenum type);
|
||||
|
||||
// Bit-field layout of a GL packed pixel type. width/shift are indexed in the client format's
|
||||
// component order (matching ReadbackChannelMapping); shift is the LSB position of the field in
|
||||
// the packed word: non-REV types pack the first component from the MSB, *_REV types from the
|
||||
// LSB (GL 3.3 table 3.6; field positions mirror the GL CTS glcPackedPixelsTests pack_* oracle).
|
||||
struct PackedReadbackLayout {
|
||||
Int fieldCount; // format components stored in the packed word
|
||||
Int width[4]; // bit width of each component's field
|
||||
Int shift[4]; // LSB bit position of each component's field
|
||||
SizeT byteSize; // packed word size in bytes (1, 2 or 4)
|
||||
Bool isFloatPacked; // 10F_11F_11F_REV / 5_9_9_9_REV: fields hold unsigned small floats
|
||||
};
|
||||
Bool GetPackedReadbackLayout(GLenum type, PackedReadbackLayout& out);
|
||||
|
||||
// Unsigned small-float encoders (EXT_packed_float / EXT_texture_shared_exponent semantics).
|
||||
Uint32 EncodeFloatToUnsignedF11(Float value);
|
||||
Uint32 EncodeFloatToUnsignedF10(Float value);
|
||||
Uint32 EncodeSharedExponentRGB9E5(const Float rgb[3]);
|
||||
|
||||
// Destination bytes per pixel for a (format mapping, type) readback pair; 0 when the pair is
|
||||
// not convertible (unknown type, packed field count != format component count, floating-point
|
||||
// or packed-float type with an integer format).
|
||||
SizeT GetReadbackDstPixelSize(const ReadbackChannelMapping& mapping, GLenum type);
|
||||
|
||||
// Repacks one row of wide RGBA(_INTEGER) texels (4 components of wideType each) into the
|
||||
// client's (format, type) layout. src holds width * 4 * GetReadbackComponentSize(wideType)
|
||||
// bytes, dst receives width * GetReadbackDstPixelSize(mapping, type) bytes.
|
||||
void ConvertWideReadbackRow(const Uint8* src, Uint8* dst, SizeT width, GLenum wideType,
|
||||
const ReadbackChannelMapping& mapping, GLenum type);
|
||||
} // namespace ReadbackImpl
|
||||
|
||||
namespace PrgramImpl {
|
||||
String ProcessOutColorLocations(const String& glslCode);
|
||||
String ForceSupporterOutput(const String& glslCode);
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "Includes.h"
|
||||
#include "Init.h"
|
||||
#include <MG_Backend/BackendObjects.h>
|
||||
#include <MG_Backend/DirectGLES/Utils.h>
|
||||
#include <MG_Impl/GLImpl/Buffer/GL_Buffer.h>
|
||||
#include <MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h>
|
||||
#include <MG_Impl/GLImpl/Getter/GL_Getter.h>
|
||||
@@ -496,3 +497,180 @@ TEST_F(FramebufferTest, BlitNamedFramebufferAllowsDefaultFramebufferZero) {
|
||||
EXPECT_EQ(MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Read).GetBoundObject(), defaultRead);
|
||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||
}
|
||||
|
||||
// ---- Packed-type readback encoding ------------------------------------------------------------------
|
||||
// Oracle-independent guard for the DirectGLES client-format readback conversion: feeds known wide RGBA
|
||||
// rows through ReadbackImpl::ConvertWideReadbackRow and asserts the exact packed words. Field positions
|
||||
// were hand-computed from GL 3.3 table 3.6 and match the GL CTS packed_pixels comparison functions
|
||||
// (glcPackedPixelsTests.cpp pack_UNSIGNED_*): non-REV types pack the first format component from the
|
||||
// most significant bit, *_REV types from the least significant bit.
|
||||
|
||||
namespace {
|
||||
namespace ReadbackImpl = MG_Backend::DirectGLES::ReadbackImpl;
|
||||
|
||||
// Converts a row of wide pixels (4 components of wideType each) into `format`/`type` words.
|
||||
template <typename WordT, typename SrcT>
|
||||
Vector<WordT> ConvertWideRowToPackedWords(const Vector<SrcT>& wide, GLenum wideType, GLenum format,
|
||||
GLenum type) {
|
||||
ReadbackImpl::ReadbackChannelMapping mapping{};
|
||||
EXPECT_TRUE(ReadbackImpl::GetReadbackChannelMapping(format, mapping));
|
||||
EXPECT_EQ(ReadbackImpl::GetReadbackDstPixelSize(mapping, type), sizeof(WordT));
|
||||
const SizeT width = wide.size() / 4;
|
||||
Vector<WordT> out(width, static_cast<WordT>(0));
|
||||
ReadbackImpl::ConvertWideReadbackRow(reinterpret_cast<const Uint8*>(wide.data()),
|
||||
reinterpret_cast<Uint8*>(out.data()), width, wideType, mapping,
|
||||
type);
|
||||
return out;
|
||||
}
|
||||
|
||||
// Normalized encodes read the wide row as RGBA8 (values are v / 255).
|
||||
template <typename WordT>
|
||||
Vector<WordT> ConvertRGBA8Row(const Vector<Uint8>& rgba, GLenum format, GLenum type) {
|
||||
return ConvertWideRowToPackedWords<WordT>(rgba, GL_UNSIGNED_BYTE, format, type);
|
||||
}
|
||||
|
||||
// Wide RGBA8 pattern shared by the normalized-encode tests. Expected fields below are
|
||||
// round(v / 255 * (2^bits - 1)), computed by hand per pixel.
|
||||
// R G B A
|
||||
const Vector<Uint8> kRGBA8Row{255, 0, 128, 64, // P0
|
||||
10, 250, 33, 200, // P1
|
||||
85, 170, 255, 0}; // P2
|
||||
} // namespace
|
||||
|
||||
TEST(PackedReadbackEncodeTest, EncodesUnsignedShort565) {
|
||||
// P0: R=31 G=0 B=round(128*31/255)=16 -> 31<<11 | 0<<5 | 16 = 0xF810
|
||||
// P1: R=round(10*31/255)=1 G=round(250*63/255)=62 B=round(33*31/255)=4 -> 1<<11|62<<5|4 = 0x0FC4
|
||||
// P2: R=round(85*31/255)=10 G=round(170*63/255)=42 B=31 -> 10<<11|42<<5|31 = 0x555F
|
||||
const auto words = ConvertRGBA8Row<Uint16>(kRGBA8Row, GL_RGB, GL_UNSIGNED_SHORT_5_6_5);
|
||||
EXPECT_EQ(words[0], 0xF810u);
|
||||
EXPECT_EQ(words[1], 0x0FC4u);
|
||||
EXPECT_EQ(words[2], 0x555Fu);
|
||||
}
|
||||
|
||||
TEST(PackedReadbackEncodeTest, EncodesUnsignedShort565Rev) {
|
||||
// REV packs R from the LSB: P2 -> 10 | 42<<5 | 31<<11 = 0xFD4A
|
||||
const auto words = ConvertRGBA8Row<Uint16>(kRGBA8Row, GL_RGB, GL_UNSIGNED_SHORT_5_6_5_REV);
|
||||
EXPECT_EQ(words[2], 0xFD4Au);
|
||||
}
|
||||
|
||||
TEST(PackedReadbackEncodeTest, EncodesUnsignedShort4444) {
|
||||
// P0: R=15 G=0 B=round(128*15/255)=8 A=round(64*15/255)=4 -> 0xF084
|
||||
// P2: R=round(85*15/255)=5 G=round(170*15/255)=10 B=15 A=0 -> 0x5AF0
|
||||
const auto words = ConvertRGBA8Row<Uint16>(kRGBA8Row, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4);
|
||||
EXPECT_EQ(words[0], 0xF084u);
|
||||
EXPECT_EQ(words[2], 0x5AF0u);
|
||||
}
|
||||
|
||||
TEST(PackedReadbackEncodeTest, EncodesUnsignedShort4444Rev) {
|
||||
// P0 fields R=15 G=0 B=8 A=4 packed from the LSB -> 15 | 0<<4 | 8<<8 | 4<<12 = 0x480F
|
||||
const auto words = ConvertRGBA8Row<Uint16>(kRGBA8Row, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4_REV);
|
||||
EXPECT_EQ(words[0], 0x480Fu);
|
||||
}
|
||||
|
||||
TEST(PackedReadbackEncodeTest, EncodesUnsignedShort5551) {
|
||||
// P0: R=31 G=0 B=16 A=round(64/255)=0 -> 31<<11 | 16<<1 = 0xF820
|
||||
// P1: R=1 G=round(250*31/255)=30 B=4 A=round(200/255)=1 -> 1<<11|30<<6|4<<1|1 = 0x0F89
|
||||
const auto words = ConvertRGBA8Row<Uint16>(kRGBA8Row, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1);
|
||||
EXPECT_EQ(words[0], 0xF820u);
|
||||
EXPECT_EQ(words[1], 0x0F89u);
|
||||
}
|
||||
|
||||
TEST(PackedReadbackEncodeTest, EncodesUnsignedShort1555Rev) {
|
||||
// P1 fields R=1 G=30 B=4 A=1 packed from the LSB -> 1 | 30<<5 | 4<<10 | 1<<15 = 0x93C1
|
||||
const auto words = ConvertRGBA8Row<Uint16>(kRGBA8Row, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV);
|
||||
EXPECT_EQ(words[1], 0x93C1u);
|
||||
}
|
||||
|
||||
TEST(PackedReadbackEncodeTest, EncodesUnsignedInt2101010Rev) {
|
||||
// P0: R=1023 G=0 B=round(128*1023/255)=514 A=round(64*3/255)=1 -> 1023|514<<20|1<<30 = 0x602003FF
|
||||
// P2: R=round(85*1023/255)=341 G=round(170*1023/255)=682 B=1023 A=0 -> 0x3FFAA955
|
||||
const auto words = ConvertRGBA8Row<Uint32>(kRGBA8Row, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV);
|
||||
EXPECT_EQ(words[0], 0x602003FFu);
|
||||
EXPECT_EQ(words[2], 0x3FFAA955u);
|
||||
}
|
||||
|
||||
TEST(PackedReadbackEncodeTest, EncodesUnsignedInt1010102) {
|
||||
// P2 fields R=341 G=682 B=1023 A=0 packed from the MSB -> 341<<22 | 682<<12 | 1023<<2 = 0x556AAFFC
|
||||
const auto words = ConvertRGBA8Row<Uint32>(kRGBA8Row, GL_RGBA, GL_UNSIGNED_INT_10_10_10_2);
|
||||
EXPECT_EQ(words[2], 0x556AAFFCu);
|
||||
}
|
||||
|
||||
TEST(PackedReadbackEncodeTest, EncodesUnsignedByte332) {
|
||||
// P0: R=7 G=0 B=round(128*3/255)=2 -> 7<<5 | 2 = 0xE2
|
||||
// P1: R=round(10*7/255)=0 G=round(250*7/255)=7 B=round(33*3/255)=0 -> 7<<2 = 0x1C
|
||||
const auto words = ConvertRGBA8Row<Uint8>(kRGBA8Row, GL_RGB, GL_UNSIGNED_BYTE_3_3_2);
|
||||
EXPECT_EQ(words[0], 0xE2u);
|
||||
EXPECT_EQ(words[1], 0x1Cu);
|
||||
}
|
||||
|
||||
TEST(PackedReadbackEncodeTest, EncodesUnsignedByte233Rev) {
|
||||
// P0 fields R=7 G=0 B=2 packed from the LSB -> 7 | 0<<3 | 2<<6 = 0x87
|
||||
const auto words = ConvertRGBA8Row<Uint8>(kRGBA8Row, GL_RGB, GL_UNSIGNED_BYTE_2_3_3_REV);
|
||||
EXPECT_EQ(words[0], 0x87u);
|
||||
}
|
||||
|
||||
TEST(PackedReadbackEncodeTest, Encodes8888KeepsLegacyByteOrder) {
|
||||
// Regression for the previously supported types: P0 = (255, 0, 128, 64).
|
||||
const auto msbFirst = ConvertRGBA8Row<Uint32>(kRGBA8Row, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8);
|
||||
EXPECT_EQ(msbFirst[0], 0xFF008040u);
|
||||
const auto lsbFirst = ConvertRGBA8Row<Uint32>(kRGBA8Row, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV);
|
||||
EXPECT_EQ(lsbFirst[0], 0x408000FFu);
|
||||
}
|
||||
|
||||
TEST(PackedReadbackEncodeTest, EncodesBGRAWithChannelMapping) {
|
||||
// BGRA's first format component is Blue: P0 fields B=8 G=0 R=15 A=4 -> 8<<12 | 15<<4 | 4 = 0x80F4
|
||||
const auto words = ConvertRGBA8Row<Uint16>(kRGBA8Row, GL_BGRA, GL_UNSIGNED_SHORT_4_4_4_4);
|
||||
EXPECT_EQ(words[0], 0x80F4u);
|
||||
}
|
||||
|
||||
TEST(PackedReadbackEncodeTest, EncodesIntegerRGBA2101010RevWithFieldClamp) {
|
||||
// Integer sources clamp to each field's unsigned range (10/10/10/2 bits).
|
||||
const Vector<Uint32> wide{1023u, 1024u, 5u, 4u};
|
||||
const auto words =
|
||||
ConvertWideRowToPackedWords<Uint32>(wide, GL_UNSIGNED_INT, GL_RGBA_INTEGER, GL_UNSIGNED_INT_2_10_10_10_REV);
|
||||
EXPECT_EQ(words[0], 0xC05FFFFFu); // 1023 | 1023<<10 | 5<<20 | 3<<30
|
||||
}
|
||||
|
||||
TEST(PackedReadbackEncodeTest, EncodesIntegerNegativeValuesClampToZero) {
|
||||
const Vector<Int32> wide{-5, 2, 100000, 1};
|
||||
const auto words =
|
||||
ConvertWideRowToPackedWords<Uint32>(wide, GL_INT, GL_RGBA_INTEGER, GL_UNSIGNED_INT_2_10_10_10_REV);
|
||||
EXPECT_EQ(words[0], 0x7FF00800u); // 0 | 2<<10 | 1023<<20 | 1<<30
|
||||
}
|
||||
|
||||
TEST(PackedReadbackEncodeTest, EncodesIntegerRGB565) {
|
||||
const Vector<Uint32> wide{31u, 64u, 2u, 0u};
|
||||
const auto words =
|
||||
ConvertWideRowToPackedWords<Uint16>(wide, GL_UNSIGNED_INT, GL_RGB_INTEGER, GL_UNSIGNED_SHORT_5_6_5);
|
||||
EXPECT_EQ(words[0], 0xFFE2u); // 31<<11 | 63<<5 | 2 (G clamps 64 -> 63)
|
||||
}
|
||||
|
||||
TEST(PackedReadbackEncodeTest, EncodesPackedFloat10F11F11FRev) {
|
||||
// F11(1.0)=0x3C0 F11(0.5)=0x380 F10(0.25)=0x1A0 -> 0x3C0 | 0x380<<11 | 0x1A0<<22 = 0x681C03C0.
|
||||
// Second pixel: values above 65024 clamp to the max finite F11 (0x7BF), negatives go to zero.
|
||||
const Vector<Float> wide{1.0f, 0.5f, 0.25f, 1.0f, 100000.0f, -1.0f, 0.25f, 1.0f};
|
||||
const auto words = ConvertWideRowToPackedWords<Uint32>(wide, GL_FLOAT, GL_RGB, GL_UNSIGNED_INT_10F_11F_11F_REV);
|
||||
EXPECT_EQ(words[0], 0x681C03C0u);
|
||||
EXPECT_EQ(words[1], 0x680007BFu);
|
||||
}
|
||||
|
||||
TEST(PackedReadbackEncodeTest, EncodesSharedExponent5999Rev) {
|
||||
// (1.0, 0.5, 0.25): shared exponent 16, fields 256/128/64 -> 256 | 128<<9 | 64<<18 | 16<<27
|
||||
const Vector<Float> wide{1.0f, 0.5f, 0.25f, 1.0f};
|
||||
const auto words = ConvertWideRowToPackedWords<Uint32>(wide, GL_FLOAT, GL_RGB, GL_UNSIGNED_INT_5_9_9_9_REV);
|
||||
EXPECT_EQ(words[0], 0x81010100u);
|
||||
}
|
||||
|
||||
TEST(PackedReadbackEncodeTest, RejectsMismatchedPackedFieldCounts) {
|
||||
ReadbackImpl::ReadbackChannelMapping rgba{};
|
||||
ASSERT_TRUE(ReadbackImpl::GetReadbackChannelMapping(GL_RGBA, rgba));
|
||||
ReadbackImpl::ReadbackChannelMapping rgbInteger{};
|
||||
ASSERT_TRUE(ReadbackImpl::GetReadbackChannelMapping(GL_RGB_INTEGER, rgbInteger));
|
||||
|
||||
// 3-field packed types never pair with 4-component formats and vice versa.
|
||||
EXPECT_EQ(ReadbackImpl::GetReadbackDstPixelSize(rgba, GL_UNSIGNED_SHORT_5_6_5), 0u);
|
||||
EXPECT_EQ(ReadbackImpl::GetReadbackDstPixelSize(rgbInteger, GL_UNSIGNED_SHORT_4_4_4_4), 0u);
|
||||
// Packed-float RGB types never pair with integer formats.
|
||||
EXPECT_EQ(ReadbackImpl::GetReadbackDstPixelSize(rgbInteger, GL_UNSIGNED_INT_5_9_9_9_REV), 0u);
|
||||
EXPECT_EQ(ReadbackImpl::GetReadbackDstPixelSize(rgbInteger, GL_UNSIGNED_INT_10F_11F_11F_REV), 0u);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user