mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 12:18:30 +09:00
[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:
@@ -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";
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user