[Feat] (MG_Impl, MG_State, MG_Backend, MG_Util): packed 2_10_10_10 and GL_BGRA vertex array formats

glVertexAttribPointer now accepts the GL 3.3 Core packed types
GL_INT_/GL_UNSIGNED_INT_2_10_10_10_REV and the GL_BGRA size, clearing the
two long-standing "// TODO: implement GL_BGRA support" markers. Adds the
format end to end across the frontend, VAO state, and both backends.

- DataType: add Int2101010Rev / Uint2101010Rev with GLToMG / MGToGL /
  MGToStr converter cases.
- Validation (ValidateVertexAttribFormat): the full glVertexAttribPointer /
  glVertexAttribIPointer error table -- size is 1..4 or GL_BGRA (else
  INVALID_VALUE, which takes precedence); a packed type requires size 4 or
  GL_BGRA (else INVALID_OPERATION); GL_BGRA requires GL_UNSIGNED_BYTE or a
  packed type AND normalized == GL_TRUE (else INVALID_OPERATION); the
  integer path rejects packed types (INVALID_ENUM) and GL_BGRA size
  (INVALID_VALUE).
- VAO: store GL_BGRA as size 4 plus a new IsBgra flag (reset on the
  binding-format path).
- DirectVulkan: map the packed/BGRA formats to
  VK_FORMAT_A2B10G10R10_* (normal) and VK_FORMAT_A2R10G10B10_* /
  VK_FORMAT_B8G8R8A8_UNORM (BGRA reversed), fold IsBgra into the pipeline
  hash, and size packed/BGRA elements as one 4-byte word via
  GetAttributeByteSize. (Vulkan *_SNORM decodes with the GL 4.2 symmetric
  rule, a documented deviation from the 3.3 signed formula.)
- DirectGLES: round-trip the packed enum through the loader, pass GL_BGRA
  as the driver size argument, and size client uploads with the packed
  4-byte word.

Tests: 4 VertexArrayTest cases covering packed/BGRA storage and the full
float/integer error table; the packed-size hard-fail is mutation-verified.
VertexArrayTest 42/42, SanityTest 30/30, library builds clean.
This commit is contained in:
2026-07-10 23:25:25 -04:00
parent 0cada09aa7
commit 4dd2b2216c
13 changed files with 273 additions and 23 deletions
+17 -5
View File
@@ -572,6 +572,16 @@ namespace MobileGL::MG_Backend::DirectGLES {
return 0;
}
}
// Tightly-packed byte size of one vertex element: 4 for the 2_10_10_10 types and GL_BGRA
// (one 32-bit word / 4 bytes), componentSize * size otherwise. 0 for unknown types.
SizeT GetAttributeByteSize(DataType type, int size, Bool isBgra) {
if (type == DataType::Int2101010Rev || type == DataType::Uint2101010Rev || isBgra) {
return 4;
}
const SizeT componentSize = GetDataTypeSize(type);
return componentSize == 0 ? 0 : componentSize * static_cast<SizeT>(size);
}
} // namespace
BackendVertexArrayObject::BackendVertexArrayObject() {
@@ -665,8 +675,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
if (!attrib.IsInteger) {
// GL_BGRA is passed to the driver as the size argument (the driver reorders BGRA).
const GLint glSize = attrib.IsBgra ? static_cast<GLint>(GL_BGRA) : attrib.Size;
g_GLESFuncs.glVertexAttribPointer(
attribIndex, attrib.Size, MG_Util::ConvertDataTypeToGLEnum(attrib.Type),
attribIndex, glSize, MG_Util::ConvertDataTypeToGLEnum(attrib.Type),
attrib.Normalized ? GL_TRUE : GL_FALSE, attrib.Stride, (const void*)attrib.Offset);
} else {
g_GLESFuncs.glVertexAttribIPointer(attribIndex, attrib.Size,
@@ -720,12 +732,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
const auto* clientData = reinterpret_cast<const Uint8*>(attrib.Offset);
const SizeT componentSize = GetDataTypeSize(attrib.Type);
if (!clientData || componentSize == 0 || attrib.Size <= 0) {
const SizeT elementSize = GetAttributeByteSize(attrib.Type, attrib.Size, attrib.IsBgra);
if (!clientData || elementSize == 0 || attrib.Size <= 0) {
continue;
}
const SizeT elementSize = componentSize * static_cast<SizeT>(attrib.Size);
const SizeT stride = attrib.Stride > 0 ? static_cast<SizeT>(attrib.Stride) : elementSize;
const SizeT uploadSize = static_cast<SizeT>(first + count - 1) * stride + elementSize;
@@ -743,8 +754,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
GL_STREAM_DRAW);
if (!attrib.IsInteger) {
const GLint glSize = attrib.IsBgra ? static_cast<GLint>(GL_BGRA) : attrib.Size;
g_GLESFuncs.glVertexAttribPointer(
attribIndex, attrib.Size, MG_Util::ConvertDataTypeToGLEnum(attrib.Type),
attribIndex, glSize, MG_Util::ConvertDataTypeToGLEnum(attrib.Type),
attrib.Normalized ? GL_TRUE : GL_FALSE, static_cast<GLsizei>(stride), nullptr);
} else {
g_GLESFuncs.glVertexAttribIPointer(attribIndex, attrib.Size,
@@ -29,6 +29,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Stride, sizeof(attr.Stride)));
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Offset, sizeof(attr.Offset)));
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.IsInteger, sizeof(attr.IsInteger)));
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.IsBgra, sizeof(attr.IsBgra)));
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Divisor, sizeof(attr.Divisor)));
const SizeT bufferKey = reinterpret_cast<SizeT>(attr.Buffer.get());
@@ -73,7 +74,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
continue;
}
const auto vkFormat = ToVkVertexFormat(attr.Type, attr.Size, attr.Normalized, attr.IsInteger);
const auto vkFormat = ToVkVertexFormat(attr.Type, attr.Size, attr.Normalized, attr.IsInteger, attr.IsBgra);
if (vkFormat == VK_FORMAT_UNDEFINED) {
MGLOG_E("Unsupported vertex attribute layout (location=%u, type=%s, size=%d): the array is "
"enabled but cannot be mapped to a VkFormat",
@@ -82,8 +83,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
continue;
}
const SizeT componentSize = GetComponentSize(attr.Type);
if (componentSize == 0) {
const SizeT attribByteSize = GetAttributeByteSize(attr.Type, attr.Size, attr.IsBgra);
if (attribByteSize == 0) {
MGLOG_E("Vertex attribute with unknown component size (location=%u, type=%s): the array is "
"enabled but cannot be sized",
location, MG_Util::ConvertDataTypeToString(attr.Type).c_str());
@@ -91,9 +92,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
continue;
}
const Uint32 stride = attr.Stride > 0
? static_cast<Uint32>(attr.Stride)
: static_cast<Uint32>(componentSize * static_cast<SizeT>(attr.Size));
const Uint32 stride =
attr.Stride > 0 ? static_cast<Uint32>(attr.Stride) : static_cast<Uint32>(attribByteSize);
const VkVertexInputRate inputRate =
(attr.Divisor == 0) ? VK_VERTEX_INPUT_RATE_VERTEX : VK_VERTEX_INPUT_RATE_INSTANCE;
@@ -124,8 +124,32 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return entry;
}
VkFormat VertexInputStateFactory::ToVkVertexFormat(DataType type, Int size, Bool normalized, Bool isInteger) {
VkFormat VertexInputStateFactory::ToVkVertexFormat(DataType type, Int size, Bool normalized, Bool isInteger,
Bool isBgra) {
if (isBgra) {
// GL_BGRA: four reversed-order components, always normalized (enforced at validation), only
// legal with GL_UNSIGNED_BYTE or a 2_10_10_10 type. The reversed VkFormats put the
// components back into R,G,B,A order for the shader.
switch (type) {
case DataType::Uint8:
return VK_FORMAT_B8G8R8A8_UNORM;
case DataType::Uint2101010Rev:
return VK_FORMAT_A2R10G10B10_UNORM_PACK32;
case DataType::Int2101010Rev:
return VK_FORMAT_A2R10G10B10_SNORM_PACK32;
default:
return VK_FORMAT_UNDEFINED;
}
}
switch (type) {
case DataType::Uint2101010Rev:
// Packed 2_10_10_10 travels the float-normalizing path only; size is always 4. SNORM/UNORM
// normalize, SSCALED/USCALED cast the packed field to float.
if (isInteger || size != 4) return VK_FORMAT_UNDEFINED;
return normalized ? VK_FORMAT_A2B10G10R10_UNORM_PACK32 : VK_FORMAT_A2B10G10R10_USCALED_PACK32;
case DataType::Int2101010Rev:
if (isInteger || size != 4) return VK_FORMAT_UNDEFINED;
return normalized ? VK_FORMAT_A2B10G10R10_SNORM_PACK32 : VK_FORMAT_A2B10G10R10_SSCALED_PACK32;
case DataType::Float32:
switch (size) {
case 1: return VK_FORMAT_R32_SFLOAT;
@@ -248,4 +272,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return 0;
}
}
SizeT VertexInputStateFactory::GetAttributeByteSize(DataType type, Int size, Bool isBgra) {
// The packed 2_10_10_10 types are a single 32-bit word for all 4 components; GL_BGRA is always
// 4 components (GL_UNSIGNED_BYTE x4 = 4 bytes, or a packed word = 4 bytes) -- both are 4 bytes.
if (type == DataType::Int2101010Rev || type == DataType::Uint2101010Rev || isBgra) {
return 4;
}
const SizeT componentSize = GetComponentSize(type);
return componentSize == 0 ? 0 : componentSize * static_cast<SizeT>(size);
}
} // namespace MobileGL::MG_Backend::DirectVulkan
@@ -49,9 +49,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const MG_State::GLState::VertexArrayObject& vao, HashType hash);
const BackendVertexInputState& GetOrCreateVertexInputState(const MG_State::GLState::VertexArrayObject& vao);
static SizeT GetComponentSize(DataType type);
// Tightly-packed byte size of one vertex element for this attribute: componentSize * size for
// normal types, and 4 (one packed word) for the 2_10_10_10 types and GL_BGRA. Returns 0 for
// an unknown/unsupported type.
static SizeT GetAttributeByteSize(DataType type, Int size, Bool isBgra);
private:
static VkFormat ToVkVertexFormat(DataType type, Int size, Bool normalized, Bool isInteger);
static VkFormat ToVkVertexFormat(DataType type, Int size, Bool normalized, Bool isInteger, Bool isBgra = false);
const VulkanRendererConfig& m_config;
UnorderedMap<HashType, BackendVertexInputState> m_cache;
@@ -208,12 +208,12 @@ namespace MobileGL::MG_Impl::GLImpl {
vao->EnableAttribute(index);
}
// TODO: implement GL_BGRA support
void VertexAttribIPointer_State(GLuint index, GLint size, GLenum type, GLsizei stride, const void* pointer) {
if (!VertexArrayImpl::ValidateVertexAttributeIndex(index)) return;
DataType dataType = MG_Util::ConvertGLEnumToDataType(type);
if (!VertexArrayImpl::ValidateVertexAttribPointerParams(index, size, dataType, stride)) return;
// Integer path: never normalized, never BGRA/packed (the validator rejects those).
if (!VertexArrayImpl::ValidateVertexAttribFormat(index, size, dataType, false, stride, true)) return;
auto& vao = MG_State::pGLContext->GetBoundVertexArray();
if (!vao) {
@@ -228,17 +228,17 @@ namespace MobileGL::MG_Impl::GLImpl {
auto offset = reinterpret_cast<SizeT>(pointer);
vao->SetAttributeFormat(index, size, dataType, false, stride, offset, true);
vao->SetAttributeFormat(index, size, dataType, false, stride, offset, true, false);
vao->BindAttributeBuffer(index, vbo);
}
// TODO: implement GL_BGRA support
void VertexAttribPointer_State(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride,
const void* pointer) {
if (!VertexArrayImpl::ValidateVertexAttributeIndex(index)) return;
DataType dataType = MG_Util::ConvertGLEnumToDataType(type);
if (!VertexArrayImpl::ValidateVertexAttribPointerParams(index, size, dataType, stride)) return;
if (!VertexArrayImpl::ValidateVertexAttribFormat(index, size, dataType, normalized == GL_TRUE, stride, false))
return;
auto& vao = MG_State::pGLContext->GetBoundVertexArray();
if (!vao) {
@@ -253,7 +253,11 @@ namespace MobileGL::MG_Impl::GLImpl {
SizeT offset = reinterpret_cast<SizeT>(pointer);
vao->SetAttributeFormat(index, size, dataType, normalized, stride, offset, false);
// GL_BGRA is a 4-component reversed-order format; store 4 components and mark it BGRA so the
// backend can pick the reversed VkFormat / pass GL_BGRA through to a GLES driver.
const bool isBgra = (size == static_cast<GLint>(GL_BGRA));
const int effectiveSize = isBgra ? 4 : size;
vao->SetAttributeFormat(index, effectiveSize, dataType, normalized, stride, offset, false, isBgra);
vao->BindAttributeBuffer(index, vbo);
}
@@ -89,4 +89,85 @@ namespace MobileGL::MG_Impl::GLImpl::VertexArrayImpl {
return true;
}
Bool ValidateVertexAttribFormat(Uint index, GLint sizeRaw, DataType type, Bool normalized, Int stride,
Bool integerPath) {
constexpr const char* fn = "ValidateVertexAttribFormat";
if (type == DataType::Unknown) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidEnum,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", fn,
std::format("Invalid type for attribute {}.", index)));
return false;
}
const bool isPacked = (type == DataType::Int2101010Rev || type == DataType::Uint2101010Rev);
// The packed 2_10_10_10 types are float-normalizing only; glVertexAttribIPointer never accepts them.
if (integerPath && isPacked) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidEnum,
MakeUnique<GenericErrorInfo>(
"MG_Impl/GLImpl", fn,
std::format("glVertexAttribIPointer does not accept packed 2_10_10_10 types (attribute {}).",
index)));
return false;
}
if (sizeRaw == static_cast<GLint>(GL_BGRA)) {
// GL_BGRA is a float-path-only size: it needs GL_UNSIGNED_BYTE or a 2_10_10_10 type and
// normalized == GL_TRUE. On the integer path it is simply an out-of-range size.
if (integerPath) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>(
"MG_Impl/GLImpl", fn,
std::format("GL_BGRA size is not valid for glVertexAttribIPointer (attribute {}).", index)));
return false;
}
if (!(type == DataType::Uint8 || isPacked)) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
MakeUnique<GenericErrorInfo>(
"MG_Impl/GLImpl", fn,
std::format("GL_BGRA size requires GL_UNSIGNED_BYTE or a 2_10_10_10 type (attribute {}).",
index)));
return false;
}
if (!normalized) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
MakeUnique<GenericErrorInfo>(
"MG_Impl/GLImpl", fn,
std::format("GL_BGRA size requires normalized == GL_TRUE (attribute {}).", index)));
return false;
}
} else {
// Size-range (INVALID_VALUE) takes precedence over the packed-size-must-be-4 rule.
if (sizeRaw < 1 || sizeRaw > 4) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>(
"MG_Impl/GLImpl", fn,
std::format("Invalid size {} for attribute {}. Must be 1-4 or GL_BGRA.", sizeRaw, index)));
return false;
}
if (isPacked && sizeRaw != 4) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
MakeUnique<GenericErrorInfo>(
"MG_Impl/GLImpl", fn,
std::format("A 2_10_10_10 type requires size 4 or GL_BGRA (attribute {}).", index)));
return false;
}
}
if (stride < 0) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", fn,
std::format("Negative stride {} for attribute {}.", stride, index)));
return false;
}
return true;
}
} // namespace MobileGL::MG_Impl::GLImpl::VertexArrayImpl
@@ -19,4 +19,9 @@ namespace MobileGL::MG_Impl::GLImpl::VertexArrayImpl {
Bool ValidateVertexArrayObject(Uint index);
Bool ValidateVertexAttributeIndex(Uint index);
Bool ValidateVertexAttribPointerParams(Uint index, SizeT size, DataType type, Int stride);
// Full glVertexAttribPointer / glVertexAttribIPointer format validation, including the packed
// 2_10_10_10 types and GL_BGRA size. sizeRaw is the untranslated GL size (possibly GL_BGRA);
// integerPath selects the glVertexAttribIPointer rules.
Bool ValidateVertexAttribFormat(Uint index, GLint sizeRaw, DataType type, Bool normalized, Int stride,
Bool integerPath);
} // namespace MobileGL::MG_Impl::GLImpl::VertexArrayImpl
@@ -48,7 +48,7 @@ namespace MobileGL::MG_State::GLState {
}
void VertexArrayObject::SetAttributeFormat(Uint index, int size, DataType type, Bool normalized, int stride,
SizeT offset, Bool isInteger) {
SizeT offset, Bool isInteger, Bool isBgra) {
if (index >= MAX_VERTEX_ATTRIBS) return;
// The classic pointer-style API takes back full ownership of the resolved fields.
@@ -56,7 +56,8 @@ namespace MobileGL::MG_State::GLState {
if (m_attributes[index].Size == size && m_attributes[index].Type == type &&
m_attributes[index].Normalized == normalized && m_attributes[index].Stride == stride &&
m_attributes[index].Offset == offset && m_attributes[index].IsInteger == isInteger) {
m_attributes[index].Offset == offset && m_attributes[index].IsInteger == isInteger &&
m_attributes[index].IsBgra == isBgra) {
return;
}
@@ -71,6 +72,7 @@ namespace MobileGL::MG_State::GLState {
attr.Stride = stride;
attr.Offset = offset;
attr.IsInteger = isInteger;
attr.IsBgra = isBgra;
BumpAttributeFormatVersion(index);
}
@@ -191,11 +193,12 @@ namespace MobileGL::MG_State::GLState {
auto& attr = m_attributes[attribIndex];
if (attr.Size != size || attr.Type != type || attr.Normalized != normalized || attr.IsInteger != isInteger ||
m_attributeRelativeOffset[attribIndex] != relativeOffset) {
attr.IsBgra || m_attributeRelativeOffset[attribIndex] != relativeOffset) {
attr.Size = size;
attr.Type = type;
attr.Normalized = normalized;
attr.IsInteger = isInteger;
attr.IsBgra = false; // the binding-format path (glVertexAttribFormat) does not carry BGRA
m_attributeRelativeOffset[attribIndex] = relativeOffset;
BumpAttributeFormatVersion(attribIndex);
}
@@ -22,6 +22,8 @@ namespace MobileGL {
int Stride = 0;
SizeT Offset = 0;
Bool IsInteger = false;
// GL_BGRA vertex size: four components in reversed (B,G,R,A) memory order. Size stays 4.
Bool IsBgra = false;
Uint Divisor = 0;
SharedPtr<BufferObject> Buffer;
};
@@ -58,7 +60,7 @@ namespace MobileGL {
Bool IsAttributeEnabled(Uint index) const;
void SetAttributeFormat(Uint index, int size, DataType type, Bool normalized, int stride, SizeT offset,
Bool isInteger);
Bool isInteger, Bool isBgra = false);
void BindAttributeBuffer(Uint index, const SharedPtr<BufferObject>& buffer);
@@ -1127,3 +1127,92 @@ TEST_F(GeneralVertexArrayTest, CurrentAttrib_PackedValidation) {
EXPECT_EQ(GetError(), GL_INVALID_VALUE);
}
// ---- packed / GL_BGRA vertex ARRAY format (glVertexAttribPointer) --------------------------------
// F1: a 2_10_10_10 array format with size 4 is stored verbatim (normalized or not).
TEST_F(GeneralVertexArrayTest, ArrayFormat_PackedStored) {
CreateVAO();
CreateVBO(GL_ARRAY_BUFFER, 64);
VertexAttribPointer(0, 4, GL_INT_2_10_10_10_REV, GL_TRUE, 0, nullptr);
EXPECT_EQ(GetError(), GL_NO_ERROR);
const auto& a0 = MG_State::pGLContext->GetBoundVertexArray()->GetAttribute(0);
EXPECT_EQ(a0.Size, 4);
EXPECT_EQ(a0.Type, DataType::Int2101010Rev);
EXPECT_TRUE(a0.Normalized);
EXPECT_FALSE(a0.IsBgra);
EXPECT_FALSE(a0.IsInteger);
VertexAttribPointer(1, 4, GL_UNSIGNED_INT_2_10_10_10_REV, GL_FALSE, 0, nullptr);
EXPECT_EQ(GetError(), GL_NO_ERROR);
const auto& a1 = MG_State::pGLContext->GetBoundVertexArray()->GetAttribute(1);
EXPECT_EQ(a1.Type, DataType::Uint2101010Rev);
EXPECT_FALSE(a1.Normalized);
}
// F2: GL_BGRA is stored as size 4 with the IsBgra flag set, for GL_UNSIGNED_BYTE and 2_10_10_10.
TEST_F(GeneralVertexArrayTest, ArrayFormat_BgraStored) {
CreateVAO();
CreateVBO(GL_ARRAY_BUFFER, 64);
VertexAttribPointer(0, GL_BGRA, GL_UNSIGNED_BYTE, GL_TRUE, 0, nullptr);
EXPECT_EQ(GetError(), GL_NO_ERROR);
const auto& a0 = MG_State::pGLContext->GetBoundVertexArray()->GetAttribute(0);
EXPECT_EQ(a0.Size, 4); // GL_BGRA is stored as 4 components
EXPECT_EQ(a0.Type, DataType::Uint8);
EXPECT_TRUE(a0.IsBgra);
VertexAttribPointer(1, GL_BGRA, GL_INT_2_10_10_10_REV, GL_TRUE, 0, nullptr);
EXPECT_EQ(GetError(), GL_NO_ERROR);
const auto& a1 = MG_State::pGLContext->GetBoundVertexArray()->GetAttribute(1);
EXPECT_EQ(a1.Size, 4);
EXPECT_TRUE(a1.IsBgra);
EXPECT_EQ(a1.Type, DataType::Int2101010Rev);
}
// F3: the float-path error table -- add the format, then hard-fail the illegal combinations.
TEST_F(GeneralVertexArrayTest, ArrayFormat_FloatPathErrors) {
CreateVAO();
CreateVBO(GL_ARRAY_BUFFER, 64);
// 2_10_10_10 requires size 4 or GL_BGRA -> size 3 is GL_INVALID_OPERATION.
VertexAttribPointer(0, 3, GL_INT_2_10_10_10_REV, GL_TRUE, 0, nullptr);
EXPECT_EQ(GetError(), GL_INVALID_OPERATION);
// Size-range takes precedence: size 7 (packed or not) is GL_INVALID_VALUE.
VertexAttribPointer(0, 7, GL_INT_2_10_10_10_REV, GL_TRUE, 0, nullptr);
EXPECT_EQ(GetError(), GL_INVALID_VALUE);
VertexAttribPointer(0, 0, GL_FLOAT, GL_FALSE, 0, nullptr);
EXPECT_EQ(GetError(), GL_INVALID_VALUE);
// GL_BGRA requires normalized == GL_TRUE.
VertexAttribPointer(0, GL_BGRA, GL_UNSIGNED_BYTE, GL_FALSE, 0, nullptr);
EXPECT_EQ(GetError(), GL_INVALID_OPERATION);
// GL_BGRA requires GL_UNSIGNED_BYTE or a 2_10_10_10 type.
VertexAttribPointer(0, GL_BGRA, GL_FLOAT, GL_TRUE, 0, nullptr);
EXPECT_EQ(GetError(), GL_INVALID_OPERATION);
VertexAttribPointer(0, GL_BGRA, GL_SHORT, GL_TRUE, 0, nullptr);
EXPECT_EQ(GetError(), GL_INVALID_OPERATION);
}
// F4: glVertexAttribIPointer rejects packed types (INVALID_ENUM) and GL_BGRA (INVALID_VALUE); a plain
// integer format still works.
TEST_F(GeneralVertexArrayTest, ArrayFormat_IntegerPathRejectsPackedAndBgra) {
CreateVAO();
CreateVBO(GL_ARRAY_BUFFER, 64);
VertexAttribIPointer(0, 4, GL_INT_2_10_10_10_REV, 0, nullptr);
EXPECT_EQ(GetError(), GL_INVALID_ENUM);
VertexAttribIPointer(0, GL_BGRA, GL_INT, 0, nullptr);
EXPECT_EQ(GetError(), GL_INVALID_VALUE);
VertexAttribIPointer(0, 4, GL_INT, 0, nullptr);
EXPECT_EQ(GetError(), GL_NO_ERROR);
const auto& a0 = MG_State::pGLContext->GetBoundVertexArray()->GetAttribute(0);
EXPECT_EQ(a0.Type, DataType::Int32);
EXPECT_TRUE(a0.IsInteger);
EXPECT_FALSE(a0.IsBgra);
}
@@ -32,6 +32,10 @@ namespace MobileGL {
return DataType::Float32;
case GL_DOUBLE:
return DataType::Float64;
case GL_INT_2_10_10_10_REV:
return DataType::Int2101010Rev;
case GL_UNSIGNED_INT_2_10_10_10_REV:
return DataType::Uint2101010Rev;
default:
return DataType::Unknown;
}
@@ -32,6 +32,10 @@ namespace MobileGL {
return GL_FLOAT;
case DataType::Float64:
return GL_DOUBLE;
case DataType::Int2101010Rev:
return GL_INT_2_10_10_10_REV;
case DataType::Uint2101010Rev:
return GL_UNSIGNED_INT_2_10_10_10_REV;
default:
return GL_UNKNOWN_MGL;
}
@@ -32,6 +32,10 @@ namespace MobileGL {
return "Float64";
case DataType::Fixed32:
return "Fixed32";
case DataType::Int2101010Rev:
return "Int2101010Rev";
case DataType::Uint2101010Rev:
return "Uint2101010Rev";
case DataType::Unknown:
default:
return "Unknown";
+4
View File
@@ -109,6 +109,10 @@ namespace MobileGL {
Float32,
Float64,
Fixed32,
// Packed vertex types (GL_ARB_vertex_type_2_10_10_10_rev): four components (10/10/10/2 bits)
// in one 32-bit word. Only valid as a vertex array format with size 4 or GL_BGRA.
Int2101010Rev,
Uint2101010Rev,
Unknown = -1
};