mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-12 06:08: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;
|
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
|
} // namespace
|
||||||
|
|
||||||
BackendVertexArrayObject::BackendVertexArrayObject() {
|
BackendVertexArrayObject::BackendVertexArrayObject() {
|
||||||
@@ -665,8 +675,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!attrib.IsInteger) {
|
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(
|
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);
|
attrib.Normalized ? GL_TRUE : GL_FALSE, attrib.Stride, (const void*)attrib.Offset);
|
||||||
} else {
|
} else {
|
||||||
g_GLESFuncs.glVertexAttribIPointer(attribIndex, attrib.Size,
|
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 auto* clientData = reinterpret_cast<const Uint8*>(attrib.Offset);
|
||||||
const SizeT componentSize = GetDataTypeSize(attrib.Type);
|
const SizeT elementSize = GetAttributeByteSize(attrib.Type, attrib.Size, attrib.IsBgra);
|
||||||
if (!clientData || componentSize == 0 || attrib.Size <= 0) {
|
if (!clientData || elementSize == 0 || attrib.Size <= 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SizeT elementSize = componentSize * static_cast<SizeT>(attrib.Size);
|
|
||||||
const SizeT stride = attrib.Stride > 0 ? static_cast<SizeT>(attrib.Stride) : elementSize;
|
const SizeT stride = attrib.Stride > 0 ? static_cast<SizeT>(attrib.Stride) : elementSize;
|
||||||
const SizeT uploadSize = static_cast<SizeT>(first + count - 1) * 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);
|
GL_STREAM_DRAW);
|
||||||
|
|
||||||
if (!attrib.IsInteger) {
|
if (!attrib.IsInteger) {
|
||||||
|
const GLint glSize = attrib.IsBgra ? static_cast<GLint>(GL_BGRA) : attrib.Size;
|
||||||
g_GLESFuncs.glVertexAttribPointer(
|
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);
|
attrib.Normalized ? GL_TRUE : GL_FALSE, static_cast<GLsizei>(stride), nullptr);
|
||||||
} else {
|
} else {
|
||||||
g_GLESFuncs.glVertexAttribIPointer(attribIndex, attrib.Size,
|
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.Stride, sizeof(attr.Stride)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Offset, sizeof(attr.Offset)));
|
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.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)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Divisor, sizeof(attr.Divisor)));
|
||||||
|
|
||||||
const SizeT bufferKey = reinterpret_cast<SizeT>(attr.Buffer.get());
|
const SizeT bufferKey = reinterpret_cast<SizeT>(attr.Buffer.get());
|
||||||
@@ -73,7 +74,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
continue;
|
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) {
|
if (vkFormat == VK_FORMAT_UNDEFINED) {
|
||||||
MGLOG_E("Unsupported vertex attribute layout (location=%u, type=%s, size=%d): the array is "
|
MGLOG_E("Unsupported vertex attribute layout (location=%u, type=%s, size=%d): the array is "
|
||||||
"enabled but cannot be mapped to a VkFormat",
|
"enabled but cannot be mapped to a VkFormat",
|
||||||
@@ -82,8 +83,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SizeT componentSize = GetComponentSize(attr.Type);
|
const SizeT attribByteSize = GetAttributeByteSize(attr.Type, attr.Size, attr.IsBgra);
|
||||||
if (componentSize == 0) {
|
if (attribByteSize == 0) {
|
||||||
MGLOG_E("Vertex attribute with unknown component size (location=%u, type=%s): the array is "
|
MGLOG_E("Vertex attribute with unknown component size (location=%u, type=%s): the array is "
|
||||||
"enabled but cannot be sized",
|
"enabled but cannot be sized",
|
||||||
location, MG_Util::ConvertDataTypeToString(attr.Type).c_str());
|
location, MG_Util::ConvertDataTypeToString(attr.Type).c_str());
|
||||||
@@ -91,9 +92,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Uint32 stride = attr.Stride > 0
|
const Uint32 stride =
|
||||||
? static_cast<Uint32>(attr.Stride)
|
attr.Stride > 0 ? static_cast<Uint32>(attr.Stride) : static_cast<Uint32>(attribByteSize);
|
||||||
: static_cast<Uint32>(componentSize * static_cast<SizeT>(attr.Size));
|
|
||||||
const VkVertexInputRate inputRate =
|
const VkVertexInputRate inputRate =
|
||||||
(attr.Divisor == 0) ? VK_VERTEX_INPUT_RATE_VERTEX : VK_VERTEX_INPUT_RATE_INSTANCE;
|
(attr.Divisor == 0) ? VK_VERTEX_INPUT_RATE_VERTEX : VK_VERTEX_INPUT_RATE_INSTANCE;
|
||||||
|
|
||||||
@@ -124,8 +124,32 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
return entry;
|
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) {
|
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:
|
case DataType::Float32:
|
||||||
switch (size) {
|
switch (size) {
|
||||||
case 1: return VK_FORMAT_R32_SFLOAT;
|
case 1: return VK_FORMAT_R32_SFLOAT;
|
||||||
@@ -248,4 +272,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
return 0;
|
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
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
|
|||||||
@@ -49,9 +49,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
const MG_State::GLState::VertexArrayObject& vao, HashType hash);
|
const MG_State::GLState::VertexArrayObject& vao, HashType hash);
|
||||||
const BackendVertexInputState& GetOrCreateVertexInputState(const MG_State::GLState::VertexArrayObject& vao);
|
const BackendVertexInputState& GetOrCreateVertexInputState(const MG_State::GLState::VertexArrayObject& vao);
|
||||||
static SizeT GetComponentSize(DataType type);
|
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:
|
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;
|
const VulkanRendererConfig& m_config;
|
||||||
UnorderedMap<HashType, BackendVertexInputState> m_cache;
|
UnorderedMap<HashType, BackendVertexInputState> m_cache;
|
||||||
|
|||||||
@@ -208,12 +208,12 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
vao->EnableAttribute(index);
|
vao->EnableAttribute(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: implement GL_BGRA support
|
|
||||||
void VertexAttribIPointer_State(GLuint index, GLint size, GLenum type, GLsizei stride, const void* pointer) {
|
void VertexAttribIPointer_State(GLuint index, GLint size, GLenum type, GLsizei stride, const void* pointer) {
|
||||||
if (!VertexArrayImpl::ValidateVertexAttributeIndex(index)) return;
|
if (!VertexArrayImpl::ValidateVertexAttributeIndex(index)) return;
|
||||||
|
|
||||||
DataType dataType = MG_Util::ConvertGLEnumToDataType(type);
|
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();
|
auto& vao = MG_State::pGLContext->GetBoundVertexArray();
|
||||||
if (!vao) {
|
if (!vao) {
|
||||||
@@ -228,17 +228,17 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
|
|
||||||
auto offset = reinterpret_cast<SizeT>(pointer);
|
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);
|
vao->BindAttributeBuffer(index, vbo);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: implement GL_BGRA support
|
|
||||||
void VertexAttribPointer_State(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride,
|
void VertexAttribPointer_State(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride,
|
||||||
const void* pointer) {
|
const void* pointer) {
|
||||||
if (!VertexArrayImpl::ValidateVertexAttributeIndex(index)) return;
|
if (!VertexArrayImpl::ValidateVertexAttributeIndex(index)) return;
|
||||||
|
|
||||||
DataType dataType = MG_Util::ConvertGLEnumToDataType(type);
|
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();
|
auto& vao = MG_State::pGLContext->GetBoundVertexArray();
|
||||||
if (!vao) {
|
if (!vao) {
|
||||||
@@ -253,7 +253,11 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
|
|
||||||
SizeT offset = reinterpret_cast<SizeT>(pointer);
|
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);
|
vao->BindAttributeBuffer(index, vbo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -89,4 +89,85 @@ namespace MobileGL::MG_Impl::GLImpl::VertexArrayImpl {
|
|||||||
|
|
||||||
return true;
|
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
|
} // namespace MobileGL::MG_Impl::GLImpl::VertexArrayImpl
|
||||||
|
|||||||
@@ -19,4 +19,9 @@ namespace MobileGL::MG_Impl::GLImpl::VertexArrayImpl {
|
|||||||
Bool ValidateVertexArrayObject(Uint index);
|
Bool ValidateVertexArrayObject(Uint index);
|
||||||
Bool ValidateVertexAttributeIndex(Uint index);
|
Bool ValidateVertexAttributeIndex(Uint index);
|
||||||
Bool ValidateVertexAttribPointerParams(Uint index, SizeT size, DataType type, Int stride);
|
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
|
} // 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,
|
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;
|
if (index >= MAX_VERTEX_ATTRIBS) return;
|
||||||
|
|
||||||
// The classic pointer-style API takes back full ownership of the resolved fields.
|
// 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 &&
|
if (m_attributes[index].Size == size && m_attributes[index].Type == type &&
|
||||||
m_attributes[index].Normalized == normalized && m_attributes[index].Stride == stride &&
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,6 +72,7 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
attr.Stride = stride;
|
attr.Stride = stride;
|
||||||
attr.Offset = offset;
|
attr.Offset = offset;
|
||||||
attr.IsInteger = isInteger;
|
attr.IsInteger = isInteger;
|
||||||
|
attr.IsBgra = isBgra;
|
||||||
|
|
||||||
BumpAttributeFormatVersion(index);
|
BumpAttributeFormatVersion(index);
|
||||||
}
|
}
|
||||||
@@ -191,11 +193,12 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
|
|
||||||
auto& attr = m_attributes[attribIndex];
|
auto& attr = m_attributes[attribIndex];
|
||||||
if (attr.Size != size || attr.Type != type || attr.Normalized != normalized || attr.IsInteger != isInteger ||
|
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.Size = size;
|
||||||
attr.Type = type;
|
attr.Type = type;
|
||||||
attr.Normalized = normalized;
|
attr.Normalized = normalized;
|
||||||
attr.IsInteger = isInteger;
|
attr.IsInteger = isInteger;
|
||||||
|
attr.IsBgra = false; // the binding-format path (glVertexAttribFormat) does not carry BGRA
|
||||||
m_attributeRelativeOffset[attribIndex] = relativeOffset;
|
m_attributeRelativeOffset[attribIndex] = relativeOffset;
|
||||||
BumpAttributeFormatVersion(attribIndex);
|
BumpAttributeFormatVersion(attribIndex);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ namespace MobileGL {
|
|||||||
int Stride = 0;
|
int Stride = 0;
|
||||||
SizeT Offset = 0;
|
SizeT Offset = 0;
|
||||||
Bool IsInteger = false;
|
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;
|
Uint Divisor = 0;
|
||||||
SharedPtr<BufferObject> Buffer;
|
SharedPtr<BufferObject> Buffer;
|
||||||
};
|
};
|
||||||
@@ -58,7 +60,7 @@ namespace MobileGL {
|
|||||||
Bool IsAttributeEnabled(Uint index) const;
|
Bool IsAttributeEnabled(Uint index) const;
|
||||||
|
|
||||||
void SetAttributeFormat(Uint index, int size, DataType type, Bool normalized, int stride, SizeT offset,
|
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);
|
void BindAttributeBuffer(Uint index, const SharedPtr<BufferObject>& buffer);
|
||||||
|
|
||||||
|
|||||||
@@ -1127,3 +1127,92 @@ TEST_F(GeneralVertexArrayTest, CurrentAttrib_PackedValidation) {
|
|||||||
EXPECT_EQ(GetError(), GL_INVALID_VALUE);
|
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;
|
return DataType::Float32;
|
||||||
case GL_DOUBLE:
|
case GL_DOUBLE:
|
||||||
return DataType::Float64;
|
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:
|
default:
|
||||||
return DataType::Unknown;
|
return DataType::Unknown;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,10 @@ namespace MobileGL {
|
|||||||
return GL_FLOAT;
|
return GL_FLOAT;
|
||||||
case DataType::Float64:
|
case DataType::Float64:
|
||||||
return GL_DOUBLE;
|
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:
|
default:
|
||||||
return GL_UNKNOWN_MGL;
|
return GL_UNKNOWN_MGL;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,10 @@ namespace MobileGL {
|
|||||||
return "Float64";
|
return "Float64";
|
||||||
case DataType::Fixed32:
|
case DataType::Fixed32:
|
||||||
return "Fixed32";
|
return "Fixed32";
|
||||||
|
case DataType::Int2101010Rev:
|
||||||
|
return "Int2101010Rev";
|
||||||
|
case DataType::Uint2101010Rev:
|
||||||
|
return "Uint2101010Rev";
|
||||||
case DataType::Unknown:
|
case DataType::Unknown:
|
||||||
default:
|
default:
|
||||||
return "Unknown";
|
return "Unknown";
|
||||||
|
|||||||
@@ -109,6 +109,10 @@ namespace MobileGL {
|
|||||||
Float32,
|
Float32,
|
||||||
Float64,
|
Float64,
|
||||||
Fixed32,
|
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
|
Unknown = -1
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user