From 0cada09aa7cbbd2ffb32deef676e9c77608b0264 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Fri, 10 Jul 2026 23:07:13 -0400 Subject: [PATCH] [Feat] (MG_Impl/GLImpl): implement 8 packed glVertexAttribP*ui current-value setters glVertexAttribP{1,2,3,4}ui and their *uiv forms set the CURRENT generic vertex attribute value from a packed 2_10_10_10_REV word (they are the packed members of the immediate VertexAttrib* family, not the array-format path), so they funnel into SetCurrentVertexAttributeFloat and reuse the existing index validation. - Add DecodePacked2101010: unpacks x=[0..9], y=[10..19], z=[20..29] (10-bit) and w=[30..31] (2-bit) from one 32-bit word. Signed fields are two's- complement (sign-extended per width); normalized conversion uses the GL 3.3 (2c+1)/(2^b-1) form (10-bit /1023, 2-bit /3), matching the existing NormalizeSigned* helpers -- NOT the GL 4.2 clamp form. - type accepts only GL_INT_2_10_10_10_REV / GL_UNSIGNED_INT_2_10_10_10_REV (GL_INVALID_ENUM otherwise; the 4.4-era 10F_11F_11F_REV is not legal in 3.3). P1/P2/P3 consume the first 1/2/3 components; the rest take the (0,0,0,1) defaults and are cleared each call. The *uiv forms dereference a single packed word, not an array. Tests: 4 VertexArrayTest cases (unsigned decode, signed GL-3.3 formula, component-count/defaults, type/index/uiv validation). The signed test is mutation-verified: z==0 -> 1/1023 fails against the GL 4.2 form. VertexArrayTest 38/38, SanityTest 30/30. --- .../MG_Impl/GLImpl/Exporting/Definitions.cpp | 16 +-- .../GLImpl/VertexArray/GL_VertexArray.cpp | 117 ++++++++++++++++++ .../GLImpl/VertexArray/GL_VertexArray.h | 10 ++ .../MG_Test/VertexArray/VertexArrayTest.cpp | 115 +++++++++++++++++ 4 files changed, 250 insertions(+), 8 deletions(-) diff --git a/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp b/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp index be415c3a..02f8649a 100644 --- a/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp +++ b/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp @@ -871,14 +871,14 @@ DECLARE_GL_FUNCTION_STUB_HEAD(GLint, GetFragDataIndex, GLuint program, const GLc DECLARE_GL_FUNCTION_HEAD(void, QueryCounter, GLuint id, GLenum target) DECLARE_GL_FUNCTION_END_NO_RETURN(void, QueryCounter, id, target) DECLARE_GL_FUNCTION_HEAD(void, GetQueryObjecti64v, GLuint id, GLenum pname, GLint64* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetQueryObjecti64v, id, pname, params) DECLARE_GL_FUNCTION_HEAD(void, GetQueryObjectui64v, GLuint id, GLenum pname, GLuint64* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetQueryObjectui64v, id, pname, params) -DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribP1ui, GLuint index, GLenum type, GLboolean normalized, GLuint value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribP1ui, index, type, normalized, value) -DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribP1uiv, GLuint index, GLenum type, GLboolean normalized, const GLuint* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribP1uiv, index, type, normalized, value) -DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribP2ui, GLuint index, GLenum type, GLboolean normalized, GLuint value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribP2ui, index, type, normalized, value) -DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribP2uiv, GLuint index, GLenum type, GLboolean normalized, const GLuint* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribP2uiv, index, type, normalized, value) -DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribP3ui, GLuint index, GLenum type, GLboolean normalized, GLuint value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribP3ui, index, type, normalized, value) -DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribP3uiv, GLuint index, GLenum type, GLboolean normalized, const GLuint* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribP3uiv, index, type, normalized, value) -DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribP4ui, GLuint index, GLenum type, GLboolean normalized, GLuint value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribP4ui, index, type, normalized, value) -DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribP4uiv, GLuint index, GLenum type, GLboolean normalized, const GLuint* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribP4uiv, index, type, normalized, value) +DECLARE_GL_FUNCTION_HEAD(void, VertexAttribP1ui, GLuint index, GLenum type, GLboolean normalized, GLuint value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribP1ui, index, type, normalized, value) +DECLARE_GL_FUNCTION_HEAD(void, VertexAttribP1uiv, GLuint index, GLenum type, GLboolean normalized, const GLuint* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribP1uiv, index, type, normalized, value) +DECLARE_GL_FUNCTION_HEAD(void, VertexAttribP2ui, GLuint index, GLenum type, GLboolean normalized, GLuint value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribP2ui, index, type, normalized, value) +DECLARE_GL_FUNCTION_HEAD(void, VertexAttribP2uiv, GLuint index, GLenum type, GLboolean normalized, const GLuint* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribP2uiv, index, type, normalized, value) +DECLARE_GL_FUNCTION_HEAD(void, VertexAttribP3ui, GLuint index, GLenum type, GLboolean normalized, GLuint value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribP3ui, index, type, normalized, value) +DECLARE_GL_FUNCTION_HEAD(void, VertexAttribP3uiv, GLuint index, GLenum type, GLboolean normalized, const GLuint* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribP3uiv, index, type, normalized, value) +DECLARE_GL_FUNCTION_HEAD(void, VertexAttribP4ui, GLuint index, GLenum type, GLboolean normalized, GLuint value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribP4ui, index, type, normalized, value) +DECLARE_GL_FUNCTION_HEAD(void, VertexAttribP4uiv, GLuint index, GLenum type, GLboolean normalized, const GLuint* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribP4uiv, index, type, normalized, value) DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexP2ui, GLenum type, GLuint value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexP2ui, type, value) DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexP2uiv, GLenum type, const GLuint* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexP2uiv, type, value) DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexP3ui, GLenum type, GLuint value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexP3ui, type, value) diff --git a/MobileGL/MG_Impl/GLImpl/VertexArray/GL_VertexArray.cpp b/MobileGL/MG_Impl/GLImpl/VertexArray/GL_VertexArray.cpp index dbc2bf6d..1adbc51f 100644 --- a/MobileGL/MG_Impl/GLImpl/VertexArray/GL_VertexArray.cpp +++ b/MobileGL/MG_Impl/GLImpl/VertexArray/GL_VertexArray.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include namespace MobileGL::MG_Impl::GLImpl { @@ -41,6 +42,41 @@ namespace MobileGL::MG_Impl::GLImpl { } // (b = 8 unsigned normalization is VertexAttrib4Nub's x * (1/255).) + // Sign-extend a `bits`-wide two's-complement field held in the low bits of `field`. + constexpr GLint SignExtendField(GLuint field, int bits) { + const GLuint signBit = 1u << (bits - 1); + return (field & signBit) ? static_cast(field | (~0u << bits)) : static_cast(field); + } + + // Decode one GL_INT_/GL_UNSIGNED_INT_2_10_10_10_REV packed word into four float components. + // The _REV layout packs x in bits [0..9], y in [10..19], z in [20..29], w in [30..31]; x/y/z + // are 10-bit fields and w is a 2-bit field. Signed fields are two's-complement, and normalized + // conversion uses the GL 3.3 (2c+1)/(2^b-1) form (matching NormalizeSigned* above), NOT the + // GL 4.2 clamp form. + Array DecodePacked2101010(GLuint value, bool signedType, bool normalized) { + const GLuint fx = value & 0x3FFu; + const GLuint fy = (value >> 10) & 0x3FFu; + const GLuint fz = (value >> 20) & 0x3FFu; + const GLuint fw = (value >> 30) & 0x3u; + if (signedType) { + const GLint sx = SignExtendField(fx, 10); + const GLint sy = SignExtendField(fy, 10); + const GLint sz = SignExtendField(fz, 10); + const GLint sw = SignExtendField(fw, 2); + if (normalized) { + return {(2 * sx + 1) / 1023.0f, (2 * sy + 1) / 1023.0f, (2 * sz + 1) / 1023.0f, + (2 * sw + 1) / 3.0f}; + } + return {static_cast(sx), static_cast(sy), static_cast(sz), + static_cast(sw)}; + } + if (normalized) { + return {fx / 1023.0f, fy / 1023.0f, fz / 1023.0f, fw / 3.0f}; + } + return {static_cast(fx), static_cast(fy), static_cast(fz), + static_cast(fw)}; + } + static bool ValidateCurrentVertexAttribIndex(GLuint index, const char* funcName) { if (!VertexArrayImpl::ValidateVertexAttributeIndex(index)) return false; if (index == 0) { @@ -499,6 +535,87 @@ namespace MobileGL::MG_Impl::GLImpl { VertexAttribI4ui(index, v[0], v[1], v[2], v[3]); } + // Shared body for glVertexAttribP{1,2,3,4}ui(v). These set the CURRENT generic vertex attribute + // value (they are the packed members of the immediate VertexAttrib* family, not the array-format + // path), so they take the float current-value funnel. The single packed word is always fully + // decoded, but only the first `componentCount` components are written; the rest keep the generic + // attribute defaults (0, 0, 0, 1). type must be one of the two 2_10_10_10_REV packed enums. + static void VertexAttribP_Common(GLuint index, GLenum type, GLboolean normalized, GLuint value, + int componentCount, const char* funcName) { + if (!ValidateCurrentVertexAttribIndex(index, funcName)) return; + + bool signedType; + if (type == GL_INT_2_10_10_10_REV) { + signedType = true; + } else if (type == GL_UNSIGNED_INT_2_10_10_10_REV) { + signedType = false; + } else { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidEnum, + MakeUnique("MG_Impl/GLImpl", funcName, + "glVertexAttribP*ui type must be GL_INT_2_10_10_10_REV or " + "GL_UNSIGNED_INT_2_10_10_10_REV; got " + + MG_Util::ConvertGLEnumToString(type) + ".")); + return; + } + + const Array decoded = DecodePacked2101010(value, signedType, normalized == GL_TRUE); + Array out = {0.0f, 0.0f, 0.0f, 1.0f}; + for (int i = 0; i < componentCount; ++i) out[i] = decoded[i]; + MG_State::pGLContext->SetCurrentVertexAttributeFloat(index, out); + } + + void VertexAttribP1ui(GLuint index, GLenum type, GLboolean normalized, GLuint value) { + VertexAttribP_Common(index, type, normalized, value, 1, __func__); + } + void VertexAttribP2ui(GLuint index, GLenum type, GLboolean normalized, GLuint value) { + VertexAttribP_Common(index, type, normalized, value, 2, __func__); + } + void VertexAttribP3ui(GLuint index, GLenum type, GLboolean normalized, GLuint value) { + VertexAttribP_Common(index, type, normalized, value, 3, __func__); + } + void VertexAttribP4ui(GLuint index, GLenum type, GLboolean normalized, GLuint value) { + VertexAttribP_Common(index, type, normalized, value, 4, __func__); + } + + // The *uiv forms dereference a pointer to a SINGLE packed GLuint (never an array of N words). + void VertexAttribP1uiv(GLuint index, GLenum type, GLboolean normalized, const GLuint* value) { + if (!value) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidValue, + MakeUnique("MG_Impl/GLImpl", __func__, "value pointer cannot be null.")); + return; + } + VertexAttribP_Common(index, type, normalized, value[0], 1, __func__); + } + void VertexAttribP2uiv(GLuint index, GLenum type, GLboolean normalized, const GLuint* value) { + if (!value) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidValue, + MakeUnique("MG_Impl/GLImpl", __func__, "value pointer cannot be null.")); + return; + } + VertexAttribP_Common(index, type, normalized, value[0], 2, __func__); + } + void VertexAttribP3uiv(GLuint index, GLenum type, GLboolean normalized, const GLuint* value) { + if (!value) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidValue, + MakeUnique("MG_Impl/GLImpl", __func__, "value pointer cannot be null.")); + return; + } + VertexAttribP_Common(index, type, normalized, value[0], 3, __func__); + } + void VertexAttribP4uiv(GLuint index, GLenum type, GLboolean normalized, const GLuint* value) { + if (!value) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidValue, + MakeUnique("MG_Impl/GLImpl", __func__, "value pointer cannot be null.")); + return; + } + VertexAttribP_Common(index, type, normalized, value[0], 4, __func__); + } + void VertexAttrib4Nub(GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w) { constexpr float kInv255 = 1.0f / 255.0f; VertexAttrib4f(index, x * kInv255, y * kInv255, z * kInv255, w * kInv255); diff --git a/MobileGL/MG_Impl/GLImpl/VertexArray/GL_VertexArray.h b/MobileGL/MG_Impl/GLImpl/VertexArray/GL_VertexArray.h index 22196b33..14fbb4dd 100644 --- a/MobileGL/MG_Impl/GLImpl/VertexArray/GL_VertexArray.h +++ b/MobileGL/MG_Impl/GLImpl/VertexArray/GL_VertexArray.h @@ -23,6 +23,16 @@ namespace MobileGL::MG_Impl::GLImpl { void VertexAttribI4ui(GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); void VertexAttribI4iv(GLuint index, const GLint* v); void VertexAttribI4uiv(GLuint index, const GLuint* v); + // Packed current-value setters (GL_INT_/GL_UNSIGNED_INT_2_10_10_10_REV). Decode one packed word + // into the first 1/2/3/4 components of the generic attribute's float current value. + void VertexAttribP1ui(GLuint index, GLenum type, GLboolean normalized, GLuint value); + void VertexAttribP2ui(GLuint index, GLenum type, GLboolean normalized, GLuint value); + void VertexAttribP3ui(GLuint index, GLenum type, GLboolean normalized, GLuint value); + void VertexAttribP4ui(GLuint index, GLenum type, GLboolean normalized, GLuint value); + void VertexAttribP1uiv(GLuint index, GLenum type, GLboolean normalized, const GLuint* value); + void VertexAttribP2uiv(GLuint index, GLenum type, GLboolean normalized, const GLuint* value); + void VertexAttribP3uiv(GLuint index, GLenum type, GLboolean normalized, const GLuint* value); + void VertexAttribP4uiv(GLuint index, GLenum type, GLboolean normalized, const GLuint* value); void VertexAttrib4Nub(GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); void VertexAttrib4Nubv(GLuint index, const GLubyte* v); void VertexAttrib4ubv(GLuint index, const GLubyte* v); diff --git a/MobileGL/MG_Test/VertexArray/VertexArrayTest.cpp b/MobileGL/MG_Test/VertexArray/VertexArrayTest.cpp index 6ef33fec..dd564f77 100644 --- a/MobileGL/MG_Test/VertexArray/VertexArrayTest.cpp +++ b/MobileGL/MG_Test/VertexArray/VertexArrayTest.cpp @@ -1012,3 +1012,118 @@ TEST_F(GeneralVertexArrayTest, CurrentAttrib_GetVertexAttribdv) { EXPECT_DOUBLE_EQ(d2[0], 9.0); } +// Family E: packed glVertexAttribP*ui. The _REV layout packs x in bits [0..9], y in [10..19], +// z in [20..29], w in [30..31]; x/y/z are 10-bit fields and w is a 2-bit field. +// E1: unsigned decode (normalized c/(2^b-1), and unnormalized cast-to-float). +TEST_F(GeneralVertexArrayTest, CurrentAttrib_PackedUnsigned) { + CreateVAO(); + GLfloat out[4]; + + // x=1023, y=0, z=512, w=3. + const GLuint packed = 1023u | (0u << 10) | (512u << 20) | (3u << 30); // 0xE00003FF + VertexAttribP4ui(1, GL_UNSIGNED_INT_2_10_10_10_REV, GL_TRUE, packed); + GetVertexAttribfv(1, GL_CURRENT_VERTEX_ATTRIB, out); + EXPECT_EQ(GetError(), GL_NO_ERROR); + EXPECT_FLOAT_EQ(out[0], 1.0f); // 1023/1023 + EXPECT_FLOAT_EQ(out[1], 0.0f); // 0/1023 + EXPECT_FLOAT_EQ(out[2], 512.0f / 1023.0f); // 10-bit divisor, NOT 1023 for w + EXPECT_FLOAT_EQ(out[3], 1.0f); // 3/3 (2-bit divisor) + + // Unnormalized: the field values are cast straight to float, no scaling. + VertexAttribP4ui(1, GL_UNSIGNED_INT_2_10_10_10_REV, GL_FALSE, packed); + GetVertexAttribfv(1, GL_CURRENT_VERTEX_ATTRIB, out); + EXPECT_FLOAT_EQ(out[0], 1023.0f); + EXPECT_FLOAT_EQ(out[2], 512.0f); + EXPECT_FLOAT_EQ(out[3], 3.0f); + EXPECT_EQ(GetError(), GL_NO_ERROR); +} + +// E2: signed normalization uses the GL 3.3 (2c+1)/(2^b-1) form -- z==0 -> 1/1023 (NOT 0.0), which is +// exactly what the GL 4.2 c/(2^(b-1)-1) rule would get wrong. +TEST_F(GeneralVertexArrayTest, CurrentAttrib_PackedSignedUsesGl33Formula) { + CreateVAO(); + GLfloat out[4]; + + // x=511 (max +), y=-512 (min, 10-bit 0x200), z=0, w=1 (max + for 2-bit). + const GLuint packed = 0x1FFu | (0x200u << 10) | (0u << 20) | (1u << 30); + VertexAttribP4ui(1, GL_INT_2_10_10_10_REV, GL_TRUE, packed); + GetVertexAttribfv(1, GL_CURRENT_VERTEX_ATTRIB, out); + EXPECT_EQ(GetError(), GL_NO_ERROR); + EXPECT_FLOAT_EQ(out[0], 1.0f); // (2*511+1)/1023 = 1023/1023 + EXPECT_FLOAT_EQ(out[1], -1.0f); // (2*-512+1)/1023 = -1023/1023 + EXPECT_FLOAT_EQ(out[2], 1.0f / 1023.0f); // (2*0+1)/1023 -- 4.2 rule would give 0.0 + EXPECT_FLOAT_EQ(out[3], 1.0f); // (2*1+1)/3 = 3/3 + + // 2-bit signed minimum: w field = 0b10 = -2 -> (2*-2+1)/3 = -1.0. + VertexAttribP4ui(1, GL_INT_2_10_10_10_REV, GL_TRUE, 2u << 30); + GetVertexAttribfv(1, GL_CURRENT_VERTEX_ATTRIB, out); + EXPECT_FLOAT_EQ(out[3], -1.0f); + EXPECT_EQ(GetError(), GL_NO_ERROR); +} + +// E3: P1/P2/P3 consume the first 1/2/3 components from the single packed word; unconsumed components +// take the (0,0,0,1) defaults, and a shorter call clears stale components from a prior P4 to the slot. +TEST_F(GeneralVertexArrayTest, CurrentAttrib_PackedComponentCountAndDefaults) { + CreateVAO(); + GLfloat out[4]; + + VertexAttribP1ui(1, GL_UNSIGNED_INT_2_10_10_10_REV, GL_FALSE, 5u); + GetVertexAttribfv(1, GL_CURRENT_VERTEX_ATTRIB, out); + EXPECT_FLOAT_EQ(out[0], 5.0f); + EXPECT_FLOAT_EQ(out[1], 0.0f); + EXPECT_FLOAT_EQ(out[2], 0.0f); + EXPECT_FLOAT_EQ(out[3], 1.0f); + + VertexAttribP2ui(1, GL_UNSIGNED_INT_2_10_10_10_REV, GL_FALSE, 5u | (7u << 10)); + GetVertexAttribfv(1, GL_CURRENT_VERTEX_ATTRIB, out); + EXPECT_FLOAT_EQ(out[1], 7.0f); + EXPECT_FLOAT_EQ(out[2], 0.0f); + EXPECT_FLOAT_EQ(out[3], 1.0f); + + VertexAttribP3ui(1, GL_UNSIGNED_INT_2_10_10_10_REV, GL_FALSE, 5u | (7u << 10) | (9u << 20)); + GetVertexAttribfv(1, GL_CURRENT_VERTEX_ATTRIB, out); + EXPECT_FLOAT_EQ(out[2], 9.0f); + EXPECT_FLOAT_EQ(out[3], 1.0f); + + // Set all four, then a P1 must reset z and w back to the defaults. + VertexAttribP4ui(1, GL_UNSIGNED_INT_2_10_10_10_REV, GL_FALSE, 1u | (2u << 10) | (3u << 20) | (1u << 30)); + VertexAttribP1ui(1, GL_UNSIGNED_INT_2_10_10_10_REV, GL_FALSE, 5u); + GetVertexAttribfv(1, GL_CURRENT_VERTEX_ATTRIB, out); + EXPECT_FLOAT_EQ(out[0], 5.0f); + EXPECT_FLOAT_EQ(out[2], 0.0f); // not stale 3 + EXPECT_FLOAT_EQ(out[3], 1.0f); // not stale 1-from-w + EXPECT_EQ(GetError(), GL_NO_ERROR); +} + +// E4: type/index validation and the uiv single-word form. +TEST_F(GeneralVertexArrayTest, CurrentAttrib_PackedValidation) { + CreateVAO(); + GLfloat out[4]; + + // A non-packed type is GL_INVALID_ENUM and changes nothing. + VertexAttribP4ui(1, GL_UNSIGNED_INT_2_10_10_10_REV, GL_FALSE, 42u); + VertexAttribP4ui(1, GL_INT, GL_FALSE, 7u); // GL_INT is not a legal packed type + EXPECT_EQ(GetError(), GL_INVALID_ENUM); + GetVertexAttribfv(1, GL_CURRENT_VERTEX_ATTRIB, out); + EXPECT_FLOAT_EQ(out[0], 42.0f); // unchanged by the failed call + + // Attribute 0 is rejected by MobileGL policy (GL_INVALID_OPERATION). + VertexAttribP4ui(0, GL_UNSIGNED_INT_2_10_10_10_REV, GL_FALSE, 1u); + EXPECT_EQ(GetError(), GL_INVALID_OPERATION); + + // Out-of-range index -> GL_INVALID_VALUE. + VertexAttribP4ui(VertexArrayImpl::GetMaxVertexAttribs(), GL_UNSIGNED_INT_2_10_10_10_REV, GL_FALSE, 1u); + EXPECT_EQ(GetError(), GL_INVALID_VALUE); + + // The uiv form dereferences a single packed word. + const GLuint word = 1023u; // x=1023 normalized -> 1.0 + VertexAttribP1uiv(2, GL_UNSIGNED_INT_2_10_10_10_REV, GL_TRUE, &word); + GetVertexAttribfv(2, GL_CURRENT_VERTEX_ATTRIB, out); + EXPECT_EQ(GetError(), GL_NO_ERROR); + EXPECT_FLOAT_EQ(out[0], 1.0f); + + // Null uiv pointer -> GL_INVALID_VALUE. + VertexAttribP4uiv(1, GL_UNSIGNED_INT_2_10_10_10_REV, GL_FALSE, nullptr); + EXPECT_EQ(GetError(), GL_INVALID_VALUE); +} +