From c3af0719f0e52e192f6015a1b604c116611d9937 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Tue, 6 Oct 2026 10:11:19 -0400 Subject: [PATCH] [Feat] (GL): implement GL_ARB_shader_objects over the core's shared shader / program name space. --- CMakeLists.txt | 1 + .../MG_Impl/GLImpl/Exporting/Definitions.cpp | 170 ++++++- MobileGL/MG_Impl/GLImpl/Program/GL_Program.h | 8 + .../GLImpl/Program/GL_ShaderObjectsARB.cpp | 138 ++++++ MobileGL/MG_Impl/GetProcAddress.cpp | 40 ++ MobileGL/MG_IntegrationTest/CMakeLists.txt | 4 + .../Scenarios/ArbShaderObjectsScenario.cpp | 466 ++++++++++++++++++ 7 files changed, 816 insertions(+), 11 deletions(-) create mode 100644 MobileGL/MG_Impl/GLImpl/Program/GL_ShaderObjectsARB.cpp create mode 100644 MobileGL/MG_IntegrationTest/Scenarios/ArbShaderObjectsScenario.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 0c940015..33876138 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp b/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp index a7bd8146..a177f46d 100644 --- a/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp +++ b/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp @@ -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(reinterpret_cast(handle)); +#else + return handle; +#endif + } + inline GLhandleARB ArbHandle(GLuint name) { +#if defined(__APPLE__) + return reinterpret_cast(static_cast(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 names(maxCount > 0 ? static_cast(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(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) { diff --git a/MobileGL/MG_Impl/GLImpl/Program/GL_Program.h b/MobileGL/MG_Impl/GLImpl/Program/GL_Program.h index 83414f05..461745ee 100644 --- a/MobileGL/MG_Impl/GLImpl/Program/GL_Program.h +++ b/MobileGL/MG_Impl/GLImpl/Program/GL_Program.h @@ -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 diff --git a/MobileGL/MG_Impl/GLImpl/Program/GL_ShaderObjectsARB.cpp b/MobileGL/MG_Impl/GLImpl/Program/GL_ShaderObjectsARB.cpp new file mode 100644 index 00000000..742d9e20 --- /dev/null +++ b/MobileGL/MG_Impl/GLImpl/Program/GL_ShaderObjectsARB.cpp @@ -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 + +#include + +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("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, ¤t); + return static_cast(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(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(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 diff --git a/MobileGL/MG_Impl/GetProcAddress.cpp b/MobileGL/MG_Impl/GetProcAddress.cpp index 6073cf26..1046ae42 100644 --- a/MobileGL/MG_Impl/GetProcAddress.cpp +++ b/MobileGL/MG_Impl/GetProcAddress.cpp @@ -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); diff --git a/MobileGL/MG_IntegrationTest/CMakeLists.txt b/MobileGL/MG_IntegrationTest/CMakeLists.txt index 9615b629..83642f12 100644 --- a/MobileGL/MG_IntegrationTest/CMakeLists.txt +++ b/MobileGL/MG_IntegrationTest/CMakeLists.txt @@ -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 diff --git a/MobileGL/MG_IntegrationTest/Scenarios/ArbShaderObjectsScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/ArbShaderObjectsScenario.cpp new file mode 100644 index 00000000..86f3d4dc --- /dev/null +++ b/MobileGL/MG_IntegrationTest/Scenarios/ArbShaderObjectsScenario.cpp @@ -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 +#include +#include +#include + +#include "../Harness/HeadlessGL.h" +#include "../Harness/ScenarioFixture.h" + +#ifdef GLAPI +#undef GLAPI +#endif +#define GL_GLEXT_PROTOTYPES +#include +#include +#undef GL_GLEXT_PROTOTYPES + +#include + +// 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(eglGetProcAddress(name)), nullptr) << name << " does not resolve"; + } + EXPECT_EQ(reinterpret_cast(eglGetProcAddress("glCreateShaderObjectARB")), + reinterpret_cast(&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 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(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, ¤t); + EXPECT_EQ(static_cast(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(GL_INVALID_ENUM)) << "GetHandleARB(bad pname)"; + glGetObjectParameterivARB(program, GL_COMPILE_STATUS, &value); + EXPECT_EQ(FirstGLError(), static_cast(GL_INVALID_OPERATION)) << "COMPILE_STATUS of a program"; + glGetObjectParameterivARB(shader, GL_LINK_STATUS, &value); + EXPECT_EQ(FirstGLError(), static_cast(GL_INVALID_OPERATION)) << "LINK_STATUS of a shader"; + glGetObjectParameterivARB(program, MGITEST_OBJECT_SUBTYPE_ARB, &value); + EXPECT_EQ(FirstGLError(), static_cast(GL_INVALID_OPERATION)) << "OBJECT_SUBTYPE of a program"; + glGetObjectParameterivARB(program, GL_TEXTURE_2D, &value); + EXPECT_EQ(FirstGLError(), static_cast(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(GL_INVALID_VALUE)) << "a name that is no object"; + glDeleteObjectARB(stranger); + EXPECT_EQ(FirstGLError(), static_cast(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(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 live; + std::vector 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