Files
MobileGL/MobileGL/MG_Impl/GLImpl/VertexArray/GL_VertexArray.cpp
T

1427 lines
70 KiB
C++

// MobileGL - MobileGL/MG_Impl/GLImpl/VertexArray/GL_VertexArray.cpp
// Copyright (c) 2025-2026 MobileGL-Dev
// Licensed under the GNU Lesser General Public License v3.0:
// https://www.gnu.org/licenses/gpl-3.0.txt
// https://www.gnu.org/licenses/lgpl-3.0.txt
// SPDX-License-Identifier: LGPL-3.0-only
// End of Source File Header
#include "GL_VertexArray.h"
#include "Validators.h"
#include <MG_Backend/BackendObjects.h>
#include <MG_Impl/GLImpl/Buffer/Validators.h>
#include <MG_State/GLState/Core.h>
#include <MG_State/GLState/ErrorState/Error.h>
#include <MG_Util/Converters/GLToMG/DataTypeConverter.h>
#include <MG_Util/Converters/GLToStr/GLEnumConverter.h>
#include <MG_Util/Converters/MGToGL/DataTypeConverter.h>
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).)
// 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<GLint>(field | (~0u << bits)) : static_cast<GLint>(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<GLfloat, 4> 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<GLfloat>(sx), static_cast<GLfloat>(sy), static_cast<GLfloat>(sz),
static_cast<GLfloat>(sw)};
}
if (normalized) {
return {fx / 1023.0f, fy / 1023.0f, fz / 1023.0f, fw / 3.0f};
}
return {static_cast<GLfloat>(fx), static_cast<GLfloat>(fy), static_cast<GLfloat>(fz),
static_cast<GLfloat>(fw)};
}
static bool ValidateCurrentVertexAttribIndex(GLuint index, const char* funcName) {
// GL 3.3 core 2.7: VertexAttrib* sets the current value of ANY generic attribute,
// including index 0 - only an out-of-range index is an error (INVALID_VALUE).
// "Attribute 0 is immutable" was legacy immediate-mode lore; rejecting it broke GL
// CTS's per-case state reset, which writes vertexAttrib4f(0, 0,0,0,1) after every case.
static_cast<void>(funcName);
return VertexArrayImpl::ValidateVertexAttributeIndex(index);
}
static bool TryGetVertexAttribute(GLuint index, const MG_State::GLState::VertexAttribute** outAttr) {
if (!VertexArrayImpl::ValidateVertexAttributeIndex(index)) return false;
auto& vao = MG_State::pGLContext->GetBoundVertexArray();
if (!vao) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "No vertex array object is bound."));
return false;
}
*outAttr = &vao->GetAttribute(index);
return true;
}
static bool IsCurrentVertexAttribQuery(GLenum pname) {
return pname == GL_CURRENT_VERTEX_ATTRIB;
}
// The two ARB_vertex_attrib_binding per-attribute queries. They do not live on the
// resolved VertexAttribute (which is the flat, already-combined view) but on the VAO's
// binding-point mapping, so they need the object, not the attribute.
static bool TryGetVertexAttribBindingQuery(GLuint index, GLenum pname, GLint& out) {
if (pname != GL_VERTEX_ATTRIB_BINDING && pname != GL_VERTEX_ATTRIB_RELATIVE_OFFSET) return false;
const auto& vao = MG_State::pGLContext->GetBoundVertexArray();
if (!vao) {
out = 0;
return true;
}
out = pname == GL_VERTEX_ATTRIB_BINDING ? static_cast<GLint>(vao->GetAttributeBindingIndex(index))
: static_cast<GLint>(vao->GetAttributeRelativeOffset(index));
return true;
}
// The stride a pointer-style call gives its binding point: the argument when it is non-zero,
// otherwise the tightly packed element size (GL 4.6 core 10.3.2). A packed 2_10_10_10 or
// 10F_11F_11F attribute is one 32-bit word regardless of its component count.
static int EffectiveVertexStride(GLsizei stride, GLint size, GLenum type) {
if (stride != 0) return static_cast<int>(stride);
switch (type) {
case GL_INT_2_10_10_10_REV:
case GL_UNSIGNED_INT_2_10_10_10_REV:
case GL_UNSIGNED_INT_10F_11F_11F_REV:
return 4;
default:
break;
}
return static_cast<int>(size * MG_Util::GetGLTypeSize(type));
}
// glBindVertexBuffers / glVertexArrayVertexBuffers take a range of binding points, and a
// range that runs past the last one is INVALID_OPERATION rather than the INVALID_VALUE a
// single out-of-range index gets (GL 4.6 core 10.3.1).
static bool ValidateVertexBindingRange(GLuint first, GLsizei count, const char* funcName) {
if (count < 0) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", funcName, "count must be non-negative."));
return false;
}
if (static_cast<Uint64>(first) + static_cast<Uint64>(count) >
VertexArrayImpl::GetMaxVertexAttribBindings()) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", funcName,
"first + count exceeds GL_MAX_VERTEX_ATTRIB_BINDINGS."));
return false;
}
return true;
}
static bool ValidateVertexBindingIndex(GLuint bindingindex, const char* funcName) {
if (bindingindex >= VertexArrayImpl::GetMaxVertexAttribBindings()) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", funcName,
"bindingindex exceeds GL_MAX_VERTEX_ATTRIB_BINDINGS."));
return false;
}
return true;
}
static SharedPtr<MG_State::GLState::VertexArrayObject> GetBoundVertexArrayOrError(const char* funcName) {
auto& vao = MG_State::pGLContext->GetBoundVertexArray();
if (!vao) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", funcName, "No vertex array object is bound."));
}
return vao;
}
// The ARB_vertex_attrib_binding entry points that take no vertex array name modify the
// *bound* vertex array, and in a core profile the default vertex array (name 0) is not
// one: every one of them is INVALID_OPERATION there (GL 4.6 core 10.3.1, and the tail of
// each KHR-GL4x.vertex_attrib_binding.negative-* case checks exactly this). MobileGL
// keeps a real object at name 0 for the compatibility paths, so GetBoundVertexArray
// never returns null and the rule has to be spelled out - behind the same gate the VAO-0
// draw rule already uses (MOBILEGL_RELAXED_SEMANTICS, plus "the context never asked for
// a core profile"), so applications that legitimately run relaxed keep working.
static SharedPtr<MG_State::GLState::VertexArrayObject> GetBoundVertexArrayForBindingApi(const char* funcName) {
auto vao = GetBoundVertexArrayOrError(funcName);
if (!vao) return nullptr;
if (vao->GetExternalIndex() == 0 && !MG_State::IsRelaxedSemanticsActive()) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
MakeUnique<GenericErrorInfo>(
"MG_Impl/GLImpl", funcName,
"The default vertex array object cannot be modified in a core profile."));
return nullptr;
}
return vao;
}
static bool ValidateVertexAttribPname(GLenum pname) {
switch (pname) {
case GL_VERTEX_ATTRIB_ARRAY_ENABLED:
case GL_VERTEX_ATTRIB_ARRAY_SIZE:
case GL_VERTEX_ATTRIB_ARRAY_STRIDE:
case GL_VERTEX_ATTRIB_ARRAY_TYPE:
case GL_VERTEX_ATTRIB_ARRAY_NORMALIZED:
case GL_CURRENT_VERTEX_ATTRIB:
case GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING:
case GL_VERTEX_ATTRIB_ARRAY_INTEGER:
// Core since GL 4.1 (ARB_vertex_attrib_64bit). It was rejected while no attribute could
// ever be long; now that IsLong is real state the pname has to be accepted.
case GL_VERTEX_ATTRIB_ARRAY_LONG:
case GL_VERTEX_ATTRIB_ARRAY_DIVISOR:
case GL_VERTEX_ATTRIB_ARRAY_POINTER:
// ARB_vertex_attrib_binding (core since GL 4.3). The binding-point view is real
// state on the VAO (GetAttributeBindingIndex / GetAttributeRelativeOffset), so
// both of its per-attribute queries are answerable.
case GL_VERTEX_ATTRIB_BINDING:
case GL_VERTEX_ATTRIB_RELATIVE_OFFSET:
return true;
default:
MG_State::pGLContext->RecordError(
ErrorCode::InvalidEnum,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
"Unsupported vertex attrib pname: " + std::to_string(pname)));
return false;
}
}
} // namespace
SharedPtr<MG_State::GLState::VertexArrayObject> GetNamedVertexArrayObject_State(GLuint vaobj,
const char* caller) {
// Name zero is not a vertex array object in a core profile: it names the default vertex
// array, which the by-name (direct state access) entry points never accept. MobileGL keeps a
// real object at index 0 for the compatibility paths, so the generic name validation below
// would otherwise let it through (GL 4.6 core 10.3.1).
if (vaobj == 0) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
"Vertex array name 0 is not a vertex array object."));
return nullptr;
}
if (!VertexArrayImpl::ValidateVertexArrayName(vaobj)) return nullptr;
if (!VertexArrayImpl::ValidateVertexArrayObject(vaobj)) return nullptr;
return MG_State::pGLContext->GetVertexArrayObject(vaobj);
}
SharedPtr<MG_State::GLState::BufferObject> GetVertexArrayBufferObject_State(GLuint buffer, const char* caller) {
if (!BufferImpl::ValidateBufferName(buffer, true)) return nullptr;
if (buffer == 0) return nullptr;
auto& bufferObject = MG_State::pGLContext->GetBufferObject(buffer);
if (!bufferObject) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
std::format("Buffer object {} does not exist.", buffer)));
return nullptr;
}
return bufferObject;
}
void DisableVertexAttribArray_State(GLuint index) {
if (!VertexArrayImpl::ValidateVertexAttributeIndex(index)) return;
auto& vao = MG_State::pGLContext->GetBoundVertexArray();
if (!vao) {
MG_State::pGLContext->RecordError(ErrorCode::InvalidOperation,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl",
"EnableVertexAttribArray_State",
"No vertex array object is bound."));
return;
}
vao->DisableAttribute(index);
}
void EnableVertexAttribArray_State(GLuint index) {
if (!VertexArrayImpl::ValidateVertexAttributeIndex(index)) return;
auto& vao = MG_State::pGLContext->GetBoundVertexArray();
if (!vao) {
MG_State::pGLContext->RecordError(ErrorCode::InvalidOperation,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl",
"EnableVertexAttribArray_State",
"No vertex array object is bound."));
return;
}
vao->EnableAttribute(index);
}
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);
// Integer path: never normalized, never BGRA/packed (the validator rejects those).
if (!VertexArrayImpl::ValidateVertexAttribFormat(index, size, type, dataType, false, stride, true)) return;
auto& vao = MG_State::pGLContext->GetBoundVertexArray();
if (!vao) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation, MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "VertexAttribPointer_State",
"No vertex array object is bound."));
return;
}
auto& vboSlot = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Vertex);
auto& vbo = vboSlot.GetBoundObject();
auto offset = reinterpret_cast<SizeT>(pointer);
const int effectiveStride = EffectiveVertexStride(stride, size, type);
vao->SetAttributeFormat(index, size, dataType, false, stride, offset, true, false, effectiveStride);
vao->BindAttributeBuffer(index, vbo);
vao->MirrorPointerIntoBinding(index, vbo, offset, effectiveStride);
}
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::ValidateVertexAttribFormat(index, size, type, dataType, normalized == GL_TRUE, stride, false))
return;
auto& vao = MG_State::pGLContext->GetBoundVertexArray();
if (!vao) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation, MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "VertexAttribPointer_State",
"No vertex array object is bound."));
return;
}
auto& vboSlot = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Vertex);
auto& vbo = vboSlot.GetBoundObject();
SizeT offset = reinterpret_cast<SizeT>(pointer);
// 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;
const int effectiveStride = EffectiveVertexStride(stride, effectiveSize, type);
vao->SetAttributeFormat(index, effectiveSize, dataType, normalized, stride, offset, false, isBgra,
effectiveStride);
vao->BindAttributeBuffer(index, vbo);
vao->MirrorPointerIntoBinding(index, vbo, offset, effectiveStride);
}
void BindVertexArray_State(GLuint array) {
if (array == 0) {
MG_State::pGLContext->BindVertexArray(0);
return;
}
if (!VertexArrayImpl::ValidateVertexArrayName(array)) return;
if (!MG_State::pGLContext->ValidateVertexArrayObject(array)) {
MG_State::pGLContext->CreateVertexArrayObject(array);
}
MG_State::pGLContext->BindVertexArray(array);
}
void DeleteVertexArrays_State(GLsizei n, const GLuint* arrays) {
if (n < 0) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "DeleteVertexArrays_State", "n must be non-negative."));
return;
}
for (GLsizei i = 0; i < n; ++i) {
GLuint vao = arrays[i];
if (vao == 0) continue;
// GL 3.3 core 2.10: unknown names are silently ignored on delete; the shared bind-path
// validator would record INVALID_OPERATION instead.
if (!MG_State::pGLContext->ValidateVertexArrayName(vao)) continue;
if (MG_State::pGLContext->GetBoundVertexArray() &&
MG_State::pGLContext->GetBoundVertexArray() == MG_State::pGLContext->GetVertexArrayObject(vao)) {
MG_State::pGLContext->BindVertexArray(0);
}
MG_State::pGLContext->MarkVertexArrayForDeletion(vao);
}
}
void GenVertexArrays_State(GLsizei n, GLuint* arrays) {
if (n < 0) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "GenVertexArrays_State", "n must be non-negative."));
return;
}
Vector<Uint> vaos;
MG_State::pGLContext->GenVertexArrayNames(n, vaos);
Memcpy(arrays, vaos.data(), n * sizeof(GLuint));
}
void CreateVertexArrays_State(GLsizei n, GLuint* arrays) {
if (n < 0) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "CreateVertexArrays_State", "n must be non-negative."));
return;
}
Vector<Uint> vaos;
MG_State::pGLContext->GenVertexArrayNames(n, vaos);
for (GLsizei i = 0; i < n; ++i) {
MG_State::pGLContext->CreateVertexArrayObject(vaos[i]);
arrays[i] = vaos[i];
}
}
void DisableVertexArrayAttrib_State(GLuint vaobj, GLuint index) {
auto vao = GetNamedVertexArrayObject_State(vaobj, "DisableVertexArrayAttrib_State");
if (!vao) return;
if (!VertexArrayImpl::ValidateVertexAttributeIndex(index)) return;
vao->DisableAttribute(index);
}
void EnableVertexArrayAttrib_State(GLuint vaobj, GLuint index) {
auto vao = GetNamedVertexArrayObject_State(vaobj, "EnableVertexArrayAttrib_State");
if (!vao) return;
if (!VertexArrayImpl::ValidateVertexAttributeIndex(index)) return;
vao->EnableAttribute(index);
}
void VertexArrayElementBuffer_State(GLuint vaobj, GLuint buffer) {
auto vao = GetNamedVertexArrayObject_State(vaobj, "VertexArrayElementBuffer_State");
if (!vao) return;
auto bufferObject = GetVertexArrayBufferObject_State(buffer, "VertexArrayElementBuffer_State");
if (buffer != 0 && !bufferObject) return;
vao->GetIndexBufferBindingSlot().Bind(bufferObject);
}
static void VertexBufferBinding_State(const SharedPtr<MG_State::GLState::VertexArrayObject>& vao,
GLuint bindingindex, GLuint buffer, GLintptr offset, GLsizei stride,
const char* caller) {
if (!ValidateVertexBindingIndex(bindingindex, caller)) return;
if (offset < 0 || stride < 0) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller, "offset and stride must be non-negative."));
return;
}
if (static_cast<Uint>(stride) > VertexArrayImpl::GetMaxVertexAttribStride()) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
"stride exceeds GL_MAX_VERTEX_ATTRIB_STRIDE."));
return;
}
auto bufferObject = GetVertexArrayBufferObject_State(buffer, caller);
if (buffer != 0 && !bufferObject) return;
vao->SetBindingBuffer(bindingindex, bufferObject, static_cast<SizeT>(offset), stride);
}
void VertexArrayVertexBuffer_State(GLuint vaobj, GLuint bindingindex, GLuint buffer, GLintptr offset,
GLsizei stride) {
auto vao = GetNamedVertexArrayObject_State(vaobj, "VertexArrayVertexBuffer_State");
if (!vao) return;
VertexBufferBinding_State(vao, bindingindex, buffer, offset, stride, "VertexArrayVertexBuffer_State");
}
void VertexArrayVertexBuffers_State(GLuint vaobj, GLuint first, GLsizei count, const GLuint* buffers,
const GLintptr* offsets, const GLsizei* strides) {
auto vao = GetNamedVertexArrayObject_State(vaobj, "VertexArrayVertexBuffers_State");
if (!vao) return;
if (!ValidateVertexBindingRange(first, count, "VertexArrayVertexBuffers_State")) return;
for (GLsizei i = 0; i < count; ++i) {
if (!buffers) {
VertexBufferBinding_State(vao, first + i, 0, 0, 16, "VertexArrayVertexBuffers_State");
} else {
VertexBufferBinding_State(vao, first + i, buffers[i], offsets ? offsets[i] : 0,
strides ? strides[i] : 16, "VertexArrayVertexBuffers_State");
}
}
}
static void VertexAttribFormatSeparate_State(const SharedPtr<MG_State::GLState::VertexArrayObject>& vao,
GLuint attribindex, GLint size, GLenum type, GLboolean normalized,
GLuint relativeoffset, Bool isInteger, const char* caller) {
static_cast<void>(caller);
if (!VertexArrayImpl::ValidateVertexAttributeIndex(attribindex)) return;
DataType dataType = MG_Util::ConvertGLEnumToDataType(type);
// The separate-format entry points take the same size/type rules as the pointer ones,
// GL_BGRA included, so they need the full format validation rather than the pointer-only
// subset - that one reports GL_BGRA as an out-of-range size.
if (!VertexArrayImpl::ValidateVertexAttribFormat(attribindex, size, type, dataType, normalized == GL_TRUE, 0,
isInteger))
return;
if (!VertexArrayImpl::ValidateVertexAttribRelativeOffset(relativeoffset)) return;
const Bool isBgra = (size == static_cast<GLint>(GL_BGRA));
vao->SetAttributeFormatSeparate(attribindex, isBgra ? 4 : size, dataType, normalized, isInteger,
relativeoffset, isBgra);
}
// The long (64-bit) attribute format: the values reach the shader as doubles, unconverted
// (GL 4.6 core 10.3.2). ValidateVertexAttribLFormat has already pinned type to GL_DOUBLE, so the
// recorded DataType is always Float64 - what IsLong adds is that this is the *unconverted* form,
// as opposed to VertexAttribFormat(GL_DOUBLE), which asks for a float conversion.
//
// Whether the backend can FEED it at full precision is detected, not assumed: DirectVulkan
// needs shaderFloat64, and DirectGLES can never have it at all. What that costs is PRECISION,
// not the call and no longer the array: GL 4.6 core 10.3.2 defines no error for a well-formed
// glVertexAttribLFormat, and a GL 4.3 context has 64-bit attributes in core, so declining the
// call would be non-conformant and would make the four pure state queries
// (VERTEX_ATTRIB_ARRAY_SIZE / _TYPE / _LONG / _RELATIVE_OFFSET) unanswerable
// (KHR-GL43.vertex_attrib_binding.basic-state1/3). The format is therefore RECORDED here and
// the array is NARROWED to float32 at draw, matching the fp64 demotion every shader already
// gets (DemoteFloat64Pass) - loudly, once, naming the cost. The matching startup POST row is in
// MG_Util/SelfTest/DriverPost.cpp; the draw-side narrowing is DirectGLES/Managers.cpp and, on
// DirectVulkan, VertexInputStateFactory's Float64 case.
static void VertexAttribLFormatSeparate_State(const SharedPtr<MG_State::GLState::VertexArrayObject>& vao,
GLuint attribindex, GLint size, GLenum type,
GLuint relativeoffset) {
if (!VertexArrayImpl::ValidateVertexAttributeIndex(attribindex)) return;
if (!VertexArrayImpl::ValidateVertexAttribLFormat(attribindex, size, type)) return;
if (!VertexArrayImpl::ValidateVertexAttribRelativeOffset(relativeoffset)) return;
if (!MG_Backend::pActiveBackendObject ||
!MG_Backend::pActiveBackendObject->GetDynamicParameters().SupportsFloat64VertexAttributes) {
MGLOG_W_ONCE("VertexAttribLFormat: attribute %u asked for a 64-bit (GL_DOUBLE) format, but this "
"backend has no double-precision vertex attribute support - the format is recorded "
"and queryable, and the array is FETCHED AT FLOAT32 PRECISION at draw (the same "
"narrowing the shader's dvec inputs already get); see the \"64-bit vertex "
"attributes\" / \"shaderFloat64\" POST row for what that costs",
attribindex);
}
vao->SetAttributeFormatSeparate(attribindex, size, MG_Util::ConvertGLEnumToDataType(type),
/*normalized: */ false, /*isInteger: */ false, relativeoffset,
/*isBgra: */ false, /*isLong: */ true);
}
void VertexArrayAttribFormat_State(GLuint vaobj, GLuint attribindex, GLint size, GLenum type,
GLboolean normalized, GLuint relativeoffset) {
auto vao = GetNamedVertexArrayObject_State(vaobj, "VertexArrayAttribFormat_State");
if (!vao) return;
VertexAttribFormatSeparate_State(vao, attribindex, size, type, normalized, relativeoffset, false,
"VertexArrayAttribFormat_State");
}
void VertexArrayAttribIFormat_State(GLuint vaobj, GLuint attribindex, GLint size, GLenum type,
GLuint relativeoffset) {
auto vao = GetNamedVertexArrayObject_State(vaobj, "VertexArrayAttribIFormat_State");
if (!vao) return;
VertexAttribFormatSeparate_State(vao, attribindex, size, type, GL_FALSE, relativeoffset, true,
"VertexArrayAttribIFormat_State");
}
void VertexArrayAttribBinding_State(GLuint vaobj, GLuint attribindex, GLuint bindingindex) {
auto vao = GetNamedVertexArrayObject_State(vaobj, "VertexArrayAttribBinding_State");
if (!vao) return;
if (!VertexArrayImpl::ValidateVertexAttributeIndex(attribindex)) return;
if (!ValidateVertexBindingIndex(bindingindex, "VertexArrayAttribBinding_State")) return;
vao->SetAttributeBinding(attribindex, bindingindex);
}
void VertexArrayBindingDivisor_State(GLuint vaobj, GLuint bindingindex, GLuint divisor) {
auto vao = GetNamedVertexArrayObject_State(vaobj, "VertexArrayBindingDivisor_State");
if (!vao) return;
if (!ValidateVertexBindingIndex(bindingindex, "VertexArrayBindingDivisor_State")) return;
vao->SetBindingDivisor(bindingindex, divisor);
}
GLboolean IsVertexArray_State(GLuint array) {
if (array == 0) return GL_FALSE;
return MG_State::pGLContext->ValidateVertexArrayObject(array) ? GL_TRUE : GL_FALSE;
}
void VertexAttribDivisor_State(GLuint index, GLuint divisor) {
if (!VertexArrayImpl::ValidateVertexAttributeIndex(index)) return;
auto& vao = MG_State::pGLContext->GetBoundVertexArray();
if (!vao) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation, MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "VertexAttribDivisor_State",
"No vertex array object is bound."));
return;
}
vao->SetAttributeDivisor(index, divisor);
}
/* @INSERTION_POINT:FUNCTION_IMPLEMENTATION@ */
void VertexAttrib1f(GLuint index, GLfloat x) {
if (!ValidateCurrentVertexAttribIndex(index, __func__)) return;
MG_State::pGLContext->SetCurrentVertexAttributeFloat(index, {x, 0.0f, 0.0f, 1.0f});
}
void VertexAttrib1fv(GLuint index, const GLfloat* v) {
if (!v) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "value pointer cannot be null."));
return;
}
VertexAttrib1f(index, v[0]);
}
void VertexAttrib2f(GLuint index, GLfloat x, GLfloat y) {
if (!ValidateCurrentVertexAttribIndex(index, __func__)) return;
MG_State::pGLContext->SetCurrentVertexAttributeFloat(index, {x, y, 0.0f, 1.0f});
}
void VertexAttrib2fv(GLuint index, const GLfloat* v) {
if (!v) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "value pointer cannot be null."));
return;
}
VertexAttrib2f(index, v[0], v[1]);
}
void VertexAttrib3f(GLuint index, GLfloat x, GLfloat y, GLfloat z) {
if (!ValidateCurrentVertexAttribIndex(index, __func__)) return;
MG_State::pGLContext->SetCurrentVertexAttributeFloat(index, {x, y, z, 1.0f});
}
void VertexAttrib3fv(GLuint index, const GLfloat* v) {
if (!v) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "value pointer cannot be null."));
return;
}
VertexAttrib3f(index, v[0], v[1], v[2]);
}
void VertexAttrib4f(GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w) {
if (!ValidateCurrentVertexAttribIndex(index, __func__)) return;
MG_State::pGLContext->SetCurrentVertexAttributeFloat(index, {x, y, z, w});
}
void VertexAttrib4fv(GLuint index, const GLfloat* v) {
if (!v) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "value pointer cannot be null."));
return;
}
VertexAttrib4f(index, v[0], v[1], v[2], v[3]);
}
void VertexAttribI4i(GLuint index, GLint x, GLint y, GLint z, GLint w) {
if (!ValidateCurrentVertexAttribIndex(index, __func__)) return;
MG_State::pGLContext->SetCurrentVertexAttributeInt(index, {x, y, z, w});
}
void VertexAttribI4ui(GLuint index, GLuint x, GLuint y, GLuint z, GLuint w) {
if (!ValidateCurrentVertexAttribIndex(index, __func__)) return;
MG_State::pGLContext->SetCurrentVertexAttributeUint(index, {x, y, z, w});
}
void VertexAttribI4iv(GLuint index, const GLint* v) {
if (!v) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "value pointer cannot be null."));
return;
}
VertexAttribI4i(index, v[0], v[1], v[2], v[3]);
}
void VertexAttribI4uiv(GLuint index, const GLuint* v) {
if (!v) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "value pointer cannot be null."));
return;
}
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<GenericErrorInfo>("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<GLfloat, 4> decoded = DecodePacked2101010(value, signedType, normalized == GL_TRUE);
Array<GLfloat, 4> 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<GenericErrorInfo>("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<GenericErrorInfo>("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<GenericErrorInfo>("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<GenericErrorInfo>("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);
}
void VertexAttrib4Nubv(GLuint index, const GLubyte* v) {
if (!v) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "value pointer cannot be null."));
return;
}
VertexAttrib4Nub(index, v[0], v[1], v[2], v[3]);
}
void VertexAttrib4ubv(GLuint index, const GLubyte* v) {
if (!v) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "value pointer cannot be null."));
return;
}
VertexAttrib4f(index, static_cast<GLfloat>(v[0]), static_cast<GLfloat>(v[1]), static_cast<GLfloat>(v[2]),
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(
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] = current.floatValue[0];
params[1] = current.floatValue[1];
params[2] = current.floatValue[2];
params[3] = 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.0f : 0.0f;
return;
case GL_VERTEX_ATTRIB_ARRAY_SIZE:
params[0] = static_cast<GLfloat>(attr->Size);
return;
case GL_VERTEX_ATTRIB_ARRAY_STRIDE:
params[0] = static_cast<GLfloat>(attr->LegacyStride);
return;
case GL_VERTEX_ATTRIB_ARRAY_TYPE:
params[0] = static_cast<GLfloat>(MG_Util::ConvertDataTypeToGLEnum(attr->Type));
return;
case GL_VERTEX_ATTRIB_ARRAY_NORMALIZED:
params[0] = attr->Normalized ? 1.0f : 0.0f;
return;
case GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING:
params[0] = attr->Buffer ? static_cast<GLfloat>(attr->Buffer->GetExternalIndex()) : 0.0f;
return;
case GL_VERTEX_ATTRIB_ARRAY_INTEGER:
params[0] = attr->IsInteger ? 1.0f : 0.0f;
return;
case GL_VERTEX_ATTRIB_ARRAY_LONG:
params[0] = attr->IsLong ? 1.0f : 0.0f;
return;
case GL_VERTEX_ATTRIB_ARRAY_DIVISOR:
params[0] = static_cast<GLfloat>(attr->Divisor);
return;
case GL_VERTEX_ATTRIB_BINDING:
case GL_VERTEX_ATTRIB_RELATIVE_OFFSET: {
GLint value = 0;
TryGetVertexAttribBindingQuery(index, pname, value);
params[0] = static_cast<GLfloat>(value);
return;
}
default:
MG_State::pGLContext->RecordError(
ErrorCode::InvalidEnum,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
"Unsupported float vertex attrib pname: " + std::to_string(pname)));
return;
}
}
// 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->LegacyStride);
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_LONG:
params[0] = attr->IsLong ? 1.0 : 0.0;
return;
case GL_VERTEX_ATTRIB_ARRAY_DIVISOR:
params[0] = static_cast<GLdouble>(attr->Divisor);
return;
case GL_VERTEX_ATTRIB_BINDING:
case GL_VERTEX_ATTRIB_RELATIVE_OFFSET: {
GLint value = 0;
TryGetVertexAttribBindingQuery(index, pname, value);
params[0] = static_cast<GLdouble>(value);
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(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "params pointer cannot be null."));
return;
}
if (!VertexArrayImpl::ValidateVertexAttributeIndex(index)) return;
if (!ValidateVertexAttribPname(pname)) return;
if (IsCurrentVertexAttribQuery(pname)) {
const auto& current = MG_State::pGLContext->GetCurrentVertexAttribute(index);
params[0] = current.intValue[0];
params[1] = current.intValue[1];
params[2] = current.intValue[2];
params[3] = current.intValue[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 ? GL_TRUE : GL_FALSE;
return;
case GL_VERTEX_ATTRIB_ARRAY_SIZE:
params[0] = attr->Size;
return;
// The legacy shadow, not the resolved draw stride: GL 4.6 core table 23.3 defines this
// as the last glVertexAttrib*Pointer argument, which glBindVertexBuffer must not
// overwrite even though it does overwrite what the backend actually reads.
case GL_VERTEX_ATTRIB_ARRAY_STRIDE:
params[0] = attr->LegacyStride;
return;
case GL_VERTEX_ATTRIB_ARRAY_TYPE:
params[0] = static_cast<GLint>(MG_Util::ConvertDataTypeToGLEnum(attr->Type));
return;
case GL_VERTEX_ATTRIB_ARRAY_NORMALIZED:
params[0] = attr->Normalized ? GL_TRUE : GL_FALSE;
return;
case GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING:
params[0] = attr->Buffer ? static_cast<GLint>(attr->Buffer->GetExternalIndex()) : 0;
return;
case GL_VERTEX_ATTRIB_ARRAY_INTEGER:
params[0] = attr->IsInteger ? GL_TRUE : GL_FALSE;
return;
case GL_VERTEX_ATTRIB_ARRAY_LONG:
params[0] = attr->IsLong ? GL_TRUE : GL_FALSE;
return;
case GL_VERTEX_ATTRIB_ARRAY_DIVISOR:
params[0] = static_cast<GLint>(attr->Divisor);
return;
case GL_VERTEX_ATTRIB_BINDING:
case GL_VERTEX_ATTRIB_RELATIVE_OFFSET:
TryGetVertexAttribBindingQuery(index, pname, params[0]);
return;
default:
MG_State::pGLContext->RecordError(
ErrorCode::InvalidEnum,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
"Unsupported integer vertex attrib pname: " + std::to_string(pname)));
return;
}
}
void GetVertexAttribPointerv(GLuint index, GLenum pname, void** pointer) {
if (!pointer) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "pointer cannot be null."));
return;
}
if (!VertexArrayImpl::ValidateVertexAttributeIndex(index)) return;
if (pname != GL_VERTEX_ATTRIB_ARRAY_POINTER) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidEnum,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
"pname must be GL_VERTEX_ATTRIB_ARRAY_POINTER."));
return;
}
auto& vao = MG_State::pGLContext->GetBoundVertexArray();
if (!vao) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "No vertex array object is bound."));
return;
}
const auto& attr = vao->GetAttribute(index);
*pointer = reinterpret_cast<void*>(attr.LegacyPointer);
}
void GetVertexAttribIiv(GLuint index, GLenum pname, GLint* params) {
GetVertexAttribiv(index, pname, params);
}
void GetVertexAttribIuiv(GLuint index, GLenum pname, GLuint* params) {
if (!params) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "params pointer cannot be null."));
return;
}
if (!VertexArrayImpl::ValidateVertexAttributeIndex(index)) return;
if (!ValidateVertexAttribPname(pname)) return;
if (IsCurrentVertexAttribQuery(pname)) {
const auto& current = MG_State::pGLContext->GetCurrentVertexAttribute(index);
params[0] = current.uintValue[0];
params[1] = current.uintValue[1];
params[2] = current.uintValue[2];
params[3] = current.uintValue[3];
return;
}
GLint signedParams[4] = {};
GetVertexAttribiv(index, pname, signedParams);
params[0] = static_cast<GLuint>(signedParams[0]);
params[1] = static_cast<GLuint>(signedParams[1]);
params[2] = static_cast<GLuint>(signedParams[2]);
params[3] = static_cast<GLuint>(signedParams[3]);
}
void CreateVertexArrays(GLsizei n, GLuint* arrays) {
CreateVertexArrays_State(n, arrays);
}
void DisableVertexArrayAttrib(GLuint vaobj, GLuint index) {
DisableVertexArrayAttrib_State(vaobj, index);
}
void EnableVertexArrayAttrib(GLuint vaobj, GLuint index) {
EnableVertexArrayAttrib_State(vaobj, index);
}
void VertexArrayElementBuffer(GLuint vaobj, GLuint buffer) {
VertexArrayElementBuffer_State(vaobj, buffer);
}
void VertexArrayVertexBuffer(GLuint vaobj, GLuint bindingindex, GLuint buffer, GLintptr offset, GLsizei stride) {
VertexArrayVertexBuffer_State(vaobj, bindingindex, buffer, offset, stride);
}
// glGetVertexArrayiv reports exactly one thing (GL 4.6 core table 23.4): which buffer the
// named vertex array takes its indices from. Everything else about a vertex array is
// per-attribute and belongs to the indexed queries below.
void GetVertexArrayiv(GLuint vaobj, GLenum pname, GLint* param) {
auto vao = GetNamedVertexArrayObject_State(vaobj, __func__);
if (!vao || !param) return;
if (pname != GL_ELEMENT_ARRAY_BUFFER_BINDING) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidEnum,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
"pname must be GL_ELEMENT_ARRAY_BUFFER_BINDING."));
return;
}
const auto& indexBuffer = vao->GetIndexBufferBindingSlot().GetBoundObject();
*param = indexBuffer ? static_cast<GLint>(indexBuffer->GetExternalIndex()) : 0;
}
void GetVertexArrayIndexediv(GLuint vaobj, GLuint index, GLenum pname, GLint* param) {
auto vao = GetNamedVertexArrayObject_State(vaobj, __func__);
if (!vao || !param) return;
if (!VertexArrayImpl::ValidateVertexAttributeIndex(index)) return;
const auto& attr = vao->GetAttribute(index);
switch (pname) {
case GL_VERTEX_ATTRIB_ARRAY_ENABLED:
*param = attr.Enabled ? GL_TRUE : GL_FALSE;
return;
case GL_VERTEX_ATTRIB_ARRAY_SIZE:
*param = static_cast<GLint>(attr.Size);
return;
case GL_VERTEX_ATTRIB_ARRAY_STRIDE:
*param = static_cast<GLint>(attr.LegacyStride);
return;
case GL_VERTEX_ATTRIB_ARRAY_TYPE:
*param = static_cast<GLint>(MG_Util::ConvertDataTypeToGLEnum(attr.Type));
return;
case GL_VERTEX_ATTRIB_ARRAY_NORMALIZED:
*param = attr.Normalized ? GL_TRUE : GL_FALSE;
return;
case GL_VERTEX_ATTRIB_ARRAY_INTEGER:
*param = attr.IsInteger ? GL_TRUE : GL_FALSE;
return;
case GL_VERTEX_ATTRIB_ARRAY_LONG:
*param = attr.IsLong ? GL_TRUE : GL_FALSE;
return;
case GL_VERTEX_ATTRIB_ARRAY_DIVISOR:
*param = static_cast<GLint>(attr.Divisor);
return;
case GL_VERTEX_ATTRIB_RELATIVE_OFFSET:
*param = static_cast<GLint>(vao->GetAttributeRelativeOffset(index));
return;
case GL_VERTEX_ATTRIB_BINDING:
*param = static_cast<GLint>(vao->GetAttributeBindingIndex(index));
return;
default:
MG_State::pGLContext->RecordError(
ErrorCode::InvalidEnum,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
"pname is not an accepted indexed vertex array query."));
return;
}
}
// Only GL_VERTEX_BINDING_OFFSET needs 64 bits. Its `index` names a vertex buffer binding
// point directly (GL 4.6 core 10.3.1), not an attribute - unlike every pname the 32-bit
// indexed query above accepts, which is why this one does not go through an attribute's
// binding index.
void GetVertexArrayIndexed64iv(GLuint vaobj, GLuint index, GLenum pname, GLint64* param) {
auto vao = GetNamedVertexArrayObject_State(vaobj, __func__);
if (!vao || !param) return;
if (!VertexArrayImpl::ValidateVertexAttributeIndex(index)) return;
if (pname != GL_VERTEX_BINDING_OFFSET) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidEnum,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "pname must be GL_VERTEX_BINDING_OFFSET."));
return;
}
*param = static_cast<GLint64>(vao->GetBindingPoint(index).Offset);
}
void VertexArrayAttribFormat(GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLboolean normalized,
GLuint relativeoffset) {
VertexArrayAttribFormat_State(vaobj, attribindex, size, type, normalized, relativeoffset);
}
void VertexArrayAttribIFormat(GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset) {
VertexArrayAttribIFormat_State(vaobj, attribindex, size, type, relativeoffset);
}
void VertexArrayAttribBinding(GLuint vaobj, GLuint attribindex, GLuint bindingindex) {
VertexArrayAttribBinding_State(vaobj, attribindex, bindingindex);
}
void VertexArrayBindingDivisor(GLuint vaobj, GLuint bindingindex, GLuint divisor) {
VertexArrayBindingDivisor_State(vaobj, bindingindex, divisor);
}
void VertexArrayVertexBuffers(GLuint vaobj, GLuint first, GLsizei count, const GLuint* buffers,
const GLintptr* offsets, const GLsizei* strides) {
VertexArrayVertexBuffers_State(vaobj, first, count, buffers, offsets, strides);
}
void BindVertexBuffer(GLuint bindingindex, GLuint buffer, GLintptr offset, GLsizei stride) {
auto vao = GetBoundVertexArrayForBindingApi("BindVertexBuffer");
if (!vao) return;
VertexBufferBinding_State(vao, bindingindex, buffer, offset, stride, "BindVertexBuffer");
}
void BindVertexBuffers(GLuint first, GLsizei count, const GLuint* buffers, const GLintptr* offsets,
const GLsizei* strides) {
auto vao = GetBoundVertexArrayForBindingApi("BindVertexBuffers");
if (!vao) return;
if (!ValidateVertexBindingRange(first, count, "BindVertexBuffers")) return;
for (GLsizei i = 0; i < count; ++i) {
if (!buffers) {
VertexBufferBinding_State(vao, first + i, 0, 0, 16, "BindVertexBuffers");
} else {
VertexBufferBinding_State(vao, first + i, buffers[i], offsets ? offsets[i] : 0,
strides ? strides[i] : 16, "BindVertexBuffers");
}
}
}
void VertexAttribFormat(GLuint attribindex, GLint size, GLenum type, GLboolean normalized, GLuint relativeoffset) {
auto vao = GetBoundVertexArrayForBindingApi("VertexAttribFormat");
if (!vao) return;
VertexAttribFormatSeparate_State(vao, attribindex, size, type, normalized, relativeoffset, false,
"VertexAttribFormat");
}
void VertexAttribIFormat(GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset) {
auto vao = GetBoundVertexArrayForBindingApi("VertexAttribIFormat");
if (!vao) return;
VertexAttribFormatSeparate_State(vao, attribindex, size, type, GL_FALSE, relativeoffset, true,
"VertexAttribIFormat");
}
void VertexAttribLFormat(GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset) {
auto vao = GetBoundVertexArrayForBindingApi("VertexAttribLFormat");
if (!vao) return;
VertexAttribLFormatSeparate_State(vao, attribindex, size, type, relativeoffset);
}
void VertexArrayAttribLFormat(GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset) {
auto vao = GetNamedVertexArrayObject_State(vaobj, "VertexArrayAttribLFormat");
if (!vao) return;
VertexAttribLFormatSeparate_State(vao, attribindex, size, type, relativeoffset);
}
void VertexAttribBinding(GLuint attribindex, GLuint bindingindex) {
auto vao = GetBoundVertexArrayForBindingApi("VertexAttribBinding");
if (!vao) return;
if (!VertexArrayImpl::ValidateVertexAttributeIndex(attribindex)) return;
if (!ValidateVertexBindingIndex(bindingindex, "VertexAttribBinding")) return;
vao->SetAttributeBinding(attribindex, bindingindex);
}
void VertexBindingDivisor(GLuint bindingindex, GLuint divisor) {
auto vao = GetBoundVertexArrayForBindingApi("VertexBindingDivisor");
if (!vao) return;
if (!ValidateVertexBindingIndex(bindingindex, "VertexBindingDivisor")) return;
vao->SetBindingDivisor(bindingindex, divisor);
}
void VertexAttribDivisor(GLuint index, GLuint divisor) {
VertexAttribDivisor_State(index, divisor);
}
GLboolean IsVertexArray(GLuint array) {
return IsVertexArray_State(array);
}
void DisableVertexAttribArray(GLuint index) {
DisableVertexAttribArray_State(index);
}
void EnableVertexAttribArray(GLuint index) {
EnableVertexAttribArray_State(index);
}
void VertexAttribIPointer(GLuint index, GLint size, GLenum type, GLsizei stride, const void* pointer) {
VertexAttribIPointer_State(index, size, type, stride, pointer);
}
void VertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride,
const void* pointer) {
VertexAttribPointer_State(index, size, type, normalized, stride, pointer);
}
void BindVertexArray(GLuint array) {
BindVertexArray_State(array);
}
void DeleteVertexArrays(GLsizei n, const GLuint* arrays) {
DeleteVertexArrays_State(n, arrays);
}
void GenVertexArrays(GLsizei n, GLuint* arrays) {
GenVertexArrays_State(n, arrays);
}
} // namespace MobileGL::MG_Impl::GLImpl