[Feat] (MG_Impl/GLImpl): implement 42 stubbed glVertexAttrib*/glGetVertexAttribdv current-value entry points

These set (or query) the current generic vertex attribute value, GL_CURRENT_VERTEX_ATTRIB.
All funnel into the existing, correct primitives -- VertexAttrib4f / VertexAttribI4i /
VertexAttribI4ui, and GetVertexAttribfv for the double query -- so the new bodies add only a
null-pointer guard; index validation (incl. the deliberate index-0 rejection) is inherited.

Families implemented (of the 49 core glVertexAttrib* setter stubs, all but the 8 packed
glVertexAttribP*ui, which need a real 2_10_10_10 DataType and are left for later):

* d / dv / s / sv and 4bv / 4iv / 4uiv / 4usv: value-preserving conversion to float. These do
  NOT normalize -- only the N forms do.
* 4Nbv / 4Nsv / 4Niv / 4Nusv / 4Nuiv: normalized. Signed normalization uses the GL 3.3 Core
  formula f = (2c + 1) / (2^b - 1), which maps the full signed range onto exactly [-1, 1] (byte
  -128 -> -1.0, 127 -> +1.0) and cannot represent 0 exactly (0 -> 1/(2^b-1)). This is NOT the
  GL 4.2 revision f = max(c/(2^(b-1)-1), -1); using that here would be a conformance bug.
  Unsigned normalization is the version-independent c/(2^b-1). The 32-bit forms compute in double
  because 2*INT_MAX overflows int32 and neither 2^32-1 nor 2^31-1 is representable as float.
* VertexAttribI{1,2,3}{i,iv,ui,uiv} and I4{bv,sv,ubv,usv}: integer forms, writing the integer
  current-value view verbatim (never the float one). Signed sign-extend to VertexAttribI4i,
  unsigned zero-extend to VertexAttribI4ui; w defaults to the integer 1. I4ubv/I4usv route to the
  unsigned setter (distinct from the normalized-float 4Nubv).
* glGetVertexAttribdv mirrors GetVertexAttribfv: reads the float view as four doubles for
  GL_CURRENT_VERTEX_ATTRIB (no bound VAO required), one value for the array pnames, same error rules.

Covered by 5 new round-trip tests whose boundary values (byte -128 -> -1.0 exact, 0 -> 1/255,
INT_MIN/MAX endpoints exact, ushort 65535 non-normalized -> 65535.0, integer w == 1) discriminate
the correct formulas; the signed-normalization test was verified to fail against the GL 4.2 form.
This commit is contained in:
2026-07-10 12:23:23 -04:00
parent d40f753983
commit d5e19cb7ba
4 changed files with 481 additions and 42 deletions
@@ -726,22 +726,22 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, PopName) DECLARE_GL_FUNCTION_STUB_END_NO_RET
DECLARE_GL_FUNCTION_HEAD(void, ClampColor, GLenum target, GLenum clamp) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClampColor, target, clamp)
DECLARE_GL_FUNCTION_STUB_HEAD(void, BeginConditionalRender, GLuint id, GLenum mode) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BeginConditionalRender, id, mode)
DECLARE_GL_FUNCTION_STUB_HEAD(void, EndConditionalRender, void) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, EndConditionalRender)
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribI1i, GLuint index, GLint x) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribI1i, index, x)
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribI2i, GLuint index, GLint x, GLint y) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribI2i, index, x, y)
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribI3i, GLuint index, GLint x, GLint y, GLint z) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribI3i, index, x, y, z)
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribI1ui, GLuint index, GLuint x) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribI1ui, index, x)
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribI2ui, GLuint index, GLuint x, GLuint y) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribI2ui, index, x, y)
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribI3ui, GLuint index, GLuint x, GLuint y, GLuint z) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribI3ui, index, x, y, z)
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribI1iv, GLuint index, const GLint* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribI1iv, index, v)
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribI2iv, GLuint index, const GLint* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribI2iv, index, v)
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribI3iv, GLuint index, const GLint* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribI3iv, index, v)
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribI1uiv, GLuint index, const GLuint* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribI1uiv, index, v)
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribI2uiv, GLuint index, const GLuint* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribI2uiv, index, v)
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribI3uiv, GLuint index, const GLuint* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribI3uiv, index, v)
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribI4bv, GLuint index, const GLbyte* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribI4bv, index, v)
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribI4sv, GLuint index, const GLshort* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribI4sv, index, v)
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribI4ubv, GLuint index, const GLubyte* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribI4ubv, index, v)
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribI4usv, GLuint index, const GLushort* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribI4usv, index, v)
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribI1i, GLuint index, GLint x) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribI1i, index, x)
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribI2i, GLuint index, GLint x, GLint y) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribI2i, index, x, y)
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribI3i, GLuint index, GLint x, GLint y, GLint z) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribI3i, index, x, y, z)
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribI1ui, GLuint index, GLuint x) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribI1ui, index, x)
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribI2ui, GLuint index, GLuint x, GLuint y) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribI2ui, index, x, y)
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribI3ui, GLuint index, GLuint x, GLuint y, GLuint z) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribI3ui, index, x, y, z)
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribI1iv, GLuint index, const GLint* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribI1iv, index, v)
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribI2iv, GLuint index, const GLint* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribI2iv, index, v)
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribI3iv, GLuint index, const GLint* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribI3iv, index, v)
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribI1uiv, GLuint index, const GLuint* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribI1uiv, index, v)
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribI2uiv, GLuint index, const GLuint* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribI2uiv, index, v)
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribI3uiv, GLuint index, const GLuint* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribI3uiv, index, v)
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribI4bv, GLuint index, const GLbyte* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribI4bv, index, v)
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribI4sv, GLuint index, const GLshort* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribI4sv, index, v)
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribI4ubv, GLuint index, const GLubyte* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribI4ubv, index, v)
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribI4usv, GLuint index, const GLushort* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribI4usv, index, v)
DECLARE_GL_FUNCTION_HEAD(void, BindFragDataLocation, GLuint program, GLuint color, const GLchar* name) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindFragDataLocation, program, color, name)
DECLARE_GL_FUNCTION_HEAD(void, FramebufferTexture1D, GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level) DECLARE_GL_FUNCTION_END_NO_RETURN(void, FramebufferTexture1D, target, attachment, textarget, texture, level)
DECLARE_GL_FUNCTION_HEAD(void, FramebufferTexture3D, GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset) DECLARE_GL_FUNCTION_END_NO_RETURN(void, FramebufferTexture3D, target, attachment, textarget, texture, level, zoffset)
@@ -831,35 +831,35 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, WindowPos3s, GLshort x, GLshort y, GLshort z
DECLARE_GL_FUNCTION_STUB_HEAD(void, WindowPos3sv, const GLshort* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, WindowPos3sv, v)
DECLARE_GL_FUNCTION_HEAD(void, GetQueryObjectiv, GLuint id, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetQueryObjectiv, id, pname, params)
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetBufferSubData, GLenum target, GLintptr offset, GLsizeiptr size, void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetBufferSubData, target, offset, size, data)
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetVertexAttribdv, GLuint index, GLenum pname, GLdouble* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetVertexAttribdv, index, pname, params)
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib1d, GLuint index, GLdouble x) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib1d, index, x)
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib1dv, GLuint index, const GLdouble* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib1dv, index, v)
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib1s, GLuint index, GLshort x) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib1s, index, x)
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib1sv, GLuint index, const GLshort* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib1sv, index, v)
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib2d, GLuint index, GLdouble x, GLdouble y) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib2d, index, x, y)
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib2dv, GLuint index, const GLdouble* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib2dv, index, v)
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib2s, GLuint index, GLshort x, GLshort y) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib2s, index, x, y)
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib2sv, GLuint index, const GLshort* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib2sv, index, v)
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib3d, GLuint index, GLdouble x, GLdouble y, GLdouble z) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib3d, index, x, y, z)
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib3dv, GLuint index, const GLdouble* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib3dv, index, v)
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib3s, GLuint index, GLshort x, GLshort y, GLshort z) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib3s, index, x, y, z)
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib3sv, GLuint index, const GLshort* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib3sv, index, v)
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib4Nbv, GLuint index, const GLbyte* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib4Nbv, index, v)
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib4Niv, GLuint index, const GLint* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib4Niv, index, v)
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib4Nsv, GLuint index, const GLshort* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib4Nsv, index, v)
DECLARE_GL_FUNCTION_HEAD(void, GetVertexAttribdv, GLuint index, GLenum pname, GLdouble* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetVertexAttribdv, index, pname, params)
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib1d, GLuint index, GLdouble x) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib1d, index, x)
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib1dv, GLuint index, const GLdouble* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib1dv, index, v)
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib1s, GLuint index, GLshort x) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib1s, index, x)
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib1sv, GLuint index, const GLshort* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib1sv, index, v)
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib2d, GLuint index, GLdouble x, GLdouble y) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib2d, index, x, y)
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib2dv, GLuint index, const GLdouble* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib2dv, index, v)
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib2s, GLuint index, GLshort x, GLshort y) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib2s, index, x, y)
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib2sv, GLuint index, const GLshort* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib2sv, index, v)
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib3d, GLuint index, GLdouble x, GLdouble y, GLdouble z) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib3d, index, x, y, z)
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib3dv, GLuint index, const GLdouble* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib3dv, index, v)
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib3s, GLuint index, GLshort x, GLshort y, GLshort z) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib3s, index, x, y, z)
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib3sv, GLuint index, const GLshort* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib3sv, index, v)
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib4Nbv, GLuint index, const GLbyte* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib4Nbv, index, v)
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib4Niv, GLuint index, const GLint* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib4Niv, index, v)
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib4Nsv, GLuint index, const GLshort* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib4Nsv, index, v)
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib4Nub, GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib4Nub, index, x, y, z, w)
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib4Nubv, GLuint index, const GLubyte* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib4Nubv, index, v)
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib4Nuiv, GLuint index, const GLuint* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib4Nuiv, index, v)
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib4Nusv, GLuint index, const GLushort* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib4Nusv, index, v)
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib4bv, GLuint index, const GLbyte* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib4bv, index, v)
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib4d, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib4d, index, x, y, z, w)
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib4dv, GLuint index, const GLdouble* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib4dv, index, v)
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib4iv, GLuint index, const GLint* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib4iv, index, v)
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib4s, GLuint index, GLshort x, GLshort y, GLshort z, GLshort w) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib4s, index, x, y, z, w)
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib4sv, GLuint index, const GLshort* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib4sv, index, v)
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib4Nuiv, GLuint index, const GLuint* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib4Nuiv, index, v)
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib4Nusv, GLuint index, const GLushort* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib4Nusv, index, v)
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib4bv, GLuint index, const GLbyte* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib4bv, index, v)
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib4d, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib4d, index, x, y, z, w)
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib4dv, GLuint index, const GLdouble* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib4dv, index, v)
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib4iv, GLuint index, const GLint* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib4iv, index, v)
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib4s, GLuint index, GLshort x, GLshort y, GLshort z, GLshort w) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib4s, index, x, y, z, w)
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib4sv, GLuint index, const GLshort* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib4sv, index, v)
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib4ubv, GLuint index, const GLubyte* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib4ubv, index, v)
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib4uiv, GLuint index, const GLuint* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib4uiv, index, v)
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttrib4usv, GLuint index, const GLushort* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttrib4usv, index, v)
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib4uiv, GLuint index, const GLuint* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib4uiv, index, v)
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib4usv, GLuint index, const GLushort* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib4usv, index, v)
DECLARE_GL_FUNCTION_STUB_HEAD(void, PrimitiveRestartIndex, GLuint index) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PrimitiveRestartIndex, index)
DECLARE_GL_FUNCTION_HEAD(void, GetActiveUniformName, GLuint program, GLuint uniformIndex, GLsizei bufSize, GLsizei* length, GLchar* uniformName) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetActiveUniformName, program, uniformIndex, bufSize, length, uniformName)
DECLARE_GL_FUNCTION_HEAD(void, MultiDrawElementsBaseVertex, GLenum mode, const GLsizei* count, GLenum type, const void* const* indices, GLsizei drawcount, const GLint* basevertex) DECLARE_GL_FUNCTION_END_NO_RETURN(void, MultiDrawElementsBaseVertex, mode, count, type, indices, drawcount, basevertex)
@@ -16,6 +16,31 @@
namespace MobileGL::MG_Impl::GLImpl {
namespace {
// GL 3.3 Core signed normalized fixed-point -> float (spec §2.1.2 Eq 2.2):
// f = (2c + 1) / (2^b - 1)
// This maps the FULL signed range [-2^(b-1), 2^(b-1)-1] onto exactly [-1, 1] (so -128 -> -1.0
// and 127 -> +1.0 with no clamp), and consequently cannot represent 0 exactly (0 -> 1/(2^b-1)).
// NOTE: GL 4.2 later switched signed normalization to f = max(c/(2^(b-1)-1), -1); do NOT use that
// form here -- it is not GL 3.3 Core. Unsigned normalization (f = c/(2^b-1)) is unchanged.
// The bit width fixes the arithmetic type: 8/16-bit stay exact in int/float, but the 32-bit forms
// must use double because 2*INT_MAX overflows int32 and neither 2^32-1 nor 2^31-1 is float-exact.
constexpr GLfloat NormalizeSignedByte(GLbyte c) { // b = 8, divisor 2^8 - 1 = 255
return (2 * static_cast<int>(c) + 1) / 255.0f;
}
constexpr GLfloat NormalizeSignedShort(GLshort c) { // b = 16, divisor 2^16 - 1 = 65535
return (2 * static_cast<int>(c) + 1) / 65535.0f;
}
constexpr GLfloat NormalizeSignedInt(GLint c) { // b = 32, divisor 2^32 - 1 (double!)
return static_cast<GLfloat>((2.0 * static_cast<double>(c) + 1.0) / 4294967295.0);
}
constexpr GLfloat NormalizeUnsignedShort(GLushort c) { // b = 16
return static_cast<GLfloat>(c) / 65535.0f;
}
constexpr GLfloat NormalizeUnsignedInt(GLuint c) { // b = 32 (double!)
return static_cast<GLfloat>(static_cast<double>(c) / 4294967295.0);
}
// (b = 8 unsigned normalization is VertexAttrib4Nub's x * (1/255).)
static bool ValidateCurrentVertexAttribIndex(GLuint index, const char* funcName) {
if (!VertexArrayImpl::ValidateVertexAttributeIndex(index)) return false;
if (index == 0) {
@@ -500,6 +525,152 @@ namespace MobileGL::MG_Impl::GLImpl {
static_cast<GLfloat>(v[3]));
}
// ---- Stubbed glVertexAttrib* current-value setters, funnelled into the primitives above -------
// These add ONLY a null-pointer guard: index validation (incl. the index-0 rejection) is inherited
// from VertexAttrib4f / VertexAttribI4i / VertexAttribI4ui via ValidateCurrentVertexAttribIndex, so
// the funnels must not re-validate it. Component fill matches the primitives: unspecified middle
// components are 0, unspecified w is 1 (integer 1 for the I* forms).
#define MG_ATTRIB_NULL_GUARD(ptr) \
if (!(ptr)) { \
MG_State::pGLContext->RecordError( \
ErrorCode::InvalidValue, \
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "value pointer cannot be null.")); \
return; \
}
// Family A -- GLdouble, value-preserving narrowing to float.
void VertexAttrib1d(GLuint index, GLdouble x) { VertexAttrib4f(index, static_cast<GLfloat>(x), 0.0f, 0.0f, 1.0f); }
void VertexAttrib2d(GLuint index, GLdouble x, GLdouble y) {
VertexAttrib4f(index, static_cast<GLfloat>(x), static_cast<GLfloat>(y), 0.0f, 1.0f);
}
void VertexAttrib3d(GLuint index, GLdouble x, GLdouble y, GLdouble z) {
VertexAttrib4f(index, static_cast<GLfloat>(x), static_cast<GLfloat>(y), static_cast<GLfloat>(z), 1.0f);
}
void VertexAttrib4d(GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w) {
VertexAttrib4f(index, static_cast<GLfloat>(x), static_cast<GLfloat>(y), static_cast<GLfloat>(z),
static_cast<GLfloat>(w));
}
void VertexAttrib1dv(GLuint index, const GLdouble* v) { MG_ATTRIB_NULL_GUARD(v) VertexAttrib1d(index, v[0]); }
void VertexAttrib2dv(GLuint index, const GLdouble* v) { MG_ATTRIB_NULL_GUARD(v) VertexAttrib2d(index, v[0], v[1]); }
void VertexAttrib3dv(GLuint index, const GLdouble* v) {
MG_ATTRIB_NULL_GUARD(v) VertexAttrib3d(index, v[0], v[1], v[2]);
}
void VertexAttrib4dv(GLuint index, const GLdouble* v) {
MG_ATTRIB_NULL_GUARD(v) VertexAttrib4d(index, v[0], v[1], v[2], v[3]);
}
// Family A -- GLshort, value-preserving (sign kept), NOT normalized.
void VertexAttrib1s(GLuint index, GLshort x) { VertexAttrib4f(index, static_cast<GLfloat>(x), 0.0f, 0.0f, 1.0f); }
void VertexAttrib2s(GLuint index, GLshort x, GLshort y) {
VertexAttrib4f(index, static_cast<GLfloat>(x), static_cast<GLfloat>(y), 0.0f, 1.0f);
}
void VertexAttrib3s(GLuint index, GLshort x, GLshort y, GLshort z) {
VertexAttrib4f(index, static_cast<GLfloat>(x), static_cast<GLfloat>(y), static_cast<GLfloat>(z), 1.0f);
}
void VertexAttrib4s(GLuint index, GLshort x, GLshort y, GLshort z, GLshort w) {
VertexAttrib4f(index, static_cast<GLfloat>(x), static_cast<GLfloat>(y), static_cast<GLfloat>(z),
static_cast<GLfloat>(w));
}
void VertexAttrib1sv(GLuint index, const GLshort* v) { MG_ATTRIB_NULL_GUARD(v) VertexAttrib1s(index, v[0]); }
void VertexAttrib2sv(GLuint index, const GLshort* v) { MG_ATTRIB_NULL_GUARD(v) VertexAttrib2s(index, v[0], v[1]); }
void VertexAttrib3sv(GLuint index, const GLshort* v) {
MG_ATTRIB_NULL_GUARD(v) VertexAttrib3s(index, v[0], v[1], v[2]);
}
void VertexAttrib4sv(GLuint index, const GLshort* v) {
MG_ATTRIB_NULL_GUARD(v) VertexAttrib4s(index, v[0], v[1], v[2], v[3]);
}
// Family A -- 4-component *v with no scalar sibling, value-preserving. NOT the normalized 4N* forms.
void VertexAttrib4bv(GLuint index, const GLbyte* v) {
MG_ATTRIB_NULL_GUARD(v)
VertexAttrib4f(index, static_cast<GLfloat>(v[0]), static_cast<GLfloat>(v[1]), static_cast<GLfloat>(v[2]),
static_cast<GLfloat>(v[3]));
}
void VertexAttrib4iv(GLuint index, const GLint* v) {
MG_ATTRIB_NULL_GUARD(v)
VertexAttrib4f(index, static_cast<GLfloat>(v[0]), static_cast<GLfloat>(v[1]), static_cast<GLfloat>(v[2]),
static_cast<GLfloat>(v[3]));
}
void VertexAttrib4uiv(GLuint index, const GLuint* v) {
MG_ATTRIB_NULL_GUARD(v)
VertexAttrib4f(index, static_cast<GLfloat>(v[0]), static_cast<GLfloat>(v[1]), static_cast<GLfloat>(v[2]),
static_cast<GLfloat>(v[3]));
}
void VertexAttrib4usv(GLuint index, const GLushort* v) {
MG_ATTRIB_NULL_GUARD(v)
VertexAttrib4f(index, static_cast<GLfloat>(v[0]), static_cast<GLfloat>(v[1]), static_cast<GLfloat>(v[2]),
static_cast<GLfloat>(v[3]));
}
// Family B -- normalized 4N* forms (GL 3.3 Core Eq 2.1/2.2 via the Normalize* helpers).
void VertexAttrib4Nbv(GLuint index, const GLbyte* v) {
MG_ATTRIB_NULL_GUARD(v)
VertexAttrib4f(index, NormalizeSignedByte(v[0]), NormalizeSignedByte(v[1]), NormalizeSignedByte(v[2]),
NormalizeSignedByte(v[3]));
}
void VertexAttrib4Nsv(GLuint index, const GLshort* v) {
MG_ATTRIB_NULL_GUARD(v)
VertexAttrib4f(index, NormalizeSignedShort(v[0]), NormalizeSignedShort(v[1]), NormalizeSignedShort(v[2]),
NormalizeSignedShort(v[3]));
}
void VertexAttrib4Niv(GLuint index, const GLint* v) {
MG_ATTRIB_NULL_GUARD(v)
VertexAttrib4f(index, NormalizeSignedInt(v[0]), NormalizeSignedInt(v[1]), NormalizeSignedInt(v[2]),
NormalizeSignedInt(v[3]));
}
void VertexAttrib4Nusv(GLuint index, const GLushort* v) {
MG_ATTRIB_NULL_GUARD(v)
VertexAttrib4f(index, NormalizeUnsignedShort(v[0]), NormalizeUnsignedShort(v[1]),
NormalizeUnsignedShort(v[2]), NormalizeUnsignedShort(v[3]));
}
void VertexAttrib4Nuiv(GLuint index, const GLuint* v) {
MG_ATTRIB_NULL_GUARD(v)
VertexAttrib4f(index, NormalizeUnsignedInt(v[0]), NormalizeUnsignedInt(v[1]), NormalizeUnsignedInt(v[2]),
NormalizeUnsignedInt(v[3]));
}
// Family C -- pure integer forms. Signed -> VertexAttribI4i (sign-extend), unsigned ->
// VertexAttribI4ui (zero-extend). w defaults to the integer 1 / 1u. Never touches the float view.
void VertexAttribI1i(GLuint index, GLint x) { VertexAttribI4i(index, x, 0, 0, 1); }
void VertexAttribI2i(GLuint index, GLint x, GLint y) { VertexAttribI4i(index, x, y, 0, 1); }
void VertexAttribI3i(GLuint index, GLint x, GLint y, GLint z) { VertexAttribI4i(index, x, y, z, 1); }
void VertexAttribI1ui(GLuint index, GLuint x) { VertexAttribI4ui(index, x, 0u, 0u, 1u); }
void VertexAttribI2ui(GLuint index, GLuint x, GLuint y) { VertexAttribI4ui(index, x, y, 0u, 1u); }
void VertexAttribI3ui(GLuint index, GLuint x, GLuint y, GLuint z) { VertexAttribI4ui(index, x, y, z, 1u); }
void VertexAttribI1iv(GLuint index, const GLint* v) { MG_ATTRIB_NULL_GUARD(v) VertexAttribI1i(index, v[0]); }
void VertexAttribI2iv(GLuint index, const GLint* v) { MG_ATTRIB_NULL_GUARD(v) VertexAttribI2i(index, v[0], v[1]); }
void VertexAttribI3iv(GLuint index, const GLint* v) {
MG_ATTRIB_NULL_GUARD(v) VertexAttribI3i(index, v[0], v[1], v[2]);
}
void VertexAttribI1uiv(GLuint index, const GLuint* v) { MG_ATTRIB_NULL_GUARD(v) VertexAttribI1ui(index, v[0]); }
void VertexAttribI2uiv(GLuint index, const GLuint* v) {
MG_ATTRIB_NULL_GUARD(v) VertexAttribI2ui(index, v[0], v[1]);
}
void VertexAttribI3uiv(GLuint index, const GLuint* v) {
MG_ATTRIB_NULL_GUARD(v) VertexAttribI3ui(index, v[0], v[1], v[2]);
}
void VertexAttribI4bv(GLuint index, const GLbyte* v) {
MG_ATTRIB_NULL_GUARD(v)
VertexAttribI4i(index, static_cast<GLint>(v[0]), static_cast<GLint>(v[1]), static_cast<GLint>(v[2]),
static_cast<GLint>(v[3]));
}
void VertexAttribI4sv(GLuint index, const GLshort* v) {
MG_ATTRIB_NULL_GUARD(v)
VertexAttribI4i(index, static_cast<GLint>(v[0]), static_cast<GLint>(v[1]), static_cast<GLint>(v[2]),
static_cast<GLint>(v[3]));
}
void VertexAttribI4ubv(GLuint index, const GLubyte* v) {
MG_ATTRIB_NULL_GUARD(v)
VertexAttribI4ui(index, static_cast<GLuint>(v[0]), static_cast<GLuint>(v[1]), static_cast<GLuint>(v[2]),
static_cast<GLuint>(v[3]));
}
void VertexAttribI4usv(GLuint index, const GLushort* v) {
MG_ATTRIB_NULL_GUARD(v)
VertexAttribI4ui(index, static_cast<GLuint>(v[0]), static_cast<GLuint>(v[1]), static_cast<GLuint>(v[2]),
static_cast<GLuint>(v[3]));
}
#undef MG_ATTRIB_NULL_GUARD
void GetVertexAttribfv(GLuint index, GLenum pname, GLfloat* params) {
if (!params) {
MG_State::pGLContext->RecordError(
@@ -558,6 +729,66 @@ namespace MobileGL::MG_Impl::GLImpl {
}
}
// The double query mirrors GetVertexAttribfv exactly (it is the other float-domain getter): the
// current value is read from the float view, and float -> double widening is lossless.
void GetVertexAttribdv(GLuint index, GLenum pname, GLdouble* params) {
if (!params) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "params pointer cannot be null."));
return;
}
// GL_CURRENT_VERTEX_ATTRIB is context state and returns before TryGetVertexAttribute, so the
// index bound has to be enforced up front or an out-of-range index reads past the array.
if (!VertexArrayImpl::ValidateVertexAttributeIndex(index)) return;
if (!ValidateVertexAttribPname(pname)) return;
if (IsCurrentVertexAttribQuery(pname)) {
const auto& current = MG_State::pGLContext->GetCurrentVertexAttribute(index);
params[0] = static_cast<GLdouble>(current.floatValue[0]);
params[1] = static_cast<GLdouble>(current.floatValue[1]);
params[2] = static_cast<GLdouble>(current.floatValue[2]);
params[3] = static_cast<GLdouble>(current.floatValue[3]);
return;
}
const MG_State::GLState::VertexAttribute* attr = nullptr;
if (!TryGetVertexAttribute(index, &attr)) return;
switch (pname) {
case GL_VERTEX_ATTRIB_ARRAY_ENABLED:
params[0] = attr->Enabled ? 1.0 : 0.0;
return;
case GL_VERTEX_ATTRIB_ARRAY_SIZE:
params[0] = static_cast<GLdouble>(attr->Size);
return;
case GL_VERTEX_ATTRIB_ARRAY_STRIDE:
params[0] = static_cast<GLdouble>(attr->Stride);
return;
case GL_VERTEX_ATTRIB_ARRAY_TYPE:
params[0] = static_cast<GLdouble>(MG_Util::ConvertDataTypeToGLEnum(attr->Type));
return;
case GL_VERTEX_ATTRIB_ARRAY_NORMALIZED:
params[0] = attr->Normalized ? 1.0 : 0.0;
return;
case GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING:
params[0] = attr->Buffer ? static_cast<GLdouble>(attr->Buffer->GetExternalIndex()) : 0.0;
return;
case GL_VERTEX_ATTRIB_ARRAY_INTEGER:
params[0] = attr->IsInteger ? 1.0 : 0.0;
return;
case GL_VERTEX_ATTRIB_ARRAY_DIVISOR:
params[0] = static_cast<GLdouble>(attr->Divisor);
return;
default:
MG_State::pGLContext->RecordError(
ErrorCode::InvalidEnum,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
"Unsupported double vertex attrib pname: " + std::to_string(pname)));
return;
}
}
void GetVertexAttribiv(GLuint index, GLenum pname, GLint* params) {
if (!params) {
MG_State::pGLContext->RecordError(
@@ -26,7 +26,53 @@ namespace MobileGL::MG_Impl::GLImpl {
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);
// Float-domain current-value setters that funnel into VertexAttrib4f. The d/s/bv/iv/uiv/usv
// forms are value-preserving (only the 4N* forms normalize per GL 3.3 Core Eq 2.1/2.2).
void VertexAttrib1d(GLuint index, GLdouble x);
void VertexAttrib2d(GLuint index, GLdouble x, GLdouble y);
void VertexAttrib3d(GLuint index, GLdouble x, GLdouble y, GLdouble z);
void VertexAttrib4d(GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w);
void VertexAttrib1dv(GLuint index, const GLdouble* v);
void VertexAttrib2dv(GLuint index, const GLdouble* v);
void VertexAttrib3dv(GLuint index, const GLdouble* v);
void VertexAttrib4dv(GLuint index, const GLdouble* v);
void VertexAttrib1s(GLuint index, GLshort x);
void VertexAttrib2s(GLuint index, GLshort x, GLshort y);
void VertexAttrib3s(GLuint index, GLshort x, GLshort y, GLshort z);
void VertexAttrib4s(GLuint index, GLshort x, GLshort y, GLshort z, GLshort w);
void VertexAttrib1sv(GLuint index, const GLshort* v);
void VertexAttrib2sv(GLuint index, const GLshort* v);
void VertexAttrib3sv(GLuint index, const GLshort* v);
void VertexAttrib4sv(GLuint index, const GLshort* v);
void VertexAttrib4bv(GLuint index, const GLbyte* v);
void VertexAttrib4iv(GLuint index, const GLint* v);
void VertexAttrib4uiv(GLuint index, const GLuint* v);
void VertexAttrib4usv(GLuint index, const GLushort* v);
void VertexAttrib4Nbv(GLuint index, const GLbyte* v);
void VertexAttrib4Nsv(GLuint index, const GLshort* v);
void VertexAttrib4Niv(GLuint index, const GLint* v);
void VertexAttrib4Nusv(GLuint index, const GLushort* v);
void VertexAttrib4Nuiv(GLuint index, const GLuint* v);
// Pure-integer current-value setters that funnel into VertexAttribI4i / VertexAttribI4ui and
// write the integer view, never the float one.
void VertexAttribI1i(GLuint index, GLint x);
void VertexAttribI2i(GLuint index, GLint x, GLint y);
void VertexAttribI3i(GLuint index, GLint x, GLint y, GLint z);
void VertexAttribI1ui(GLuint index, GLuint x);
void VertexAttribI2ui(GLuint index, GLuint x, GLuint y);
void VertexAttribI3ui(GLuint index, GLuint x, GLuint y, GLuint z);
void VertexAttribI1iv(GLuint index, const GLint* v);
void VertexAttribI2iv(GLuint index, const GLint* v);
void VertexAttribI3iv(GLuint index, const GLint* v);
void VertexAttribI1uiv(GLuint index, const GLuint* v);
void VertexAttribI2uiv(GLuint index, const GLuint* v);
void VertexAttribI3uiv(GLuint index, const GLuint* v);
void VertexAttribI4bv(GLuint index, const GLbyte* v);
void VertexAttribI4sv(GLuint index, const GLshort* v);
void VertexAttribI4ubv(GLuint index, const GLubyte* v);
void VertexAttribI4usv(GLuint index, const GLushort* v);
void GetVertexAttribfv(GLuint index, GLenum pname, GLfloat* params);
void GetVertexAttribdv(GLuint index, GLenum pname, GLdouble* params);
void GetVertexAttribiv(GLuint index, GLenum pname, GLint* params);
void GetVertexAttribPointerv(GLuint index, GLenum pname, void** pointer);
void GetVertexAttribIiv(GLuint index, GLenum pname, GLint* params);
@@ -850,3 +850,165 @@ TEST_F(GeneralVertexArrayTest, General_CurrentVertexAttribQueryRejectsOutOfRange
EXPECT_EQ(uints[0], 1u);
}
// ---- newly-implemented glVertexAttrib* current-value setter funnels -------------------------------
// Family A: the d/s/bv/iv/uiv/usv forms are value-preserving, NOT normalized; and the short/scalar
// forms fill omitted components with (0,0,1).
TEST_F(GeneralVertexArrayTest, CurrentAttrib_NonNormalizedValuePreserving) {
CreateVAO();
GLfloat out[4];
// 3-component short: 32767 must stay 32767.0f (proves no normalization), w filled to 1.
VertexAttrib3s(1, -5, 0, 32767);
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], 32767.0f);
EXPECT_FLOAT_EQ(out[3], 1.0f);
// 4-component byte vector: w comes from v[3], not forced to 1.
const GLbyte bytes[4] = {1, 2, 3, 4};
VertexAttrib4bv(1, bytes);
GetVertexAttribfv(1, GL_CURRENT_VERTEX_ATTRIB, out);
EXPECT_FLOAT_EQ(out[0], 1.0f);
EXPECT_FLOAT_EQ(out[3], 4.0f);
// ushort 65535 must NOT normalize to 1.0.
const GLushort ushorts[4] = {65535, 0, 0, 0};
VertexAttrib4usv(1, ushorts);
GetVertexAttribfv(1, GL_CURRENT_VERTEX_ATTRIB, out);
EXPECT_FLOAT_EQ(out[0], 65535.0f);
// 1-component double: fills (0,0,1).
VertexAttrib1d(1, 0.5);
GetVertexAttribfv(1, GL_CURRENT_VERTEX_ATTRIB, out);
EXPECT_FLOAT_EQ(out[0], 0.5f);
EXPECT_FLOAT_EQ(out[1], 0.0f);
EXPECT_FLOAT_EQ(out[2], 0.0f);
EXPECT_FLOAT_EQ(out[3], 1.0f);
EXPECT_EQ(GetError(), GL_NO_ERROR);
}
// Family B: GL 3.3 Core signed normalization is (2c+1)/(2^b-1) -- maps the full range to exactly
// [-1,1] (byte -128 -> -1, 127 -> +1) and cannot represent 0 exactly (0 -> 1/255). This is the test
// that fails against the GL 4.2 c/(2^(b-1)-1) rule.
TEST_F(GeneralVertexArrayTest, CurrentAttrib_SignedNormalizedUsesGl33Formula) {
CreateVAO();
GLfloat out[4];
const GLbyte extremes[4] = {-128, 127, 0, 127};
VertexAttrib4Nbv(1, extremes);
GetVertexAttribfv(1, GL_CURRENT_VERTEX_ATTRIB, out);
EXPECT_FLOAT_EQ(out[0], -1.0f); // exact, no clamp
EXPECT_FLOAT_EQ(out[1], 1.0f); // exact
EXPECT_FLOAT_EQ(out[2], 1.0f / 255.0f); // 0 -> 1/255, NOT 0.0
EXPECT_FLOAT_EQ(out[3], 1.0f);
const GLshort sExtremes[4] = {-32768, 32767, 0, 0};
VertexAttrib4Nsv(1, sExtremes);
GetVertexAttribfv(1, GL_CURRENT_VERTEX_ATTRIB, out);
EXPECT_FLOAT_EQ(out[0], -1.0f);
EXPECT_FLOAT_EQ(out[1], 1.0f);
// 32-bit signed: endpoints must be exact -- fails if computed in float or if 2*INT_MAX overflows.
const GLint iExtremes[4] = {INT_MIN, INT_MAX, 0, 0};
VertexAttrib4Niv(1, iExtremes);
GetVertexAttribfv(1, GL_CURRENT_VERTEX_ATTRIB, out);
EXPECT_FLOAT_EQ(out[0], -1.0f);
EXPECT_FLOAT_EQ(out[1], 1.0f);
EXPECT_EQ(GetError(), GL_NO_ERROR);
}
// Family B unsigned normalization is unchanged across versions: c/(2^b-1), 0 -> 0, max -> 1.
TEST_F(GeneralVertexArrayTest, CurrentAttrib_UnsignedNormalized) {
CreateVAO();
GLfloat out[4];
const GLushort us[4] = {0, 65535, 0, 0};
VertexAttrib4Nusv(1, us);
GetVertexAttribfv(1, GL_CURRENT_VERTEX_ATTRIB, out);
EXPECT_FLOAT_EQ(out[0], 0.0f);
EXPECT_FLOAT_EQ(out[1], 1.0f);
// 32-bit unsigned endpoints must be exact (needs double divisor).
const GLuint ui[4] = {0u, 0xFFFFFFFFu, 0u, 0u};
VertexAttrib4Nuiv(1, ui);
GetVertexAttribfv(1, GL_CURRENT_VERTEX_ATTRIB, out);
EXPECT_FLOAT_EQ(out[0], 0.0f);
EXPECT_FLOAT_EQ(out[1], 1.0f);
EXPECT_EQ(GetError(), GL_NO_ERROR);
}
// Family C: the I* forms write the INTEGER view verbatim (no float round-trip), fill w with integer 1,
// and route signed/unsigned to the right setter.
TEST_F(GeneralVertexArrayTest, CurrentAttrib_IntegerFunnels) {
CreateVAO();
// Scalar signed: w must be the integer 1, not 0.
VertexAttribI1i(1, 7);
GLint iv[4];
GetVertexAttribIiv(1, GL_CURRENT_VERTEX_ATTRIB, iv);
EXPECT_EQ(iv[0], 7);
EXPECT_EQ(iv[1], 0);
EXPECT_EQ(iv[2], 0);
EXPECT_EQ(iv[3], 1);
// INT_MAX must survive verbatim -- would corrupt to 2147483648 through the float view.
const GLint big[4] = {INT_MAX, 0, 0, 0};
VertexAttribI4iv(1, big);
GetVertexAttribIiv(1, GL_CURRENT_VERTEX_ATTRIB, iv);
EXPECT_EQ(iv[0], INT_MAX);
// I4bv sign-extends into the signed view.
const GLbyte sb[4] = {-100, 1, 2, 3};
VertexAttribI4bv(1, sb);
GetVertexAttribIiv(1, GL_CURRENT_VERTEX_ATTRIB, iv);
EXPECT_EQ(iv[0], -100);
// I4ubv zero-extends into the UNSIGNED view (not the normalized-float 4Nubv path).
const GLubyte ub[4] = {200, 0, 0, 0};
VertexAttribI4ubv(1, ub);
GLuint uv[4];
GetVertexAttribIuiv(1, GL_CURRENT_VERTEX_ATTRIB, uv);
EXPECT_EQ(uv[0], 200u);
// I2uiv reads exactly 2 elements; w == 1u.
const GLuint two[2] = {5u, 6u};
VertexAttribI2uiv(1, two);
GetVertexAttribIuiv(1, GL_CURRENT_VERTEX_ATTRIB, uv);
EXPECT_EQ(uv[0], 5u);
EXPECT_EQ(uv[1], 6u);
EXPECT_EQ(uv[2], 0u);
EXPECT_EQ(uv[3], 1u);
EXPECT_EQ(GetError(), GL_NO_ERROR);
}
// Family D: glGetVertexAttribdv reports the float-view current value as four doubles, needs no bound
// VAO, and enforces the same error rules as its float sibling.
TEST_F(GeneralVertexArrayTest, CurrentAttrib_GetVertexAttribdv) {
CreateVAO();
VertexAttrib4f(1, 0.25f, 0.5f, 0.75f, 1.0f);
GLdouble d[4] = {-1.0, -1.0, -1.0, -1.0};
GetVertexAttribdv(1, GL_CURRENT_VERTEX_ATTRIB, d);
EXPECT_EQ(GetError(), GL_NO_ERROR);
EXPECT_DOUBLE_EQ(d[0], 0.25);
EXPECT_DOUBLE_EQ(d[1], 0.5);
EXPECT_DOUBLE_EQ(d[2], 0.75);
EXPECT_DOUBLE_EQ(d[3], 1.0);
// Null params -> GL_INVALID_VALUE, nothing written.
GetVertexAttribdv(1, GL_CURRENT_VERTEX_ATTRIB, nullptr);
EXPECT_EQ(GetError(), GL_INVALID_VALUE);
// Out-of-range index -> GL_INVALID_VALUE, params untouched.
GLdouble d2[4] = {9.0, 9.0, 9.0, 9.0};
GetVertexAttribdv(VertexArrayImpl::GetMaxVertexAttribs(), GL_CURRENT_VERTEX_ATTRIB, d2);
EXPECT_EQ(GetError(), GL_INVALID_VALUE);
EXPECT_DOUBLE_EQ(d2[0], 9.0);
}