[Feat] (GL): implement GL_ARB_shader_objects over the core's shared shader / program name space.

This commit is contained in:
2026-10-06 10:11:19 -04:00
parent c641ed92fb
commit c3af0719f0
7 changed files with 816 additions and 11 deletions
+1
View File
@@ -428,6 +428,7 @@ set(SOURCE_FILES
MobileGL/MG_Impl/GLImpl/Framebuffer/Validators.cpp
MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.cpp
MobileGL/MG_Impl/GLImpl/Program/GL_Program.cpp
MobileGL/MG_Impl/GLImpl/Program/GL_ShaderObjectsARB.cpp
MobileGL/MG_Impl/GLImpl/Program/ProgramInterface.cpp
MobileGL/MG_Impl/GLImpl/Program/GL_ProgramPipeline.cpp
MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp
+159 -11
View File
@@ -1307,17 +1307,6 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, GetnMinmaxARB, GLenum target, GLboolean rese
DECLARE_GL_FUNCTION_STUB_HEAD(void, FramebufferSampleLocationsfvARB, GLenum target, GLuint start, GLsizei count, const GLfloat* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, FramebufferSampleLocationsfvARB, target, start, count, v)
DECLARE_GL_FUNCTION_STUB_HEAD(void, NamedFramebufferSampleLocationsfvARB, GLuint framebuffer, GLuint start, GLsizei count, const GLfloat* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, NamedFramebufferSampleLocationsfvARB, framebuffer, start, count, v)
DECLARE_GL_FUNCTION_STUB_HEAD(void, EvaluateDepthValuesARB, void) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, EvaluateDepthValuesARB, )
DECLARE_GL_FUNCTION_STUB_HEAD(void, DeleteObjectARB, GLhandleARB obj) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DeleteObjectARB, obj)
DECLARE_GL_FUNCTION_STUB_HEAD(GLhandleARB, GetHandleARB, GLenum pname) DECLARE_GL_FUNCTION_STUB_END(GLhandleARB, GetHandleARB, pname)
DECLARE_GL_FUNCTION_STUB_HEAD(void, DetachObjectARB, GLhandleARB containerObj, GLhandleARB attachedObj) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DetachObjectARB, containerObj, attachedObj)
DECLARE_GL_FUNCTION_STUB_HEAD(GLhandleARB, CreateShaderObjectARB, GLenum shaderType) DECLARE_GL_FUNCTION_STUB_END(GLhandleARB, CreateShaderObjectARB, shaderType)
DECLARE_GL_FUNCTION_STUB_HEAD(GLhandleARB, CreateProgramObjectARB, void) DECLARE_GL_FUNCTION_STUB_END(GLhandleARB, CreateProgramObjectARB, )
DECLARE_GL_FUNCTION_STUB_HEAD(void, AttachObjectARB, GLhandleARB containerObj, GLhandleARB obj) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, AttachObjectARB, containerObj, obj)
DECLARE_GL_FUNCTION_STUB_HEAD(void, UseProgramObjectARB, GLhandleARB programObj) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, UseProgramObjectARB, programObj)
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetObjectParameterfvARB, GLhandleARB obj, GLenum pname, GLfloat* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetObjectParameterfvARB, obj, pname, params)
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetObjectParameterivARB, GLhandleARB obj, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetObjectParameterivARB, obj, pname, params)
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetInfoLogARB, GLhandleARB obj, GLsizei maxLength, GLsizei* length, GLcharARB* infoLog) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetInfoLogARB, obj, maxLength, length, infoLog)
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetAttachedObjectsARB, GLhandleARB containerObj, GLsizei maxCount, GLsizei* count, GLhandleARB* obj) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetAttachedObjectsARB, containerObj, maxCount, count, obj)
DECLARE_GL_FUNCTION_STUB_HEAD(void, NamedStringARB, GLenum type, GLint namelen, const GLchar* name, GLint strinen, const GLchar* string) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, NamedStringARB, type, namelen, name, strinen, string)
DECLARE_GL_FUNCTION_STUB_HEAD(void, DeleteNamedStringARB, GLint namelen, const GLchar* name) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DeleteNamedStringARB, namelen, name)
DECLARE_GL_FUNCTION_STUB_HEAD(void, CompileShaderIncludeARB, GLuint shader, GLsizei count, const GLchar* const* path, const GLint* length) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CompileShaderIncludeARB, shader, count, path, length)
@@ -2996,6 +2985,165 @@ MOBILEGL_GL_API void glGetnUniformdvARB(GLuint program, GLint location, GLsizei
glGetnUniformdv(program, location, bufSize, params);
}
// ---- GL_ARB_shader_objects --------------------------------------------------------------------
// A handle is a name of the core's one shared shader/program name space: CreateShaderObjectARB and
// CreateProgramObjectARB hand out exactly what glCreateShader / glCreateProgram would, so an ARB
// handle works with every core call and a core name with every ARB call. The calls whose meaning
// is the core one under another name forward to the core entry point; the ones that ask which
// kind of object a handle names (DeleteObjectARB, GetHandleARB, GetObjectParameter*ARB,
// GetInfoLogARB) are GLImpl's (Program/GL_ShaderObjectsARB.cpp). GLhandleARB is GLuint everywhere
// but Apple, where the header makes it a pointer - hence the two conversions.
namespace {
inline GLuint ArbName(GLhandleARB handle) {
#if defined(__APPLE__)
return static_cast<GLuint>(reinterpret_cast<uintptr_t>(handle));
#else
return handle;
#endif
}
inline GLhandleARB ArbHandle(GLuint name) {
#if defined(__APPLE__)
return reinterpret_cast<GLhandleARB>(static_cast<uintptr_t>(name));
#else
return name;
#endif
}
} // namespace
MOBILEGL_GL_API GLhandleARB glCreateShaderObjectARB(GLenum shaderType) { return ArbHandle(glCreateShader(shaderType)); }
MOBILEGL_GL_API GLhandleARB glCreateProgramObjectARB(void) { return ArbHandle(glCreateProgram()); }
MOBILEGL_GL_API void glDeleteObjectARB(GLhandleARB obj) {
const MobileGL::MG_Impl::EGLImpl::GLStreamScope mglStreamScope;
MobileGL::MG_Impl::GLImpl::DeleteObjectARB(ArbName(obj));
}
MOBILEGL_GL_API GLhandleARB glGetHandleARB(GLenum pname) {
const MobileGL::MG_Impl::EGLImpl::GLStreamScope mglStreamScope;
return ArbHandle(MobileGL::MG_Impl::GLImpl::GetHandleARB(pname));
}
MOBILEGL_GL_API void glDetachObjectARB(GLhandleARB containerObj, GLhandleARB attachedObj) {
glDetachShader(ArbName(containerObj), ArbName(attachedObj));
}
MOBILEGL_GL_API void glAttachObjectARB(GLhandleARB containerObj, GLhandleARB obj) {
glAttachShader(ArbName(containerObj), ArbName(obj));
}
MOBILEGL_GL_API void glShaderSourceARB(GLhandleARB shaderObj, GLsizei count, const GLcharARB** string,
const GLint* length) {
glShaderSource(ArbName(shaderObj), count, string, length);
}
MOBILEGL_GL_API void glCompileShaderARB(GLhandleARB shaderObj) { glCompileShader(ArbName(shaderObj)); }
MOBILEGL_GL_API void glLinkProgramARB(GLhandleARB programObj) { glLinkProgram(ArbName(programObj)); }
MOBILEGL_GL_API void glUseProgramObjectARB(GLhandleARB programObj) { glUseProgram(ArbName(programObj)); }
MOBILEGL_GL_API void glValidateProgramARB(GLhandleARB programObj) { glValidateProgram(ArbName(programObj)); }
MOBILEGL_GL_API void glGetObjectParameterfvARB(GLhandleARB obj, GLenum pname, GLfloat* params) {
const MobileGL::MG_Impl::EGLImpl::GLStreamScope mglStreamScope;
MobileGL::MG_Impl::GLImpl::GetObjectParameterfvARB(ArbName(obj), pname, params);
}
MOBILEGL_GL_API void glGetObjectParameterivARB(GLhandleARB obj, GLenum pname, GLint* params) {
const MobileGL::MG_Impl::EGLImpl::GLStreamScope mglStreamScope;
MobileGL::MG_Impl::GLImpl::GetObjectParameterivARB(ArbName(obj), pname, params);
}
MOBILEGL_GL_API void glGetInfoLogARB(GLhandleARB obj, GLsizei maxLength, GLsizei* length, GLcharARB* infoLog) {
const MobileGL::MG_Impl::EGLImpl::GLStreamScope mglStreamScope;
MobileGL::MG_Impl::GLImpl::GetInfoLogARB(ArbName(obj), maxLength, length, infoLog);
}
MOBILEGL_GL_API void glGetAttachedObjectsARB(GLhandleARB containerObj, GLsizei maxCount, GLsizei* count,
GLhandleARB* obj) {
#if defined(__APPLE__)
Vector<GLuint> names(maxCount > 0 ? static_cast<SizeT>(maxCount) : 0);
GLsizei written = 0;
glGetAttachedShaders(ArbName(containerObj), maxCount, &written, names.empty() ? nullptr : names.data());
if (obj != nullptr) {
for (GLsizei i = 0; i < written; ++i) obj[i] = ArbHandle(names[static_cast<SizeT>(i)]);
}
if (count != nullptr) *count = written;
#else
glGetAttachedShaders(ArbName(containerObj), maxCount, count, obj);
#endif
}
MOBILEGL_GL_API GLint glGetUniformLocationARB(GLhandleARB programObj, const GLcharARB* name) {
return glGetUniformLocation(ArbName(programObj), name);
}
MOBILEGL_GL_API void glGetActiveUniformARB(GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei* length,
GLint* size, GLenum* type, GLcharARB* name) {
glGetActiveUniform(ArbName(programObj), index, maxLength, length, size, type, name);
}
MOBILEGL_GL_API void glGetUniformfvARB(GLhandleARB programObj, GLint location, GLfloat* params) {
glGetUniformfv(ArbName(programObj), location, params);
}
MOBILEGL_GL_API void glGetUniformivARB(GLhandleARB programObj, GLint location, GLint* params) {
glGetUniformiv(ArbName(programObj), location, params);
}
MOBILEGL_GL_API void glGetShaderSourceARB(GLhandleARB obj, GLsizei maxLength, GLsizei* length, GLcharARB* source) {
glGetShaderSource(ArbName(obj), maxLength, length, source);
}
MOBILEGL_GL_API void glUniform1fARB(GLint location, GLfloat v0) { glUniform1f(location, v0); }
MOBILEGL_GL_API void glUniform2fARB(GLint location, GLfloat v0, GLfloat v1) { glUniform2f(location, v0, v1); }
MOBILEGL_GL_API void glUniform3fARB(GLint location, GLfloat v0, GLfloat v1, GLfloat v2) {
glUniform3f(location, v0, v1, v2);
}
MOBILEGL_GL_API void glUniform4fARB(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3) {
glUniform4f(location, v0, v1, v2, v3);
}
MOBILEGL_GL_API void glUniform1iARB(GLint location, GLint v0) { glUniform1i(location, v0); }
MOBILEGL_GL_API void glUniform2iARB(GLint location, GLint v0, GLint v1) { glUniform2i(location, v0, v1); }
MOBILEGL_GL_API void glUniform3iARB(GLint location, GLint v0, GLint v1, GLint v2) { glUniform3i(location, v0, v1, v2); }
MOBILEGL_GL_API void glUniform4iARB(GLint location, GLint v0, GLint v1, GLint v2, GLint v3) {
glUniform4i(location, v0, v1, v2, v3);
}
MOBILEGL_GL_API void glUniform1fvARB(GLint location, GLsizei count, const GLfloat* value) {
glUniform1fv(location, count, value);
}
MOBILEGL_GL_API void glUniform2fvARB(GLint location, GLsizei count, const GLfloat* value) {
glUniform2fv(location, count, value);
}
MOBILEGL_GL_API void glUniform3fvARB(GLint location, GLsizei count, const GLfloat* value) {
glUniform3fv(location, count, value);
}
MOBILEGL_GL_API void glUniform4fvARB(GLint location, GLsizei count, const GLfloat* value) {
glUniform4fv(location, count, value);
}
MOBILEGL_GL_API void glUniform1ivARB(GLint location, GLsizei count, const GLint* value) {
glUniform1iv(location, count, value);
}
MOBILEGL_GL_API void glUniform2ivARB(GLint location, GLsizei count, const GLint* value) {
glUniform2iv(location, count, value);
}
MOBILEGL_GL_API void glUniform3ivARB(GLint location, GLsizei count, const GLint* value) {
glUniform3iv(location, count, value);
}
MOBILEGL_GL_API void glUniform4ivARB(GLint location, GLsizei count, const GLint* value) {
glUniform4iv(location, count, value);
}
MOBILEGL_GL_API void glUniformMatrix2fvARB(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) {
glUniformMatrix2fv(location, count, transpose, value);
}
MOBILEGL_GL_API void glUniformMatrix3fvARB(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) {
glUniformMatrix3fv(location, count, transpose, value);
}
MOBILEGL_GL_API void glUniformMatrix4fvARB(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) {
glUniformMatrix4fv(location, count, transpose, value);
}
// KHR_robustness and EXT_robustness name the same entry points with their own suffixes (an ES
// application, ANGLE's GL backend among them, looks them up by those names).
MOBILEGL_GL_API GLenum glGetGraphicsResetStatusKHR(void) {
@@ -197,4 +197,12 @@ namespace MobileGL::MG_Impl::GLImpl {
void TransformFeedbackVaryings(GLuint program, GLsizei count, const GLchar* const* varyings, GLenum bufferMode);
void GetTransformFeedbackVarying(GLuint program, GLuint index, GLsizei bufSize, GLsizei* length, GLsizei* size,
GLenum* type, GLchar* name);
// GL_ARB_shader_objects (GL_ShaderObjectsARB.cpp): the entry points that ask which kind of object
// a handle names. The rest of the extension is the core calls under ARB names (Definitions.cpp).
void DeleteObjectARB(GLuint obj);
GLuint GetHandleARB(GLenum pname);
void GetObjectParameterivARB(GLuint obj, GLenum pname, GLint* params);
void GetObjectParameterfvARB(GLuint obj, GLenum pname, GLfloat* params);
void GetInfoLogARB(GLuint obj, GLsizei maxLength, GLsizei* length, GLchar* infoLog);
} // namespace MobileGL::MG_Impl::GLImpl
@@ -0,0 +1,138 @@
// MobileGL - MobileGL/MG_Impl/GLImpl/Program/GL_ShaderObjectsARB.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
// GL_ARB_shader_objects: the entry points whose meaning is not just "the core call under another
// name". A handle IS a shader or program name of the core's one shared shader/program name space
// (GL 4.6 §7.1 / §7.3): glCreateShaderObjectARB and glCreateProgramObjectARB hand out exactly what
// glCreateShader / glCreateProgram would, so every core call takes an ARB handle and every ARB call
// takes a core name. What this file adds is the part of the extension that asks "which kind of
// object is this handle" - deleting, querying and reading the info log of either kind through one
// entry point - and the one query of the current program by handle.
#include "GL_Program.h"
#include "../Getter/GL_Getter.h"
#include <MG_State/GLState/Core.h>
#include <string>
namespace MobileGL::MG_Impl::GLImpl {
namespace {
// The ARB tokens that have no core spelling. The rest of GetObjectParameter's pnames ARE the
// core values (OBJECT_DELETE_STATUS_ARB == GL_DELETE_STATUS ... 0x8B80-0x8B88, and
// ARB_vertex_shader's two attribute counts 0x8B89 / 0x8B8A).
constexpr GLenum kProgramObjectArb = 0x8B40;
constexpr GLenum kShaderObjectArb = 0x8B48;
constexpr GLenum kObjectTypeArb = 0x8B4E;
constexpr GLenum kObjectSubtypeArb = 0x8B4F;
enum class ObjectKind { None, Program, Shader };
ObjectKind KindOf(GLuint obj) {
if (obj == 0) return ObjectKind::None;
if (IsProgram(obj)) return ObjectKind::Program;
if (IsShader(obj)) return ObjectKind::Shader;
return ObjectKind::None;
}
void Fail(ErrorCode code, const char* func, const std::string& what) {
MG_State::pGLContext->RecordError(code, MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", func, what));
}
} // namespace
// DeleteObjectARB deletes whichever kind the handle names, with the core's deferred-deletion
// rules (a program in use, a shader still attached). Zero is ignored, as for glDeleteShader /
// glDeleteProgram; any other name that is not a shader or program object is INVALID_VALUE.
void DeleteObjectARB(GLuint obj) {
if (obj == 0) return;
switch (KindOf(obj)) {
case ObjectKind::Program: DeleteProgram(obj); return;
case ObjectKind::Shader: DeleteShader(obj); return;
case ObjectKind::None: break;
}
Fail(ErrorCode::InvalidValue, __func__, std::to_string(obj) + " is not a shader or program object.");
}
// GetHandleARB(PROGRAM_OBJECT_ARB) is the program object in use - GL_CURRENT_PROGRAM, 0 when
// none. It is the only pname the extension defines.
GLuint GetHandleARB(GLenum pname) {
if (pname != kProgramObjectArb) {
Fail(ErrorCode::InvalidEnum, __func__, "pname must be GL_PROGRAM_OBJECT_ARB.");
return 0;
}
GLint current = 0;
GetIntegerv(GL_CURRENT_PROGRAM, &current);
return static_cast<GLuint>(current);
}
namespace {
// The ARB spec's table of pnames, per kind. An unknown pname is INVALID_ENUM; a known one the
// object's kind does not have (COMPILE_STATUS of a program, LINK_STATUS of a shader, SUBTYPE
// of a program) is INVALID_OPERATION; a handle that names no shader or program is
// INVALID_VALUE. True when `*out` holds the answer (every pname here is a single value).
bool QueryObjectParameter(GLuint obj, GLenum pname, GLint* out, const char* func) {
bool programParam = false, shaderParam = false;
switch (pname) {
case kObjectTypeArb:
case GL_DELETE_STATUS:
case GL_INFO_LOG_LENGTH: programParam = shaderParam = true; break;
case kObjectSubtypeArb:
case GL_COMPILE_STATUS:
case GL_SHADER_SOURCE_LENGTH: shaderParam = true; break;
case GL_LINK_STATUS:
case GL_VALIDATE_STATUS:
case GL_ATTACHED_SHADERS:
case GL_ACTIVE_UNIFORMS:
case GL_ACTIVE_UNIFORM_MAX_LENGTH:
case GL_ACTIVE_ATTRIBUTES:
case GL_ACTIVE_ATTRIBUTE_MAX_LENGTH: programParam = true; break;
default: Fail(ErrorCode::InvalidEnum, func, "pname is not a GetObjectParameterARB parameter."); return false;
}
const ObjectKind kind = KindOf(obj);
if (kind == ObjectKind::None) {
Fail(ErrorCode::InvalidValue, func, std::to_string(obj) + " is not a shader or program object.");
return false;
}
if ((kind == ObjectKind::Program && !programParam) || (kind == ObjectKind::Shader && !shaderParam)) {
Fail(ErrorCode::InvalidOperation, func, "pname is not a parameter of this kind of object.");
return false;
}
if (pname == kObjectTypeArb) {
*out = static_cast<GLint>(kind == ObjectKind::Program ? kProgramObjectArb : kShaderObjectArb);
} else if (pname == kObjectSubtypeArb) {
GetShaderiv(obj, GL_SHADER_TYPE, out);
} else if (kind == ObjectKind::Program) {
GetProgramiv(obj, pname, out);
} else {
GetShaderiv(obj, pname, out);
}
return true;
}
} // namespace
void GetObjectParameterivARB(GLuint obj, GLenum pname, GLint* params) {
GLint value = 0;
if (QueryObjectParameter(obj, pname, &value, __func__) && params != nullptr) *params = value;
}
void GetObjectParameterfvARB(GLuint obj, GLenum pname, GLfloat* params) {
GLint value = 0;
if (QueryObjectParameter(obj, pname, &value, __func__) && params != nullptr) *params = static_cast<GLfloat>(value);
}
void GetInfoLogARB(GLuint obj, GLsizei maxLength, GLsizei* length, GLchar* infoLog) {
switch (KindOf(obj)) {
case ObjectKind::Program: GetProgramInfoLog(obj, maxLength, length, infoLog); return;
case ObjectKind::Shader: GetShaderInfoLog(obj, maxLength, length, infoLog); return;
case ObjectKind::None: break;
}
Fail(ErrorCode::InvalidValue, __func__, std::to_string(obj) + " is not a shader or program object.");
}
} // namespace MobileGL::MG_Impl::GLImpl
+40
View File
@@ -824,6 +824,46 @@ namespace MobileGL::MG_Impl {
GETPROC(glMultiDrawArraysIndirectCount, name);
GETPROC(glMultiDrawElementsIndirectCount, name);
GETPROC(glPolygonOffsetClamp, name);
// GL_ARB_shader_objects (Definitions.cpp): advertised, so every entry point resolves.
GETPROC(glDeleteObjectARB, name);
GETPROC(glGetHandleARB, name);
GETPROC(glDetachObjectARB, name);
GETPROC(glCreateShaderObjectARB, name);
GETPROC(glShaderSourceARB, name);
GETPROC(glCompileShaderARB, name);
GETPROC(glCreateProgramObjectARB, name);
GETPROC(glAttachObjectARB, name);
GETPROC(glLinkProgramARB, name);
GETPROC(glUseProgramObjectARB, name);
GETPROC(glValidateProgramARB, name);
GETPROC(glUniform1fARB, name);
GETPROC(glUniform2fARB, name);
GETPROC(glUniform3fARB, name);
GETPROC(glUniform4fARB, name);
GETPROC(glUniform1iARB, name);
GETPROC(glUniform2iARB, name);
GETPROC(glUniform3iARB, name);
GETPROC(glUniform4iARB, name);
GETPROC(glUniform1fvARB, name);
GETPROC(glUniform2fvARB, name);
GETPROC(glUniform3fvARB, name);
GETPROC(glUniform4fvARB, name);
GETPROC(glUniform1ivARB, name);
GETPROC(glUniform2ivARB, name);
GETPROC(glUniform3ivARB, name);
GETPROC(glUniform4ivARB, name);
GETPROC(glUniformMatrix2fvARB, name);
GETPROC(glUniformMatrix3fvARB, name);
GETPROC(glUniformMatrix4fvARB, name);
GETPROC(glGetObjectParameterfvARB, name);
GETPROC(glGetObjectParameterivARB, name);
GETPROC(glGetInfoLogARB, name);
GETPROC(glGetAttachedObjectsARB, name);
GETPROC(glGetUniformLocationARB, name);
GETPROC(glGetActiveUniformARB, name);
GETPROC(glGetUniformfvARB, name);
GETPROC(glGetUniformivARB, name);
GETPROC(glGetShaderSourceARB, name);
GETPROC(glPrimitiveBoundingBoxARB, name);
GETPROC(glGetTextureHandleARB, name);
GETPROC(glGetTextureSamplerHandleARB, name);
@@ -71,6 +71,7 @@ add_executable(MobileGLIntegrationTest
Scenarios/SyncWireScenario.cpp
Scenarios/DrawParametersScenario.cpp
Scenarios/AsyncCompileScenario.cpp
Scenarios/ArbShaderObjectsScenario.cpp
Scenarios/XfbAfterClipDistanceScenario.cpp
Scenarios/UnwrittenPositionOutputScenario.cpp
Scenarios/SampleMaskScopeScenario.cpp
@@ -2466,6 +2467,9 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
endmacro()
mgl_itest_register_split_arms("ClearThenReadPixelsScenario.*" "")
# GL_ARB_shader_objects (P13): ARB-created shader / program objects travel the record arm like
# core-created ones, so the scenario runs on every split arm as well as both monoliths.
mgl_itest_register_split_arms("ArbShaderObjectsScenario.*" "")
mgl_itest_register_split_arms("TriangleScenario.*" "")
mgl_itest_register_split_arms("PersistentCoherentMapScenario.*" "")
# P11 A2 (MG_Remote/CONTRACT-P11.md §2): the >= 16 MiB arena on all three arms. Its tier case
@@ -0,0 +1,466 @@
// MobileGL - MobileGL/MG_IntegrationTest/Scenarios/ArbShaderObjectsScenario.cpp
// Copyright (c) 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
//
// Scenario - GL_ARB_shader_objects, which MobileGL advertises (KWin asks for it) and whose entry
// points were stubs until P13: glCreateShaderObjectARB / glCreateProgramObjectARB returned 1.
//
// WHAT IS PINNED:
// * an ARB handle is a name of the core's ONE shared shader/program name space (GL 4.6 §7.1): it
// never collides with a core-created name, and core and ARB calls take each other's names;
// * a program built entirely through ARB calls - mixed with core calls on the same objects -
// links, is made current, takes a uniform and draws the right pixels;
// * the extension's own queries: GetHandleARB(PROGRAM_OBJECT_ARB) is the current program,
// GetObjectParameter*ARB answers OBJECT_TYPE / OBJECT_SUBTYPE and the core status pnames per
// kind, GetInfoLogARB / GetAttachedObjectsARB read either kind, and DeleteObjectARB deletes
// either kind with the core's deferred-deletion rules;
// * the errors the extension names (INVALID_ENUM / INVALID_OPERATION / INVALID_VALUE);
// * every advertised entry point resolves through eglGetProcAddress (glvnd and libepoxy find
// them that way, not by symbol);
// * and, because shader/program names share one space while every glGen* family has its own,
// the per-type glGen* name spaces: a generated name is reserved but is no object until its
// first bind (where the spec says so), name 0 is never handed out, and a deleted name is
// free again.
//
// ARMS: both backends, monolith and every split arm (the record arm carries shader and program
// objects by {slot, gen} whichever entry point created them).
#include <cstdint>
#include <set>
#include <string>
#include <vector>
#include "../Harness/HeadlessGL.h"
#include "../Harness/ScenarioFixture.h"
#ifdef GLAPI
#undef GLAPI
#endif
#define GL_GLEXT_PROTOTYPES
#include <GL/gl.h>
#include <GL/glcorearb.h>
#undef GL_GLEXT_PROTOTYPES
#include <EGL/egl.h>
// The extension's own tokens, spelled out: glcorearb.h has none of them.
#define MGITEST_PROGRAM_OBJECT_ARB 0x8B40
#define MGITEST_SHADER_OBJECT_ARB 0x8B48
#define MGITEST_OBJECT_TYPE_ARB 0x8B4E
#define MGITEST_OBJECT_SUBTYPE_ARB 0x8B4F
// GLhandleARB is GLuint everywhere but Apple, which this module does not build for. Resolved by
// the linker straight into MobileGL, like every other gl* call here.
extern "C" {
GLuint glCreateShaderObjectARB(GLenum shaderType);
GLuint glCreateProgramObjectARB(void);
void glDeleteObjectARB(GLuint obj);
GLuint glGetHandleARB(GLenum pname);
void glDetachObjectARB(GLuint containerObj, GLuint attachedObj);
void glAttachObjectARB(GLuint containerObj, GLuint obj);
void glShaderSourceARB(GLuint shaderObj, GLsizei count, const char** string, const GLint* length);
void glCompileShaderARB(GLuint shaderObj);
void glLinkProgramARB(GLuint programObj);
void glUseProgramObjectARB(GLuint programObj);
void glValidateProgramARB(GLuint programObj);
void glUniform4fARB(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3);
void glGetObjectParameterivARB(GLuint obj, GLenum pname, GLint* params);
void glGetObjectParameterfvARB(GLuint obj, GLenum pname, GLfloat* params);
void glGetInfoLogARB(GLuint obj, GLsizei maxLength, GLsizei* length, char* infoLog);
void glGetAttachedObjectsARB(GLuint containerObj, GLsizei maxCount, GLsizei* count, GLuint* obj);
GLint glGetUniformLocationARB(GLuint programObj, const char* name);
void glGetUniformfvARB(GLuint programObj, GLint location, GLfloat* params);
void glGetShaderSourceARB(GLuint obj, GLsizei maxLength, GLsizei* length, char* source);
}
namespace MGITest {
namespace {
// A full-viewport triangle from gl_VertexID: no attribute, so the draw depends on nothing
// but the program and its uniform.
constexpr const char* kVertexSource = R"(#version 330 core
void main() {
vec2 p = vec2(float((gl_VertexID & 1) << 2) - 1.0, float((gl_VertexID & 2) << 1) - 1.0);
gl_Position = vec4(p, 0.0, 1.0);
}
)";
constexpr const char* kFragmentSource = R"(#version 330 core
uniform vec4 uColor;
out vec4 oColor;
void main() { oColor = uColor; }
)";
constexpr int kEdge = 16;
class ArbShaderObjectsScenario : public ScenarioTest {
protected:
void SetUp() override {
ScenarioTest::SetUp();
if (!Ready()) return;
m_target = MakeColorFbo(kEdge, kEdge);
ASSERT_NE(m_target.fbo, 0u) << "could not create the render target";
glGenVertexArrays(1, &m_vao);
FirstGLError();
}
void TearDown() override {
if (Ready()) {
glUseProgram(0);
if (m_vao != 0) glDeleteVertexArrays(1, &m_vao);
DestroyColorFbo(m_target);
}
ScenarioTest::TearDown();
}
// A shader built entirely through the extension, its compile status read both ways.
GLuint ArbShader(GLenum type, const char* source) {
const GLuint shader = glCreateShaderObjectARB(type);
EXPECT_NE(shader, 0u);
glShaderSourceARB(shader, 1, &source, nullptr);
glCompileShaderARB(shader);
GLint arbStatus = 0, coreStatus = 0;
glGetObjectParameterivARB(shader, GL_COMPILE_STATUS, &arbStatus);
glGetShaderiv(shader, GL_COMPILE_STATUS, &coreStatus);
if (arbStatus != GL_TRUE) {
char log[1024] = {};
glGetInfoLogARB(shader, sizeof(log), nullptr, log);
ADD_FAILURE() << "ARB compile failed: " << log;
}
EXPECT_EQ(arbStatus, coreStatus) << "the ARB and core status queries disagree";
return shader;
}
void DrawInto() {
BindFbo(m_target);
ClearTo(0.0f, 0.0f, 0.0f, 1.0f);
glBindVertexArray(m_vao);
glDrawArrays(GL_TRIANGLES, 0, 3);
}
ColorFbo m_target;
GLuint m_vao = 0;
};
TEST_F(ArbShaderObjectsScenario, EveryEntryPointResolvesThroughGetProcAddress) {
if (!Ready()) return;
const char* names[] = {
"glDeleteObjectARB", "glGetHandleARB", "glDetachObjectARB", "glCreateShaderObjectARB",
"glShaderSourceARB", "glCompileShaderARB", "glCreateProgramObjectARB", "glAttachObjectARB",
"glLinkProgramARB", "glUseProgramObjectARB", "glValidateProgramARB", "glUniform1fARB",
"glUniform2fARB", "glUniform3fARB", "glUniform4fARB", "glUniform1iARB", "glUniform2iARB",
"glUniform3iARB", "glUniform4iARB", "glUniform1fvARB", "glUniform2fvARB", "glUniform3fvARB",
"glUniform4fvARB", "glUniform1ivARB", "glUniform2ivARB", "glUniform3ivARB", "glUniform4ivARB",
"glUniformMatrix2fvARB", "glUniformMatrix3fvARB", "glUniformMatrix4fvARB",
"glGetObjectParameterfvARB", "glGetObjectParameterivARB", "glGetInfoLogARB",
"glGetAttachedObjectsARB", "glGetUniformLocationARB", "glGetActiveUniformARB",
"glGetUniformfvARB", "glGetUniformivARB", "glGetShaderSourceARB"};
for (const char* name : names) {
EXPECT_NE(reinterpret_cast<void*>(eglGetProcAddress(name)), nullptr) << name << " does not resolve";
}
EXPECT_EQ(reinterpret_cast<void*>(eglGetProcAddress("glCreateShaderObjectARB")),
reinterpret_cast<void*>(&glCreateShaderObjectARB))
<< "eglGetProcAddress hands out a different glCreateShaderObjectARB than the library exports";
}
// One name space for shaders and programs, whichever entry point created them; core and
// ARB calls take each other's names, and OBJECT_TYPE / OBJECT_SUBTYPE say which kind.
TEST_F(ArbShaderObjectsScenario, ArbAndCoreHandlesShareOneNameSpace) {
if (!Ready()) return;
const GLuint coreShader = glCreateShader(GL_FRAGMENT_SHADER);
const GLuint arbShader = glCreateShaderObjectARB(GL_VERTEX_SHADER);
const GLuint coreProgram = glCreateProgram();
const GLuint arbProgram = glCreateProgramObjectARB();
ASSERT_EQ(FirstGLError(), 0u);
const std::set<GLuint> names{coreShader, arbShader, coreProgram, arbProgram};
EXPECT_EQ(names.size(), 4u) << "an ARB handle collided with a core-created name";
EXPECT_EQ(names.count(0u), 0u) << "a create handed out name 0";
EXPECT_EQ(glIsShader(arbShader), GL_TRUE);
EXPECT_EQ(glIsProgram(arbProgram), GL_TRUE);
EXPECT_EQ(glIsProgram(arbShader), GL_FALSE);
EXPECT_EQ(glIsShader(arbProgram), GL_FALSE);
GLint value = 0;
glGetShaderiv(arbShader, GL_SHADER_TYPE, &value);
EXPECT_EQ(value, GL_VERTEX_SHADER);
glGetObjectParameterivARB(coreShader, MGITEST_OBJECT_SUBTYPE_ARB, &value);
EXPECT_EQ(value, GL_FRAGMENT_SHADER);
glGetObjectParameterivARB(coreShader, MGITEST_OBJECT_TYPE_ARB, &value);
EXPECT_EQ(value, MGITEST_SHADER_OBJECT_ARB);
glGetObjectParameterivARB(arbProgram, MGITEST_OBJECT_TYPE_ARB, &value);
EXPECT_EQ(value, MGITEST_PROGRAM_OBJECT_ARB);
GLfloat asFloat = 0.0f;
glGetObjectParameterfvARB(arbShader, MGITEST_OBJECT_SUBTYPE_ARB, &asFloat);
EXPECT_EQ(asFloat, static_cast<GLfloat>(GL_VERTEX_SHADER));
EXPECT_EQ(FirstGLError(), 0u);
// Attachments cross the two spellings too.
glAttachShader(arbProgram, coreShader);
glAttachObjectARB(coreProgram, arbShader);
GLuint attached[4] = {};
GLsizei count = 0;
glGetAttachedObjectsARB(arbProgram, 4, &count, attached);
ASSERT_EQ(count, 1);
EXPECT_EQ(attached[0], coreShader);
glGetAttachedShaders(coreProgram, 4, &count, attached);
ASSERT_EQ(count, 1);
EXPECT_EQ(attached[0], arbShader);
glGetObjectParameterivARB(coreProgram, GL_ATTACHED_SHADERS, &value);
EXPECT_EQ(value, 1);
glDetachObjectARB(arbProgram, coreShader);
glGetProgramiv(arbProgram, GL_ATTACHED_SHADERS, &value);
EXPECT_EQ(value, 0);
EXPECT_EQ(FirstGLError(), 0u);
glDeleteObjectARB(coreShader);
glDeleteShader(arbShader); // attached to coreProgram: flagged, not yet gone
glDeleteObjectARB(arbProgram);
glDeleteProgram(coreProgram); // takes arbShader with it
EXPECT_EQ(glIsShader(coreShader), GL_FALSE);
EXPECT_EQ(glIsProgram(arbProgram), GL_FALSE);
EXPECT_EQ(glIsProgram(coreProgram), GL_FALSE);
EXPECT_EQ(glIsShader(arbShader), GL_FALSE);
EXPECT_EQ(FirstGLError(), 0u);
}
// A program built through the extension, mixed with core calls on the same objects, draws.
TEST_F(ArbShaderObjectsScenario, AnArbProgramMixedWithCoreCallsDraws) {
if (!Ready()) return;
const GLuint vs = ArbShader(GL_VERTEX_SHADER, kVertexSource);
const GLuint fs = ArbShader(GL_FRAGMENT_SHADER, kFragmentSource);
const GLuint program = glCreateProgramObjectARB();
glAttachObjectARB(program, vs);
glAttachShader(program, fs); // core call on an ARB handle
glLinkProgramARB(program);
GLint linked = 0;
glGetProgramiv(program, GL_LINK_STATUS, &linked);
if (linked != GL_TRUE) {
char log[1024] = {};
glGetInfoLogARB(program, sizeof(log), nullptr, log);
FAIL() << "ARB link failed: " << log;
}
GLint arbLinked = 0;
glGetObjectParameterivARB(program, GL_LINK_STATUS, &arbLinked);
EXPECT_EQ(arbLinked, GL_TRUE);
glValidateProgramARB(program);
glUseProgramObjectARB(program);
EXPECT_EQ(glGetHandleARB(MGITEST_PROGRAM_OBJECT_ARB), program);
GLint current = 0;
glGetIntegerv(GL_CURRENT_PROGRAM, &current);
EXPECT_EQ(static_cast<GLuint>(current), program);
const GLint arbLocation = glGetUniformLocationARB(program, "uColor");
EXPECT_EQ(arbLocation, glGetUniformLocation(program, "uColor"));
ASSERT_GE(arbLocation, 0);
glUniform4fARB(arbLocation, 0.0f, 1.0f, 0.0f, 1.0f);
GLfloat readBack[4] = {};
glGetUniformfvARB(program, arbLocation, readBack);
EXPECT_EQ(readBack[1], 1.0f);
ASSERT_EQ(FirstGLError(), 0u);
DrawInto();
const Image green = ReadPixels(kEdge, kEdge);
EXPECT_TRUE(RegionIsMostly(green, 0, kEdge - 1, 0, kEdge - 1, "green", 0.0, "ARB program, ARB uniform"));
// The same program through the core spelling of the uniform, then released by the core
// call and observed through the ARB query.
glUniform4f(glGetUniformLocation(program, "uColor"), 0.0f, 0.0f, 1.0f, 1.0f);
DrawInto();
const Image blue = ReadPixels(kEdge, kEdge);
EXPECT_TRUE(RegionIsMostly(blue, 0, kEdge - 1, 0, kEdge - 1, "blue", 0.0, "core uniform on an ARB program"));
glUseProgram(0);
EXPECT_EQ(glGetHandleARB(MGITEST_PROGRAM_OBJECT_ARB), 0u);
char source[512] = {};
GLsizei sourceLength = 0;
glGetShaderSourceARB(fs, sizeof(source), &sourceLength, source);
EXPECT_EQ(std::string(source), std::string(kFragmentSource));
glDeleteObjectARB(vs);
glDeleteObjectARB(fs);
glDeleteObjectARB(program);
EXPECT_EQ(FirstGLError(), 0u);
}
// DeleteObjectARB follows the core's deferred deletion: a program in use and a shader still
// attached are flagged (DELETE_STATUS) and stay objects until released.
TEST_F(ArbShaderObjectsScenario, DeleteObjectArbDefersLikeTheCore) {
if (!Ready()) return;
const GLuint vs = ArbShader(GL_VERTEX_SHADER, kVertexSource);
const GLuint fs = ArbShader(GL_FRAGMENT_SHADER, kFragmentSource);
const GLuint program = glCreateProgramObjectARB();
glAttachObjectARB(program, vs);
glAttachObjectARB(program, fs);
glLinkProgramARB(program);
glUseProgramObjectARB(program);
glDeleteObjectARB(program);
glDeleteObjectARB(vs);
GLint status = 0;
EXPECT_EQ(glIsProgram(program), GL_TRUE) << "a program in use was deleted at once";
glGetObjectParameterivARB(program, GL_DELETE_STATUS, &status);
EXPECT_EQ(status, GL_TRUE);
EXPECT_EQ(glIsShader(vs), GL_TRUE) << "an attached shader was deleted at once";
glGetObjectParameterivARB(vs, GL_DELETE_STATUS, &status);
EXPECT_EQ(status, GL_TRUE);
EXPECT_EQ(FirstGLError(), 0u);
glUseProgramObjectARB(0);
EXPECT_EQ(glIsProgram(program), GL_FALSE) << "releasing the flagged program did not delete it";
EXPECT_EQ(glIsShader(vs), GL_FALSE) << "deleting the program did not take its flagged shader";
EXPECT_EQ(glIsShader(fs), GL_TRUE);
glDeleteObjectARB(fs);
EXPECT_EQ(glIsShader(fs), GL_FALSE);
EXPECT_EQ(FirstGLError(), 0u);
}
TEST_F(ArbShaderObjectsScenario, TheExtensionsErrorsAreTheSpecs) {
if (!Ready()) return;
const GLuint shader = glCreateShaderObjectARB(GL_FRAGMENT_SHADER);
const GLuint program = glCreateProgramObjectARB();
GLint value = -7;
EXPECT_EQ(glGetHandleARB(GL_SHADER_TYPE), 0u);
EXPECT_EQ(FirstGLError(), static_cast<unsigned>(GL_INVALID_ENUM)) << "GetHandleARB(bad pname)";
glGetObjectParameterivARB(program, GL_COMPILE_STATUS, &value);
EXPECT_EQ(FirstGLError(), static_cast<unsigned>(GL_INVALID_OPERATION)) << "COMPILE_STATUS of a program";
glGetObjectParameterivARB(shader, GL_LINK_STATUS, &value);
EXPECT_EQ(FirstGLError(), static_cast<unsigned>(GL_INVALID_OPERATION)) << "LINK_STATUS of a shader";
glGetObjectParameterivARB(program, MGITEST_OBJECT_SUBTYPE_ARB, &value);
EXPECT_EQ(FirstGLError(), static_cast<unsigned>(GL_INVALID_OPERATION)) << "OBJECT_SUBTYPE of a program";
glGetObjectParameterivARB(program, GL_TEXTURE_2D, &value);
EXPECT_EQ(FirstGLError(), static_cast<unsigned>(GL_INVALID_ENUM)) << "an unknown pname";
EXPECT_EQ(value, -7) << "a refused query wrote its output";
const GLuint stranger = shader + program + 1000u;
glGetObjectParameterivARB(stranger, MGITEST_OBJECT_TYPE_ARB, &value);
EXPECT_EQ(FirstGLError(), static_cast<unsigned>(GL_INVALID_VALUE)) << "a name that is no object";
glDeleteObjectARB(stranger);
EXPECT_EQ(FirstGLError(), static_cast<unsigned>(GL_INVALID_VALUE)) << "DeleteObjectARB(no object)";
glDeleteObjectARB(0);
EXPECT_EQ(FirstGLError(), 0u) << "DeleteObjectARB(0) is ignored";
char log[8] = {};
glGetInfoLogARB(stranger, sizeof(log), nullptr, log);
EXPECT_EQ(FirstGLError(), static_cast<unsigned>(GL_INVALID_VALUE)) << "GetInfoLogARB(no object)";
glDeleteObjectARB(shader);
glDeleteObjectARB(program);
EXPECT_EQ(FirstGLError(), 0u);
}
// Names freed by DeleteObjectARB may come back from either create, and never while live.
TEST_F(ArbShaderObjectsScenario, FreedNamesAreReusedWithoutCollision) {
if (!Ready()) return;
std::set<GLuint> live;
std::vector<GLuint> freed;
for (int round = 0; round < 4; ++round) {
for (int i = 0; i < 8; ++i) {
const GLuint names[4] = {glCreateShaderObjectARB(GL_VERTEX_SHADER), glCreateShader(GL_FRAGMENT_SHADER),
glCreateProgramObjectARB(), glCreateProgram()};
for (GLuint name : names) {
EXPECT_NE(name, 0u);
EXPECT_TRUE(live.insert(name).second) << "name " << name << " handed out while live";
}
}
// Free half of them, through both spellings.
int k = 0;
for (auto it = live.begin(); it != live.end();) {
if ((k++ & 1) == 0) {
if (k & 2) {
glDeleteObjectARB(*it);
} else if (glIsProgram(*it)) {
glDeleteProgram(*it);
} else {
glDeleteShader(*it);
}
freed.push_back(*it);
it = live.erase(it);
} else {
++it;
}
}
}
for (GLuint name : live) EXPECT_TRUE(glIsShader(name) || glIsProgram(name)) << name;
for (GLuint name : live) glDeleteObjectARB(name);
EXPECT_EQ(FirstGLError(), 0u);
}
// THE OTHER NAME SPACES, which the extension does not touch: each glGen* family has its own,
// a generated name is reserved but is not an object until its first bind (samplers are the
// exception - glGenSamplers creates them), name 0 is never generated, and a deleted name is
// no object any more.
TEST_F(ArbShaderObjectsScenario, GenNameSpacesAreSeparateAndBindCreates) {
if (!Ready()) return;
const GLuint shader = glCreateShader(GL_VERTEX_SHADER);
GLuint buffer = 0, texture = 0, vao = 0, fbo = 0, rbo = 0, sampler = 0, query = 0, pipeline = 0, xfb = 0;
glGenBuffers(1, &buffer);
glGenTextures(1, &texture);
glGenVertexArrays(1, &vao);
glGenFramebuffers(1, &fbo);
glGenRenderbuffers(1, &rbo);
glGenSamplers(1, &sampler);
glGenQueries(1, &query);
glGenProgramPipelines(1, &pipeline);
glGenTransformFeedbacks(1, &xfb);
ASSERT_EQ(FirstGLError(), 0u);
for (GLuint name : {buffer, texture, vao, fbo, rbo, sampler, query, pipeline, xfb}) EXPECT_NE(name, 0u);
// Reserved, not yet objects.
EXPECT_EQ(glIsBuffer(buffer), GL_FALSE);
EXPECT_EQ(glIsTexture(texture), GL_FALSE);
EXPECT_EQ(glIsVertexArray(vao), GL_FALSE);
EXPECT_EQ(glIsFramebuffer(fbo), GL_FALSE);
EXPECT_EQ(glIsRenderbuffer(rbo), GL_FALSE);
EXPECT_EQ(glIsSampler(sampler), GL_TRUE) << "glGenSamplers creates its objects";
EXPECT_EQ(glIsQuery(query), GL_FALSE);
EXPECT_EQ(glIsProgramPipeline(pipeline), GL_FALSE);
EXPECT_EQ(glIsTransformFeedback(xfb), GL_FALSE);
// The first bind creates.
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBindTexture(GL_TEXTURE_2D, texture);
glBindVertexArray(vao);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
glBindProgramPipeline(pipeline);
glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, xfb);
glBeginQuery(GL_SAMPLES_PASSED, query);
glEndQuery(GL_SAMPLES_PASSED);
ASSERT_EQ(FirstGLError(), 0u);
EXPECT_EQ(glIsBuffer(buffer), GL_TRUE);
EXPECT_EQ(glIsTexture(texture), GL_TRUE);
EXPECT_EQ(glIsVertexArray(vao), GL_TRUE);
EXPECT_EQ(glIsFramebuffer(fbo), GL_TRUE);
EXPECT_EQ(glIsRenderbuffer(rbo), GL_TRUE);
EXPECT_EQ(glIsQuery(query), GL_TRUE);
EXPECT_EQ(glIsProgramPipeline(pipeline), GL_TRUE);
EXPECT_EQ(glIsTransformFeedback(xfb), GL_TRUE);
// Separate spaces: being a buffer says nothing about being a shader, and the reverse.
EXPECT_EQ(glIsShader(buffer), buffer == shader ? GL_TRUE : GL_FALSE);
EXPECT_EQ(glIsBuffer(shader), shader == buffer ? GL_TRUE : GL_FALSE);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindTexture(GL_TEXTURE_2D, 0);
glBindVertexArray(0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
glBindProgramPipeline(0);
glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, 0);
glDeleteBuffers(1, &buffer);
glDeleteTextures(1, &texture);
glDeleteVertexArrays(1, &vao);
glDeleteFramebuffers(1, &fbo);
glDeleteRenderbuffers(1, &rbo);
glDeleteSamplers(1, &sampler);
glDeleteQueries(1, &query);
glDeleteProgramPipelines(1, &pipeline);
glDeleteTransformFeedbacks(1, &xfb);
ASSERT_EQ(FirstGLError(), 0u);
EXPECT_EQ(glIsBuffer(buffer), GL_FALSE);
EXPECT_EQ(glIsTexture(texture), GL_FALSE);
EXPECT_EQ(glIsVertexArray(vao), GL_FALSE);
EXPECT_EQ(glIsFramebuffer(fbo), GL_FALSE);
EXPECT_EQ(glIsRenderbuffer(rbo), GL_FALSE);
EXPECT_EQ(glIsSampler(sampler), GL_FALSE);
EXPECT_EQ(glIsQuery(query), GL_FALSE);
EXPECT_EQ(glIsProgramPipeline(pipeline), GL_FALSE);
EXPECT_EQ(glIsTransformFeedback(xfb), GL_FALSE);
EXPECT_EQ(glIsShader(shader), GL_TRUE) << "deleting other kinds' names touched a shader";
glDeleteShader(shader);
EXPECT_EQ(FirstGLError(), 0u);
}
} // namespace
} // namespace MGITest