mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-11 21:58:31 +09:00
[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:
@@ -1036,7 +1036,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
const auto& backendTextureIt = TextureImpl::g_backendTextureObjects.find(textureObject.get());
|
const auto& backendTextureIt = TextureImpl::g_backendTextureObjects.find(textureObject.get());
|
||||||
if (backendTextureIt == TextureImpl::g_backendTextureObjects.end()) continue;
|
if (backendTextureIt == TextureImpl::g_backendTextureObjects.end()) continue;
|
||||||
|
|
||||||
GLenum targetGL = MG_Util::ConvertTextureTargetToGLEnum(target);
|
GLenum targetGL = TextureImpl::ConvertTextureTargetToBackendGLEnum(target);
|
||||||
backendTextureIt->second->Bind(targetGL, unit);
|
backendTextureIt->second->Bind(targetGL, unit);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1847,7 +1847,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
if (!exist) {
|
if (!exist) {
|
||||||
backendObj = MakeShared<TextureImpl::BackendTextureObject>();
|
backendObj = MakeShared<TextureImpl::BackendTextureObject>();
|
||||||
}
|
}
|
||||||
backendObj->Bind(target, unit);
|
backendObj->Bind(TextureImpl::ConvertTextureTargetToBackendGLEnum(textureTarget), unit);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -2137,10 +2137,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
GLenum textureTarget =
|
GLenum textureTarget = TextureImpl::ConvertTextureUploadTargetToBackendGLEnum(
|
||||||
MG_Util::ConvertTextureUploadTargetToGLEnum(attachmentObject.GetTextureUploadTarget());
|
attachmentObject.GetTextureUploadTarget());
|
||||||
if (textureTarget == GL_UNKNOWN_MGL) {
|
if (textureTarget == GL_UNKNOWN_MGL) {
|
||||||
textureTarget = MG_Util::ConvertTextureTargetToGLEnum(texture->GetTarget());
|
textureTarget = TextureImpl::ConvertTextureTargetToBackendGLEnum(texture->GetTarget());
|
||||||
}
|
}
|
||||||
|
|
||||||
const GLuint backendFBOId = backendFBO->GetBackendFramebufferId();
|
const GLuint backendFBOId = backendFBO->GetBackendFramebufferId();
|
||||||
@@ -2637,7 +2637,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
backendTexture->Bind(target, unitIndex);
|
const GLenum backendTarget =
|
||||||
|
TextureImpl::ConvertTextureTargetToBackendGLEnum(MG_Util::ConvertGLEnumToTextureTarget(target));
|
||||||
|
backendTexture->Bind(backendTarget, unitIndex);
|
||||||
DebugImpl::ErrorLopper::Clear();
|
DebugImpl::ErrorLopper::Clear();
|
||||||
// ANGLE/Mesa may validate the currently bound FBO while generating mipmaps.
|
// ANGLE/Mesa may validate the currently bound FBO while generating mipmaps.
|
||||||
// Also detach the source texture from synced FBO objects for ANGLE's validation.
|
// Also detach the source texture from synced FBO objects for ANGLE's validation.
|
||||||
@@ -2645,8 +2647,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
DebugImpl::ErrorLopper::Clear();
|
DebugImpl::ErrorLopper::Clear();
|
||||||
// Bind a complete internal FBO that does not reference the source texture.
|
// Bind a complete internal FBO that does not reference the source texture.
|
||||||
ScopedCompleteFramebufferBinding completeFramebuffer;
|
ScopedCompleteFramebufferBinding completeFramebuffer;
|
||||||
g_GLESFuncs.glGenerateMipmap(target);
|
g_GLESFuncs.glGenerateMipmap(backendTarget);
|
||||||
RecordGLError("glGenerateMipmap", target, texture->GetFormat());
|
RecordGLError("glGenerateMipmap", backendTarget, texture->GetFormat());
|
||||||
}
|
}
|
||||||
|
|
||||||
const GLubyte* GetString(GLenum name) {
|
const GLubyte* GetString(GLenum name) {
|
||||||
@@ -3068,88 +3070,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
return (rowBytes + resolvedAlignment - 1) & ~(resolvedAlignment - 1);
|
return (rowBytes + resolvedAlignment - 1) & ~(resolvedAlignment - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Int GetFloatReadbackChannelCount(GLenum format) {
|
|
||||||
switch (format) {
|
|
||||||
case GL_RED:
|
|
||||||
return 1;
|
|
||||||
case GL_RGBA:
|
|
||||||
return 4;
|
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static Bool ReadPixelsFloatViaUnsignedByte(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format,
|
|
||||||
void* pixels) {
|
|
||||||
if (width <= 0 || height <= 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const Int dstChannels = GetFloatReadbackChannelCount(format);
|
|
||||||
if (dstChannels == 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const GLenum readFormat = format == GL_RED ? GL_RED : GL_RGBA;
|
|
||||||
const Int readChannels = format == GL_RED ? 1 : 4;
|
|
||||||
Vector<Uint8> raw(static_cast<SizeT>(width) * static_cast<SizeT>(height) *
|
|
||||||
static_cast<SizeT>(readChannels));
|
|
||||||
|
|
||||||
GLint prevPixelPackBuffer = 0;
|
|
||||||
g_GLESFuncs.glGetIntegerv(GL_PIXEL_PACK_BUFFER_BINDING, &prevPixelPackBuffer);
|
|
||||||
g_GLESFuncs.glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
|
||||||
g_GLESFuncs.glPixelStorei(GL_PACK_ALIGNMENT, 1);
|
|
||||||
g_GLESFuncs.glPixelStorei(GL_PACK_ROW_LENGTH, 0);
|
|
||||||
g_GLESFuncs.glPixelStorei(GL_PACK_SKIP_ROWS, 0);
|
|
||||||
g_GLESFuncs.glPixelStorei(GL_PACK_SKIP_PIXELS, 0);
|
|
||||||
g_GLESFuncs.glReadPixels(x, y, width, height, readFormat, GL_UNSIGNED_BYTE, raw.data());
|
|
||||||
const GLenum readError = g_GLESFuncs.glGetError();
|
|
||||||
g_GLESFuncs.glBindBuffer(GL_PIXEL_PACK_BUFFER, static_cast<GLuint>(prevPixelPackBuffer));
|
|
||||||
if (readError != GL_NO_ERROR) {
|
|
||||||
MGLOG_E("ReadPixels: GL_FLOAT fallback read failed: %s",
|
|
||||||
MG_Util::ConvertGLEnumToString(readError).c_str());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto packParams = MG_State::pGLContext->GetPixelStoreParameters(false);
|
|
||||||
const SizeT rowPixels = static_cast<SizeT>(packParams.RowLength > 0 ? packParams.RowLength : width);
|
|
||||||
const SizeT dstPixelBytes = static_cast<SizeT>(dstChannels) * sizeof(Float);
|
|
||||||
const SizeT dstRowStride = AlignPixelRow(rowPixels * dstPixelBytes, packParams.Alignment);
|
|
||||||
const SizeT dstOffset = static_cast<SizeT>(std::max(packParams.SkipRows, 0)) * dstRowStride +
|
|
||||||
static_cast<SizeT>(std::max(packParams.SkipPixels, 0)) * dstPixelBytes;
|
|
||||||
const SizeT packedSize = dstOffset + static_cast<SizeT>(height - 1) * dstRowStride +
|
|
||||||
static_cast<SizeT>(width) * dstPixelBytes;
|
|
||||||
Vector<Uint8> packed(packedSize, 0);
|
|
||||||
|
|
||||||
for (GLsizei row = 0; row < height; ++row) {
|
|
||||||
const Uint8* srcRow = raw.data() + static_cast<SizeT>(row) * static_cast<SizeT>(width) *
|
|
||||||
static_cast<SizeT>(readChannels);
|
|
||||||
auto* dstRow = reinterpret_cast<Float*>(packed.data() + dstOffset +
|
|
||||||
static_cast<SizeT>(row) * dstRowStride);
|
|
||||||
for (GLsizei col = 0; col < width; ++col) {
|
|
||||||
const Uint8* src = srcRow + static_cast<SizeT>(col) * static_cast<SizeT>(readChannels);
|
|
||||||
Float* dst = dstRow + static_cast<SizeT>(col) * static_cast<SizeT>(dstChannels);
|
|
||||||
// TODO: extend readback packing to all desktop GL read formats instead of only normalized RED/RGBA.
|
|
||||||
for (Int component = 0; component < dstChannels; ++component) {
|
|
||||||
dst[component] = static_cast<Float>(src[component]) / 255.0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto& pixelPackBufferObject =
|
|
||||||
MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject();
|
|
||||||
if (pixelPackBufferObject) {
|
|
||||||
const SizeT pboOffset = reinterpret_cast<SizeT>(pixels);
|
|
||||||
if (pboOffset + packed.size() > pixelPackBufferObject->GetSize()) {
|
|
||||||
MGLOG_E("ReadPixels: GL_FLOAT fallback PBO is too small");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
pixelPackBufferObject->WritebackFromBackend({packed.data(), packed.size()}, pboOffset);
|
|
||||||
} else if (pixels != nullptr && !packed.empty()) {
|
|
||||||
Memcpy(pixels, packed.data(), packed.size());
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Bool ReadPixelsDepthFloatViaUnsignedInt(GLint x, GLint y, GLsizei width, GLsizei height, void* pixels) {
|
static Bool ReadPixelsDepthFloatViaUnsignedInt(GLint x, GLint y, GLsizei width, GLsizei height, void* pixels) {
|
||||||
if (width <= 0 || height <= 0) {
|
if (width <= 0 || height <= 0) {
|
||||||
return true;
|
return true;
|
||||||
@@ -3180,29 +3100,29 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
static_cast<SizeT>(std::max(packParams.SkipPixels, 0)) * dstPixelBytes;
|
static_cast<SizeT>(std::max(packParams.SkipPixels, 0)) * dstPixelBytes;
|
||||||
const SizeT packedSize = dstOffset + static_cast<SizeT>(height - 1) * dstRowStride +
|
const SizeT packedSize = dstOffset + static_cast<SizeT>(height - 1) * dstRowStride +
|
||||||
static_cast<SizeT>(width) * dstPixelBytes;
|
static_cast<SizeT>(width) * dstPixelBytes;
|
||||||
Vector<Uint8> packed(packedSize, 0);
|
// Only actual pixel rows are written so PACK skip/row-length gap regions stay untouched.
|
||||||
|
|
||||||
for (GLsizei row = 0; row < height; ++row) {
|
|
||||||
const Uint32* srcRow = raw.data() + static_cast<SizeT>(row) * static_cast<SizeT>(width);
|
|
||||||
auto* dstRow = reinterpret_cast<Float*>(packed.data() + dstOffset +
|
|
||||||
static_cast<SizeT>(row) * dstRowStride);
|
|
||||||
for (GLsizei col = 0; col < width; ++col) {
|
|
||||||
// TODO: preserve native depth precision when GLES exposes float depth readback directly.
|
|
||||||
dstRow[col] = static_cast<Float>(static_cast<Double>(srcRow[col]) / 4294967295.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto& pixelPackBufferObject =
|
const auto& pixelPackBufferObject =
|
||||||
MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject();
|
MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject();
|
||||||
if (pixelPackBufferObject) {
|
const SizeT pboOffset = reinterpret_cast<SizeT>(pixels);
|
||||||
const SizeT pboOffset = reinterpret_cast<SizeT>(pixels);
|
if (pixelPackBufferObject && pboOffset + packedSize > pixelPackBufferObject->GetSize()) {
|
||||||
if (pboOffset + packed.size() > pixelPackBufferObject->GetSize()) {
|
MGLOG_E("ReadPixels: depth GL_FLOAT fallback PBO is too small");
|
||||||
MGLOG_E("ReadPixels: depth GL_FLOAT fallback PBO is too small");
|
return true;
|
||||||
return true;
|
}
|
||||||
|
Vector<Float> rowBuf(static_cast<SizeT>(width));
|
||||||
|
for (GLsizei row = 0; row < height; ++row) {
|
||||||
|
const Uint32* srcRow = raw.data() + static_cast<SizeT>(row) * static_cast<SizeT>(width);
|
||||||
|
for (GLsizei col = 0; col < width; ++col) {
|
||||||
|
// TODO: preserve native depth precision when GLES exposes float depth readback directly.
|
||||||
|
rowBuf[col] = static_cast<Float>(static_cast<Double>(srcRow[col]) / 4294967295.0);
|
||||||
|
}
|
||||||
|
const SizeT rowOffset = dstOffset + static_cast<SizeT>(row) * dstRowStride;
|
||||||
|
if (pixelPackBufferObject) {
|
||||||
|
pixelPackBufferObject->WritebackFromBackend(
|
||||||
|
{rowBuf.data(), static_cast<SizeT>(width) * sizeof(Float)}, pboOffset + rowOffset);
|
||||||
|
} else if (pixels != nullptr) {
|
||||||
|
Memcpy(static_cast<Uint8*>(pixels) + rowOffset, rowBuf.data(),
|
||||||
|
static_cast<SizeT>(width) * sizeof(Float));
|
||||||
}
|
}
|
||||||
pixelPackBufferObject->WritebackFromBackend({packed.data(), packed.size()}, pboOffset);
|
|
||||||
} else if (pixels != nullptr && !packed.empty()) {
|
|
||||||
Memcpy(pixels, packed.data(), packed.size());
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -3237,29 +3157,29 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
static_cast<SizeT>(std::max(packParams.SkipPixels, 0)) * dstPixelBytes;
|
static_cast<SizeT>(std::max(packParams.SkipPixels, 0)) * dstPixelBytes;
|
||||||
const SizeT packedSize = dstOffset + static_cast<SizeT>(height - 1) * dstRowStride +
|
const SizeT packedSize = dstOffset + static_cast<SizeT>(height - 1) * dstRowStride +
|
||||||
static_cast<SizeT>(width) * dstPixelBytes;
|
static_cast<SizeT>(width) * dstPixelBytes;
|
||||||
Vector<Uint8> packed(packedSize, 0);
|
// Only actual pixel rows are written so PACK skip/row-length gap regions stay untouched.
|
||||||
|
|
||||||
for (GLsizei row = 0; row < height; ++row) {
|
|
||||||
const Uint8* srcRow = raw.data() + static_cast<SizeT>(row) * static_cast<SizeT>(width);
|
|
||||||
auto* dstRow = reinterpret_cast<Uint32*>(packed.data() + dstOffset +
|
|
||||||
static_cast<SizeT>(row) * dstRowStride);
|
|
||||||
for (GLsizei col = 0; col < width; ++col) {
|
|
||||||
// TODO: switch to native uint stencil readback if the GLES backend exposes it.
|
|
||||||
dstRow[col] = srcRow[col];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto& pixelPackBufferObject =
|
const auto& pixelPackBufferObject =
|
||||||
MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject();
|
MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject();
|
||||||
if (pixelPackBufferObject) {
|
const SizeT pboOffset = reinterpret_cast<SizeT>(pixels);
|
||||||
const SizeT pboOffset = reinterpret_cast<SizeT>(pixels);
|
if (pixelPackBufferObject && pboOffset + packedSize > pixelPackBufferObject->GetSize()) {
|
||||||
if (pboOffset + packed.size() > pixelPackBufferObject->GetSize()) {
|
MGLOG_E("ReadPixels: stencil GL_UNSIGNED_INT fallback PBO is too small");
|
||||||
MGLOG_E("ReadPixels: stencil GL_UNSIGNED_INT fallback PBO is too small");
|
return true;
|
||||||
return true;
|
}
|
||||||
|
Vector<Uint32> rowBuf(static_cast<SizeT>(width));
|
||||||
|
for (GLsizei row = 0; row < height; ++row) {
|
||||||
|
const Uint8* srcRow = raw.data() + static_cast<SizeT>(row) * static_cast<SizeT>(width);
|
||||||
|
for (GLsizei col = 0; col < width; ++col) {
|
||||||
|
// TODO: switch to native uint stencil readback if the GLES backend exposes it.
|
||||||
|
rowBuf[col] = srcRow[col];
|
||||||
|
}
|
||||||
|
const SizeT rowOffset = dstOffset + static_cast<SizeT>(row) * dstRowStride;
|
||||||
|
if (pixelPackBufferObject) {
|
||||||
|
pixelPackBufferObject->WritebackFromBackend(
|
||||||
|
{rowBuf.data(), static_cast<SizeT>(width) * sizeof(Uint32)}, pboOffset + rowOffset);
|
||||||
|
} else if (pixels != nullptr) {
|
||||||
|
Memcpy(static_cast<Uint8*>(pixels) + rowOffset, rowBuf.data(),
|
||||||
|
static_cast<SizeT>(width) * sizeof(Uint32));
|
||||||
}
|
}
|
||||||
pixelPackBufferObject->WritebackFromBackend({packed.data(), packed.size()}, pboOffset);
|
|
||||||
} else if (pixels != nullptr && !packed.empty()) {
|
|
||||||
Memcpy(pixels, packed.data(), packed.size());
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -3291,6 +3211,94 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Component-array read formats usable as (possibly narrow) wide-read sources.
|
||||||
|
static Int GetWideReadChannelCount(GLenum format) {
|
||||||
|
switch (format) {
|
||||||
|
case GL_RED:
|
||||||
|
case GL_RED_INTEGER:
|
||||||
|
return 1;
|
||||||
|
case GL_RG:
|
||||||
|
case GL_RG_INTEGER:
|
||||||
|
return 2;
|
||||||
|
case GL_RGB:
|
||||||
|
case GL_RGB_INTEGER:
|
||||||
|
return 3;
|
||||||
|
case GL_RGBA:
|
||||||
|
case GL_RGBA_INTEGER:
|
||||||
|
return 4;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Bool IsIntegerReadFormat(GLint format) {
|
||||||
|
return format == GL_RED_INTEGER || format == GL_RG_INTEGER || format == GL_RGB_INTEGER ||
|
||||||
|
format == GL_RGBA_INTEGER;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expands a tightly-packed narrow read (1-3 channels per texel) into the 4-channel wide RGBA
|
||||||
|
// layout ConvertWideReadbackRow expects. Missing G/B read zero; missing A reads one, encoded in
|
||||||
|
// the source component type.
|
||||||
|
static void ExpandNarrowWideRead(Vector<Uint8>& data, SizeT pixelCount, Int srcChannels, GLenum componentType) {
|
||||||
|
const SizeT componentSize = GetReadbackComponentSize(componentType);
|
||||||
|
if (componentSize == 0 || srcChannels <= 0 || srcChannels >= 4) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Uint8 zeroBits[4] = {0, 0, 0, 0};
|
||||||
|
Uint8 oneBits[4] = {0, 0, 0, 0};
|
||||||
|
switch (componentType) {
|
||||||
|
case GL_UNSIGNED_BYTE:
|
||||||
|
oneBits[0] = 0xFF;
|
||||||
|
break;
|
||||||
|
case GL_BYTE:
|
||||||
|
oneBits[0] = 0x7F;
|
||||||
|
break;
|
||||||
|
case GL_UNSIGNED_SHORT: {
|
||||||
|
const Uint16 one = 0xFFFF;
|
||||||
|
Memcpy(oneBits, &one, sizeof(one));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case GL_SHORT: {
|
||||||
|
const Int16 one = 0x7FFF;
|
||||||
|
Memcpy(oneBits, &one, sizeof(one));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case GL_HALF_FLOAT: {
|
||||||
|
const Uint16 one = 0x3C00;
|
||||||
|
Memcpy(oneBits, &one, sizeof(one));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case GL_FLOAT: {
|
||||||
|
const Float one = 1.0f;
|
||||||
|
Memcpy(oneBits, &one, sizeof(one));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case GL_UNSIGNED_INT:
|
||||||
|
case GL_INT: {
|
||||||
|
const Uint32 one = 1;
|
||||||
|
Memcpy(oneBits, &one, sizeof(one));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector<Uint8> expanded(pixelCount * 4 * componentSize);
|
||||||
|
for (SizeT i = 0; i < pixelCount; ++i) {
|
||||||
|
const Uint8* src = data.data() + i * static_cast<SizeT>(srcChannels) * componentSize;
|
||||||
|
Uint8* dst = expanded.data() + i * 4 * componentSize;
|
||||||
|
for (Int ch = 0; ch < 4; ++ch) {
|
||||||
|
if (ch < srcChannels) {
|
||||||
|
Memcpy(dst + static_cast<SizeT>(ch) * componentSize, src + static_cast<SizeT>(ch) * componentSize,
|
||||||
|
componentSize);
|
||||||
|
} else {
|
||||||
|
Memcpy(dst + static_cast<SizeT>(ch) * componentSize, ch == 3 ? oneBits : zeroBits, componentSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data = std::move(expanded);
|
||||||
|
}
|
||||||
|
|
||||||
static void DrainESErrors() {
|
static void DrainESErrors() {
|
||||||
for (Int i = 0; i < 32 && g_GLESFuncs.glGetError() != GL_NO_ERROR; ++i) {
|
for (Int i = 0; i < 32 && g_GLESFuncs.glGetError() != GL_NO_ERROR; ++i) {
|
||||||
}
|
}
|
||||||
@@ -3314,16 +3322,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
return componentType != 0 ? static_cast<GLenum>(componentType) : GL_UNSIGNED_NORMALIZED;
|
return componentType != 0 ? static_cast<GLenum>(componentType) : GL_UNSIGNED_NORMALIZED;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reads the current READ framebuffer as wide RGBA(_INTEGER) and repacks the pixels into the client's
|
// Repacks wide RGBA(_INTEGER) rows into the client's (format, type) layout, honoring the
|
||||||
// (format, type) layout. Returns false when the combination is not convertible (the caller keeps its
|
// client-side PACK parameters and the bound pixel-pack buffer. `wide` holds `height` rows of
|
||||||
// "not implemented" skip); returns true when the request was handled, even if it degraded to a logged no-op.
|
// `width` texels, 4 components x GetReadbackComponentSize(wideType) bytes each.
|
||||||
static Bool ReadPixelsViaFormatConversion(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format,
|
// honorPackImageParams: GL_PACK_IMAGE_HEIGHT / GL_PACK_SKIP_IMAGES apply to GetTexImage of 3D
|
||||||
GLenum type, void* pixels) {
|
// images only; ReadPixels ignores them (GL 3.3 section 4.3.1).
|
||||||
ReadbackChannelMapping mapping{};
|
static Bool StoreWideRowsToClient(const Uint8* wide, GLenum wideType, GLsizei width, GLsizei height,
|
||||||
if (!GetReadbackChannelMapping(format, mapping)) {
|
const ReadbackChannelMapping& mapping, GLenum type, void* pixels,
|
||||||
return false;
|
Bool honorPackImageParams) {
|
||||||
}
|
|
||||||
// Covers unknown types, packed field-count/format mismatches and float types on integer formats.
|
|
||||||
const SizeT dstPixelBytes = GetReadbackDstPixelSize(mapping, type);
|
const SizeT dstPixelBytes = GetReadbackDstPixelSize(mapping, type);
|
||||||
if (dstPixelBytes == 0) {
|
if (dstPixelBytes == 0) {
|
||||||
return false;
|
return false;
|
||||||
@@ -3332,91 +3338,20 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
const Bool isPackedType = ReadbackImpl::GetPackedReadbackLayout(type, packedLayout);
|
const Bool isPackedType = ReadbackImpl::GetPackedReadbackLayout(type, packedLayout);
|
||||||
const SizeT dstComponentSize = GetReadbackComponentSize(type);
|
const SizeT dstComponentSize = GetReadbackComponentSize(type);
|
||||||
|
|
||||||
if (width <= 0 || height <= 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const auto& pixelPackBufferObject =
|
const auto& pixelPackBufferObject =
|
||||||
MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject();
|
MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject();
|
||||||
if (!pixelPackBufferObject && pixels == nullptr) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const GLenum attachmentComponentType = QueryReadAttachmentComponentType();
|
|
||||||
const Bool integerAttachment =
|
|
||||||
attachmentComponentType == GL_INT || attachmentComponentType == GL_UNSIGNED_INT;
|
|
||||||
if (mapping.isInteger != integerAttachment) {
|
|
||||||
MGLOG_E("Readback conversion: integer-ness of format %s does not match the read buffer, skipping",
|
|
||||||
MG_Util::ConvertGLEnumToString(format).c_str());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prefer the implementation-defined pair (full precision on e.g. norm16 buffers), then the
|
|
||||||
// spec-guaranteed pair for the attachment class.
|
|
||||||
GLint implFormat = 0;
|
|
||||||
GLint implType = 0;
|
|
||||||
g_GLESFuncs.glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &implFormat);
|
|
||||||
g_GLESFuncs.glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, &implType);
|
|
||||||
|
|
||||||
const GLenum wideFormat = mapping.isInteger ? GL_RGBA_INTEGER : GL_RGBA;
|
|
||||||
GLenum wideTypeCandidates[3];
|
|
||||||
Int wideTypeCandidateCount = 0;
|
|
||||||
if (mapping.isInteger) {
|
|
||||||
if (implFormat == GL_RGBA_INTEGER && (implType == GL_INT || implType == GL_UNSIGNED_INT)) {
|
|
||||||
wideTypeCandidates[wideTypeCandidateCount++] = static_cast<GLenum>(implType);
|
|
||||||
}
|
|
||||||
wideTypeCandidates[wideTypeCandidateCount++] =
|
|
||||||
attachmentComponentType == GL_INT ? GL_INT : GL_UNSIGNED_INT;
|
|
||||||
} else {
|
|
||||||
if (implFormat == GL_RGBA && CanDecodeWideSourceType(static_cast<GLenum>(implType))) {
|
|
||||||
wideTypeCandidates[wideTypeCandidateCount++] = static_cast<GLenum>(implType);
|
|
||||||
}
|
|
||||||
if (attachmentComponentType == GL_FLOAT) {
|
|
||||||
wideTypeCandidates[wideTypeCandidateCount++] = GL_FLOAT;
|
|
||||||
}
|
|
||||||
wideTypeCandidates[wideTypeCandidateCount++] = GL_UNSIGNED_BYTE;
|
|
||||||
}
|
|
||||||
|
|
||||||
GLint prevPixelPackBuffer = 0;
|
|
||||||
g_GLESFuncs.glGetIntegerv(GL_PIXEL_PACK_BUFFER_BINDING, &prevPixelPackBuffer);
|
|
||||||
g_GLESFuncs.glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
|
||||||
g_GLESFuncs.glPixelStorei(GL_PACK_ALIGNMENT, 1);
|
|
||||||
g_GLESFuncs.glPixelStorei(GL_PACK_ROW_LENGTH, 0);
|
|
||||||
g_GLESFuncs.glPixelStorei(GL_PACK_SKIP_ROWS, 0);
|
|
||||||
g_GLESFuncs.glPixelStorei(GL_PACK_SKIP_PIXELS, 0);
|
|
||||||
|
|
||||||
Vector<Uint8> wide;
|
|
||||||
GLenum wideType = GL_NONE;
|
|
||||||
DrainESErrors();
|
|
||||||
for (Int i = 0; i < wideTypeCandidateCount; ++i) {
|
|
||||||
const GLenum candidate = wideTypeCandidates[i];
|
|
||||||
Bool alreadyTried = false;
|
|
||||||
for (Int j = 0; j < i; ++j) {
|
|
||||||
alreadyTried = alreadyTried || wideTypeCandidates[j] == candidate;
|
|
||||||
}
|
|
||||||
if (alreadyTried) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const SizeT candidateComponentSize = GetReadbackComponentSize(candidate);
|
|
||||||
wide.resize(static_cast<SizeT>(width) * static_cast<SizeT>(height) * 4 * candidateComponentSize);
|
|
||||||
g_GLESFuncs.glReadPixels(x, y, width, height, wideFormat, candidate, wide.data());
|
|
||||||
if (g_GLESFuncs.glGetError() == GL_NO_ERROR) {
|
|
||||||
wideType = candidate;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
g_GLESFuncs.glBindBuffer(GL_PIXEL_PACK_BUFFER, static_cast<GLuint>(prevPixelPackBuffer));
|
|
||||||
if (wideType == GL_NONE) {
|
|
||||||
MGLOG_E("Readback conversion: ES accepted no wide read type for format %s type %s, skipping readback",
|
|
||||||
MG_Util::ConvertGLEnumToString(format).c_str(), MG_Util::ConvertGLEnumToString(type).c_str());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Destination layout is computed from the client-side PACK parameters; only the actual pixel
|
// 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.
|
// rows are written so skip regions of the destination stay untouched. GL_PACK_SKIP_IMAGES
|
||||||
|
// skips whole 2D images of GL_PACK_IMAGE_HEIGHT (or `height`) rows for 3D readbacks.
|
||||||
const auto packParams = MG_State::pGLContext->GetPixelStoreParameters(false);
|
const auto packParams = MG_State::pGLContext->GetPixelStoreParameters(false);
|
||||||
const SizeT rowPixels = static_cast<SizeT>(packParams.RowLength > 0 ? packParams.RowLength : width);
|
const SizeT rowPixels = static_cast<SizeT>(packParams.RowLength > 0 ? packParams.RowLength : width);
|
||||||
const SizeT dstRowStride = AlignPixelRow(rowPixels * dstPixelBytes, packParams.Alignment);
|
const SizeT dstRowStride = AlignPixelRow(rowPixels * dstPixelBytes, packParams.Alignment);
|
||||||
const SizeT dstSkipOffset = static_cast<SizeT>(std::max(packParams.SkipRows, 0)) * dstRowStride +
|
const SizeT imageRows = static_cast<SizeT>(packParams.ImageHeight > 0 ? packParams.ImageHeight : height);
|
||||||
|
const SizeT skipImages =
|
||||||
|
honorPackImageParams ? static_cast<SizeT>(std::max(packParams.SkipImages, 0)) : SizeT{0};
|
||||||
|
const SizeT dstSkipOffset = skipImages * imageRows * dstRowStride +
|
||||||
|
static_cast<SizeT>(std::max(packParams.SkipRows, 0)) * dstRowStride +
|
||||||
static_cast<SizeT>(std::max(packParams.SkipPixels, 0)) * dstPixelBytes;
|
static_cast<SizeT>(std::max(packParams.SkipPixels, 0)) * dstPixelBytes;
|
||||||
const SizeT dstRowBytes = static_cast<SizeT>(width) * dstPixelBytes;
|
const SizeT dstRowBytes = static_cast<SizeT>(width) * dstPixelBytes;
|
||||||
|
|
||||||
@@ -3435,7 +3370,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
Vector<Uint8> convertedRow(dstRowBytes);
|
Vector<Uint8> convertedRow(dstRowBytes);
|
||||||
|
|
||||||
for (GLsizei row = 0; row < height; ++row) {
|
for (GLsizei row = 0; row < height; ++row) {
|
||||||
const Uint8* srcRow = wide.data() + static_cast<SizeT>(row) * static_cast<SizeT>(width) * srcPixelBytes;
|
const Uint8* srcRow = wide + static_cast<SizeT>(row) * static_cast<SizeT>(width) * srcPixelBytes;
|
||||||
ReadbackImpl::ConvertWideReadbackRow(srcRow, convertedRow.data(), static_cast<SizeT>(width), wideType,
|
ReadbackImpl::ConvertWideReadbackRow(srcRow, convertedRow.data(), static_cast<SizeT>(width), wideType,
|
||||||
mapping, type);
|
mapping, type);
|
||||||
|
|
||||||
@@ -3456,13 +3391,207 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
Memcpy(static_cast<Uint8*>(pixels) + dstOffset, convertedRow.data(), dstRowBytes);
|
Memcpy(static_cast<Uint8*>(pixels) + dstOffset, convertedRow.data(), dstRowBytes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reads the current READ framebuffer as wide RGBA(_INTEGER) and repacks the pixels into the client's
|
||||||
|
// (format, type) layout. Returns false when the combination is not convertible (the caller keeps its
|
||||||
|
// "not implemented" skip); returns true when the request was handled, even if it degraded to a logged no-op.
|
||||||
|
static Bool ReadPixelsViaFormatConversion(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format,
|
||||||
|
GLenum type, void* pixels, Bool honorPackImageParams = false) {
|
||||||
|
ReadbackChannelMapping mapping{};
|
||||||
|
if (!GetReadbackChannelMapping(format, mapping)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (width <= 0 || height <= 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const auto& pixelPackBufferObject =
|
||||||
|
MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject();
|
||||||
|
if (!pixelPackBufferObject && pixels == nullptr) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const GLenum attachmentComponentType = QueryReadAttachmentComponentType();
|
||||||
|
const Bool integerAttachment =
|
||||||
|
attachmentComponentType == GL_INT || attachmentComponentType == GL_UNSIGNED_INT;
|
||||||
|
if (mapping.isInteger != integerAttachment) {
|
||||||
|
MGLOG_E("Readback conversion: integer-ness of format %s does not match the read buffer, skipping",
|
||||||
|
MG_Util::ConvertGLEnumToString(format).c_str());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prefer the implementation-defined pair (full precision on e.g. norm16 buffers, and possibly
|
||||||
|
// a narrow format like GL_RED/GL_UNSIGNED_SHORT), then the spec/extension-guaranteed pair for
|
||||||
|
// the attachment class. Narrow reads are expanded to RGBA on the CPU afterwards.
|
||||||
|
GLint implFormat = 0;
|
||||||
|
GLint implType = 0;
|
||||||
|
g_GLESFuncs.glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &implFormat);
|
||||||
|
g_GLESFuncs.glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, &implType);
|
||||||
|
|
||||||
|
struct WideReadCandidate {
|
||||||
|
GLenum format;
|
||||||
|
GLenum type;
|
||||||
|
};
|
||||||
|
WideReadCandidate candidates[4];
|
||||||
|
Int candidateCount = 0;
|
||||||
|
if (mapping.isInteger) {
|
||||||
|
if (GetWideReadChannelCount(static_cast<GLenum>(implFormat)) > 0 && IsIntegerReadFormat(implFormat) &&
|
||||||
|
(implType == GL_INT || implType == GL_UNSIGNED_INT)) {
|
||||||
|
candidates[candidateCount++] = {static_cast<GLenum>(implFormat), static_cast<GLenum>(implType)};
|
||||||
|
}
|
||||||
|
candidates[candidateCount++] = {
|
||||||
|
GL_RGBA_INTEGER,
|
||||||
|
attachmentComponentType == GL_INT ? static_cast<GLenum>(GL_INT) : static_cast<GLenum>(GL_UNSIGNED_INT)};
|
||||||
|
} else {
|
||||||
|
if (GetWideReadChannelCount(static_cast<GLenum>(implFormat)) > 0 && !IsIntegerReadFormat(implFormat) &&
|
||||||
|
(CanDecodeWideSourceType(static_cast<GLenum>(implType)) ||
|
||||||
|
(implFormat == GL_RGBA && implType == GL_UNSIGNED_INT_2_10_10_10_REV))) {
|
||||||
|
candidates[candidateCount++] = {static_cast<GLenum>(implFormat), static_cast<GLenum>(implType)};
|
||||||
|
}
|
||||||
|
if (attachmentComponentType == GL_FLOAT) {
|
||||||
|
candidates[candidateCount++] = {GL_RGBA, GL_FLOAT};
|
||||||
|
}
|
||||||
|
if (attachmentComponentType == GL_SIGNED_NORMALIZED) {
|
||||||
|
// EXT_render_snorm attachments read back as RGBA/BYTE (8-bit) or RGBA/SHORT (16-bit).
|
||||||
|
candidates[candidateCount++] = {GL_RGBA, GL_SHORT};
|
||||||
|
candidates[candidateCount++] = {GL_RGBA, GL_BYTE};
|
||||||
|
} else {
|
||||||
|
candidates[candidateCount++] = {GL_RGBA, GL_UNSIGNED_BYTE};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GLint prevPixelPackBuffer = 0;
|
||||||
|
g_GLESFuncs.glGetIntegerv(GL_PIXEL_PACK_BUFFER_BINDING, &prevPixelPackBuffer);
|
||||||
|
g_GLESFuncs.glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
||||||
|
g_GLESFuncs.glPixelStorei(GL_PACK_ALIGNMENT, 1);
|
||||||
|
g_GLESFuncs.glPixelStorei(GL_PACK_ROW_LENGTH, 0);
|
||||||
|
g_GLESFuncs.glPixelStorei(GL_PACK_SKIP_ROWS, 0);
|
||||||
|
g_GLESFuncs.glPixelStorei(GL_PACK_SKIP_PIXELS, 0);
|
||||||
|
|
||||||
|
Vector<Uint8> wide;
|
||||||
|
GLenum wideType = GL_NONE;
|
||||||
|
GLenum readFormat = GL_NONE;
|
||||||
|
Int readChannels = 0;
|
||||||
|
DrainESErrors();
|
||||||
|
for (Int i = 0; i < candidateCount; ++i) {
|
||||||
|
const WideReadCandidate candidate = candidates[i];
|
||||||
|
Bool alreadyTried = false;
|
||||||
|
for (Int j = 0; j < i; ++j) {
|
||||||
|
alreadyTried =
|
||||||
|
alreadyTried || (candidates[j].format == candidate.format && candidates[j].type == candidate.type);
|
||||||
|
}
|
||||||
|
if (alreadyTried) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const Int channels = GetWideReadChannelCount(candidate.format);
|
||||||
|
const SizeT candidateComponentSize = GetReadbackComponentSize(candidate.type);
|
||||||
|
wide.resize(static_cast<SizeT>(width) * static_cast<SizeT>(height) *
|
||||||
|
static_cast<SizeT>(channels) * candidateComponentSize);
|
||||||
|
g_GLESFuncs.glReadPixels(x, y, width, height, candidate.format, candidate.type, wide.data());
|
||||||
|
if (g_GLESFuncs.glGetError() == GL_NO_ERROR) {
|
||||||
|
wideType = candidate.type;
|
||||||
|
readFormat = candidate.format;
|
||||||
|
readChannels = channels;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g_GLESFuncs.glBindBuffer(GL_PIXEL_PACK_BUFFER, static_cast<GLuint>(prevPixelPackBuffer));
|
||||||
|
if (wideType == GL_NONE) {
|
||||||
|
MGLOG_E("Readback conversion: ES accepted no wide read type for format %s type %s, skipping readback",
|
||||||
|
MG_Util::ConvertGLEnumToString(format).c_str(), MG_Util::ConvertGLEnumToString(type).c_str());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wideType == GL_UNSIGNED_INT_2_10_10_10_REV) {
|
||||||
|
// Unpack the packed words into a float wide buffer (full 10-bit precision on e.g.
|
||||||
|
// GL_RGB10_A2 attachments, whose implementation read pair is RGBA/2_10_10_10_REV).
|
||||||
|
const SizeT pixelCount = static_cast<SizeT>(width) * static_cast<SizeT>(height);
|
||||||
|
Vector<Uint8> floatWide(pixelCount * 4 * sizeof(Float));
|
||||||
|
auto* dst = reinterpret_cast<Float*>(floatWide.data());
|
||||||
|
for (SizeT i = 0; i < pixelCount; ++i) {
|
||||||
|
Uint32 word;
|
||||||
|
Memcpy(&word, wide.data() + i * 4, sizeof(word));
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
wide = std::move(floatWide);
|
||||||
|
wideType = GL_FLOAT;
|
||||||
|
readChannels = 4;
|
||||||
|
}
|
||||||
|
if (readChannels < 4) {
|
||||||
|
ExpandNarrowWideRead(wide, static_cast<SizeT>(width) * static_cast<SizeT>(height), readChannels, wideType);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!StoreWideRowsToClient(wide.data(), wideType, width, height, mapping, type, pixels,
|
||||||
|
honorPackImageParams)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
MGLOG_D("Readback conversion: converted %s/%s from wide %s/%s", MG_Util::ConvertGLEnumToString(format).c_str(),
|
MGLOG_D("Readback conversion: converted %s/%s from wide %s/%s", MG_Util::ConvertGLEnumToString(format).c_str(),
|
||||||
MG_Util::ConvertGLEnumToString(type).c_str(), MG_Util::ConvertGLEnumToString(wideFormat).c_str(),
|
MG_Util::ConvertGLEnumToString(type).c_str(), MG_Util::ConvertGLEnumToString(readFormat).c_str(),
|
||||||
MG_Util::ConvertGLEnumToString(wideType).c_str());
|
MG_Util::ConvertGLEnumToString(wideType).c_str());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetTexImage fallback for internal formats the ES driver cannot attach to a framebuffer
|
||||||
|
// (SNORM, RGB16, RGB9_E5, ...): decodes the canonical CPU shadow-mip storage into wide RGBA
|
||||||
|
// rows and repacks them into the client layout. Only valid while the shadow copy is
|
||||||
|
// authoritative, which holds for non-renderable formats (they can never be GPU-written).
|
||||||
|
static Bool GetTexImageViaShadowConversion(MG_State::GLState::TextureObjectMipmap* textureMipmapObject,
|
||||||
|
TextureUploadTarget uploadTarget, GLint level, GLsizei width,
|
||||||
|
GLsizei height, GLenum format, GLenum type, void* pixels) {
|
||||||
|
ReadbackChannelMapping mapping{};
|
||||||
|
if (!GetReadbackChannelMapping(format, mapping)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (GetReadbackDstPixelSize(mapping, type) == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (width <= 0 || height <= 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const auto& pixelPackBufferObject =
|
||||||
|
MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject();
|
||||||
|
if (!pixelPackBufferObject && pixels == nullptr) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const void* shadow = textureMipmapObject->MapMipmapData(uploadTarget, level);
|
||||||
|
if (!shadow) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector<Uint8> wide;
|
||||||
|
Bool isInteger = false;
|
||||||
|
Bool isSigned = false;
|
||||||
|
if (!MG_Util::PixelStoreProcessor::DecodeShadowDataToWideRGBA(
|
||||||
|
textureMipmapObject->GetFormat(), shadow, static_cast<SizeT>(width) * static_cast<SizeT>(height),
|
||||||
|
wide, isInteger, isSigned)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (mapping.isInteger != isInteger) {
|
||||||
|
// Spec-invalid combinations are rejected with GL errors at the state layer already.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const GLenum wideType = isInteger ? (isSigned ? GL_INT : GL_UNSIGNED_INT) : GL_FLOAT;
|
||||||
|
if (!StoreWideRowsToClient(wide.data(), wideType, width, height, mapping, type, pixels,
|
||||||
|
/*honorPackImageParams=*/true)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
MGLOG_D("GetTexImage: converted %s/%s from the CPU shadow copy",
|
||||||
|
MG_Util::ConvertGLEnumToString(format).c_str(), MG_Util::ConvertGLEnumToString(type).c_str());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static Bool IsLegacyNativeReadPixelsFormat(GLenum format) {
|
static Bool IsLegacyNativeReadPixelsFormat(GLenum format) {
|
||||||
return format == GL_RGBA || format == GL_RGBA_INTEGER || format == GL_RED || format == GL_RED_INTEGER ||
|
return format == GL_RGBA || format == GL_RGBA_INTEGER || format == GL_RED || format == GL_RED_INTEGER ||
|
||||||
format == GL_DEPTH_COMPONENT || format == GL_STENCIL_INDEX;
|
format == GL_DEPTH_COMPONENT || format == GL_STENCIL_INDEX;
|
||||||
@@ -3509,7 +3638,16 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
MGLOG_E("ReadPixels: bound READ FBO is not complete");
|
MGLOG_E("ReadPixels: bound READ FBO is not complete");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!useNativeReadback) {
|
// ES only guarantees GL_RGBA/GL_UNSIGNED_BYTE and GL_RGBA_INTEGER/GL_(UNSIGNED_)INT for the
|
||||||
|
// matching attachment class; every other convertible color layout (including GL_RGBA/GL_FLOAT
|
||||||
|
// and legacy GL_RED reads) goes through the wide-format conversion, which picks a wide type
|
||||||
|
// the driver accepts for the current attachment. GL_PACK_SWAP_BYTES has no ES equivalent, so
|
||||||
|
// it always takes the conversion path (which swaps on the CPU).
|
||||||
|
const Bool packSwapBytes = MG_State::pGLContext->GetPixelStoreParameters(false).SwapBytes;
|
||||||
|
const Bool nativeFastPair = !packSwapBytes &&
|
||||||
|
((format == GL_RGBA && type == GL_UNSIGNED_BYTE) ||
|
||||||
|
(format == GL_RGBA_INTEGER && (type == GL_UNSIGNED_INT || type == GL_INT)));
|
||||||
|
if (convertible && !nativeFastPair) {
|
||||||
if (ReadPixelsViaFormatConversion(x, y, width, height, format, type, pixels)) {
|
if (ReadPixelsViaFormatConversion(x, y, width, height, format, type, pixels)) {
|
||||||
MGLOG_D("ReadPixels: finished via client-format conversion");
|
MGLOG_D("ReadPixels: finished via client-format conversion");
|
||||||
return;
|
return;
|
||||||
@@ -3528,10 +3666,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
MGLOG_D("ReadPixels: finished via stencil GL_UNSIGNED_INT fallback");
|
MGLOG_D("ReadPixels: finished via stencil GL_UNSIGNED_INT fallback");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (type == GL_FLOAT && ReadPixelsFloatViaUnsignedByte(x, y, width, height, format, pixels)) {
|
|
||||||
MGLOG_D("ReadPixels: finished via GL_FLOAT fallback");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle PBO
|
// Handle PBO
|
||||||
auto& pixelPackBufferObject =
|
auto& pixelPackBufferObject =
|
||||||
@@ -3667,18 +3801,29 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
TempFBOBinder tempFBOBinder(true);
|
TempFBOBinder tempFBOBinder(true);
|
||||||
|
|
||||||
MGLOG_D("GetTexImage: glFramebufferTexture2D(level=%d)", level);
|
MGLOG_D("GetTexImage: glFramebufferTexture2D(level=%d)", level);
|
||||||
g_GLESFuncs.glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target, backendTexId, level);
|
// The temp FBO is reused across GetTexImage calls: detach the previous color attachment first
|
||||||
|
// so a failed attach below leaves the FBO incomplete instead of silently reading stale contents.
|
||||||
|
g_GLESFuncs.glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
|
||||||
|
const GLenum backendAttachTarget = TextureImpl::ConvertTextureUploadTargetToBackendGLEnum(
|
||||||
|
MG_Util::ConvertGLEnumToTextureUploadTarget(target));
|
||||||
|
if (backendAttachTarget == GL_TEXTURE_3D || backendAttachTarget == GL_TEXTURE_2D_ARRAY) {
|
||||||
|
// ES cannot attach 3D/array textures through glFramebufferTexture2D; read layer 0. Reads
|
||||||
|
// of deeper slices are served from the CPU shadow instead (see the shadow-first branch).
|
||||||
|
g_GLESFuncs.glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, backendTexId, level, 0);
|
||||||
|
} else {
|
||||||
|
g_GLESFuncs.glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
|
||||||
|
backendAttachTarget == GL_UNKNOWN_MGL ? target : backendAttachTarget,
|
||||||
|
backendTexId, level);
|
||||||
|
}
|
||||||
MGLOG_D("GetTexImage: glReadBuffer(GL_COLOR_ATTACHMENT0)");
|
MGLOG_D("GetTexImage: glReadBuffer(GL_COLOR_ATTACHMENT0)");
|
||||||
g_GLESFuncs.glReadBuffer(GL_COLOR_ATTACHMENT0);
|
g_GLESFuncs.glReadBuffer(GL_COLOR_ATTACHMENT0);
|
||||||
|
|
||||||
GLenum fbStatus = g_GLESFuncs.glCheckFramebufferStatus(GL_READ_FRAMEBUFFER);
|
GLenum fbStatus = g_GLESFuncs.glCheckFramebufferStatus(GL_READ_FRAMEBUFFER);
|
||||||
MGLOG_D("GetTexImage: GL_READ_FRAMEBUFFER status = %s", MG_Util::ConvertGLEnumToString(fbStatus).c_str());
|
MGLOG_D("GetTexImage: GL_READ_FRAMEBUFFER status = %s", MG_Util::ConvertGLEnumToString(fbStatus).c_str());
|
||||||
|
|
||||||
if (fbStatus != GL_FRAMEBUFFER_COMPLETE) {
|
// Non-renderable internal formats (SNORM, RGB16, RGB9_E5, ...) leave the temp FBO incomplete;
|
||||||
MGLOG_E("GetTexImage: READ FBO incomplete");
|
// those readbacks are served from the CPU shadow copy below instead of bailing out.
|
||||||
MGLOG_E("GetTexImage: bound READ FBO is not complete");
|
const Bool tempFBOComplete = fbStatus == GL_FRAMEBUFFER_COMPLETE;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__](auto err) {
|
DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__](auto err) {
|
||||||
MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str());
|
MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str());
|
||||||
@@ -3716,15 +3861,45 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
|
|
||||||
MGLOG_D("GetTexImage: mip level %d size = %dx%d", level, size.x(), size.y());
|
MGLOG_D("GetTexImage: mip level %d size = %dx%d", level, size.x(), size.y());
|
||||||
|
|
||||||
if (!useNativeReadback) {
|
// Prefer the client-format conversion for every convertible combination: the "native" ES pairs
|
||||||
if (ReadPixelsViaFormatConversion(0, 0, size.x(), size.y(), format, type, pixels)) {
|
// are only guaranteed for matching attachment classes (e.g. GL_RGBA/GL_UNSIGNED_INT is invalid
|
||||||
|
// for normalized attachments), while the conversion path reads a wide format that is always
|
||||||
|
// accepted and repacks on the CPU.
|
||||||
|
if (convertible) {
|
||||||
|
// 3D/array images read back every slice, but the FBO path can only read one layer:
|
||||||
|
// multi-slice reads are served from the CPU shadow (depth as extra rows, tight layout).
|
||||||
|
const GLsizei shadowRows = size.y() * std::max(size.z(), 1);
|
||||||
|
const Bool multiSlice = size.z() > 1;
|
||||||
|
if (multiSlice &&
|
||||||
|
GetTexImageViaShadowConversion(textureMipmapObject,
|
||||||
|
MG_Util::ConvertGLEnumToTextureUploadTarget(target), level, size.x(),
|
||||||
|
shadowRows, format, type, pixels)) {
|
||||||
|
MGLOG_D("GetTexImage: finished via shadow conversion");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (tempFBOComplete && ReadPixelsViaFormatConversion(0, 0, size.x(), size.y(), format, type, pixels,
|
||||||
|
/*honorPackImageParams=*/true)) {
|
||||||
MGLOG_D("GetTexImage: finished via client-format conversion");
|
MGLOG_D("GetTexImage: finished via client-format conversion");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (GetTexImageViaShadowConversion(textureMipmapObject,
|
||||||
|
MG_Util::ConvertGLEnumToTextureUploadTarget(target), level, size.x(),
|
||||||
|
shadowRows, format, type, pixels)) {
|
||||||
|
MGLOG_D("GetTexImage: finished via shadow conversion");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!tempFBOComplete) {
|
||||||
|
MGLOG_E("GetTexImage: READ FBO incomplete and no shadow copy available, skipping readback");
|
||||||
|
return;
|
||||||
|
}
|
||||||
MGLOG_E("GetTexImage: format %s with type %s is not implemented yet, skipping readback",
|
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());
|
MG_Util::ConvertGLEnumToString(format).c_str(), MG_Util::ConvertGLEnumToString(type).c_str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!tempFBOComplete) {
|
||||||
|
MGLOG_E("GetTexImage: bound READ FBO is not complete");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Handle PBO
|
// Handle PBO
|
||||||
auto& pixelPackBufferObject =
|
auto& pixelPackBufferObject =
|
||||||
|
|||||||
@@ -1476,10 +1476,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
return 2;
|
return 2;
|
||||||
case TextureInternalFormat::RGB8Snorm:
|
case TextureInternalFormat::RGB8Snorm:
|
||||||
case TextureInternalFormat::RGB16:
|
case TextureInternalFormat::RGB16:
|
||||||
|
case TextureInternalFormat::RGB10: // stored as RGB16 (UNorm16 shadow)
|
||||||
|
case TextureInternalFormat::RGB12: // stored as RGB16 (UNorm16 shadow)
|
||||||
case TextureInternalFormat::RGB16Snorm:
|
case TextureInternalFormat::RGB16Snorm:
|
||||||
return 3;
|
return 3;
|
||||||
case TextureInternalFormat::RGBA8Snorm:
|
case TextureInternalFormat::RGBA8Snorm:
|
||||||
case TextureInternalFormat::RGBA16:
|
case TextureInternalFormat::RGBA16:
|
||||||
|
case TextureInternalFormat::RGBA12: // stored as RGBA16 (UNorm16 shadow)
|
||||||
case TextureInternalFormat::RGBA16Snorm:
|
case TextureInternalFormat::RGBA16Snorm:
|
||||||
return 4;
|
return 4;
|
||||||
default:
|
default:
|
||||||
@@ -1574,7 +1577,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
MGLOG_D("Syncing texture mipmaps with backend ID %u to backend for state ID %u", m_backendTextureId,
|
MGLOG_D("Syncing texture mipmaps with backend ID %u to backend for state ID %u", m_backendTextureId,
|
||||||
stateTextureObject->GetExternalIndex());
|
stateTextureObject->GetExternalIndex());
|
||||||
|
|
||||||
GLenum target = MG_Util::ConvertTextureTargetToGLEnum(stateTextureObject->GetTarget());
|
GLenum target = ConvertTextureTargetToBackendGLEnum(stateTextureObject->GetTarget());
|
||||||
auto targetInternal = stateTextureObject->GetTarget();
|
auto targetInternal = stateTextureObject->GetTarget();
|
||||||
MGLOG_D(" Texture target for syncing is %s",
|
MGLOG_D(" Texture target for syncing is %s",
|
||||||
MG_Util::ConvertTextureTargetToString(targetInternal).c_str());
|
MG_Util::ConvertTextureTargetToString(targetInternal).c_str());
|
||||||
@@ -1695,7 +1698,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
auto levelTexelSize = textureMipmapObject->GetMipmapTexelSize(uploadTarget, level);
|
auto levelTexelSize = textureMipmapObject->GetMipmapTexelSize(uploadTarget, level);
|
||||||
auto levelByteSize = textureMipmapObject->GetMipmapByteSize(uploadTarget, level);
|
auto levelByteSize = textureMipmapObject->GetMipmapByteSize(uploadTarget, level);
|
||||||
bool levelDirty = textureMipmapObject->IsStorageDirty(uploadTarget, level);
|
bool levelDirty = textureMipmapObject->IsStorageDirty(uploadTarget, level);
|
||||||
auto glUploadTarget = MG_Util::ConvertTextureUploadTargetToGLEnum(uploadTarget);
|
auto glUploadTarget = ConvertTextureUploadTargetToBackendGLEnum(uploadTarget);
|
||||||
auto* pData = (levelDirty && levelByteSize != 0)
|
auto* pData = (levelDirty && levelByteSize != 0)
|
||||||
? textureMipmapObject->MapMipmapData(uploadTarget, level)
|
? textureMipmapObject->MapMipmapData(uploadTarget, level)
|
||||||
: nullptr;
|
: nullptr;
|
||||||
@@ -1706,19 +1709,22 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
|
|
||||||
DebugImpl::ErrorLopper::Clear();
|
DebugImpl::ErrorLopper::Clear();
|
||||||
g_GLESFuncs.glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
g_GLESFuncs.glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||||
switch (stateTextureObject->GetTarget()) {
|
const IntVec3 uploadSize =
|
||||||
|
GetBackendUploadSize(stateTextureObject->GetTarget(), levelTexelSize);
|
||||||
|
switch (MapToBackendTextureTarget(stateTextureObject->GetTarget())) {
|
||||||
case TextureTarget::Texture2D:
|
case TextureTarget::Texture2D:
|
||||||
case TextureTarget::TextureCubeMap:
|
case TextureTarget::TextureCubeMap:
|
||||||
g_GLESFuncs.glTexImage2D(
|
g_GLESFuncs.glTexImage2D(
|
||||||
glUploadTarget, static_cast<GLint>(level), (GLint)glInternalFormat,
|
glUploadTarget, static_cast<GLint>(level), (GLint)glInternalFormat,
|
||||||
static_cast<GLsizei>(levelTexelSize.x()), static_cast<GLsizei>(levelTexelSize.y()),
|
static_cast<GLsizei>(uploadSize.x()), static_cast<GLsizei>(uploadSize.y()),
|
||||||
0, glFormat, glType, uploadData);
|
0, glFormat, glType, uploadData);
|
||||||
break;
|
break;
|
||||||
case TextureTarget::Texture3D:
|
case TextureTarget::Texture3D:
|
||||||
|
case TextureTarget::Texture2DArray:
|
||||||
g_GLESFuncs.glTexImage3D(
|
g_GLESFuncs.glTexImage3D(
|
||||||
glUploadTarget, static_cast<GLint>(level), (GLint)glInternalFormat,
|
glUploadTarget, static_cast<GLint>(level), (GLint)glInternalFormat,
|
||||||
static_cast<GLsizei>(levelTexelSize.x()), static_cast<GLsizei>(levelTexelSize.y()),
|
static_cast<GLsizei>(uploadSize.x()), static_cast<GLsizei>(uploadSize.y()),
|
||||||
static_cast<GLsizei>(levelTexelSize.z()), 0, glFormat, glType, uploadData);
|
static_cast<GLsizei>(uploadSize.z()), 0, glFormat, glType, uploadData);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
MGLOG_E("Unhandled texture target %s",
|
MGLOG_E("Unhandled texture target %s",
|
||||||
@@ -1782,18 +1788,20 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
} else if (stateTextureObject->IsImmutable() || m_imageBindableStorageRequired) {
|
} else if (stateTextureObject->IsImmutable() || m_imageBindableStorageRequired) {
|
||||||
DebugImpl::ErrorLopper::Clear();
|
DebugImpl::ErrorLopper::Clear();
|
||||||
g_GLESFuncs.glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
g_GLESFuncs.glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||||
switch (targetInternal) {
|
const IntVec3 storageSize = GetBackendUploadSize(targetInternal, baseSize);
|
||||||
|
switch (MapToBackendTextureTarget(targetInternal)) {
|
||||||
case TextureTarget::Texture2D:
|
case TextureTarget::Texture2D:
|
||||||
case TextureTarget::TextureCubeMap:
|
case TextureTarget::TextureCubeMap:
|
||||||
g_GLESFuncs.glTexStorage2D(target, static_cast<GLsizei>(mipmapCount), glInternalFormat,
|
g_GLESFuncs.glTexStorage2D(target, static_cast<GLsizei>(mipmapCount), glInternalFormat,
|
||||||
static_cast<GLsizei>(baseSize.x()),
|
static_cast<GLsizei>(storageSize.x()),
|
||||||
static_cast<GLsizei>(baseSize.y()));
|
static_cast<GLsizei>(storageSize.y()));
|
||||||
break;
|
break;
|
||||||
case TextureTarget::Texture3D:
|
case TextureTarget::Texture3D:
|
||||||
|
case TextureTarget::Texture2DArray:
|
||||||
g_GLESFuncs.glTexStorage3D(target, static_cast<GLsizei>(mipmapCount), glInternalFormat,
|
g_GLESFuncs.glTexStorage3D(target, static_cast<GLsizei>(mipmapCount), glInternalFormat,
|
||||||
static_cast<GLsizei>(baseSize.x()),
|
static_cast<GLsizei>(storageSize.x()),
|
||||||
static_cast<GLsizei>(baseSize.y()),
|
static_cast<GLsizei>(storageSize.y()),
|
||||||
static_cast<GLsizei>(baseSize.z()));
|
static_cast<GLsizei>(storageSize.z()));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
MGLOG_E("Unhandled immutable texture target %s",
|
MGLOG_E("Unhandled immutable texture target %s",
|
||||||
@@ -1817,7 +1825,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
if (levelDirty && levelByteSize != 0) {
|
if (levelDirty && levelByteSize != 0) {
|
||||||
auto levelTexelSize =
|
auto levelTexelSize =
|
||||||
textureMipmapObject->GetMipmapTexelSize(uploadTarget, level);
|
textureMipmapObject->GetMipmapTexelSize(uploadTarget, level);
|
||||||
auto glUploadTarget = MG_Util::ConvertTextureUploadTargetToGLEnum(uploadTarget);
|
auto glUploadTarget = ConvertTextureUploadTargetToBackendGLEnum(uploadTarget);
|
||||||
auto* pData = textureMipmapObject->MapMipmapData(uploadTarget, level);
|
auto* pData = textureMipmapObject->MapMipmapData(uploadTarget, level);
|
||||||
Vector<Float> convertedUploadData;
|
Vector<Float> convertedUploadData;
|
||||||
const void* uploadData = PrepareNormFloatFallbackUpload(
|
const void* uploadData = PrepareNormFloatFallbackUpload(
|
||||||
@@ -1826,20 +1834,23 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
|
|
||||||
DebugImpl::ErrorLopper::Clear();
|
DebugImpl::ErrorLopper::Clear();
|
||||||
g_GLESFuncs.glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
g_GLESFuncs.glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||||
switch (targetInternal) {
|
const IntVec3 uploadSize =
|
||||||
|
GetBackendUploadSize(targetInternal, levelTexelSize);
|
||||||
|
switch (MapToBackendTextureTarget(targetInternal)) {
|
||||||
case TextureTarget::Texture2D:
|
case TextureTarget::Texture2D:
|
||||||
case TextureTarget::TextureCubeMap:
|
case TextureTarget::TextureCubeMap:
|
||||||
g_GLESFuncs.glTexSubImage2D(
|
g_GLESFuncs.glTexSubImage2D(
|
||||||
glUploadTarget, static_cast<GLint>(level), 0, 0,
|
glUploadTarget, static_cast<GLint>(level), 0, 0,
|
||||||
static_cast<GLsizei>(levelTexelSize.x()),
|
static_cast<GLsizei>(uploadSize.x()),
|
||||||
static_cast<GLsizei>(levelTexelSize.y()), glFormat, glType, uploadData);
|
static_cast<GLsizei>(uploadSize.y()), glFormat, glType, uploadData);
|
||||||
break;
|
break;
|
||||||
case TextureTarget::Texture3D:
|
case TextureTarget::Texture3D:
|
||||||
|
case TextureTarget::Texture2DArray:
|
||||||
g_GLESFuncs.glTexSubImage3D(
|
g_GLESFuncs.glTexSubImage3D(
|
||||||
glUploadTarget, static_cast<GLint>(level), 0, 0, 0,
|
glUploadTarget, static_cast<GLint>(level), 0, 0, 0,
|
||||||
static_cast<GLsizei>(levelTexelSize.x()),
|
static_cast<GLsizei>(uploadSize.x()),
|
||||||
static_cast<GLsizei>(levelTexelSize.y()),
|
static_cast<GLsizei>(uploadSize.y()),
|
||||||
static_cast<GLsizei>(levelTexelSize.z()), glFormat, glType, uploadData);
|
static_cast<GLsizei>(uploadSize.z()), glFormat, glType, uploadData);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@@ -1866,7 +1877,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
auto levelTexelSize = textureMipmapObject->GetMipmapTexelSize(uploadTarget, level);
|
auto levelTexelSize = textureMipmapObject->GetMipmapTexelSize(uploadTarget, level);
|
||||||
auto levelByteSize = textureMipmapObject->GetMipmapByteSize(uploadTarget, level);
|
auto levelByteSize = textureMipmapObject->GetMipmapByteSize(uploadTarget, level);
|
||||||
bool levelDirty = textureMipmapObject->IsStorageDirty(uploadTarget, level);
|
bool levelDirty = textureMipmapObject->IsStorageDirty(uploadTarget, level);
|
||||||
auto glUploadTarget = MG_Util::ConvertTextureUploadTargetToGLEnum(uploadTarget);
|
auto glUploadTarget = ConvertTextureUploadTargetToBackendGLEnum(uploadTarget);
|
||||||
auto* pData = (levelDirty && levelByteSize != 0)
|
auto* pData = (levelDirty && levelByteSize != 0)
|
||||||
? textureMipmapObject->MapMipmapData(uploadTarget, level)
|
? textureMipmapObject->MapMipmapData(uploadTarget, level)
|
||||||
: nullptr;
|
: nullptr;
|
||||||
@@ -1883,22 +1894,23 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
DebugImpl::ErrorLopper::Clear();
|
DebugImpl::ErrorLopper::Clear();
|
||||||
g_GLESFuncs.glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
g_GLESFuncs.glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||||
auto textureTarget = stateTextureObject->GetTarget();
|
auto textureTarget = stateTextureObject->GetTarget();
|
||||||
// TODO: handle more texture types
|
const IntVec3 uploadSize = GetBackendUploadSize(textureTarget, levelTexelSize);
|
||||||
switch (textureTarget) {
|
switch (MapToBackendTextureTarget(textureTarget)) {
|
||||||
case TextureTarget::Texture2D:
|
case TextureTarget::Texture2D:
|
||||||
case TextureTarget::TextureCubeMap: {
|
case TextureTarget::TextureCubeMap: {
|
||||||
g_GLESFuncs.glTexImage2D(
|
g_GLESFuncs.glTexImage2D(
|
||||||
glUploadTarget, static_cast<GLint>(level), (GLint)glInternalFormat,
|
glUploadTarget, static_cast<GLint>(level), (GLint)glInternalFormat,
|
||||||
static_cast<GLsizei>(levelTexelSize.x()),
|
static_cast<GLsizei>(uploadSize.x()),
|
||||||
static_cast<GLsizei>(levelTexelSize.y()), 0, glFormat, glType, uploadData);
|
static_cast<GLsizei>(uploadSize.y()), 0, glFormat, glType, uploadData);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case TextureTarget::Texture3D: {
|
case TextureTarget::Texture3D:
|
||||||
|
case TextureTarget::Texture2DArray: {
|
||||||
g_GLESFuncs.glTexImage3D(
|
g_GLESFuncs.glTexImage3D(
|
||||||
glUploadTarget, static_cast<GLint>(level), (GLint)glInternalFormat,
|
glUploadTarget, static_cast<GLint>(level), (GLint)glInternalFormat,
|
||||||
static_cast<GLsizei>(levelTexelSize.x()),
|
static_cast<GLsizei>(uploadSize.x()),
|
||||||
static_cast<GLsizei>(levelTexelSize.y()),
|
static_cast<GLsizei>(uploadSize.y()),
|
||||||
static_cast<GLsizei>(levelTexelSize.z()), 0, glFormat, glType, uploadData);
|
static_cast<GLsizei>(uploadSize.z()), 0, glFormat, glType, uploadData);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
@@ -1965,7 +1977,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
textureMipmapObject->GetMipmapTexelSize(uploadTarget, level).x(),
|
textureMipmapObject->GetMipmapTexelSize(uploadTarget, level).x(),
|
||||||
textureMipmapObject->GetMipmapTexelSize(uploadTarget, level).y(), byteSize);
|
textureMipmapObject->GetMipmapTexelSize(uploadTarget, level).y(), byteSize);
|
||||||
|
|
||||||
auto glUploadTarget = MG_Util::ConvertTextureUploadTargetToGLEnum(uploadTarget);
|
auto glUploadTarget = ConvertTextureUploadTargetToBackendGLEnum(uploadTarget);
|
||||||
g_GLESFuncs.glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
g_GLESFuncs.glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||||
DebugImpl::ErrorLopper::Loop(
|
DebugImpl::ErrorLopper::Loop(
|
||||||
[file = __FILE__, line = __LINE__, func = __func__](GLenum err) {
|
[file = __FILE__, line = __LINE__, func = __func__](GLenum err) {
|
||||||
@@ -1978,19 +1990,22 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
const void* uploadData = PrepareNormFloatFallbackUpload(
|
const void* uploadData = PrepareNormFloatFallbackUpload(
|
||||||
textureMipmapObject->GetFormat(), texelSize, mipData, byteSize, glType,
|
textureMipmapObject->GetFormat(), texelSize, mipData, byteSize, glType,
|
||||||
convertedUploadData);
|
convertedUploadData);
|
||||||
switch (stateTextureObject->GetTarget()) {
|
const IntVec3 uploadSize =
|
||||||
|
GetBackendUploadSize(stateTextureObject->GetTarget(), texelSize);
|
||||||
|
switch (MapToBackendTextureTarget(stateTextureObject->GetTarget())) {
|
||||||
case TextureTarget::Texture2D:
|
case TextureTarget::Texture2D:
|
||||||
case TextureTarget::TextureCubeMap:
|
case TextureTarget::TextureCubeMap:
|
||||||
g_GLESFuncs.glTexSubImage2D(glUploadTarget, static_cast<GLint>(level), 0, 0,
|
g_GLESFuncs.glTexSubImage2D(glUploadTarget, static_cast<GLint>(level), 0, 0,
|
||||||
static_cast<GLsizei>(texelSize.x()),
|
static_cast<GLsizei>(uploadSize.x()),
|
||||||
static_cast<GLsizei>(texelSize.y()), glFormat, glType,
|
static_cast<GLsizei>(uploadSize.y()), glFormat, glType,
|
||||||
uploadData);
|
uploadData);
|
||||||
break;
|
break;
|
||||||
case TextureTarget::Texture3D:
|
case TextureTarget::Texture3D:
|
||||||
|
case TextureTarget::Texture2DArray:
|
||||||
g_GLESFuncs.glTexSubImage3D(glUploadTarget, static_cast<GLint>(level), 0, 0, 0,
|
g_GLESFuncs.glTexSubImage3D(glUploadTarget, static_cast<GLint>(level), 0, 0, 0,
|
||||||
static_cast<GLsizei>(texelSize.x()),
|
static_cast<GLsizei>(uploadSize.x()),
|
||||||
static_cast<GLsizei>(texelSize.y()),
|
static_cast<GLsizei>(uploadSize.y()),
|
||||||
static_cast<GLsizei>(texelSize.z()), glFormat, glType,
|
static_cast<GLsizei>(uploadSize.z()), glFormat, glType,
|
||||||
uploadData);
|
uploadData);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -2083,7 +2098,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
MGLOG_D("Syncing texture built-in sampler with backend ID %u to backend for state ID %u",
|
MGLOG_D("Syncing texture built-in sampler with backend ID %u to backend for state ID %u",
|
||||||
m_backendTextureId, stateTextureObject->GetExternalIndex());
|
m_backendTextureId, stateTextureObject->GetExternalIndex());
|
||||||
|
|
||||||
GLenum target = MG_Util::ConvertTextureTargetToGLEnum(stateTextureObject->GetTarget());
|
GLenum target = ConvertTextureTargetToBackendGLEnum(stateTextureObject->GetTarget());
|
||||||
auto targetInternal = stateTextureObject->GetTarget();
|
auto targetInternal = stateTextureObject->GetTarget();
|
||||||
MGLOG_D(" Texture target for syncing is %s",
|
MGLOG_D(" Texture target for syncing is %s",
|
||||||
MG_Util::ConvertTextureTargetToString(targetInternal).c_str());
|
MG_Util::ConvertTextureTargetToString(targetInternal).c_str());
|
||||||
@@ -2181,7 +2196,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
MGLOG_D("Syncing texture params with backend ID %u to backend for state ID %u", m_backendTextureId,
|
MGLOG_D("Syncing texture params with backend ID %u to backend for state ID %u", m_backendTextureId,
|
||||||
stateTextureObject->GetExternalIndex());
|
stateTextureObject->GetExternalIndex());
|
||||||
|
|
||||||
GLenum target = MG_Util::ConvertTextureTargetToGLEnum(stateTextureObject->GetTarget());
|
GLenum target = ConvertTextureTargetToBackendGLEnum(stateTextureObject->GetTarget());
|
||||||
auto targetInternal = stateTextureObject->GetTarget();
|
auto targetInternal = stateTextureObject->GetTarget();
|
||||||
MGLOG_D(" Texture target for syncing is %s",
|
MGLOG_D(" Texture target for syncing is %s",
|
||||||
MG_Util::ConvertTextureTargetToString(targetInternal).c_str());
|
MG_Util::ConvertTextureTargetToString(targetInternal).c_str());
|
||||||
@@ -2338,11 +2353,21 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
g_GLESFuncs.glFramebufferTexture(glFBOTarget, glBackendAttachment,
|
g_GLESFuncs.glFramebufferTexture(glFBOTarget, glBackendAttachment,
|
||||||
backendTextureObject->GetBackendTextureId(),
|
backendTextureObject->GetBackendTextureId(),
|
||||||
static_cast<GLint>(attachmentObject.GetTextureLevel()));
|
static_cast<GLint>(attachmentObject.GetTextureLevel()));
|
||||||
|
} else if (const auto uploadTarget = attachmentObject.GetTextureUploadTarget();
|
||||||
|
uploadTarget == TextureUploadTarget::Texture3D ||
|
||||||
|
uploadTarget == TextureUploadTarget::Texture2DArray ||
|
||||||
|
uploadTarget == TextureUploadTarget::Texture2DMultisampleArray) {
|
||||||
|
// Single slice/layer of a 3D or array texture: ES has no
|
||||||
|
// glFramebufferTexture3D, layers attach via glFramebufferTextureLayer.
|
||||||
|
g_GLESFuncs.glFramebufferTextureLayer(glFBOTarget, glBackendAttachment,
|
||||||
|
backendTextureObject->GetBackendTextureId(),
|
||||||
|
static_cast<GLint>(attachmentObject.GetTextureLevel()),
|
||||||
|
static_cast<GLint>(attachmentObject.GetTextureLayer()));
|
||||||
} else {
|
} else {
|
||||||
auto glTextureTarget =
|
auto glTextureTarget = TextureImpl::ConvertTextureUploadTargetToBackendGLEnum(
|
||||||
MG_Util::ConvertTextureUploadTargetToGLEnum(attachmentObject.GetTextureUploadTarget());
|
attachmentObject.GetTextureUploadTarget());
|
||||||
if (glTextureTarget == GL_UNKNOWN_MGL) {
|
if (glTextureTarget == GL_UNKNOWN_MGL) {
|
||||||
glTextureTarget = MG_Util::ConvertTextureTargetToGLEnum(textureObject->GetTarget());
|
glTextureTarget = TextureImpl::ConvertTextureTargetToBackendGLEnum(textureObject->GetTarget());
|
||||||
}
|
}
|
||||||
backendTextureObject->Bind(glTextureTarget);
|
backendTextureObject->Bind(glTextureTarget);
|
||||||
g_GLESFuncs.glFramebufferTexture2D(glFBOTarget, glBackendAttachment, glTextureTarget,
|
g_GLESFuncs.glFramebufferTexture2D(glFBOTarget, glBackendAttachment, glTextureTarget,
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
#include "MG_State/GLState/TextureState/TextureEnum.h"
|
#include "MG_State/GLState/TextureState/TextureEnum.h"
|
||||||
#include <MG_State/GLState/TextureState/TextureObject.h>
|
#include <MG_State/GLState/TextureState/TextureObject.h>
|
||||||
#include <MG_State/GLState/Core.h>
|
#include <MG_State/GLState/Core.h>
|
||||||
|
#include <MG_Util/Converters/MGToGL/TextureEnumConverter.h>
|
||||||
|
|
||||||
namespace MobileGL::MG_Backend::DirectGLES {
|
namespace MobileGL::MG_Backend::DirectGLES {
|
||||||
String EmulateBaseInstanceInVertexShader(String source, GLenum shaderType);
|
String EmulateBaseInstanceInVertexShader(String source, GLenum shaderType);
|
||||||
@@ -254,10 +255,48 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
|
|
||||||
namespace TextureImpl {
|
namespace TextureImpl {
|
||||||
inline Bool IsSupportedTextureTarget(TextureTarget target) {
|
inline Bool IsSupportedTextureTarget(TextureTarget target) {
|
||||||
if (target == TextureTarget::Texture1D || target == TextureTarget::TextureRectangle ||
|
// Rectangle textures need non-normalized sampling ES cannot express; everything else is
|
||||||
target == TextureTarget::Texture1DArray || target == TextureTarget::Texture2DArray)
|
// either native or emulated (1D -> 2D with height 1, 1D array -> 2D array, see
|
||||||
return false;
|
// MapToBackendTextureTarget). SPIRV-Cross already emits the matching ESSL samplers and
|
||||||
return true;
|
// coordinate padding for 1D/1D-array shaders.
|
||||||
|
return target != TextureTarget::TextureRectangle;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ES has no 1D targets: 1D textures are stored as 2D (height 1) and 1D arrays as 2D arrays
|
||||||
|
// (height 1, layers in depth). Must match SPIRV-Cross's ES 1D-as-2D shader emulation.
|
||||||
|
inline TextureTarget MapToBackendTextureTarget(TextureTarget target) {
|
||||||
|
switch (target) {
|
||||||
|
case TextureTarget::Texture1D:
|
||||||
|
return TextureTarget::Texture2D;
|
||||||
|
case TextureTarget::Texture1DArray:
|
||||||
|
return TextureTarget::Texture2DArray;
|
||||||
|
default:
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline GLenum ConvertTextureTargetToBackendGLEnum(TextureTarget target) {
|
||||||
|
return MG_Util::ConvertTextureTargetToGLEnum(MapToBackendTextureTarget(target));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline GLenum ConvertTextureUploadTargetToBackendGLEnum(TextureUploadTarget uploadTarget) {
|
||||||
|
switch (uploadTarget) {
|
||||||
|
case TextureUploadTarget::Texture1D:
|
||||||
|
return GL_TEXTURE_2D;
|
||||||
|
case TextureUploadTarget::Texture1DArray:
|
||||||
|
return GL_TEXTURE_2D_ARRAY;
|
||||||
|
default:
|
||||||
|
return MG_Util::ConvertTextureUploadTargetToGLEnum(uploadTarget);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1D arrays store layers in the state-side height; the ES 2D-array image keeps height 1 and
|
||||||
|
// moves the layer count into depth.
|
||||||
|
inline IntVec3 GetBackendUploadSize(TextureTarget stateTarget, const IntVec3& texelSize) {
|
||||||
|
if (stateTarget == TextureTarget::Texture1DArray) {
|
||||||
|
return {texelSize.x(), 1, texelSize.y()};
|
||||||
|
}
|
||||||
|
return texelSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Bool IsMultisampleTextureTarget(TextureTarget target) {
|
inline Bool IsMultisampleTextureTarget(TextureTarget target) {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
#include <MG_Util/Converters/MGToGL/TextureEnumConverter.h>
|
#include <MG_Util/Converters/MGToGL/TextureEnumConverter.h>
|
||||||
#include <MG_Util/Converters/MGToGL/FramebufferEnumConverter.h>
|
#include <MG_Util/Converters/MGToGL/FramebufferEnumConverter.h>
|
||||||
#include <MG_Util/Math/HalfFloat.h>
|
#include <MG_Util/Math/HalfFloat.h>
|
||||||
|
#include <MG_Util/Math/SmallFloat.h>
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
@@ -557,38 +558,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
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) {
|
void WritePackedReadbackWord(Uint8* dst, Uint32 word, SizeT byteSize) {
|
||||||
switch (byteSize) {
|
switch (byteSize) {
|
||||||
case 1: {
|
case 1: {
|
||||||
@@ -608,43 +577,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Uint32 EncodeFloatToUnsignedF11(Float value) { return EncodeFloatToUnsignedSmallFloat(value, 6); }
|
// Shared encoders live in MG_Util/Math/SmallFloat.h so the upload conversion
|
||||||
Uint32 EncodeFloatToUnsignedF10(Float value) { return EncodeFloatToUnsignedSmallFloat(value, 5); }
|
// (PixelStoreProcessor) uses byte-identical packing; kept exported here for unit tests.
|
||||||
|
Uint32 EncodeFloatToUnsignedF11(Float value) { return MG_Util::EncodeFloatToUnsignedF11(value); }
|
||||||
// RGB9E5 shared-exponent encode, following the EXT_texture_shared_exponent spec algorithm
|
Uint32 EncodeFloatToUnsignedF10(Float value) { return MG_Util::EncodeFloatToUnsignedF10(value); }
|
||||||
// (N = 9 mantissa bits, B = 15 exponent bias, Emax = 31).
|
Uint32 EncodeSharedExponentRGB9E5(const Float rgb[3]) { return MG_Util::EncodeSharedExponentRGB9E5(rgb); }
|
||||||
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,
|
void ConvertWideReadbackRow(const Uint8* src, Uint8* dst, SizeT width, GLenum wideType,
|
||||||
const ReadbackChannelMapping& mapping, GLenum type) {
|
const ReadbackChannelMapping& mapping, GLenum type) {
|
||||||
|
|||||||
@@ -2150,8 +2150,8 @@ void main() {
|
|||||||
if (!supported) {
|
if (!supported) {
|
||||||
// SetupDraw's pre-flight should have rejected this already; never upload a null payload.
|
// SetupDraw's pre-flight should have rejected this already; never upload a null payload.
|
||||||
MGLOG_E("UploadAndBindVertexStreams skipped: unsupported current generic vertex attribute type: "
|
MGLOG_E("UploadAndBindVertexStreams skipped: unsupported current generic vertex attribute type: "
|
||||||
"program=%u location=%u type=0x%x",
|
"location=%u type=0x%x",
|
||||||
program.GetExternalIndex(), location, glType);
|
location, glType);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,6 +60,87 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Whether the backend can actually attach this color format to a framebuffer. Preferred
|
||||||
|
// source of truth is the backend's probed format-capability cache (real glCheckFramebufferStatus
|
||||||
|
// probes, so extensions like EXT_render_snorm are respected). Formats a probe-less backend
|
||||||
|
// cannot answer for fall back to a conservative static list of formats no ES driver renders to:
|
||||||
|
// shared-exponent, SNORM, three-channel norm16/float32/sRGB and three-channel integer formats.
|
||||||
|
// Desktop GL treats those as texture-only too (not in the GL 3.3 required-renderable list), so
|
||||||
|
// reporting GL_FRAMEBUFFER_UNSUPPORTED for them is legal.
|
||||||
|
Bool IsColorInternalFormatRenderable(TextureInternalFormat format) {
|
||||||
|
const SizeT formatIndex = static_cast<SizeT>(format);
|
||||||
|
if (MG_Backend::pActiveBackendObject && formatIndex < MG_Backend::kFormatCapabilityFormatCount) {
|
||||||
|
const auto& cache = MG_Backend::pActiveBackendObject->GetFormatCapabilities();
|
||||||
|
const SizeT sentinelFormat = static_cast<SizeT>(TextureInternalFormat::RGBA8);
|
||||||
|
Bool cachePopulated = false;
|
||||||
|
for (SizeT targetIndex = 0; targetIndex < MG_Backend::kFormatCapabilityTargetCount && !cachePopulated;
|
||||||
|
++targetIndex) {
|
||||||
|
cachePopulated = MG_Backend::HasFormatCapability(cache.FullCaps[targetIndex][sentinelFormat],
|
||||||
|
MG_Backend::FormatCapability::Creatable);
|
||||||
|
}
|
||||||
|
if (cachePopulated) {
|
||||||
|
for (SizeT targetIndex = 0; targetIndex < MG_Backend::kFormatCapabilityTargetCount;
|
||||||
|
++targetIndex) {
|
||||||
|
if (MG_Backend::HasFormatCapability(cache.FullCaps[targetIndex][formatIndex],
|
||||||
|
MG_Backend::FormatCapability::FramebufferRenderable) ||
|
||||||
|
MG_Backend::HasFormatCapability(cache.CaveatCaps[targetIndex][formatIndex],
|
||||||
|
MG_Backend::FormatCapability::FramebufferRenderable)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch (format) {
|
||||||
|
case TextureInternalFormat::RGB9E5:
|
||||||
|
case TextureInternalFormat::R8Snorm:
|
||||||
|
case TextureInternalFormat::RG8Snorm:
|
||||||
|
case TextureInternalFormat::RGB8Snorm:
|
||||||
|
case TextureInternalFormat::RGBA8Snorm:
|
||||||
|
case TextureInternalFormat::R16Snorm:
|
||||||
|
case TextureInternalFormat::RG16Snorm:
|
||||||
|
case TextureInternalFormat::RGB16Snorm:
|
||||||
|
case TextureInternalFormat::RGBA16Snorm:
|
||||||
|
case TextureInternalFormat::RGB16:
|
||||||
|
case TextureInternalFormat::RGB10: // stored as RGB16
|
||||||
|
case TextureInternalFormat::RGB12: // stored as RGB16
|
||||||
|
case TextureInternalFormat::RGB16F:
|
||||||
|
case TextureInternalFormat::RGB32F:
|
||||||
|
case TextureInternalFormat::RGB8I:
|
||||||
|
case TextureInternalFormat::RGB8UI:
|
||||||
|
case TextureInternalFormat::RGB16I:
|
||||||
|
case TextureInternalFormat::RGB16UI:
|
||||||
|
case TextureInternalFormat::RGB32I:
|
||||||
|
case TextureInternalFormat::RGB32UI:
|
||||||
|
case TextureInternalFormat::SRGB8:
|
||||||
|
return false;
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool HasNonRenderableColorAttachment(const MG_State::GLState::FramebufferObject& framebufferObject) {
|
||||||
|
const auto& attachments = framebufferObject.GetAllAttachmentObjects();
|
||||||
|
for (SizeT i = 0; i < attachments.size(); ++i) {
|
||||||
|
const auto type = static_cast<FramebufferAttachmentType>(i);
|
||||||
|
if (type < FramebufferAttachmentType::Color0 || type > FramebufferAttachmentType::Color31) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto& attachment = attachments[i];
|
||||||
|
if (!attachment.IsValid()) continue;
|
||||||
|
TextureInternalFormat format = TextureInternalFormat::Unknown;
|
||||||
|
if (attachment.IsTexture() && attachment.GetTexture()) {
|
||||||
|
format = attachment.GetTexture()->GetFormat();
|
||||||
|
} else if (attachment.IsRenderbuffer() && attachment.GetRenderbuffer()) {
|
||||||
|
format = attachment.GetRenderbuffer()->GetInternalFormat();
|
||||||
|
}
|
||||||
|
if (format != TextureInternalFormat::Unknown && !IsColorInternalFormatRenderable(format)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void RecordUnsupportedFramebufferTextureAttachmentError(const char* functionName, const char* detail) {
|
void RecordUnsupportedFramebufferTextureAttachmentError(const char* functionName, const char* detail) {
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
ErrorCode::InvalidOperation,
|
ErrorCode::InvalidOperation,
|
||||||
@@ -556,6 +637,62 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
return framebufferObject;
|
return framebufferObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Attaches a single layer/slice of a 3D or array texture. The attachment model stores the layer
|
||||||
|
// index; the DirectGLES backend attaches it with glFramebufferTextureLayer.
|
||||||
|
static void AttachFramebufferTextureLayer(const char* functionName, GLenum target, GLenum attachment,
|
||||||
|
GLuint texture, GLint level, GLint layer,
|
||||||
|
TextureUploadTarget textureUploadTarget) {
|
||||||
|
if (target == GL_FRAMEBUFFER) {
|
||||||
|
target = GL_DRAW_FRAMEBUFFER;
|
||||||
|
}
|
||||||
|
if (attachment == GL_DEPTH_STENCIL_ATTACHMENT) {
|
||||||
|
AttachFramebufferTextureLayer(functionName, target, GL_DEPTH_ATTACHMENT, texture, level, layer,
|
||||||
|
textureUploadTarget);
|
||||||
|
AttachFramebufferTextureLayer(functionName, target, GL_STENCIL_ATTACHMENT, texture, level, layer,
|
||||||
|
textureUploadTarget);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FramebufferAttachmentType attachmentType = MG_Util::ConvertGLEnumToFramebufferAttachmentType(attachment);
|
||||||
|
const FramebufferTarget framebufferTarget = MG_Util::ConvertGLEnumToFramebufferTarget(target);
|
||||||
|
if (!FramebufferImpl::ValidateFramebufferAttachmentType(attachmentType)) return;
|
||||||
|
if (!FramebufferImpl::ValidateFramebufferTarget(framebufferTarget)) return;
|
||||||
|
if (!TextureImpl::ValidateTextureName(texture, true)) return;
|
||||||
|
|
||||||
|
auto& bindingSlot = MG_State::pGLContext->GetFramebufferBindingSlot(framebufferTarget);
|
||||||
|
auto& framebufferObject = bindingSlot.GetBoundObject();
|
||||||
|
if (!framebufferObject) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
||||||
|
"Framebuffer target is bound to no framebuffer object."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (texture == 0) {
|
||||||
|
framebufferObject->Detach(attachmentType);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& textureObject = MG_State::pGLContext->GetTextureObject(texture);
|
||||||
|
if (!textureObject) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
||||||
|
std::format("Texture object {} is not valid.", texture)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (layer < 0) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidValue,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName, "Layer must be non-negative."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
framebufferObject->AttachTexture(attachmentType, textureObject, textureUploadTarget, level, layer,
|
||||||
|
/*layered=*/false);
|
||||||
|
}
|
||||||
|
|
||||||
void FramebufferTextureLayer_State(GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer) {
|
void FramebufferTextureLayer_State(GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer) {
|
||||||
if (texture == 0) {
|
if (texture == 0) {
|
||||||
const TextureUploadTarget detachTarget = TextureUploadTarget::Texture2D;
|
const TextureUploadTarget detachTarget = TextureUploadTarget::Texture2D;
|
||||||
@@ -563,10 +700,31 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
static_cast<void>(layer);
|
auto& textureObject = MG_State::pGLContext->GetTextureObject(texture);
|
||||||
RecordUnsupportedFramebufferTextureAttachmentError(
|
if (!textureObject) {
|
||||||
__func__,
|
MG_State::pGLContext->RecordError(
|
||||||
"Layered framebuffer texture attachments are not represented by the current framebuffer attachment model.");
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||||
|
std::format("Texture object {} is not valid.", texture)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TextureUploadTarget textureUploadTarget = TextureUploadTarget::Unknown;
|
||||||
|
switch (textureObject->GetTarget()) {
|
||||||
|
case TextureTarget::Texture3D:
|
||||||
|
textureUploadTarget = TextureUploadTarget::Texture3D;
|
||||||
|
break;
|
||||||
|
case TextureTarget::Texture2DArray:
|
||||||
|
textureUploadTarget = TextureUploadTarget::Texture2DArray;
|
||||||
|
break;
|
||||||
|
case TextureTarget::Texture2DMultisampleArray:
|
||||||
|
textureUploadTarget = TextureUploadTarget::Texture2DMultisampleArray;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
RecordUnsupportedFramebufferTextureAttachmentError(
|
||||||
|
__func__, "FramebufferTextureLayer requires a 3D, 2D array or 2D multisample array texture.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
AttachFramebufferTextureLayer(__func__, target, attachment, texture, level, layer, textureUploadTarget);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FramebufferTexture3D_State(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level,
|
void FramebufferTexture3D_State(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level,
|
||||||
@@ -578,10 +736,15 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
static_cast<void>(zoffset);
|
if (textarget != GL_TEXTURE_3D) {
|
||||||
RecordUnsupportedFramebufferTextureAttachmentError(
|
MG_State::pGLContext->RecordError(
|
||||||
__func__,
|
ErrorCode::InvalidEnum,
|
||||||
"3D framebuffer texture slice attachments are not represented by the current framebuffer attachment model.");
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||||
|
"FramebufferTexture3D requires GL_TEXTURE_3D."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
AttachFramebufferTextureLayer(__func__, target, attachment, texture, level, zoffset,
|
||||||
|
TextureUploadTarget::Texture3D);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FramebufferTexture2D_State(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level) {
|
void FramebufferTexture2D_State(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level) {
|
||||||
@@ -1252,6 +1415,9 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT :
|
GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT :
|
||||||
GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT;
|
GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT;
|
||||||
}
|
}
|
||||||
|
if (HasNonRenderableColorAttachment(*framebufferObject)) {
|
||||||
|
return GL_FRAMEBUFFER_UNSUPPORTED;
|
||||||
|
}
|
||||||
if (IsActiveBackendDirectVulkan() &&
|
if (IsActiveBackendDirectVulkan() &&
|
||||||
IsUnsupportedFramebufferForDirectVulkan(*framebufferObject)) {
|
IsUnsupportedFramebufferForDirectVulkan(*framebufferObject)) {
|
||||||
return GL_FRAMEBUFFER_UNSUPPORTED;
|
return GL_FRAMEBUFFER_UNSUPPORTED;
|
||||||
@@ -1277,6 +1443,9 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT :
|
GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT :
|
||||||
GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT;
|
GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT;
|
||||||
}
|
}
|
||||||
|
if (HasNonRenderableColorAttachment(*framebufferObject)) {
|
||||||
|
return GL_FRAMEBUFFER_UNSUPPORTED;
|
||||||
|
}
|
||||||
if (IsActiveBackendDirectVulkan() &&
|
if (IsActiveBackendDirectVulkan() &&
|
||||||
IsUnsupportedFramebufferForDirectVulkan(*framebufferObject)) {
|
IsUnsupportedFramebufferForDirectVulkan(*framebufferObject)) {
|
||||||
return GL_FRAMEBUFFER_UNSUPPORTED;
|
return GL_FRAMEBUFFER_UNSUPPORTED;
|
||||||
@@ -1626,8 +1795,8 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check framebuffer completeness
|
// Check framebuffer completeness (including formats the ES pipeline cannot attach)
|
||||||
if (!framebufferObject->CheckCompleteness()) {
|
if (!framebufferObject->CheckCompleteness() || HasNonRenderableColorAttachment(*framebufferObject)) {
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
ErrorCode::InvalidFramebufferOperation,
|
ErrorCode::InvalidFramebufferOperation,
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "ReadPixels_State", "Framebuffer is incomplete"));
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "ReadPixels_State", "Framebuffer is incomplete"));
|
||||||
@@ -1679,6 +1848,26 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
"No color buffer for color format"));
|
"No color buffer for color format"));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GL 3.3 section 4.3.1: GL_INVALID_OPERATION if format is an integer format and the read
|
||||||
|
// buffer is not an integer format, or vice versa (GL CTS packed_pixels expects the error
|
||||||
|
// for every *_INTEGER readback from a normalized attachment).
|
||||||
|
const auto& readAttachment = framebufferObject->GetAttachment(readBuffer);
|
||||||
|
TextureInternalFormat attachmentFormat = TextureInternalFormat::Unknown;
|
||||||
|
if (readAttachment.IsTexture() && readAttachment.GetTexture()) {
|
||||||
|
attachmentFormat = readAttachment.GetTexture()->GetFormat();
|
||||||
|
} else if (readAttachment.IsRenderbuffer() && readAttachment.GetRenderbuffer()) {
|
||||||
|
attachmentFormat = readAttachment.GetRenderbuffer()->GetInternalFormat();
|
||||||
|
}
|
||||||
|
if (attachmentFormat != TextureInternalFormat::Unknown &&
|
||||||
|
TextureImpl::IsIntegerColorInputFormat(textureInputFormat) !=
|
||||||
|
TextureImpl::IsIntegerColorInternalFormat(attachmentFormat)) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "ReadPixels_State",
|
||||||
|
"Integer-ness of format does not match the read buffer"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Packed-type/format pairing (GL CTS packed_pixels: e.g. GL_RED with GL_UNSIGNED_SHORT_5_6_5 must
|
// Packed-type/format pairing (GL CTS packed_pixels: e.g. GL_RED with GL_UNSIGNED_SHORT_5_6_5 must
|
||||||
|
|||||||
@@ -76,7 +76,8 @@ namespace MobileGL::MG_Impl::GLImpl::FramebufferImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Bool ValidateRenderbufferName(Uint index, Bool allowZero) {
|
Bool ValidateRenderbufferName(Uint index, Bool allowZero) {
|
||||||
if (index == 0 && !allowZero) {
|
if (index == 0) {
|
||||||
|
if (allowZero) return true; // unbind / detach never needs a live object
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
ErrorCode::InvalidValue,
|
ErrorCode::InvalidValue,
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl/FramebufferImpl", "ValidateRenderbufferName",
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl/FramebufferImpl", "ValidateRenderbufferName",
|
||||||
|
|||||||
@@ -1894,7 +1894,11 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
*params = dynamicParameters.MaxTextureSize;
|
*params = dynamicParameters.MaxTextureSize;
|
||||||
break;
|
break;
|
||||||
case GL_MAX_UNIFORM_BUFFER_BINDINGS:
|
case GL_MAX_UNIFORM_BUFFER_BINDINGS:
|
||||||
*params = std::max(dynamicParameters.MaxUniformBufferBindings, kFrontendMinUniformBufferBindings);
|
// Never advertise more bindings than the state layer's indexed-binding array can track
|
||||||
|
// (BufferState::BufferBindingPointCount): the GL CTS state reset calls glBindBufferBase
|
||||||
|
// on every advertised index and expects no error.
|
||||||
|
*params = std::clamp(dynamicParameters.MaxUniformBufferBindings, kFrontendMinUniformBufferBindings,
|
||||||
|
static_cast<GLint>(MG_State::GLState::BufferBindingPointCount));
|
||||||
break;
|
break;
|
||||||
case GL_MAX_UNIFORM_BLOCK_SIZE:
|
case GL_MAX_UNIFORM_BLOCK_SIZE:
|
||||||
*params = dynamicParameters.MaxUniformBlockSize;
|
*params = dynamicParameters.MaxUniformBlockSize;
|
||||||
|
|||||||
@@ -404,13 +404,8 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
"2D multisample textures must use depth 1."));
|
"2D multisample textures must use depth 1."));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (textureTarget == TextureTarget::Texture2DMultisampleArray && depth == 0) {
|
// depth == 0 (like width/height == 0) deallocates the image and is not an error:
|
||||||
MG_State::pGLContext->RecordError(
|
// the GL CTS state reset calls TexImage3DMultisample with all-zero sizes.
|
||||||
ErrorCode::InvalidValue,
|
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
|
|
||||||
"2D multisample array textures must have at least one layer."));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Int maxSamples = GetMaxSupportedTextureSamples(textureInternalFormat);
|
const Int maxSamples = GetMaxSupportedTextureSamples(textureInternalFormat);
|
||||||
if (samples > maxSamples) {
|
if (samples > maxSamples) {
|
||||||
@@ -557,6 +552,14 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
case GL_TEXTURE_SWIZZLE_A: {
|
case GL_TEXTURE_SWIZZLE_A: {
|
||||||
auto swizzleParam = MG_Util::ConvertGLEnumPnameToTextureSwizzleParam(pname);
|
auto swizzleParam = MG_Util::ConvertGLEnumPnameToTextureSwizzleParam(pname);
|
||||||
auto swizzleValue = MG_Util::ConvertGLEnumToTextureSwizzleParam(param);
|
auto swizzleValue = MG_Util::ConvertGLEnumToTextureSwizzleParam(param);
|
||||||
|
if (swizzleValue == TextureSwizzleParam::Unknown) {
|
||||||
|
// GL CTS texture_swizzle.api_errors: single-value TexParameter* with a value outside
|
||||||
|
// [RED, GREEN, BLUE, ALPHA, ZERO, ONE] must raise GL_INVALID_ENUM.
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidEnum,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller, "Invalid texture swizzle value."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
textureObject->SetSwizzleParam(swizzleParam, swizzleValue);
|
textureObject->SetSwizzleParam(swizzleParam, swizzleValue);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -734,6 +737,22 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Texture-parameter lookups must not raise GL_INVALID_OPERATION when the default texture
|
||||||
|
// (name 0) is bound: glTexParameter* on default textures is legal GL (the GL CTS state reset
|
||||||
|
// sets swizzles/levels on texture 0 for every unit x target and expects glGetError() to stay
|
||||||
|
// clean). Parameters set on default textures are accepted as a silent no-op.
|
||||||
|
const SharedPtr<MG_State::GLState::ITextureObject>& GetTextureObjectByTargetForParameter(
|
||||||
|
TextureUploadTarget textureUploadTarget, TextureTarget textureTarget) {
|
||||||
|
if (TextureImpl::IsProxyTextureTarget(textureUploadTarget)) {
|
||||||
|
return TextureImpl::pProxyTextureManager->GetProxyTextureObject(textureUploadTarget);
|
||||||
|
}
|
||||||
|
if (textureTarget == TextureTarget::Unknown) {
|
||||||
|
return nullTextureObject;
|
||||||
|
}
|
||||||
|
auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
|
||||||
|
return activeUnit.GetBindingSlot(textureTarget).GetBoundObject();
|
||||||
|
}
|
||||||
|
|
||||||
void GenerateMipmap_Backend(GLenum target) {
|
void GenerateMipmap_Backend(GLenum target) {
|
||||||
MG_Backend::gBackendFunctionsTable.GL.GenerateMipmap(target);
|
MG_Backend::gBackendFunctionsTable.GL.GenerateMipmap(target);
|
||||||
}
|
}
|
||||||
@@ -1041,7 +1060,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
||||||
|
|
||||||
// ======================= Processing ================================
|
// ======================= Processing ================================
|
||||||
auto& textureObject = GetTextureObjectByTarget(textureUploadTarget, textureTarget);
|
auto& textureObject = GetTextureObjectByTargetForParameter(textureUploadTarget, textureTarget);
|
||||||
if (!textureObject) return;
|
if (!textureObject) return;
|
||||||
|
|
||||||
switch (pname) {
|
switch (pname) {
|
||||||
@@ -1074,6 +1093,12 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
case GL_TEXTURE_SWIZZLE_A: {
|
case GL_TEXTURE_SWIZZLE_A: {
|
||||||
auto swizzleParam = MG_Util::ConvertGLEnumPnameToTextureSwizzleParam(pname);
|
auto swizzleParam = MG_Util::ConvertGLEnumPnameToTextureSwizzleParam(pname);
|
||||||
auto swizzleValue = MG_Util::ConvertGLEnumToTextureSwizzleParam((GLenum)param);
|
auto swizzleValue = MG_Util::ConvertGLEnumToTextureSwizzleParam((GLenum)param);
|
||||||
|
if (swizzleValue == TextureSwizzleParam::Unknown) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidEnum,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "Invalid texture swizzle value."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
textureObject->SetSwizzleParam(swizzleParam, swizzleValue);
|
textureObject->SetSwizzleParam(swizzleParam, swizzleValue);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1120,7 +1145,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
||||||
|
|
||||||
// ======================= Processing ================================
|
// ======================= Processing ================================
|
||||||
auto& textureObject = GetTextureObjectByTarget(textureUploadTarget, textureTarget);
|
auto& textureObject = GetTextureObjectByTargetForParameter(textureUploadTarget, textureTarget);
|
||||||
if (!textureObject) return;
|
if (!textureObject) return;
|
||||||
|
|
||||||
TextureParameterObject_State(textureObject, pname, param, __func__);
|
TextureParameterObject_State(textureObject, pname, param, __func__);
|
||||||
@@ -1136,7 +1161,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
||||||
|
|
||||||
// ======================= Processing ================================
|
// ======================= Processing ================================
|
||||||
auto& textureObject = GetTextureObjectByTarget(textureUploadTarget, textureTarget);
|
auto& textureObject = GetTextureObjectByTargetForParameter(textureUploadTarget, textureTarget);
|
||||||
if (!textureObject) return;
|
if (!textureObject) return;
|
||||||
SetTextureBorderColorFromFloats(textureObject, params);
|
SetTextureBorderColorFromFloats(textureObject, params);
|
||||||
break;
|
break;
|
||||||
@@ -1144,7 +1169,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
case GL_TEXTURE_SWIZZLE_RGBA: {
|
case GL_TEXTURE_SWIZZLE_RGBA: {
|
||||||
TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
|
TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
|
||||||
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
||||||
auto& textureObject = GetTextureObjectByTarget(textureUploadTarget, textureTarget);
|
auto& textureObject = GetTextureObjectByTargetForParameter(textureUploadTarget, textureTarget);
|
||||||
if (!textureObject) return;
|
if (!textureObject) return;
|
||||||
GLint signedParams[4] = {static_cast<GLint>(params[0]), static_cast<GLint>(params[1]),
|
GLint signedParams[4] = {static_cast<GLint>(params[0]), static_cast<GLint>(params[1]),
|
||||||
static_cast<GLint>(params[2]), static_cast<GLint>(params[3])};
|
static_cast<GLint>(params[2]), static_cast<GLint>(params[3])};
|
||||||
@@ -1167,7 +1192,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
||||||
|
|
||||||
// ======================= Processing ================================
|
// ======================= Processing ================================
|
||||||
auto& textureObject = GetTextureObjectByTarget(textureUploadTarget, textureTarget);
|
auto& textureObject = GetTextureObjectByTargetForParameter(textureUploadTarget, textureTarget);
|
||||||
if (!textureObject) return;
|
if (!textureObject) return;
|
||||||
SetTextureBorderColorFromInts(textureObject, params);
|
SetTextureBorderColorFromInts(textureObject, params);
|
||||||
break;
|
break;
|
||||||
@@ -1175,7 +1200,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
case GL_TEXTURE_SWIZZLE_RGBA: {
|
case GL_TEXTURE_SWIZZLE_RGBA: {
|
||||||
TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
|
TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
|
||||||
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
||||||
auto& textureObject = GetTextureObjectByTarget(textureUploadTarget, textureTarget);
|
auto& textureObject = GetTextureObjectByTargetForParameter(textureUploadTarget, textureTarget);
|
||||||
if (!textureObject) return;
|
if (!textureObject) return;
|
||||||
if (!SetTextureSwizzleParamsFromInts(textureObject, params, __func__)) {
|
if (!SetTextureSwizzleParamsFromInts(textureObject, params, __func__)) {
|
||||||
return;
|
return;
|
||||||
@@ -1193,7 +1218,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
case GL_TEXTURE_BORDER_COLOR: {
|
case GL_TEXTURE_BORDER_COLOR: {
|
||||||
TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
|
TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
|
||||||
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
||||||
auto& textureObject = GetTextureObjectByTarget(textureUploadTarget, textureTarget);
|
auto& textureObject = GetTextureObjectByTargetForParameter(textureUploadTarget, textureTarget);
|
||||||
if (!textureObject) return;
|
if (!textureObject) return;
|
||||||
SetTextureBorderColorFromIntegerInts(textureObject, params);
|
SetTextureBorderColorFromIntegerInts(textureObject, params);
|
||||||
break;
|
break;
|
||||||
@@ -1201,7 +1226,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
case GL_TEXTURE_SWIZZLE_RGBA: {
|
case GL_TEXTURE_SWIZZLE_RGBA: {
|
||||||
TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
|
TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
|
||||||
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
||||||
auto& textureObject = GetTextureObjectByTarget(textureUploadTarget, textureTarget);
|
auto& textureObject = GetTextureObjectByTargetForParameter(textureUploadTarget, textureTarget);
|
||||||
if (!textureObject) return;
|
if (!textureObject) return;
|
||||||
GLint signedParams[4] = {static_cast<GLint>(params[0]), static_cast<GLint>(params[1]),
|
GLint signedParams[4] = {static_cast<GLint>(params[0]), static_cast<GLint>(params[1]),
|
||||||
static_cast<GLint>(params[2]), static_cast<GLint>(params[3])};
|
static_cast<GLint>(params[2]), static_cast<GLint>(params[3])};
|
||||||
@@ -1221,7 +1246,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
case GL_TEXTURE_BORDER_COLOR: {
|
case GL_TEXTURE_BORDER_COLOR: {
|
||||||
TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
|
TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
|
||||||
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
||||||
auto& textureObject = GetTextureObjectByTarget(textureUploadTarget, textureTarget);
|
auto& textureObject = GetTextureObjectByTargetForParameter(textureUploadTarget, textureTarget);
|
||||||
if (!textureObject) return;
|
if (!textureObject) return;
|
||||||
SetTextureBorderColorFromUnsignedInts(textureObject, params);
|
SetTextureBorderColorFromUnsignedInts(textureObject, params);
|
||||||
break;
|
break;
|
||||||
@@ -1232,7 +1257,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
||||||
|
|
||||||
// ======================= Processing ================================
|
// ======================= Processing ================================
|
||||||
auto& textureObject = GetTextureObjectByTarget(textureUploadTarget, textureTarget);
|
auto& textureObject = GetTextureObjectByTargetForParameter(textureUploadTarget, textureTarget);
|
||||||
if (!textureObject) return;
|
if (!textureObject) return;
|
||||||
|
|
||||||
Vec4<TextureSwizzleParam> swizzleParams;
|
Vec4<TextureSwizzleParam> swizzleParams;
|
||||||
@@ -1283,6 +1308,9 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
auto& textureObject =
|
auto& textureObject =
|
||||||
isProxy ? TextureImpl::pProxyTextureManager->CreateOrReplaceProxyTextureObject(textureUploadTarget)
|
isProxy ? TextureImpl::pProxyTextureManager->CreateOrReplaceProxyTextureObject(textureUploadTarget)
|
||||||
: bindingSlot.GetBoundObject();
|
: bindingSlot.GetBoundObject();
|
||||||
|
// Respecifying the default texture (name 0) is legal GL (the GL CTS state reset resets it to
|
||||||
|
// zero size); accept it as a silent no-op since default textures carry no storage here.
|
||||||
|
if (!isProxy && !textureObject) return;
|
||||||
if (!TextureImpl::ValidateTextureObject(textureObject)) return;
|
if (!TextureImpl::ValidateTextureObject(textureObject)) return;
|
||||||
if (textureObject->GetStorageType() != TextureStorageType::Mipmap) {
|
if (textureObject->GetStorageType() != TextureStorageType::Mipmap) {
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
@@ -1324,6 +1352,9 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
auto& textureObject =
|
auto& textureObject =
|
||||||
isProxy ? TextureImpl::pProxyTextureManager->CreateOrReplaceProxyTextureObject(textureUploadTarget)
|
isProxy ? TextureImpl::pProxyTextureManager->CreateOrReplaceProxyTextureObject(textureUploadTarget)
|
||||||
: bindingSlot.GetBoundObject();
|
: bindingSlot.GetBoundObject();
|
||||||
|
// Respecifying the default texture (name 0) is legal GL (the GL CTS state reset resets it to
|
||||||
|
// zero size); accept it as a silent no-op since default textures carry no storage here.
|
||||||
|
if (!isProxy && !textureObject) return;
|
||||||
if (!TextureImpl::ValidateTextureObject(textureObject)) return;
|
if (!TextureImpl::ValidateTextureObject(textureObject)) return;
|
||||||
if (textureObject->GetStorageType() != TextureStorageType::Mipmap) {
|
if (textureObject->GetStorageType() != TextureStorageType::Mipmap) {
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
@@ -1402,6 +1433,9 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
: bindingSlot.GetBoundObject();
|
: bindingSlot.GetBoundObject();
|
||||||
|
|
||||||
// ===================== Error Checking ==============================
|
// ===================== Error Checking ==============================
|
||||||
|
// Respecifying the default texture (name 0) is legal GL (the GL CTS state reset resets every
|
||||||
|
// default texture to zero size); accept it as a silent no-op.
|
||||||
|
if (!isProxy && !textureObject) return;
|
||||||
if (!TextureImpl::ValidateTextureObject(textureObject)) return;
|
if (!TextureImpl::ValidateTextureObject(textureObject)) return;
|
||||||
if (!ValidateTextureMutable(textureObject, __func__)) return;
|
if (!ValidateTextureMutable(textureObject, __func__)) return;
|
||||||
|
|
||||||
@@ -1521,6 +1555,9 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
: bindingSlot.GetBoundObject();
|
: bindingSlot.GetBoundObject();
|
||||||
|
|
||||||
// ===================== Error Checking ==============================
|
// ===================== Error Checking ==============================
|
||||||
|
// Respecifying the default texture (name 0) is legal GL (the GL CTS state reset resets every
|
||||||
|
// default texture to zero size); accept it as a silent no-op.
|
||||||
|
if (!isProxy && !textureObject) return;
|
||||||
if (!TextureImpl::ValidateTextureObject(textureObject)) return;
|
if (!TextureImpl::ValidateTextureObject(textureObject)) return;
|
||||||
if (!ValidateTextureMutable(textureObject, __func__)) return;
|
if (!ValidateTextureMutable(textureObject, __func__)) return;
|
||||||
|
|
||||||
@@ -1625,6 +1662,8 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
auto& textureObject =
|
auto& textureObject =
|
||||||
isProxy ? TextureImpl::pProxyTextureManager->CreateOrReplaceProxyTextureObject(textureUploadTarget)
|
isProxy ? TextureImpl::pProxyTextureManager->CreateOrReplaceProxyTextureObject(textureUploadTarget)
|
||||||
: bindingSlot.GetBoundObject();
|
: bindingSlot.GetBoundObject();
|
||||||
|
// Respecifying the default texture (name 0) is legal GL; accept it as a silent no-op.
|
||||||
|
if (!isProxy && !textureObject) return;
|
||||||
if (!TextureImpl::ValidateTextureObject(textureObject)) return;
|
if (!TextureImpl::ValidateTextureObject(textureObject)) return;
|
||||||
if (!ValidateTextureMutable(textureObject, __func__)) return;
|
if (!ValidateTextureMutable(textureObject, __func__)) return;
|
||||||
|
|
||||||
@@ -1680,13 +1719,19 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
if (!TextureImpl::ValidateTextureUploadTarget(textureUploadTarget)) return;
|
if (!TextureImpl::ValidateTextureUploadTarget(textureUploadTarget)) return;
|
||||||
if (!TextureImpl::ValidateTextureInternalFormat(textureInternalFormat)) return;
|
if (!TextureImpl::ValidateTextureInternalFormat(textureInternalFormat)) return;
|
||||||
// TODO: make sure `internalformat` is in one of supported format for TexBuffer
|
// TODO: make sure `internalformat` is in one of supported format for TexBuffer
|
||||||
auto& bufferObject = MG_State::pGLContext->GetBufferObject(buffer);
|
// buffer == 0 is a legal detach (the GL CTS state reset calls glTexBuffer(..., 0) and
|
||||||
if (!bufferObject) {
|
// expects no error); only a non-zero name that does not exist is GL_INVALID_OPERATION.
|
||||||
MG_State::pGLContext->RecordError(
|
SharedPtr<MG_State::GLState::BufferObject> bufferObject;
|
||||||
ErrorCode::InvalidOperation,
|
if (buffer != 0) {
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
bufferObject = MG_State::pGLContext->GetBufferObject(buffer);
|
||||||
"`buffer` is not zero and is not the name of an existing buffer object."));
|
if (!bufferObject) {
|
||||||
return;
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>(
|
||||||
|
"MG_Impl/GLImpl", __func__,
|
||||||
|
"`buffer` is not zero and is not the name of an existing buffer object."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ======================= Processing ================================
|
// ======================= Processing ================================
|
||||||
@@ -1695,6 +1740,8 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
auto& textureObject = bindingSlot.GetBoundObject();
|
auto& textureObject = bindingSlot.GetBoundObject();
|
||||||
|
|
||||||
// ===================== Error Checking ==============================
|
// ===================== Error Checking ==============================
|
||||||
|
// Detaching from the default texture (name 0) is a silent no-op.
|
||||||
|
if (!textureObject && buffer == 0) return;
|
||||||
if (!TextureImpl::ValidateTextureObject(textureObject)) return;
|
if (!TextureImpl::ValidateTextureObject(textureObject)) return;
|
||||||
if (textureObject->GetStorageType() != TextureStorageType::Buffer) {
|
if (textureObject->GetStorageType() != TextureStorageType::Buffer) {
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
@@ -2572,14 +2619,22 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
|
|
||||||
void ActiveTexture_State(GLenum texture) {
|
void ActiveTexture_State(GLenum texture) {
|
||||||
// ===================== Error Checking ==============================
|
// ===================== Error Checking ==============================
|
||||||
if (texture < GL_TEXTURE0 || texture > GL_TEXTURE31) {
|
// The valid range is [GL_TEXTURE0, GL_TEXTURE0 + GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS):
|
||||||
|
// the GL CTS state reset iterates every advertised combined unit, so rejecting units the
|
||||||
|
// implementation itself reports would leave a sticky GL_INVALID_ENUM behind.
|
||||||
|
Int maxCombinedUnits = static_cast<Int>(MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS);
|
||||||
|
if (MG_Backend::pActiveBackendObject) {
|
||||||
|
maxCombinedUnits = std::min(
|
||||||
|
maxCombinedUnits, MG_Backend::pActiveBackendObject->GetDynamicParameters().MaxCombinedTextureImageUnits);
|
||||||
|
}
|
||||||
|
if (texture < GL_TEXTURE0 || static_cast<Int>(texture - GL_TEXTURE0) >= maxCombinedUnits) {
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
ErrorCode::InvalidEnum,
|
ErrorCode::InvalidEnum,
|
||||||
MakeUnique<GenericErrorInfo>(
|
MakeUnique<GenericErrorInfo>(
|
||||||
"MG_Impl/GLImpl", "ActiveTexture_State",
|
"MG_Impl/GLImpl", "ActiveTexture_State",
|
||||||
std::format("Texture must be one of GL_TEXTUREi, where i is in the range 0 to 31, but got "
|
std::format("Texture must be one of GL_TEXTUREi, where i is in the range 0 to {}, but got "
|
||||||
"invalid enum: 0x{:X}, which may stand for unit {}.",
|
"invalid enum: 0x{:X}, which may stand for unit {}.",
|
||||||
texture, texture - GL_TEXTURE0)));
|
maxCombinedUnits - 1, texture, texture - GL_TEXTURE0)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ namespace MobileGL::MG_Impl::GLImpl::TextureImpl {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Bool IsIntegerColorInputFormat(TextureInputFormat format) {
|
Bool IsIntegerColorInputFormat(TextureInputFormat format) {
|
||||||
return format == TextureInputFormat::RInteger || format == TextureInputFormat::RGInteger ||
|
return format == TextureInputFormat::RInteger || format == TextureInputFormat::RGInteger ||
|
||||||
format == TextureInputFormat::RGBInteger || format == TextureInputFormat::BGRInteger ||
|
format == TextureInputFormat::RGBInteger || format == TextureInputFormat::BGRInteger ||
|
||||||
format == TextureInputFormat::RGBAInteger || format == TextureInputFormat::BGRAInteger ||
|
format == TextureInputFormat::RGBAInteger || format == TextureInputFormat::BGRAInteger ||
|
||||||
@@ -183,7 +183,7 @@ namespace MobileGL::MG_Impl::GLImpl::TextureImpl {
|
|||||||
format == TextureInputFormat::AlphaInteger;
|
format == TextureInputFormat::AlphaInteger;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Bool IsIntegerColorInternalFormat(TextureInternalFormat internalFormat) {
|
Bool IsIntegerColorInternalFormat(TextureInternalFormat internalFormat) {
|
||||||
switch (internalFormat) {
|
switch (internalFormat) {
|
||||||
case TextureInternalFormat::R8I:
|
case TextureInternalFormat::R8I:
|
||||||
case TextureInternalFormat::R8UI:
|
case TextureInternalFormat::R8UI:
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ namespace MobileGL::MG_Impl::GLImpl::TextureImpl {
|
|||||||
Bool ValidateTextureSizeRange(Int width, Int height, Int depth);
|
Bool ValidateTextureSizeRange(Int width, Int height, Int depth);
|
||||||
Bool ValidateTextureInternalFormat(TextureInternalFormat format);
|
Bool ValidateTextureInternalFormat(TextureInternalFormat format);
|
||||||
Bool ValidateTextureBorderNumber(Int border);
|
Bool ValidateTextureBorderNumber(Int border);
|
||||||
|
Bool IsIntegerColorInputFormat(TextureInputFormat format);
|
||||||
|
Bool IsIntegerColorInternalFormat(TextureInternalFormat internalFormat);
|
||||||
Bool ValidateClientFormatTypePairing(TextureInputFormat format, TexturePixelDataType type);
|
Bool ValidateClientFormatTypePairing(TextureInputFormat format, TexturePixelDataType type);
|
||||||
Bool ValidateTextureInternalFormatCompatibleWithInput(TextureInputFormat format,
|
Bool ValidateTextureInternalFormatCompatibleWithInput(TextureInputFormat format,
|
||||||
TextureInternalFormat internalFormat,
|
TextureInternalFormat internalFormat,
|
||||||
|
|||||||
@@ -78,15 +78,10 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool ValidateCurrentVertexAttribIndex(GLuint index, const char* funcName) {
|
static bool ValidateCurrentVertexAttribIndex(GLuint index, const char* funcName) {
|
||||||
if (!VertexArrayImpl::ValidateVertexAttributeIndex(index)) return false;
|
// Core GL allows setting the current value of every generic attribute, including 0
|
||||||
if (index == 0) {
|
// (the GL CTS state reset calls glVertexAttrib4f(0, ...) and expects no error).
|
||||||
MG_State::pGLContext->RecordError(
|
static_cast<void>(funcName);
|
||||||
ErrorCode::InvalidOperation,
|
return VertexArrayImpl::ValidateVertexAttributeIndex(index);
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", funcName,
|
|
||||||
"Generic vertex attribute 0 current value cannot be modified."));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool TryGetVertexAttribute(GLuint index, const MG_State::GLState::VertexAttribute** outAttr) {
|
static bool TryGetVertexAttribute(GLuint index, const MG_State::GLState::VertexAttribute** outAttr) {
|
||||||
|
|||||||
@@ -674,3 +674,86 @@ TEST(PackedReadbackEncodeTest, RejectsMismatchedPackedFieldCounts) {
|
|||||||
EXPECT_EQ(ReadbackImpl::GetReadbackDstPixelSize(rgbInteger, GL_UNSIGNED_INT_5_9_9_9_REV), 0u);
|
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);
|
EXPECT_EQ(ReadbackImpl::GetReadbackDstPixelSize(rgbInteger, GL_UNSIGNED_INT_10F_11F_11F_REV), 0u);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- GL CTS packed_pixels readback root-cause regressions --------------------------------------
|
||||||
|
|
||||||
|
TEST_F(FramebufferTest, ReadPixelsRejectsIntegerFormatMismatchWithReadBuffer) {
|
||||||
|
GLuint framebuffer = 0;
|
||||||
|
GLuint texture = 0;
|
||||||
|
MG_Impl::GLImpl::CreateFramebuffers(1, &framebuffer);
|
||||||
|
MG_Impl::GLImpl::CreateTextures(GL_TEXTURE_2D, 1, &texture);
|
||||||
|
MG_Impl::GLImpl::TextureStorage2D(texture, 1, GL_RGBA8UI, 4, 4);
|
||||||
|
MG_Impl::GLImpl::NamedFramebufferTexture(framebuffer, GL_COLOR_ATTACHMENT0, texture, 0);
|
||||||
|
MG_Impl::GLImpl::BindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
|
||||||
|
|
||||||
|
MG_Backend::gBackendFunctionsTable.GL.ReadPixels = RecordReadPixels;
|
||||||
|
Uint8 pixelStorage[4 * 4 * 4] = {};
|
||||||
|
|
||||||
|
// GL 3.3 section 4.3.1: normalized format on an integer read buffer -> GL_INVALID_OPERATION
|
||||||
|
// (GL CTS packed_pixels expects the error for every mismatched combination).
|
||||||
|
MG_Impl::GLImpl::ReadPixels(0, 0, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, pixelStorage);
|
||||||
|
EXPECT_EQ(g_readPixelsCallCount, 0);
|
||||||
|
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_INVALID_OPERATION);
|
||||||
|
|
||||||
|
// The matching integer readback stays valid.
|
||||||
|
MG_Impl::GLImpl::ReadPixels(0, 0, 4, 4, GL_RGBA_INTEGER, GL_UNSIGNED_INT, pixelStorage);
|
||||||
|
EXPECT_EQ(g_readPixelsCallCount, 1);
|
||||||
|
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||||
|
|
||||||
|
// And the inverse mismatch: integer format on a normalized attachment.
|
||||||
|
GLuint normalizedTexture = 0;
|
||||||
|
MG_Impl::GLImpl::CreateTextures(GL_TEXTURE_2D, 1, &normalizedTexture);
|
||||||
|
MG_Impl::GLImpl::TextureStorage2D(normalizedTexture, 1, GL_RGBA8, 4, 4);
|
||||||
|
MG_Impl::GLImpl::NamedFramebufferTexture(framebuffer, GL_COLOR_ATTACHMENT0, normalizedTexture, 0);
|
||||||
|
MG_Impl::GLImpl::ReadPixels(0, 0, 4, 4, GL_RGBA_INTEGER, GL_UNSIGNED_INT, pixelStorage);
|
||||||
|
EXPECT_EQ(g_readPixelsCallCount, 1);
|
||||||
|
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_INVALID_OPERATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(FramebufferTest, BindRenderbufferZeroUnbindsWithoutError) {
|
||||||
|
// The GL CTS state reset calls glBindRenderbuffer(GL_RENDERBUFFER, 0) and expects no error;
|
||||||
|
// name 0 used to be reported as an invalid renderbuffer name.
|
||||||
|
MG_Impl::GLImpl::BindRenderbuffer(GL_RENDERBUFFER, 0);
|
||||||
|
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(FramebufferTest, FramebufferTexture3DAttachesSliceWithLayerTracking) {
|
||||||
|
// glFramebufferTexture3D with zoffset used to be rejected outright, leaving a sticky
|
||||||
|
// GL_INVALID_OPERATION behind (GL CTS packed_pixels varied_rectangle runs on GL_TEXTURE_3D).
|
||||||
|
GLuint framebuffer = 0;
|
||||||
|
GLuint texture = 0;
|
||||||
|
MG_Impl::GLImpl::CreateFramebuffers(1, &framebuffer);
|
||||||
|
MG_Impl::GLImpl::CreateTextures(GL_TEXTURE_3D, 1, &texture);
|
||||||
|
MG_Impl::GLImpl::TextureStorage3D(texture, 1, GL_RGBA8, 4, 4, 2);
|
||||||
|
|
||||||
|
MG_Impl::GLImpl::BindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer);
|
||||||
|
MG_Impl::GLImpl::FramebufferTexture3D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_3D, texture, 0, 1);
|
||||||
|
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||||
|
|
||||||
|
const auto framebufferObject = MG_State::pGLContext->GetFramebufferObject(framebuffer);
|
||||||
|
ASSERT_NE(framebufferObject, nullptr);
|
||||||
|
const auto& attachment = framebufferObject->GetAttachment(FramebufferAttachmentType::Color0);
|
||||||
|
ASSERT_TRUE(attachment.IsTexture());
|
||||||
|
EXPECT_EQ(attachment.GetTextureLayer(), 1);
|
||||||
|
EXPECT_FALSE(attachment.IsLayered());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(FramebufferTest, NonRenderableColorFormatsReportUnsupportedFramebuffer) {
|
||||||
|
// Without a probing backend the conservative list applies: RGB9_E5 is texture-only, so
|
||||||
|
// attaching it must not report GL_FRAMEBUFFER_COMPLETE (GL CTS packed_pixels rgb9_e5 expects
|
||||||
|
// read errors instead of silent unwritten readbacks).
|
||||||
|
GLuint framebuffer = 0;
|
||||||
|
GLuint texture = 0;
|
||||||
|
MG_Impl::GLImpl::CreateFramebuffers(1, &framebuffer);
|
||||||
|
MG_Impl::GLImpl::CreateTextures(GL_TEXTURE_2D, 1, &texture);
|
||||||
|
MG_Impl::GLImpl::TextureStorage2D(texture, 1, GL_RGB9_E5, 4, 4);
|
||||||
|
MG_Impl::GLImpl::NamedFramebufferTexture(framebuffer, GL_COLOR_ATTACHMENT0, texture, 0);
|
||||||
|
MG_Impl::GLImpl::BindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
|
||||||
|
|
||||||
|
EXPECT_EQ(MG_Impl::GLImpl::CheckFramebufferStatus(GL_READ_FRAMEBUFFER),
|
||||||
|
static_cast<GLenum>(GL_FRAMEBUFFER_UNSUPPORTED));
|
||||||
|
|
||||||
|
Uint8 pixelStorage[4 * 4 * 4] = {};
|
||||||
|
MG_Impl::GLImpl::ReadPixels(0, 0, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, pixelStorage);
|
||||||
|
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_INVALID_FRAMEBUFFER_OPERATION);
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,7 +17,11 @@
|
|||||||
#include <MG_State/GLState/Core.h>
|
#include <MG_State/GLState/Core.h>
|
||||||
#include <MG_State/GLState/TextureState/TextureObject.h>
|
#include <MG_State/GLState/TextureState/TextureObject.h>
|
||||||
#include <MG_Util/Converters/MGToMG/TextureEnumConverter.h>
|
#include <MG_Util/Converters/MGToMG/TextureEnumConverter.h>
|
||||||
|
#include <MG_Util/Converters/GLToMG/TextureEnumConverter.h>
|
||||||
|
#include <MG_Util/Math/SmallFloat.h>
|
||||||
|
#include <MG_Util/Texture/PixelStoreProcessor.h>
|
||||||
#include <MG_Util/Texture/TextureFormatProcessor.h>
|
#include <MG_Util/Texture/TextureFormatProcessor.h>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
using namespace MobileGL;
|
using namespace MobileGL;
|
||||||
|
|
||||||
@@ -1174,3 +1178,221 @@ TEST_F(TextureTest, NormalizeDepth24Stencil8UsesPackedDepthStencilType) {
|
|||||||
EXPECT_EQ(format, GL_DEPTH_STENCIL);
|
EXPECT_EQ(format, GL_DEPTH_STENCIL);
|
||||||
EXPECT_EQ(type, GL_UNSIGNED_INT_24_8);
|
EXPECT_EQ(type, GL_UNSIGNED_INT_24_8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- GL CTS packed_pixels / texture_swizzle readback root-cause regressions --------------------
|
||||||
|
|
||||||
|
TEST_F(TextureTest, NormalizeLegacySizedFormatsMapToCanonicalShadowLayouts) {
|
||||||
|
struct Case {
|
||||||
|
GLenum requested;
|
||||||
|
GLenum internalFormat;
|
||||||
|
GLenum format;
|
||||||
|
GLenum type;
|
||||||
|
};
|
||||||
|
const Case cases[] = {
|
||||||
|
// Legacy <=8-bit-per-channel formats store as UNorm8 component arrays.
|
||||||
|
{GL_R3_G3_B2, GL_RGB565, GL_RGB, GL_UNSIGNED_BYTE},
|
||||||
|
{GL_RGB4, GL_RGB565, GL_RGB, GL_UNSIGNED_BYTE},
|
||||||
|
{GL_RGB5, GL_RGB565, GL_RGB, GL_UNSIGNED_BYTE},
|
||||||
|
{GL_RGBA2, GL_RGBA4, GL_RGBA, GL_UNSIGNED_BYTE},
|
||||||
|
{GL_RGBA4, GL_RGBA4, GL_RGBA, GL_UNSIGNED_BYTE},
|
||||||
|
{GL_RGB5_A1, GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_BYTE},
|
||||||
|
// 10/12-bit channels store as UNorm16 component arrays.
|
||||||
|
{GL_RGB10, GL_RGB16, GL_RGB, GL_UNSIGNED_SHORT},
|
||||||
|
{GL_RGB12, GL_RGB16, GL_RGB, GL_UNSIGNED_SHORT},
|
||||||
|
{GL_RGBA12, GL_RGBA16, GL_RGBA, GL_UNSIGNED_SHORT},
|
||||||
|
// RGB10_A2UI keeps its native packed layout (was previously unhandled -> broken uploads).
|
||||||
|
{GL_RGB10_A2UI, GL_RGB10_A2UI, GL_RGBA_INTEGER, GL_UNSIGNED_INT_2_10_10_10_REV},
|
||||||
|
};
|
||||||
|
for (const auto& testCase : cases) {
|
||||||
|
GLenum internalFormat = 0;
|
||||||
|
GLenum format = 0;
|
||||||
|
GLenum type = 0;
|
||||||
|
MG_Util::TextureFormatProcessor::NormalizePixelFormat(testCase.requested,
|
||||||
|
PixelFormatNormalizeOptionBit::None,
|
||||||
|
&internalFormat, &format, &type);
|
||||||
|
EXPECT_EQ(internalFormat, testCase.internalFormat) << "requested 0x" << std::hex << testCase.requested;
|
||||||
|
EXPECT_EQ(format, testCase.format) << "requested 0x" << std::hex << testCase.requested;
|
||||||
|
EXPECT_EQ(type, testCase.type) << "requested 0x" << std::hex << testCase.requested;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TextureTest, ConvertsUnsignedInt1010102PixelDataType) {
|
||||||
|
// GL CTS packed_pixels uploads/reads GL_UNSIGNED_INT_10_10_10_2; the GL->MG mapping was missing,
|
||||||
|
// rejecting every valid combination as GL_INVALID_ENUM.
|
||||||
|
EXPECT_EQ(MG_Util::ConvertGLEnumToTexturePixelDataType(GL_UNSIGNED_INT_10_10_10_2),
|
||||||
|
TexturePixelDataType::UnsignedInt1010102);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TextureTest, TexParameteriRejectsInvalidSwizzleValue) {
|
||||||
|
GLuint texture = 0;
|
||||||
|
MG_Impl::GLImpl::GenTextures(1, &texture);
|
||||||
|
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, texture);
|
||||||
|
|
||||||
|
// GL CTS texture_swizzle.api_errors: values outside [RED, GREEN, BLUE, ALPHA, ZERO, ONE]
|
||||||
|
// must raise GL_INVALID_ENUM through the single-value TexParameteri path.
|
||||||
|
MG_Impl::GLImpl::TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_RGB);
|
||||||
|
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_INVALID_ENUM);
|
||||||
|
MG_Impl::GLImpl::TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, -1);
|
||||||
|
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_INVALID_ENUM);
|
||||||
|
|
||||||
|
MG_Impl::GLImpl::TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_ALPHA);
|
||||||
|
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||||
|
|
||||||
|
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TextureTest, DefaultTextureOperationsAreSilentNoOps) {
|
||||||
|
// The GL CTS state reset drives texture name 0 through TexParameter*/TexImage*/TexBuffer for
|
||||||
|
// every unit and target and expects glGetError() to stay clean.
|
||||||
|
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
MG_Impl::GLImpl::TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_RED);
|
||||||
|
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||||
|
|
||||||
|
MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
|
||||||
|
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||||
|
|
||||||
|
MG_Impl::GLImpl::TexBuffer(GL_TEXTURE_BUFFER, GL_R8, 0);
|
||||||
|
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TextureTest, BoundTexImage2DEncodesPackedInternalShadowWords) {
|
||||||
|
// RGB10_A2 / RGB9_E5 / R11F_G11F_B10F shadow bytes hold the ES upload word; uploads from
|
||||||
|
// component client data must encode instead of raw-copying (GL CTS packed_pixels rgb10_a2,
|
||||||
|
// rgb9_e5, r11f_g11f_b10f data comparisons).
|
||||||
|
GLuint texture = 0;
|
||||||
|
MG_Impl::GLImpl::GenTextures(1, &texture);
|
||||||
|
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, texture);
|
||||||
|
|
||||||
|
const Uint8 rgba8[] = {255, 0, 0, 255};
|
||||||
|
MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGB10_A2, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, rgba8);
|
||||||
|
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||||
|
{
|
||||||
|
const auto* stored = GetBoundTexture2DLevelBytes(texture);
|
||||||
|
Uint32 word = 0;
|
||||||
|
std::memcpy(&word, stored, sizeof(word));
|
||||||
|
EXPECT_EQ(word & 0x3FFu, 1023u); // red = 1.0
|
||||||
|
EXPECT_EQ((word >> 10) & 0x3FFu, 0u); // green = 0
|
||||||
|
EXPECT_EQ((word >> 20) & 0x3FFu, 0u); // blue = 0
|
||||||
|
EXPECT_EQ((word >> 30) & 0x3u, 3u); // alpha = 1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
const Float rgb[] = {1.0f, 0.5f, 0.25f};
|
||||||
|
MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGB9_E5, 1, 1, 0, GL_RGB, GL_FLOAT, rgb);
|
||||||
|
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||||
|
{
|
||||||
|
const auto* stored = GetBoundTexture2DLevelBytes(texture);
|
||||||
|
Uint32 word = 0;
|
||||||
|
std::memcpy(&word, stored, sizeof(word));
|
||||||
|
EXPECT_EQ(word, MG_Util::EncodeSharedExponentRGB9E5(rgb));
|
||||||
|
}
|
||||||
|
|
||||||
|
MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_R11F_G11F_B10F, 1, 1, 0, GL_RGB, GL_FLOAT, rgb);
|
||||||
|
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||||
|
{
|
||||||
|
const auto* stored = GetBoundTexture2DLevelBytes(texture);
|
||||||
|
Uint32 word = 0;
|
||||||
|
std::memcpy(&word, stored, sizeof(word));
|
||||||
|
const Uint32 expected = MG_Util::EncodeFloatToUnsignedF11(rgb[0]) |
|
||||||
|
(MG_Util::EncodeFloatToUnsignedF11(rgb[1]) << 11) |
|
||||||
|
(MG_Util::EncodeFloatToUnsignedF10(rgb[2]) << 22);
|
||||||
|
EXPECT_EQ(word, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TextureTest, BoundTexImage2DDecodesPackedFloatSourceTypes) {
|
||||||
|
// 5_9_9_9_REV / 10F_11F_11F_REV client data uploaded into a component internal format must be
|
||||||
|
// decoded per texel (GL CTS packed_pixels uploads every RGB internal format with these types).
|
||||||
|
GLuint texture = 0;
|
||||||
|
MG_Impl::GLImpl::GenTextures(1, &texture);
|
||||||
|
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, texture);
|
||||||
|
|
||||||
|
const Float rgb[] = {1.0f, 0.5f, 0.25f};
|
||||||
|
const Uint32 word = MG_Util::EncodeSharedExponentRGB9E5(rgb);
|
||||||
|
MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, 1, 1, 0, GL_RGB, GL_UNSIGNED_INT_5_9_9_9_REV, &word);
|
||||||
|
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||||
|
|
||||||
|
const auto* stored = GetBoundTexture2DLevelBytes(texture);
|
||||||
|
EXPECT_EQ(stored[0], 255); // 1.0
|
||||||
|
EXPECT_EQ(stored[1], 128); // 0.5
|
||||||
|
EXPECT_EQ(stored[2], 64); // 0.25
|
||||||
|
|
||||||
|
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TextureTest, UnpackSwapBytesSwapsComponentsNotWholePixels) {
|
||||||
|
// GL_UNPACK_SWAP_BYTES on the identity-layout copy path used to reverse the whole pixel
|
||||||
|
// (4 bytes for GL_RG16), garbling multi-component rows (GL CTS packed_pixels varied_rectangle
|
||||||
|
// GL_UNPACK_SWAP_BYTES cases).
|
||||||
|
GLuint texture = 0;
|
||||||
|
MG_Impl::GLImpl::GenTextures(1, &texture);
|
||||||
|
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, texture);
|
||||||
|
|
||||||
|
const Uint16 swapped[] = {0x3412, 0x7856}; // byte-swapped {0x1234, 0x5678}
|
||||||
|
MG_Impl::GLImpl::PixelStorei(GL_UNPACK_SWAP_BYTES, GL_TRUE);
|
||||||
|
MG_Impl::GLImpl::PixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||||
|
MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RG16, 1, 1, 0, GL_RG, GL_UNSIGNED_SHORT, swapped);
|
||||||
|
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||||
|
|
||||||
|
const auto* stored = GetBoundTexture2DLevelBytes(texture);
|
||||||
|
Uint16 red = 0;
|
||||||
|
Uint16 green = 0;
|
||||||
|
std::memcpy(&red, stored, sizeof(red));
|
||||||
|
std::memcpy(&green, stored + 2, sizeof(green));
|
||||||
|
EXPECT_EQ(red, 0x1234);
|
||||||
|
EXPECT_EQ(green, 0x5678);
|
||||||
|
|
||||||
|
MG_Impl::GLImpl::PixelStorei(GL_UNPACK_SWAP_BYTES, GL_FALSE);
|
||||||
|
MG_Impl::GLImpl::PixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||||
|
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TextureTest, DecodeShadowDataToWideRGBACoversComponentAndPackedLayouts) {
|
||||||
|
// GetTexImage of non-renderable formats reads the CPU shadow; the decode must cover both
|
||||||
|
// component-array and packed internal layouts.
|
||||||
|
Vector<Uint8> wide;
|
||||||
|
Bool isInteger = false;
|
||||||
|
Bool isSigned = false;
|
||||||
|
|
||||||
|
const Uint8 r8[] = {128};
|
||||||
|
ASSERT_TRUE(MG_Util::PixelStoreProcessor::DecodeShadowDataToWideRGBA(TextureInternalFormat::R8, r8, 1, wide,
|
||||||
|
isInteger, isSigned));
|
||||||
|
EXPECT_FALSE(isInteger);
|
||||||
|
{
|
||||||
|
Float rgba[4];
|
||||||
|
std::memcpy(rgba, wide.data(), sizeof(rgba));
|
||||||
|
EXPECT_NEAR(rgba[0], 128.0f / 255.0f, 1e-6f);
|
||||||
|
EXPECT_EQ(rgba[1], 0.0f);
|
||||||
|
EXPECT_EQ(rgba[2], 0.0f);
|
||||||
|
EXPECT_EQ(rgba[3], 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Float rgb[] = {1.0f, 0.5f, 0.25f};
|
||||||
|
const Uint32 e5Word = MG_Util::EncodeSharedExponentRGB9E5(rgb);
|
||||||
|
ASSERT_TRUE(MG_Util::PixelStoreProcessor::DecodeShadowDataToWideRGBA(TextureInternalFormat::RGB9E5, &e5Word, 1,
|
||||||
|
wide, isInteger, isSigned));
|
||||||
|
EXPECT_FALSE(isInteger);
|
||||||
|
{
|
||||||
|
Float rgba[4];
|
||||||
|
std::memcpy(rgba, wide.data(), sizeof(rgba));
|
||||||
|
EXPECT_NEAR(rgba[0], 1.0f, 1.0f / 256.0f);
|
||||||
|
EXPECT_NEAR(rgba[1], 0.5f, 1.0f / 256.0f);
|
||||||
|
EXPECT_NEAR(rgba[2], 0.25f, 1.0f / 256.0f);
|
||||||
|
EXPECT_EQ(rgba[3], 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Uint32 uiWord = 1023u | (511u << 10) | (255u << 20) | (2u << 30); // RGB10_A2UI
|
||||||
|
ASSERT_TRUE(MG_Util::PixelStoreProcessor::DecodeShadowDataToWideRGBA(TextureInternalFormat::RGB10A2UI, &uiWord, 1,
|
||||||
|
wide, isInteger, isSigned));
|
||||||
|
EXPECT_TRUE(isInteger);
|
||||||
|
EXPECT_FALSE(isSigned);
|
||||||
|
{
|
||||||
|
Uint32 rgba[4];
|
||||||
|
std::memcpy(rgba, wide.data(), sizeof(rgba));
|
||||||
|
EXPECT_EQ(rgba[0], 1023u);
|
||||||
|
EXPECT_EQ(rgba[1], 511u);
|
||||||
|
EXPECT_EQ(rgba[2], 255u);
|
||||||
|
EXPECT_EQ(rgba[3], 2u);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1107,9 +1107,10 @@ TEST_F(GeneralVertexArrayTest, CurrentAttrib_PackedValidation) {
|
|||||||
GetVertexAttribfv(1, GL_CURRENT_VERTEX_ATTRIB, out);
|
GetVertexAttribfv(1, GL_CURRENT_VERTEX_ATTRIB, out);
|
||||||
EXPECT_FLOAT_EQ(out[0], 42.0f); // unchanged by the failed call
|
EXPECT_FLOAT_EQ(out[0], 42.0f); // unchanged by the failed call
|
||||||
|
|
||||||
// Attribute 0 is rejected by MobileGL policy (GL_INVALID_OPERATION).
|
// Attribute 0's current value is settable in core GL (the GL CTS state reset calls
|
||||||
|
// glVertexAttrib4f(0, ...) on every attribute and expects no error).
|
||||||
VertexAttribP4ui(0, GL_UNSIGNED_INT_2_10_10_10_REV, GL_FALSE, 1u);
|
VertexAttribP4ui(0, GL_UNSIGNED_INT_2_10_10_10_REV, GL_FALSE, 1u);
|
||||||
EXPECT_EQ(GetError(), GL_INVALID_OPERATION);
|
EXPECT_EQ(GetError(), GL_NO_ERROR);
|
||||||
|
|
||||||
// Out-of-range index -> GL_INVALID_VALUE.
|
// Out-of-range index -> GL_INVALID_VALUE.
|
||||||
VertexAttribP4ui(VertexArrayImpl::GetMaxVertexAttribs(), GL_UNSIGNED_INT_2_10_10_10_REV, GL_FALSE, 1u);
|
VertexAttribP4ui(VertexArrayImpl::GetMaxVertexAttribs(), GL_UNSIGNED_INT_2_10_10_10_REV, GL_FALSE, 1u);
|
||||||
|
|||||||
@@ -308,6 +308,8 @@ namespace MobileGL {
|
|||||||
return TexturePixelDataType::UnsignedInt8888;
|
return TexturePixelDataType::UnsignedInt8888;
|
||||||
case GL_UNSIGNED_INT_8_8_8_8_REV:
|
case GL_UNSIGNED_INT_8_8_8_8_REV:
|
||||||
return TexturePixelDataType::UnsignedInt8888Rev;
|
return TexturePixelDataType::UnsignedInt8888Rev;
|
||||||
|
case GL_UNSIGNED_INT_10_10_10_2:
|
||||||
|
return TexturePixelDataType::UnsignedInt1010102;
|
||||||
case GL_UNSIGNED_INT_10F_11F_11F_REV:
|
case GL_UNSIGNED_INT_10F_11F_11F_REV:
|
||||||
return TexturePixelDataType::UnsignedInt101111Rev;
|
return TexturePixelDataType::UnsignedInt101111Rev;
|
||||||
case GL_UNSIGNED_INT_2_10_10_10_REV:
|
case GL_UNSIGNED_INT_2_10_10_10_REV:
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -15,10 +15,10 @@ namespace MobileGL {
|
|||||||
SizeT GetSizedInternalFormatSizeInBytes(TextureInternalFormat internal) {
|
SizeT GetSizedInternalFormatSizeInBytes(TextureInternalFormat internal) {
|
||||||
switch (internal) {
|
switch (internal) {
|
||||||
case TextureInternalFormat::R8:
|
case TextureInternalFormat::R8:
|
||||||
|
case TextureInternalFormat::Red: // UNorm8 shadow layout
|
||||||
case TextureInternalFormat::R8Snorm:
|
case TextureInternalFormat::R8Snorm:
|
||||||
case TextureInternalFormat::R8I:
|
case TextureInternalFormat::R8I:
|
||||||
case TextureInternalFormat::R8UI:
|
case TextureInternalFormat::R8UI:
|
||||||
case TextureInternalFormat::R3G3B2:
|
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
case TextureInternalFormat::R16:
|
case TextureInternalFormat::R16:
|
||||||
@@ -27,14 +27,17 @@ namespace MobileGL {
|
|||||||
case TextureInternalFormat::R16UI:
|
case TextureInternalFormat::R16UI:
|
||||||
case TextureInternalFormat::R16F:
|
case TextureInternalFormat::R16F:
|
||||||
case TextureInternalFormat::RG8:
|
case TextureInternalFormat::RG8:
|
||||||
|
case TextureInternalFormat::RG: // UNorm8x2 shadow layout
|
||||||
case TextureInternalFormat::RG8Snorm:
|
case TextureInternalFormat::RG8Snorm:
|
||||||
case TextureInternalFormat::RG8I:
|
case TextureInternalFormat::RG8I:
|
||||||
case TextureInternalFormat::RG8UI:
|
case TextureInternalFormat::RG8UI:
|
||||||
case TextureInternalFormat::DepthComponent16:
|
case TextureInternalFormat::DepthComponent16:
|
||||||
return 2;
|
return 2;
|
||||||
|
|
||||||
|
case TextureInternalFormat::R3G3B2: // UNorm8x3 shadow layout
|
||||||
case TextureInternalFormat::RGB4:
|
case TextureInternalFormat::RGB4:
|
||||||
case TextureInternalFormat::RGB5:
|
case TextureInternalFormat::RGB5:
|
||||||
|
case TextureInternalFormat::RGB: // UNorm8x3 shadow layout
|
||||||
case TextureInternalFormat::RGB8:
|
case TextureInternalFormat::RGB8:
|
||||||
case TextureInternalFormat::RGB8Snorm:
|
case TextureInternalFormat::RGB8Snorm:
|
||||||
case TextureInternalFormat::SRGB8:
|
case TextureInternalFormat::SRGB8:
|
||||||
@@ -43,11 +46,10 @@ namespace MobileGL {
|
|||||||
case TextureInternalFormat::DepthComponent24:
|
case TextureInternalFormat::DepthComponent24:
|
||||||
return 3;
|
return 3;
|
||||||
|
|
||||||
case TextureInternalFormat::RGB10:
|
|
||||||
case TextureInternalFormat::RGB12:
|
|
||||||
case TextureInternalFormat::RGBA2:
|
case TextureInternalFormat::RGBA2:
|
||||||
case TextureInternalFormat::RGBA4:
|
case TextureInternalFormat::RGBA4:
|
||||||
case TextureInternalFormat::RGB5A1:
|
case TextureInternalFormat::RGB5A1:
|
||||||
|
case TextureInternalFormat::RGBA: // UNorm8x4 shadow layout
|
||||||
case TextureInternalFormat::RGBA8:
|
case TextureInternalFormat::RGBA8:
|
||||||
case TextureInternalFormat::RGBA8Snorm:
|
case TextureInternalFormat::RGBA8Snorm:
|
||||||
case TextureInternalFormat::RGBA8I:
|
case TextureInternalFormat::RGBA8I:
|
||||||
@@ -72,6 +74,8 @@ namespace MobileGL {
|
|||||||
return 4;
|
return 4;
|
||||||
|
|
||||||
case TextureInternalFormat::RGB16:
|
case TextureInternalFormat::RGB16:
|
||||||
|
case TextureInternalFormat::RGB10: // UNorm16x3 shadow layout
|
||||||
|
case TextureInternalFormat::RGB12: // UNorm16x3 shadow layout
|
||||||
case TextureInternalFormat::RGB16Snorm:
|
case TextureInternalFormat::RGB16Snorm:
|
||||||
case TextureInternalFormat::RGB16F:
|
case TextureInternalFormat::RGB16F:
|
||||||
case TextureInternalFormat::RGB16I:
|
case TextureInternalFormat::RGB16I:
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
#include "PixelStoreProcessor.h"
|
#include "PixelStoreProcessor.h"
|
||||||
#include "MG_Util/Math/HalfFloat.h"
|
#include "MG_Util/Math/HalfFloat.h"
|
||||||
|
#include "MG_Util/Math/SmallFloat.h"
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
namespace MobileGL::MG_Util::PixelStoreProcessor {
|
namespace MobileGL::MG_Util::PixelStoreProcessor {
|
||||||
@@ -122,13 +123,30 @@ namespace MobileGL::MG_Util::PixelStoreProcessor {
|
|||||||
|
|
||||||
Bool GetInternalShadowLayout(TextureInternalFormat internal, InternalShadowLayout& out) {
|
Bool GetInternalShadowLayout(TextureInternalFormat internal, InternalShadowLayout& out) {
|
||||||
switch (internal) {
|
switch (internal) {
|
||||||
case TextureInternalFormat::R8: out = {1, ShadowComponent::UNorm8, false}; return true;
|
case TextureInternalFormat::R8:
|
||||||
case TextureInternalFormat::RG8: out = {2, ShadowComponent::UNorm8, false}; return true;
|
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::RGB8:
|
||||||
|
case TextureInternalFormat::RGB:
|
||||||
case TextureInternalFormat::SRGB8: out = {3, ShadowComponent::UNorm8, false}; return true;
|
case TextureInternalFormat::SRGB8: out = {3, ShadowComponent::UNorm8, false}; return true;
|
||||||
case TextureInternalFormat::RGBA8:
|
case TextureInternalFormat::RGBA8:
|
||||||
|
case TextureInternalFormat::RGBA:
|
||||||
case TextureInternalFormat::SRGB8Alpha8: out = {4, ShadowComponent::UNorm8, false}; return true;
|
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::R8Snorm: out = {1, ShadowComponent::SNorm8, false}; return true;
|
||||||
case TextureInternalFormat::RG8Snorm: out = {2, 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;
|
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;
|
case TextureInternalFormat::RGBA32I: out = {4, ShadowComponent::Int32, true}; return true;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// Packed internal layouts (RGB5A1, RGB10A2, RGB9E5, ...), depth/stencil and unsized formats
|
// Packed internal layouts (RGB10A2, RGB9E5, ...), depth/stencil and unsized formats
|
||||||
// keep the legacy copy path.
|
// have no component-array shadow layout (packed ones are handled below).
|
||||||
return false;
|
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 {
|
struct UnpackChannelMapping {
|
||||||
Int formatPosition[4]; // position of R,G,B,A within the input format's component list; -1 = missing
|
Int formatPosition[4]; // position of R,G,B,A within the input format's component list; -1 = missing
|
||||||
Int channelCount;
|
Int channelCount;
|
||||||
@@ -301,6 +383,8 @@ namespace MobileGL::MG_Util::PixelStoreProcessor {
|
|||||||
SizeT inputPixelSize;
|
SizeT inputPixelSize;
|
||||||
SizeT swapGroupSize; // UNPACK_SWAP_BYTES group: packed word size, or the component size
|
SizeT swapGroupSize; // UNPACK_SWAP_BYTES group: packed word size, or the component size
|
||||||
SizeT internalPixelSize;
|
SizeT internalPixelSize;
|
||||||
|
Bool internalIsPacked;
|
||||||
|
InternalPackedLayout internalPacked;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Returns true when the (format, type) -> internal-format upload needs a per-texel conversion;
|
// 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,
|
Bool GetUnpackConversionSpec(TextureInternalFormat internal, TextureInputFormat format,
|
||||||
TexturePixelDataType type, UnpackConversionSpec& out) {
|
TexturePixelDataType type, UnpackConversionSpec& out) {
|
||||||
InternalShadowLayout layout{};
|
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{};
|
UnpackChannelMapping mapping{};
|
||||||
if (!GetUnpackChannelMapping(format, mapping)) return false;
|
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{};
|
PackedTypeLayout packed{};
|
||||||
const Bool isPacked = GetPackedTypeLayout(type, packed);
|
const Bool isPacked = GetPackedTypeLayout(type, packed);
|
||||||
@@ -323,6 +413,13 @@ namespace MobileGL::MG_Util::PixelStoreProcessor {
|
|||||||
type == TexturePixelDataType::UnsignedInt8888Rev) {
|
type == TexturePixelDataType::UnsignedInt8888Rev) {
|
||||||
return false;
|
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 {
|
} else {
|
||||||
ShadowComponent direct{};
|
ShadowComponent direct{};
|
||||||
const Bool hasDirect = GetDirectShadowComponentForType(type, mapping.isInteger, direct);
|
const Bool hasDirect = GetDirectShadowComponentForType(type, mapping.isInteger, direct);
|
||||||
@@ -338,25 +435,46 @@ namespace MobileGL::MG_Util::PixelStoreProcessor {
|
|||||||
case TexturePixelDataType::HalfFloat:
|
case TexturePixelDataType::HalfFloat:
|
||||||
if (mapping.isInteger) return false; // rejected upstream
|
if (mapping.isInteger) return false; // rejected upstream
|
||||||
break;
|
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:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (hasDirect && direct == layout.component && mapping.channelCount == layout.channelCount &&
|
if (hasComponentLayout && hasDirect && direct == layout.component &&
|
||||||
IsIdentityChannelOrder(mapping)) {
|
mapping.channelCount == layout.channelCount && IsIdentityChannelOrder(mapping)) {
|
||||||
return false; // input already matches the shadow layout
|
return false; // input already matches the shadow layout
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
out.mapping = mapping;
|
out.mapping = mapping;
|
||||||
out.internal = layout;
|
out.internal = hasComponentLayout
|
||||||
|
? layout
|
||||||
|
: InternalShadowLayout{internalChannelCount, ShadowComponent::UNorm8, internalIsInteger};
|
||||||
out.packed = packed;
|
out.packed = packed;
|
||||||
out.isPacked = isPacked;
|
out.isPacked = isPacked;
|
||||||
out.type = type;
|
out.type = type;
|
||||||
out.inputPixelSize = GetInputBytesPerPixel(format, type);
|
out.inputPixelSize = GetInputBytesPerPixel(format, type);
|
||||||
|
const Bool isPackedFloatWord = type == TexturePixelDataType::UnsignedInt5999Rev ||
|
||||||
|
type == TexturePixelDataType::UnsignedInt101111Rev;
|
||||||
out.swapGroupSize = isPacked ? static_cast<SizeT>(packed.totalBits / 8)
|
out.swapGroupSize = isPacked ? static_cast<SizeT>(packed.totalBits / 8)
|
||||||
: GetBaseTexturePixelDataTypeSize(type);
|
: (isPackedFloatWord ? 4 : GetBaseTexturePixelDataTypeSize(type));
|
||||||
|
out.internalIsPacked = hasPackedInternal;
|
||||||
|
out.internalPacked = packedInternal;
|
||||||
out.internalPixelSize =
|
out.internalPixelSize =
|
||||||
static_cast<SizeT>(layout.channelCount) * GetShadowComponentSize(layout.component);
|
hasPackedInternal ? 4
|
||||||
|
: static_cast<SizeT>(layout.channelCount) * GetShadowComponentSize(layout.component);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -564,13 +682,36 @@ namespace MobileGL::MG_Util::PixelStoreProcessor {
|
|||||||
rgba[ch] = DecodeComponentToInt(s + static_cast<SizeT>(pos) * srcComponentSize, conv.type);
|
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) {
|
for (Int ch = 0; ch < conv.internal.channelCount; ++ch) {
|
||||||
EncodeShadowComponentInt(d + static_cast<SizeT>(ch) * dstComponentSize,
|
EncodeShadowComponentInt(d + static_cast<SizeT>(ch) * dstComponentSize,
|
||||||
conv.internal.component, rgba[ch]);
|
conv.internal.component, rgba[ch]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Float rgba[4] = {0.0f, 0.0f, 0.0f, 1.0f};
|
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);
|
const Uint32 word = ReadPackedWord(s, conv.packed.totalBits);
|
||||||
for (Int ch = 0; ch < 4; ++ch) {
|
for (Int ch = 0; ch < 4; ++ch) {
|
||||||
const Int pos = conv.mapping.formatPosition[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);
|
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) {
|
for (Int ch = 0; ch < conv.internal.channelCount; ++ch) {
|
||||||
EncodeShadowComponentFloat(d + static_cast<SizeT>(ch) * dstComponentSize,
|
EncodeShadowComponentFloat(d + static_cast<SizeT>(ch) * dstComponentSize,
|
||||||
conv.internal.component, rgba[ch]);
|
conv.internal.component, rgba[ch]);
|
||||||
@@ -687,9 +833,16 @@ namespace MobileGL::MG_Util::PixelStoreProcessor {
|
|||||||
} else {
|
} else {
|
||||||
Memcpy(layerDst, layerSrc, static_cast<SizeT>(copyWidth) * pixelSize);
|
Memcpy(layerDst, layerSrc, static_cast<SizeT>(copyWidth) * pixelSize);
|
||||||
|
|
||||||
if (params.SwapBytes && pixelSize > 1 && !isByteType) {
|
if (params.SwapBytes && !isByteType) {
|
||||||
MGLOG_D("%s: SwapBytes", __func__);
|
// GL_UNPACK_SWAP_BYTES swaps within each element (component or packed
|
||||||
SwapBytes(layerDst, pixelSize, static_cast<SizeT>(copyWidth));
|
// 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) {
|
if (params.LSBFirst && isBitmap) {
|
||||||
@@ -788,4 +941,177 @@ namespace MobileGL::MG_Util::PixelStoreProcessor {
|
|||||||
|
|
||||||
return outputPixels;
|
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
|
} // namespace MobileGL::MG_Util::PixelStoreProcessor
|
||||||
|
|||||||
@@ -22,4 +22,12 @@ namespace MobileGL::MG_Util::PixelStoreProcessor {
|
|||||||
TextureInputFormat dstInputFormat, TexturePixelDataType dstDataType,
|
TextureInputFormat dstInputFormat, TexturePixelDataType dstDataType,
|
||||||
IntVec3 dimension, Bool isBitmap, SizeT& outSize);
|
IntVec3 dimension, Bool isBitmap, SizeT& outSize);
|
||||||
void ProcessColorSwizzle(void* data, SizeT pixelCount, const Vector<TextureSwizzleParam>& swizzle);
|
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
|
} // namespace MobileGL::MG_Util::PixelStoreProcessor
|
||||||
|
|||||||
@@ -19,11 +19,14 @@ namespace MobileGL::MG_Util::TextureFormatProcessor {
|
|||||||
applicableOptions |= options & PixelFormatNormalizeOptionBit::NoDepthComponent32;
|
applicableOptions |= options & PixelFormatNormalizeOptionBit::NoDepthComponent32;
|
||||||
break;
|
break;
|
||||||
case GL_RGBA16:
|
case GL_RGBA16:
|
||||||
|
case GL_RGBA12: // stored as RGBA16 (see NormalizePixelFormat)
|
||||||
case GL_RG16:
|
case GL_RG16:
|
||||||
case GL_R16:
|
case GL_R16:
|
||||||
applicableOptions |= options & PixelFormatNormalizeOptionBit::NoNorm16;
|
applicableOptions |= options & PixelFormatNormalizeOptionBit::NoNorm16;
|
||||||
break;
|
break;
|
||||||
case GL_RGB16:
|
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::NoNorm16;
|
||||||
applicableOptions |= options & PixelFormatNormalizeOptionBit::NoRgb16;
|
applicableOptions |= options & PixelFormatNormalizeOptionBit::NoRgb16;
|
||||||
break;
|
break;
|
||||||
@@ -159,6 +162,30 @@ namespace MobileGL::MG_Util::TextureFormatProcessor {
|
|||||||
}
|
}
|
||||||
*outInternalFormat = internalFormat;
|
*outInternalFormat = internalFormat;
|
||||||
break;
|
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:
|
default:
|
||||||
*outInternalFormat = internalFormat;
|
*outInternalFormat = internalFormat;
|
||||||
break;
|
break;
|
||||||
@@ -276,6 +303,7 @@ namespace MobileGL::MG_Util::TextureFormatProcessor {
|
|||||||
*outFormat = GL_RGB;
|
*outFormat = GL_RGB;
|
||||||
break;
|
break;
|
||||||
case GL_SRGB8_ALPHA8:
|
case GL_SRGB8_ALPHA8:
|
||||||
|
case GL_SRGB_ALPHA:
|
||||||
*outFormat = GL_RGBA;
|
*outFormat = GL_RGBA;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -288,6 +316,24 @@ namespace MobileGL::MG_Util::TextureFormatProcessor {
|
|||||||
case GL_RGB5_A1:
|
case GL_RGB5_A1:
|
||||||
*outFormat = GL_RGBA;
|
*outFormat = GL_RGBA;
|
||||||
break;
|
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
|
// Depth
|
||||||
case GL_DEPTH_COMPONENT16:
|
case GL_DEPTH_COMPONENT16:
|
||||||
@@ -459,10 +505,44 @@ namespace MobileGL::MG_Util::TextureFormatProcessor {
|
|||||||
*outType = GL_UNSIGNED_INT_10F_11F_11F_REV;
|
*outType = GL_UNSIGNED_INT_10F_11F_11F_REV;
|
||||||
break;
|
break;
|
||||||
case GL_RGB10_A2:
|
case GL_RGB10_A2:
|
||||||
|
case GL_RGB10_A2UI:
|
||||||
*outType = GL_UNSIGNED_INT_2_10_10_10_REV;
|
*outType = GL_UNSIGNED_INT_2_10_10_10_REV;
|
||||||
break;
|
break;
|
||||||
case GL_RGB5_A1:
|
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;
|
break;
|
||||||
|
|
||||||
// Depth
|
// Depth
|
||||||
|
|||||||
Reference in New Issue
Block a user