diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.cpp b/MobileGL/MG_Backend/DirectGLES/Managers.cpp index 09538372..5132414b 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Managers.cpp @@ -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(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(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(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(attrib.Size); const SizeT stride = attrib.Stride > 0 ? static_cast(attrib.Stride) : elementSize; const SizeT uploadSize = static_cast(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(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(stride), nullptr); } else { g_GLESFuncs.glVertexAttribIPointer(attribIndex, attrib.Size, diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp index 048d0731..1e8802a2 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp @@ -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(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(attr.Stride) - : static_cast(componentSize * static_cast(attr.Size)); + const Uint32 stride = + attr.Stride > 0 ? static_cast(attr.Stride) : static_cast(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(size); + } } // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.h index 5661e8c4..afd65b37 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.h @@ -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 m_cache; diff --git a/MobileGL/MG_Impl/GLImpl/VertexArray/GL_VertexArray.cpp b/MobileGL/MG_Impl/GLImpl/VertexArray/GL_VertexArray.cpp index 1adbc51f..9e40c3b0 100644 --- a/MobileGL/MG_Impl/GLImpl/VertexArray/GL_VertexArray.cpp +++ b/MobileGL/MG_Impl/GLImpl/VertexArray/GL_VertexArray.cpp @@ -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(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(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(GL_BGRA)); + const int effectiveSize = isBgra ? 4 : size; + vao->SetAttributeFormat(index, effectiveSize, dataType, normalized, stride, offset, false, isBgra); vao->BindAttributeBuffer(index, vbo); } diff --git a/MobileGL/MG_Impl/GLImpl/VertexArray/Validators.cpp b/MobileGL/MG_Impl/GLImpl/VertexArray/Validators.cpp index 4842b175..f06d4ffc 100644 --- a/MobileGL/MG_Impl/GLImpl/VertexArray/Validators.cpp +++ b/MobileGL/MG_Impl/GLImpl/VertexArray/Validators.cpp @@ -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("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( + "MG_Impl/GLImpl", fn, + std::format("glVertexAttribIPointer does not accept packed 2_10_10_10 types (attribute {}).", + index))); + return false; + } + + if (sizeRaw == static_cast(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( + "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( + "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( + "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( + "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( + "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("MG_Impl/GLImpl", fn, + std::format("Negative stride {} for attribute {}.", stride, index))); + return false; + } + return true; + } } // namespace MobileGL::MG_Impl::GLImpl::VertexArrayImpl diff --git a/MobileGL/MG_Impl/GLImpl/VertexArray/Validators.h b/MobileGL/MG_Impl/GLImpl/VertexArray/Validators.h index dcf1728c..ca75201f 100644 --- a/MobileGL/MG_Impl/GLImpl/VertexArray/Validators.h +++ b/MobileGL/MG_Impl/GLImpl/VertexArray/Validators.h @@ -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 diff --git a/MobileGL/MG_State/GLState/VertexArrayState/VertexArrayObject.cpp b/MobileGL/MG_State/GLState/VertexArrayState/VertexArrayObject.cpp index 7e8e4795..0a10dba4 100644 --- a/MobileGL/MG_State/GLState/VertexArrayState/VertexArrayObject.cpp +++ b/MobileGL/MG_State/GLState/VertexArrayState/VertexArrayObject.cpp @@ -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); } diff --git a/MobileGL/MG_State/GLState/VertexArrayState/VertexArrayObject.h b/MobileGL/MG_State/GLState/VertexArrayState/VertexArrayObject.h index a93227a1..a7ee44f3 100644 --- a/MobileGL/MG_State/GLState/VertexArrayState/VertexArrayObject.h +++ b/MobileGL/MG_State/GLState/VertexArrayState/VertexArrayObject.h @@ -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 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& buffer); diff --git a/MobileGL/MG_Test/VertexArray/VertexArrayTest.cpp b/MobileGL/MG_Test/VertexArray/VertexArrayTest.cpp index dd564f77..0ce7ba85 100644 --- a/MobileGL/MG_Test/VertexArray/VertexArrayTest.cpp +++ b/MobileGL/MG_Test/VertexArray/VertexArrayTest.cpp @@ -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); +} + diff --git a/MobileGL/MG_Util/Converters/GLToMG/DataTypeConverter.cpp b/MobileGL/MG_Util/Converters/GLToMG/DataTypeConverter.cpp index 31e8f377..c6be0f89 100644 --- a/MobileGL/MG_Util/Converters/GLToMG/DataTypeConverter.cpp +++ b/MobileGL/MG_Util/Converters/GLToMG/DataTypeConverter.cpp @@ -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; } diff --git a/MobileGL/MG_Util/Converters/MGToGL/DataTypeConverter.cpp b/MobileGL/MG_Util/Converters/MGToGL/DataTypeConverter.cpp index 7b28c221..f0a7cd20 100644 --- a/MobileGL/MG_Util/Converters/MGToGL/DataTypeConverter.cpp +++ b/MobileGL/MG_Util/Converters/MGToGL/DataTypeConverter.cpp @@ -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; } diff --git a/MobileGL/MG_Util/Converters/MGToStr/DataTypeConverter.cpp b/MobileGL/MG_Util/Converters/MGToStr/DataTypeConverter.cpp index b325154b..73a55fb8 100644 --- a/MobileGL/MG_Util/Converters/MGToStr/DataTypeConverter.cpp +++ b/MobileGL/MG_Util/Converters/MGToStr/DataTypeConverter.cpp @@ -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"; diff --git a/MobileGL/MG_Util/Types.h b/MobileGL/MG_Util/Types.h index 42b6e312..1b4621b5 100644 --- a/MobileGL/MG_Util/Types.h +++ b/MobileGL/MG_Util/Types.h @@ -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 };