mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 04:08:32 +09:00
[Feat] (MG_State, MG_Util, MG_Impl/GLImpl, MG_Backend/DirectGLES): desktop-GL single-channel client formats GL_GREEN/GL_BLUE/GL_ALPHA and _INTEGER variants - validate and readback via wide-RGBA channel extraction (GL CTS packed_pixels rgba8_format_green/blue read with them), unpack GREEN/BLUE(_INTEGER) TexImage uploads into the named channel with 0/1 defaults per table 3.3; GL_ALPHA keeps the legacy Red upload mapping (R8 storage + 000R swizzle), its readback corrected at the backend to source channel 3
This commit is contained in:
@@ -3284,6 +3284,16 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
switch (format) {
|
||||
case GL_RED: outMapping = {{0, 0, 0, 0}, 1, false}; return true;
|
||||
case GL_RED_INTEGER: outMapping = {{0, 0, 0, 0}, 1, true}; return true;
|
||||
// Desktop-GL single-channel client formats (GL CTS packed_pixels rgba8_format_green/blue):
|
||||
// the destination holds one component sourced from the named channel of the wide RGBA read.
|
||||
// GL_ALPHA is mapped here from the raw enum because the state layer folds it into Red for the
|
||||
// legacy alpha-texture upload hack.
|
||||
case GL_GREEN: outMapping = {{1, 0, 0, 0}, 1, false}; return true;
|
||||
case GL_GREEN_INTEGER: outMapping = {{1, 0, 0, 0}, 1, true}; return true;
|
||||
case GL_BLUE: outMapping = {{2, 0, 0, 0}, 1, false}; return true;
|
||||
case GL_BLUE_INTEGER: outMapping = {{2, 0, 0, 0}, 1, true}; return true;
|
||||
case GL_ALPHA: outMapping = {{3, 0, 0, 0}, 1, false}; return true;
|
||||
case GL_ALPHA_INTEGER: outMapping = {{3, 0, 0, 0}, 1, true}; return true;
|
||||
case GL_RG: outMapping = {{0, 1, 0, 0}, 2, false}; return true;
|
||||
case GL_RG_INTEGER: outMapping = {{0, 1, 0, 0}, 2, true}; return true;
|
||||
case GL_RGB: outMapping = {{0, 1, 2, 0}, 3, false}; return true;
|
||||
|
||||
@@ -178,7 +178,9 @@ namespace MobileGL::MG_Impl::GLImpl::TextureImpl {
|
||||
static Bool IsIntegerColorInputFormat(TextureInputFormat format) {
|
||||
return format == TextureInputFormat::RInteger || format == TextureInputFormat::RGInteger ||
|
||||
format == TextureInputFormat::RGBInteger || format == TextureInputFormat::BGRInteger ||
|
||||
format == TextureInputFormat::RGBAInteger || format == TextureInputFormat::BGRAInteger;
|
||||
format == TextureInputFormat::RGBAInteger || format == TextureInputFormat::BGRAInteger ||
|
||||
format == TextureInputFormat::GreenInteger || format == TextureInputFormat::BlueInteger ||
|
||||
format == TextureInputFormat::AlphaInteger;
|
||||
}
|
||||
|
||||
static Bool IsIntegerColorInternalFormat(TextureInternalFormat internalFormat) {
|
||||
|
||||
@@ -76,6 +76,14 @@ namespace MobileGL {
|
||||
BGRInteger,
|
||||
RGBAInteger,
|
||||
BGRAInteger,
|
||||
// Desktop-GL single-channel client formats (table 3.3): the data holds one component that
|
||||
// feeds the G, B or A channel; the remaining channels default to 0 (color) / 1 (alpha).
|
||||
Green,
|
||||
Blue,
|
||||
Alpha,
|
||||
GreenInteger,
|
||||
BlueInteger,
|
||||
AlphaInteger,
|
||||
StencilIndex,
|
||||
DepthComponent,
|
||||
DepthStencil,
|
||||
|
||||
@@ -32,6 +32,8 @@ namespace {
|
||||
Int g_clearNamedFramebufferfvCallCount = 0;
|
||||
Int g_clearNamedFramebufferfiCallCount = 0;
|
||||
Int g_readPixelsCallCount = 0;
|
||||
GLenum g_lastReadPixelsFormat = GL_NONE;
|
||||
GLenum g_lastReadPixelsType = GL_NONE;
|
||||
|
||||
void RecordBlitNamedFramebuffer(const SharedPtr<MG_State::GLState::FramebufferObject>& readFramebuffer,
|
||||
const SharedPtr<MG_State::GLState::FramebufferObject>& drawFramebuffer,
|
||||
@@ -66,8 +68,10 @@ namespace {
|
||||
++g_clearNamedFramebufferfiCallCount;
|
||||
}
|
||||
|
||||
void RecordReadPixels(GLint, GLint, GLsizei, GLsizei, GLenum, GLenum, void*) {
|
||||
void RecordReadPixels(GLint, GLint, GLsizei, GLsizei, GLenum format, GLenum type, void*) {
|
||||
++g_readPixelsCallCount;
|
||||
g_lastReadPixelsFormat = format;
|
||||
g_lastReadPixelsType = type;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
@@ -97,6 +101,8 @@ protected:
|
||||
g_clearNamedFramebufferfvCallCount = 0;
|
||||
g_clearNamedFramebufferfiCallCount = 0;
|
||||
g_readPixelsCallCount = 0;
|
||||
g_lastReadPixelsFormat = GL_NONE;
|
||||
g_lastReadPixelsType = GL_NONE;
|
||||
MG_Backend::gBackendFunctionsTable.GL.BlitNamedFramebuffer = nullptr;
|
||||
MG_Backend::gBackendFunctionsTable.GL.ClearNamedFramebufferfv = nullptr;
|
||||
MG_Backend::gBackendFunctionsTable.GL.ClearNamedFramebufferfi = nullptr;
|
||||
@@ -255,6 +261,43 @@ TEST_F(FramebufferTest, ReadPixelsRejectsMismatchedPackedTypeFormatPairs) {
|
||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||
}
|
||||
|
||||
TEST_F(FramebufferTest, ReadPixelsForwardsSingleChannelDesktopClientFormats) {
|
||||
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_RGBA8, 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] = {};
|
||||
|
||||
// Desktop GL treats GL_GREEN/GL_BLUE/GL_ALPHA as valid ReadPixels client formats (GL CTS
|
||||
// packed_pixels rgba8_format_green failed with GL_INVALID_ENUM before). The state layer must
|
||||
// validate them and forward the raw enum to the backend, which extracts the source channel
|
||||
// from a wide RGBA read.
|
||||
const GLenum singleChannelFormats[] = {GL_GREEN, GL_BLUE, GL_ALPHA};
|
||||
Int expectedCallCount = 0;
|
||||
for (const GLenum format : singleChannelFormats) {
|
||||
MG_Impl::GLImpl::ReadPixels(0, 0, 4, 4, format, GL_UNSIGNED_BYTE, pixelStorage);
|
||||
EXPECT_EQ(g_readPixelsCallCount, ++expectedCallCount);
|
||||
EXPECT_EQ(g_lastReadPixelsFormat, format);
|
||||
EXPECT_EQ(g_lastReadPixelsType, static_cast<GLenum>(GL_UNSIGNED_BYTE));
|
||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||
}
|
||||
|
||||
// Packed-type pairing still applies: packed RGB/RGBA types never pair with single-channel formats.
|
||||
MG_Impl::GLImpl::ReadPixels(0, 0, 4, 4, GL_GREEN, GL_UNSIGNED_SHORT_5_6_5, pixelStorage);
|
||||
EXPECT_EQ(g_readPixelsCallCount, expectedCallCount);
|
||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_INVALID_OPERATION);
|
||||
|
||||
// Integer client formats reject floating-point types.
|
||||
MG_Impl::GLImpl::ReadPixels(0, 0, 4, 4, GL_GREEN_INTEGER, GL_FLOAT, pixelStorage);
|
||||
EXPECT_EQ(g_readPixelsCallCount, expectedCallCount);
|
||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_INVALID_OPERATION);
|
||||
}
|
||||
|
||||
TEST_F(FramebufferTest, NamedRenderbufferStorageAndFramebufferAttachDoNotChangeBindings) {
|
||||
GLuint framebuffer = 0;
|
||||
GLuint renderbuffer = 0;
|
||||
|
||||
@@ -329,6 +329,100 @@ TEST_F(TextureTest, TexImage2DAcceptsSpecCompliantFormatCombinations) {
|
||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||
}
|
||||
|
||||
// Desktop GL table 3.3 lists GREEN and BLUE as TexImage client formats (GL CTS packed_pixels
|
||||
// rgba8_format_green/blue upload with them and verify the readback): the single input component
|
||||
// feeds the named channel, the other color channels default to 0 and alpha to 1.
|
||||
TEST_F(TextureTest, BoundTexImage2DUnpacksGreenAndBlueIntoRgba8Channels) {
|
||||
GLuint greenTexture = 0;
|
||||
MG_Impl::GLImpl::GenTextures(1, &greenTexture);
|
||||
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, greenTexture);
|
||||
|
||||
const Uint8 pixels[] = {
|
||||
10, 20,
|
||||
30, 40,
|
||||
};
|
||||
MG_Impl::GLImpl::PixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 2, 2, 0, GL_GREEN, GL_UNSIGNED_BYTE, pixels);
|
||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||
|
||||
const auto* storedGreen = GetBoundTexture2DLevelBytes(greenTexture);
|
||||
const Uint8 expectedGreen[] = {
|
||||
0, 10, 0, 255,
|
||||
0, 20, 0, 255,
|
||||
0, 30, 0, 255,
|
||||
0, 40, 0, 255,
|
||||
};
|
||||
for (SizeT i = 0; i < sizeof(expectedGreen); ++i) {
|
||||
EXPECT_EQ(storedGreen[i], expectedGreen[i]) << "byte " << i;
|
||||
}
|
||||
|
||||
GLuint blueTexture = 0;
|
||||
MG_Impl::GLImpl::GenTextures(1, &blueTexture);
|
||||
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, blueTexture);
|
||||
MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 2, 2, 0, GL_BLUE, GL_UNSIGNED_BYTE, pixels);
|
||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||
|
||||
const auto* storedBlue = GetBoundTexture2DLevelBytes(blueTexture);
|
||||
const Uint8 expectedBlue[] = {
|
||||
0, 0, 10, 255,
|
||||
0, 0, 20, 255,
|
||||
0, 0, 30, 255,
|
||||
0, 0, 40, 255,
|
||||
};
|
||||
for (SizeT i = 0; i < sizeof(expectedBlue); ++i) {
|
||||
EXPECT_EQ(storedBlue[i], expectedBlue[i]) << "byte " << i;
|
||||
}
|
||||
MG_Impl::GLImpl::PixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||
}
|
||||
|
||||
TEST_F(TextureTest, BoundTexImage2DUnpacksGreenIntegerIntoRgba8UiChannels) {
|
||||
GLuint texture = 0;
|
||||
MG_Impl::GLImpl::GenTextures(1, &texture);
|
||||
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, texture);
|
||||
|
||||
const Uint8 pixels[] = {
|
||||
10, 20,
|
||||
30, 40,
|
||||
};
|
||||
MG_Impl::GLImpl::PixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8UI, 2, 2, 0, GL_GREEN_INTEGER, GL_UNSIGNED_BYTE, pixels);
|
||||
MG_Impl::GLImpl::PixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||
|
||||
const auto* stored = GetBoundTexture2DLevelBytes(texture);
|
||||
// Missing integer channels default to R=0, B=0, A=1.
|
||||
const Uint8 expected[] = {
|
||||
0, 10, 0, 1,
|
||||
0, 20, 0, 1,
|
||||
0, 30, 0, 1,
|
||||
0, 40, 0, 1,
|
||||
};
|
||||
for (SizeT i = 0; i < sizeof(expected); ++i) {
|
||||
EXPECT_EQ(stored[i], expected[i]) << "byte " << i;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(TextureTest, TexImage2DSingleChannelFormatsKeepIntegerNessRules) {
|
||||
GLuint texture = 0;
|
||||
MG_Impl::GLImpl::GenTextures(1, &texture);
|
||||
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, texture);
|
||||
|
||||
// Integer-ness of format and internal format must match (both directions).
|
||||
MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 2, 2, 0, GL_GREEN_INTEGER, GL_UNSIGNED_BYTE, nullptr);
|
||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_INVALID_OPERATION);
|
||||
MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8UI, 2, 2, 0, GL_BLUE, GL_UNSIGNED_BYTE, nullptr);
|
||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_INVALID_OPERATION);
|
||||
|
||||
// Integer formats reject floating-point types.
|
||||
MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8UI, 2, 2, 0, GL_BLUE_INTEGER, GL_FLOAT, nullptr);
|
||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_INVALID_OPERATION);
|
||||
|
||||
// Packed types never pair with single-channel formats.
|
||||
MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 2, 2, 0, GL_GREEN, GL_UNSIGNED_SHORT_5_6_5, nullptr);
|
||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_INVALID_OPERATION);
|
||||
}
|
||||
|
||||
TEST_F(TextureTest, TexImage3DRejectsDepthFormatsForThreeDimensionalTarget) {
|
||||
GLuint texture = 0;
|
||||
MG_Impl::GLImpl::GenTextures(1, &texture);
|
||||
|
||||
@@ -58,9 +58,17 @@ namespace MobileGL {
|
||||
|
||||
TextureInputFormat ConvertGLEnumToTextureInputFormat(GLenum format) {
|
||||
switch (format) {
|
||||
// Legacy carve-out: GL_ALPHA stays mapped to Red so alpha-texture uploads keep landing in
|
||||
// the R channel of the R8-backed storage (TexImage*_State pairs this with a 0,0,0,R
|
||||
// swizzle). Readback of GL_ALPHA is corrected at the backend, which maps the raw enum to
|
||||
// source channel 3 (DirectGLES GetReadbackChannelMapping).
|
||||
case GL_ALPHA:
|
||||
case GL_RED:
|
||||
return TextureInputFormat::Red;
|
||||
case GL_GREEN:
|
||||
return TextureInputFormat::Green;
|
||||
case GL_BLUE:
|
||||
return TextureInputFormat::Blue;
|
||||
case GL_RG:
|
||||
return TextureInputFormat::RG;
|
||||
case GL_RGB:
|
||||
@@ -73,6 +81,12 @@ namespace MobileGL {
|
||||
return TextureInputFormat::BGRA;
|
||||
case GL_RED_INTEGER:
|
||||
return TextureInputFormat::RInteger;
|
||||
case GL_GREEN_INTEGER:
|
||||
return TextureInputFormat::GreenInteger;
|
||||
case GL_BLUE_INTEGER:
|
||||
return TextureInputFormat::BlueInteger;
|
||||
case GL_ALPHA_INTEGER:
|
||||
return TextureInputFormat::AlphaInteger;
|
||||
case GL_RG_INTEGER:
|
||||
return TextureInputFormat::RGInteger;
|
||||
case GL_RGB_INTEGER:
|
||||
|
||||
@@ -535,6 +535,7 @@ namespace MobileGL {
|
||||
CASE(GL_RED_INTEGER)
|
||||
CASE(GL_GREEN_INTEGER)
|
||||
CASE(GL_BLUE_INTEGER)
|
||||
CASE(GL_ALPHA_INTEGER)
|
||||
CASE(GL_RGB_INTEGER)
|
||||
CASE(GL_RGBA_INTEGER)
|
||||
CASE(GL_BGR_INTEGER)
|
||||
|
||||
@@ -67,6 +67,18 @@ namespace MobileGL {
|
||||
return GL_RGBA_INTEGER;
|
||||
case TextureInputFormat::BGRAInteger:
|
||||
return GL_BGRA_INTEGER;
|
||||
case TextureInputFormat::Green:
|
||||
return GL_GREEN;
|
||||
case TextureInputFormat::Blue:
|
||||
return GL_BLUE;
|
||||
case TextureInputFormat::Alpha:
|
||||
return GL_ALPHA;
|
||||
case TextureInputFormat::GreenInteger:
|
||||
return GL_GREEN_INTEGER;
|
||||
case TextureInputFormat::BlueInteger:
|
||||
return GL_BLUE_INTEGER;
|
||||
case TextureInputFormat::AlphaInteger:
|
||||
return GL_ALPHA_INTEGER;
|
||||
case TextureInputFormat::StencilIndex:
|
||||
return GL_STENCIL_INDEX;
|
||||
case TextureInputFormat::DepthComponent:
|
||||
|
||||
@@ -67,6 +67,18 @@ namespace MobileGL {
|
||||
return "RGBAInteger";
|
||||
case TextureInputFormat::BGRAInteger:
|
||||
return "BGRAInteger";
|
||||
case TextureInputFormat::Green:
|
||||
return "Green";
|
||||
case TextureInputFormat::Blue:
|
||||
return "Blue";
|
||||
case TextureInputFormat::Alpha:
|
||||
return "Alpha";
|
||||
case TextureInputFormat::GreenInteger:
|
||||
return "GreenInteger";
|
||||
case TextureInputFormat::BlueInteger:
|
||||
return "BlueInteger";
|
||||
case TextureInputFormat::AlphaInteger:
|
||||
return "AlphaInteger";
|
||||
case TextureInputFormat::StencilIndex:
|
||||
return "StencilIndex";
|
||||
case TextureInputFormat::DepthComponent:
|
||||
|
||||
@@ -65,6 +65,16 @@ namespace MobileGL {
|
||||
return VK_FORMAT_R8G8B8A8_UINT;
|
||||
case TextureInputFormat::BGRAInteger:
|
||||
return VK_FORMAT_B8G8R8A8_UINT;
|
||||
// Single-channel desktop client formats: the client memory holds one component per pixel,
|
||||
// matching the R8 layouts (the channel it feeds is a pixel-transfer concern, not a layout one).
|
||||
case TextureInputFormat::Green:
|
||||
case TextureInputFormat::Blue:
|
||||
case TextureInputFormat::Alpha:
|
||||
return VK_FORMAT_R8_UNORM;
|
||||
case TextureInputFormat::GreenInteger:
|
||||
case TextureInputFormat::BlueInteger:
|
||||
case TextureInputFormat::AlphaInteger:
|
||||
return VK_FORMAT_R8_UINT;
|
||||
case TextureInputFormat::StencilIndex:
|
||||
return VK_FORMAT_S8_UINT;
|
||||
case TextureInputFormat::DepthComponent:
|
||||
|
||||
@@ -113,6 +113,12 @@ namespace MobileGL {
|
||||
switch (format) {
|
||||
case TextureInputFormat::Red:
|
||||
case TextureInputFormat::RInteger:
|
||||
case TextureInputFormat::Green:
|
||||
case TextureInputFormat::GreenInteger:
|
||||
case TextureInputFormat::Blue:
|
||||
case TextureInputFormat::BlueInteger:
|
||||
case TextureInputFormat::Alpha:
|
||||
case TextureInputFormat::AlphaInteger:
|
||||
return 1;
|
||||
case TextureInputFormat::RG:
|
||||
case TextureInputFormat::RGInteger:
|
||||
|
||||
@@ -201,6 +201,12 @@ namespace MobileGL::MG_Util::PixelStoreProcessor {
|
||||
switch (format) {
|
||||
case TextureInputFormat::Red: out = {{0, -1, -1, -1}, 1, false}; return true;
|
||||
case TextureInputFormat::RInteger: out = {{0, -1, -1, -1}, 1, true}; return true;
|
||||
case TextureInputFormat::Green: out = {{-1, 0, -1, -1}, 1, false}; return true;
|
||||
case TextureInputFormat::GreenInteger: out = {{-1, 0, -1, -1}, 1, true}; return true;
|
||||
case TextureInputFormat::Blue: out = {{-1, -1, 0, -1}, 1, false}; return true;
|
||||
case TextureInputFormat::BlueInteger: out = {{-1, -1, 0, -1}, 1, true}; return true;
|
||||
case TextureInputFormat::Alpha: out = {{-1, -1, -1, 0}, 1, false}; return true;
|
||||
case TextureInputFormat::AlphaInteger: out = {{-1, -1, -1, 0}, 1, true}; return true;
|
||||
case TextureInputFormat::RG: out = {{0, 1, -1, -1}, 2, false}; return true;
|
||||
case TextureInputFormat::RGInteger: out = {{0, 1, -1, -1}, 2, true}; return true;
|
||||
case TextureInputFormat::RGB: out = {{0, 1, 2, -1}, 3, false}; return true;
|
||||
|
||||
Reference in New Issue
Block a user