[Feat] (MG_Impl, MG_State, MG_Util): attach a buffer texture to a range of its buffer

glTexBufferRange, glTextureBuffer and glTextureBufferRange were all stubs, so a
buffer texture could only ever be attached through glTexBuffer -- by binding, and
always to the whole buffer.

Give the buffer texture the window it is supposed to address. The non-range forms
record it as offset 0 with a whole-buffer sentinel rather than the size the buffer
happens to have, so a later respecify keeps being followed instead of freezing the
texture at yesterday's size. All four entry points now share one attach path,
differing only in how they name the texture: by binding for the target forms, by
name for the DSA ones.

Both backends honour the window: DirectVulkan offsets and clamps the buffer view,
DirectGLES uses glTexBufferRange when the texture names a sub-range and keeps
plain glTexBuffer for the whole-buffer case, which also works on a driver without
the range entry point.

GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT reported 0 with a comment explaining that the
range entry points were stubbed. It now reports what the device actually requires
-- minTexelBufferOffsetAlignment on Vulkan, the driver's own value on GLES -- and
the range entry points enforce it. Zero was never a legal answer; the minimum is
1, and an application that trusted it would have built unaligned offsets.
This commit is contained in:
BZLZHH
2026-08-04 20:53:53 -04:00
parent f39e6eb82d
commit bb582203d9
14 changed files with 155 additions and 7 deletions
+2
View File
@@ -315,6 +315,8 @@ namespace MobileGL {
Int MaxComputeWorkGroupInvocations = 128;
Int MaxShaderStorageBufferBindings = 8;
Int MaxTextureBufferSize = 65536;
// GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT; 1 means the offset is unconstrained.
Int TextureBufferOffsetAlignment = 1;
Int MaxUniformBufferBindings = 24;
Int MaxUniformBlockSize = 16384;
Int MaxImageUnits = 8;
@@ -1112,6 +1112,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
m_dynamicParameters.MaxComputeWorkGroupInvocations = m_GLESCapabilities.MaxComputeWorkGroupInvocations;
m_dynamicParameters.MaxShaderStorageBufferBindings = m_GLESCapabilities.MaxShaderStorageBufferBindings;
m_dynamicParameters.MaxTextureBufferSize = m_GLESCapabilities.MaxTextureBufferSize;
m_dynamicParameters.TextureBufferOffsetAlignment = m_GLESCapabilities.TextureBufferOffsetAlignment;
m_dynamicParameters.MaxUniformBufferBindings = m_GLESCapabilities.MaxUniformBufferBindings;
m_dynamicParameters.MaxUniformBlockSize = m_GLESCapabilities.MaxUniformBlockSize;
const Int maxSupportedTextureUnits =
+18 -1
View File
@@ -2232,7 +2232,24 @@ namespace MobileGL::MG_Backend::DirectGLES {
"ID: %u, buffer ID: %u, buffer size: %zu, format: %s",
m_backendTextureId, backendId, buffer->GetSize(),
MG_Util::ConvertGLEnumToString(glInternalFormat).c_str());
g_GLESFuncs.glTexBuffer(GL_TEXTURE_BUFFER, glInternalFormat, backendId);
// A texture that names a window of the buffer needs the range form; the
// whole-buffer forms report offset 0 and the buffer's current size, which
// glTexBuffer expresses more directly (and works where the range entry point
// is absent).
const SizeT rangeOffset = textureBufferObject->GetBufferRangeOffset();
const SizeT rangeSize = textureBufferObject->GetBufferRangeSizeInBytes();
if (rangeOffset == 0 && rangeSize == buffer->GetSize()) {
g_GLESFuncs.glTexBuffer(GL_TEXTURE_BUFFER, glInternalFormat, backendId);
} else if (g_GLESFuncs.glTexBufferRange != nullptr) {
g_GLESFuncs.glTexBufferRange(GL_TEXTURE_BUFFER, glInternalFormat, backendId,
static_cast<GLintptr>(rangeOffset),
static_cast<GLsizeiptr>(rangeSize));
} else {
MGLOG_E("Texture buffer %u names a sub-range but the driver has no "
"glTexBufferRange; binding the whole buffer instead",
stateTextureObject->GetExternalIndex());
g_GLESFuncs.glTexBuffer(GL_TEXTURE_BUFFER, glInternalFormat, backendId);
}
DebugImpl::ErrorLopper::Loop(
[file = __FILE__, line = __LINE__, func = __func__, glInternalFormat, backendId](GLenum err) {
MGLOG_D("%s(%s:%d) glTexBuffer(format=%s, buffer=%u) ES error: %s",
@@ -780,6 +780,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_dynamicParameters.MaxComputeWorkGroupInvocations = m_vulkanCaps.MaxComputeWorkGroupInvocations;
m_dynamicParameters.MaxShaderStorageBufferBindings = m_vulkanCaps.MaxShaderStorageBufferBindings;
m_dynamicParameters.MaxTextureBufferSize = m_vulkanCaps.MaxTextureBufferSize;
m_dynamicParameters.TextureBufferOffsetAlignment = m_vulkanCaps.TextureBufferOffsetAlignment;
m_dynamicParameters.MaxUniformBufferBindings = m_vulkanCaps.MaxUniformBufferBindings;
m_dynamicParameters.MaxUniformBlockSize = m_vulkanCaps.MaxUniformBlockSize;
m_dynamicParameters.MaxImageUnits =
@@ -600,7 +600,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const VkDeviceSize texelSize =
static_cast<VkDeviceSize>(MG_Util::GetSizedInternalFormatSizeInBytes(internalFormat));
VkDeviceSize viewRange = slice.size;
// glTextureBufferRange addresses a window of the buffer, not all of it; the whole-buffer
// forms report the buffer's current size here, so both go through the same clamp.
const VkDeviceSize rangeOffset = static_cast<VkDeviceSize>(textureBuffer->GetBufferRangeOffset());
const VkDeviceSize rangeSize = static_cast<VkDeviceSize>(textureBuffer->GetBufferRangeSizeInBytes());
VkDeviceSize viewRange = std::min(rangeSize, slice.size > rangeOffset ? slice.size - rangeOffset : 0);
if (texelSize > 0) {
viewRange = (viewRange / texelSize) * texelSize;
}
@@ -613,7 +617,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
viewInfo.sType = VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO;
viewInfo.buffer = slice.buffer;
viewInfo.format = vkFormat;
viewInfo.offset = slice.offset;
viewInfo.offset = slice.offset + rangeOffset;
viewInfo.range = viewRange;
VkBufferView bufferView = VK_NULL_HANDLE;
@@ -434,7 +434,7 @@ DECLARE_GL_FUNCTION_HEAD(void, SamplerParameterIuiv, GLuint sampler, GLenum pnam
DECLARE_GL_FUNCTION_HEAD(void, GetSamplerParameterIiv, GLuint sampler, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetSamplerParameterIiv, sampler, pname, params)
DECLARE_GL_FUNCTION_HEAD(void, GetSamplerParameterIuiv, GLuint sampler, GLenum pname, GLuint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetSamplerParameterIuiv, sampler, pname, params)
DECLARE_GL_FUNCTION_HEAD(void, TexBuffer, GLenum target, GLenum internalformat, GLuint buffer) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TexBuffer, target, internalformat, buffer)
DECLARE_GL_FUNCTION_STUB_HEAD(void, TexBufferRange, GLenum target, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TexBufferRange, target, internalformat, buffer, offset, size)
DECLARE_GL_FUNCTION_HEAD(void, TexBufferRange, GLenum target, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TexBufferRange, target, internalformat, buffer, offset, size)
DECLARE_GL_FUNCTION_HEAD(void, TexStorage3DMultisample, GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TexStorage3DMultisample, target, samples, internalformat, width, height, depth, fixedsamplelocations)
DECLARE_GL_FUNCTION_HEAD(void*, MapBufferRange, GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access) DECLARE_GL_FUNCTION_END(void*, MapBufferRange, target, offset, length, access)
DECLARE_GL_FUNCTION_STUB_HEAD(void, ClearIndex, GLfloat c) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClearIndex, c)
@@ -1049,8 +1049,8 @@ DECLARE_GL_FUNCTION_HEAD(void, NamedRenderbufferStorage, GLuint renderbuffer, GL
DECLARE_GL_FUNCTION_HEAD(void, NamedRenderbufferStorageMultisample, GLuint renderbuffer, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedRenderbufferStorageMultisample, renderbuffer, samples, internalformat, width, height)
DECLARE_GL_FUNCTION_HEAD(void, GetNamedRenderbufferParameteriv, GLuint renderbuffer, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetNamedRenderbufferParameteriv, renderbuffer, pname, params)
DECLARE_GL_FUNCTION_HEAD(void, CreateTextures, GLenum target, GLsizei n, GLuint* textures) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CreateTextures, target, n, textures)
DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureBuffer, GLuint texture, GLenum internalformat, GLuint buffer) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureBuffer, texture, internalformat, buffer)
DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureBufferRange, GLuint texture, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureBufferRange, texture, internalformat, buffer, offset, size)
DECLARE_GL_FUNCTION_HEAD(void, TextureBuffer, GLuint texture, GLenum internalformat, GLuint buffer) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureBuffer, texture, internalformat, buffer)
DECLARE_GL_FUNCTION_HEAD(void, TextureBufferRange, GLuint texture, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureBufferRange, texture, internalformat, buffer, offset, size)
DECLARE_GL_FUNCTION_HEAD(void, TextureStorage1D, GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureStorage1D, texture, levels, internalformat, width)
DECLARE_GL_FUNCTION_HEAD(void, TextureStorage2D, GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureStorage2D, texture, levels, internalformat, width, height)
DECLARE_GL_FUNCTION_HEAD(void, TextureStorage3D, GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureStorage3D, texture, levels, internalformat, width, height, depth)
+1 -1
View File
@@ -1669,7 +1669,7 @@ namespace MobileGL::MG_Impl::GLImpl {
*params = static_cast<GLint>(MG_State::pGLContext->GetHint(pname));
return;
case GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT:
*params = 0; // texture-buffer range entrypoints are stubbed
*params = MG_Backend::pActiveBackendObject->GetDynamicParameters().TextureBufferOffsetAlignment;
return;
case GL_TIMESTAMP: {
Int64 timestamp = 0;
@@ -2035,6 +2035,65 @@ namespace MobileGL::MG_Impl::GLImpl {
MaybeAutoGenerateMipmap(target, textureObject, isProxy, level);
}
// The work glTexBuffer[Range] and glTextureBuffer[Range] all share once the texture has been
// resolved - by binding for the target forms, by name for the DSA ones. `size` is
// kWholeBuffer for the non-Range entry points, which attach the buffer as it grows rather
// than freezing the size it happens to have now.
static void AttachBufferToTexture(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject,
GLenum internalformat, GLuint buffer, GLintptr offset, SizeT size,
const char* caller) {
using MG_State::GLState::TextureObjectBuffer;
TextureInternalFormat textureInternalFormat = MG_Util::ConvertGLEnumToTextureInternalFormat(internalformat);
if (!TextureImpl::ValidateTextureInternalFormat(textureInternalFormat)) return;
auto& bufferObject = MG_State::pGLContext->GetBufferObject(buffer);
if (buffer != 0 && !bufferObject) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
"`buffer` is not zero and is not the name of an existing buffer object."));
return;
}
if (!TextureImpl::ValidateTextureObject(textureObject)) return;
if (textureObject->GetStorageType() != TextureStorageType::Buffer) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidEnum,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
"The effective target of `texture` is not `GL_TEXTURE_BUFFER`."));
return;
}
if (size != TextureObjectBuffer::kWholeBuffer) {
// GL 4.6 core 8.9: offset must be non-negative and aligned to
// GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT, and size must be positive.
if (offset < 0) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller, "offset must be non-negative."));
return;
}
if (size == 0) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller, "size must be greater than zero."));
return;
}
const Int alignment = std::max(
1, MG_Backend::pActiveBackendObject->GetDynamicParameters().TextureBufferOffsetAlignment);
if (offset % alignment != 0) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
"offset is not a multiple of GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT."));
return;
}
}
auto* texBufferObject = static_cast<TextureObjectBuffer*>(textureObject.get());
texBufferObject->GetBufferBindingSlot().Bind(bufferObject);
texBufferObject->SetBufferRange(static_cast<SizeT>(offset < 0 ? 0 : offset), size);
texBufferObject->SetInternalFormat(textureInternalFormat);
}
void TexBuffer_State(GLenum target, GLenum internalformat, GLuint buffer) {
// ======================= Converting ================================
TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
@@ -2081,6 +2140,7 @@ namespace MobileGL::MG_Impl::GLImpl {
auto& bufferSlot = texBufferObject->GetBufferBindingSlot();
bufferSlot.Bind(bufferObject);
texBufferObject->SetBufferRange(0, MG_State::GLState::TextureObjectBuffer::kWholeBuffer);
texBufferObject->SetInternalFormat(textureInternalFormat);
}
@@ -4088,6 +4148,33 @@ namespace MobileGL::MG_Impl::GLImpl {
TexBuffer_State(target, internalformat, buffer);
}
// The buffer texture bound to `target` on the active unit - what the non-DSA range form
// operates on. Kept separate from TexBuffer_State because that one resolves the target
// itself and attaches the whole buffer.
static const SharedPtr<MG_State::GLState::ITextureObject>& GetBoundBufferTexture(GLenum target,
const char* caller) {
TextureUploadTarget uploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
if (!TextureImpl::ValidateTextureUploadTarget(uploadTarget)) return nullTextureObject;
(void)caller;
auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
return activeUnit.GetBindingSlot(MG_Util::ConvertGLEnumToTextureTarget(target)).GetBoundObject();
}
void TexBufferRange(GLenum target, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size) {
AttachBufferToTexture(GetBoundBufferTexture(target, __func__), internalformat, buffer, offset,
static_cast<SizeT>(size < 0 ? 0 : size), __func__);
}
void TextureBuffer(GLuint texture, GLenum internalformat, GLuint buffer) {
AttachBufferToTexture(GetTextureObjectByName(texture, __func__), internalformat, buffer, 0,
MG_State::GLState::TextureObjectBuffer::kWholeBuffer, __func__);
}
void TextureBufferRange(GLuint texture, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size) {
AttachBufferToTexture(GetTextureObjectByName(texture, __func__), internalformat, buffer, offset,
static_cast<SizeT>(size < 0 ? 0 : size), __func__);
}
GLboolean IsTexture(GLuint texture) {
return IsTexture_State(texture);
}
@@ -42,6 +42,9 @@ namespace MobileGL::MG_Impl::GLImpl {
void GenerateTextureMipmap(GLuint texture);
void BindTextureUnit(GLuint unit, GLuint texture);
void GetTextureImage(GLuint texture, GLint level, GLenum format, GLenum type, GLsizei bufSize, void* pixels);
void TexBufferRange(GLenum target, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size);
void TextureBuffer(GLuint texture, GLenum internalformat, GLuint buffer);
void TextureBufferRange(GLuint texture, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size);
void GetTextureSubImage(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width,
GLsizei height, GLsizei depth, GLenum format, GLenum type, GLsizei bufSize, void* pixels);
void GetTextureParameterfv(GLuint texture, GLenum pname, GLfloat* params);
@@ -21,10 +21,31 @@ namespace MobileGL {
BindingSlot<BufferObject>& GetBufferBindingSlot(
TextureUploadTarget target = TextureUploadTarget::TextureBuffer);
// The window of the attached buffer the texture addresses. glTexBuffer attaches
// the whole buffer, which is expressed here as an offset of 0 and a size of
// kWholeBuffer so a later respecify of the buffer keeps being followed - a stored
// size would freeze the texture at the size the buffer happened to have.
static constexpr SizeT kWholeBuffer = ~static_cast<SizeT>(0);
void SetBufferRange(SizeT offset, SizeT size) {
m_bufferRangeOffset = offset;
m_bufferRangeSize = size;
}
SizeT GetBufferRangeOffset() const { return m_bufferRangeOffset; }
// Resolved against the buffer's current size, so kWholeBuffer tracks it.
SizeT GetBufferRangeSizeInBytes() const {
const auto& buffer = m_bufferBindingSlot.GetBoundObject();
const SizeT bufferSize = buffer != nullptr ? buffer->GetSize() : 0;
if (m_bufferRangeSize == kWholeBuffer) return bufferSize;
const SizeT available = bufferSize > m_bufferRangeOffset ? bufferSize - m_bufferRangeOffset : 0;
return std::min(m_bufferRangeSize, available);
}
protected:
Uint GetIndexOfTextureUploadTarget(TextureUploadTarget target) const override;
BindingSlot<BufferObject> m_bufferBindingSlot = BindingSlot<BufferObject>(BufferTarget::Texture);
SizeT m_bufferRangeOffset = 0;
SizeT m_bufferRangeSize = kWholeBuffer;
const Vector<TextureUploadTarget> m_uploadTargets{TextureUploadTarget::TextureBuffer};
};
} // namespace GLState
@@ -1056,6 +1056,10 @@ namespace MobileGL::MG_Util::BackendLoader {
caps.MaxComputeWorkGroupInvocations = maxComputeWorkGroupInvocations;
caps.MaxShaderStorageBufferBindings = maxShaderStorageBufferBindings;
caps.MaxTextureBufferSize = maxTextureBufferSize;
GLint textureBufferOffsetAlignment = 1;
glGetIntegerv(GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT, &textureBufferOffsetAlignment);
while (glGetError() != GL_NO_ERROR) {}
caps.TextureBufferOffsetAlignment = std::max(1, textureBufferOffsetAlignment);
caps.MaxUniformBufferBindings = maxUniformBufferBindings;
caps.MaxUniformBlockSize = maxUniformBlockSize;
caps.MaxImageUnits = maxImageUnits;
@@ -1117,6 +1117,8 @@ namespace MobileGL {
Int MaxComputeWorkGroupInvocations = 128;
Int MaxShaderStorageBufferBindings = 8;
Int MaxTextureBufferSize = 65536;
// GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT; 1 means the offset is unconstrained.
Int TextureBufferOffsetAlignment = 1;
Int MaxUniformBufferBindings = 24;
Int MaxUniformBlockSize = 16384;
Int MaxImageUnits = 8;
@@ -177,6 +177,8 @@ namespace MobileGL::MG_Util::BackendLoader {
caps.MaxComputeWorkGroupInvocations = static_cast<Int>(p.limits.maxComputeWorkGroupInvocations);
caps.MaxShaderStorageBufferBindings = static_cast<Int>(p.limits.maxDescriptorSetStorageBuffers);
caps.MaxTextureBufferSize = static_cast<Int>(p.limits.maxTexelBufferElements);
caps.TextureBufferOffsetAlignment =
static_cast<Int>(std::max<VkDeviceSize>(1, p.limits.minTexelBufferOffsetAlignment));
caps.MaxUniformBufferBindings = static_cast<Int>(p.limits.maxDescriptorSetUniformBuffers);
caps.MaxUniformBlockSize = static_cast<Int>(p.limits.maxUniformBufferRange);
caps.MaxImageUnits = static_cast<Int>(p.limits.maxPerStageDescriptorStorageImages);
@@ -268,6 +270,8 @@ namespace MobileGL::MG_Util::BackendLoader {
caps.MaxComputeWorkGroupInvocations = static_cast<Int>(properties.limits.maxComputeWorkGroupInvocations);
caps.MaxShaderStorageBufferBindings = static_cast<Int>(properties.limits.maxDescriptorSetStorageBuffers);
caps.MaxTextureBufferSize = static_cast<Int>(properties.limits.maxTexelBufferElements);
caps.TextureBufferOffsetAlignment =
static_cast<Int>(std::max<VkDeviceSize>(1, properties.limits.minTexelBufferOffsetAlignment));
caps.MaxUniformBufferBindings = static_cast<Int>(properties.limits.maxDescriptorSetUniformBuffers);
caps.MaxUniformBlockSize = static_cast<Int>(properties.limits.maxUniformBufferRange);
caps.MaxImageUnits = static_cast<Int>(properties.limits.maxPerStageDescriptorStorageImages);
@@ -54,6 +54,8 @@ namespace MobileGL {
Int MaxComputeWorkGroupInvocations = 128;
Int MaxShaderStorageBufferBindings = 8;
Int MaxTextureBufferSize = 65536;
// GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT; 1 means the offset is unconstrained.
Int TextureBufferOffsetAlignment = 1;
Int MaxUniformBufferBindings = 24;
Int MaxUniformBlockSize = 16384;
Int MaxImageUnits = 8;