fix(gl): gate fallible constructors on a complete loader table
- loader: add available(), true only after load() has resolved every required entry point (false at the start of load()). Program::from_source and the other fallible constructors return ErrorCode::Unsupported while it is false, so a failed load() with a partially populated table can no longer fault on an unresolved entry point. - caps: GL_CONTEXT_PROFILE_MASK only exists from 3.2, so query it only on 3.2+ contexts. A 3.0/3.1 query latches GL_INVALID_ENUM in the context, which the next unrelated glGetError() (e.g. read_pixel) then reports as failure. - tests: gate the dlsym/libGL.so.1 fallback assertions on libGL being resolvable, so the GL suite also passes on a GLES-only host.
This commit is contained in:
1 parent
258caa38a7
commit
6866457155
4 files changed
+59
-12
No files matched your search
@@ -25,4 +25,11 @@ using ProcLoader = void* (*)(const char*);
|
|||||||
/// context.
|
/// context.
|
||||||
bool load(ProcLoader loader = nullptr) noexcept;
|
bool load(ProcLoader loader = nullptr) noexcept;
|
||||||
|
|
||||||
|
/// True once a `load()` has resolved every required entry point of the global
|
||||||
|
/// table. This is the gate superseding per-pointer null checks: while it is
|
||||||
|
/// false the table may be only partially populated (see load()), so no `gl*`
|
||||||
|
/// entry point may be called. The fallible handle constructors report
|
||||||
|
/// `ErrorCode::Unsupported` until it becomes true.
|
||||||
|
bool available() noexcept;
|
||||||
|
|
||||||
} // namespace plot::gl
|
} // namespace plot::gl
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
#include <plot/gl/handle.hpp>
|
#include <plot/gl/handle.hpp>
|
||||||
|
|
||||||
|
#include <plot/gl/loader.hpp>
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
|
||||||
@@ -78,6 +80,9 @@ GLuint alloc_texture(GLenum internal_format, GLenum format, u32 width, u32 heigh
|
|||||||
// ------------------------------------------------------------------- Buffer
|
// ------------------------------------------------------------------- Buffer
|
||||||
|
|
||||||
bool Buffer::create(Status* st) noexcept {
|
bool Buffer::create(Status* st) noexcept {
|
||||||
|
if (!available()) {
|
||||||
|
return fail(st, ErrorCode::Unsupported, kNoLoader);
|
||||||
|
}
|
||||||
if (glGenBuffers == nullptr) {
|
if (glGenBuffers == nullptr) {
|
||||||
return fail(st, ErrorCode::Unsupported, kNoLoader);
|
return fail(st, ErrorCode::Unsupported, kNoLoader);
|
||||||
}
|
}
|
||||||
@@ -109,6 +114,9 @@ void Buffer::destroy(GLuint id) noexcept { glDeleteBuffers(1, &id); }
|
|||||||
// -------------------------------------------------------------- VertexArray
|
// -------------------------------------------------------------- VertexArray
|
||||||
|
|
||||||
bool VertexArray::create(Status* st) noexcept {
|
bool VertexArray::create(Status* st) noexcept {
|
||||||
|
if (!available()) {
|
||||||
|
return fail(st, ErrorCode::Unsupported, kNoLoader);
|
||||||
|
}
|
||||||
if (glGenVertexArrays == nullptr) {
|
if (glGenVertexArrays == nullptr) {
|
||||||
return fail(st, ErrorCode::Unsupported, kNoLoader);
|
return fail(st, ErrorCode::Unsupported, kNoLoader);
|
||||||
}
|
}
|
||||||
@@ -139,6 +147,10 @@ void VertexArray::destroy(GLuint id) noexcept { glDeleteVertexArrays(1, &id); }
|
|||||||
Program Program::from_source(const char* vertex_src, const char* fragment_src,
|
Program Program::from_source(const char* vertex_src, const char* fragment_src,
|
||||||
Status* st) noexcept {
|
Status* st) noexcept {
|
||||||
Program program;
|
Program program;
|
||||||
|
if (!available()) {
|
||||||
|
fail(st, ErrorCode::Unsupported, kNoLoader);
|
||||||
|
return program;
|
||||||
|
}
|
||||||
if (glCreateShader == nullptr || glCreateProgram == nullptr) {
|
if (glCreateShader == nullptr || glCreateProgram == nullptr) {
|
||||||
fail(st, ErrorCode::Unsupported, kNoLoader);
|
fail(st, ErrorCode::Unsupported, kNoLoader);
|
||||||
return program;
|
return program;
|
||||||
@@ -219,6 +231,9 @@ void Program::destroy(GLuint id) noexcept { glDeleteProgram(id); }
|
|||||||
// ------------------------------------------------------------------ Texture
|
// ------------------------------------------------------------------ Texture
|
||||||
|
|
||||||
bool Texture::create_rgba(u32 width, u32 height, Status* st) noexcept {
|
bool Texture::create_rgba(u32 width, u32 height, Status* st) noexcept {
|
||||||
|
if (!available()) {
|
||||||
|
return fail(st, ErrorCode::Unsupported, kNoLoader);
|
||||||
|
}
|
||||||
if (glGenTextures == nullptr) {
|
if (glGenTextures == nullptr) {
|
||||||
return fail(st, ErrorCode::Unsupported, kNoLoader);
|
return fail(st, ErrorCode::Unsupported, kNoLoader);
|
||||||
}
|
}
|
||||||
@@ -235,6 +250,9 @@ bool Texture::create_rgba(u32 width, u32 height, Status* st) noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool Texture::create_r8(u32 width, u32 height, Status* st) noexcept {
|
bool Texture::create_r8(u32 width, u32 height, Status* st) noexcept {
|
||||||
|
if (!available()) {
|
||||||
|
return fail(st, ErrorCode::Unsupported, kNoLoader);
|
||||||
|
}
|
||||||
if (glGenTextures == nullptr) {
|
if (glGenTextures == nullptr) {
|
||||||
return fail(st, ErrorCode::Unsupported, kNoLoader);
|
return fail(st, ErrorCode::Unsupported, kNoLoader);
|
||||||
}
|
}
|
||||||
@@ -280,6 +298,9 @@ void Texture::destroy(GLuint id) noexcept { glDeleteTextures(1, &id); }
|
|||||||
// -------------------------------------------------------------- Framebuffer
|
// -------------------------------------------------------------- Framebuffer
|
||||||
|
|
||||||
bool Framebuffer::create(Status* st) noexcept {
|
bool Framebuffer::create(Status* st) noexcept {
|
||||||
|
if (!available()) {
|
||||||
|
return fail(st, ErrorCode::Unsupported, kNoLoader);
|
||||||
|
}
|
||||||
if (glGenFramebuffers == nullptr) {
|
if (glGenFramebuffers == nullptr) {
|
||||||
return fail(st, ErrorCode::Unsupported, kNoLoader);
|
return fail(st, ErrorCode::Unsupported, kNoLoader);
|
||||||
}
|
}
|
||||||
@@ -310,6 +331,9 @@ void Framebuffer::destroy(GLuint id) noexcept { glDeleteFramebuffers(1, &id); }
|
|||||||
// -------------------------------------------------------------------- Fence
|
// -------------------------------------------------------------------- Fence
|
||||||
|
|
||||||
bool Fence::create(Status* st) noexcept {
|
bool Fence::create(Status* st) noexcept {
|
||||||
|
if (!available()) {
|
||||||
|
return fail(st, ErrorCode::Unsupported, kNoLoader);
|
||||||
|
}
|
||||||
if (glFenceSync == nullptr) {
|
if (glFenceSync == nullptr) {
|
||||||
return fail(st, ErrorCode::Unsupported, kNoLoader);
|
return fail(st, ErrorCode::Unsupported, kNoLoader);
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-1
@@ -30,6 +30,10 @@ PLOT_GL_FUNCS(PLOT_GL_DEF)
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
/// False until a `load()` has resolved every required entry point of the table
|
||||||
|
/// above, which may stay partially populated until then. Read via available().
|
||||||
|
bool g_table_ready = false;
|
||||||
|
|
||||||
/// The system GL library, opened on the first name that neither the caller's
|
/// The system GL library, opened on the first name that neither the caller's
|
||||||
/// resolver nor the global namespace can supply. Never closed: the entry
|
/// resolver nor the global namespace can supply. Never closed: the entry
|
||||||
/// points handed out must stay valid for the whole process.
|
/// points handed out must stay valid for the whole process.
|
||||||
@@ -138,7 +142,7 @@ bool query_caps(Caps& out) noexcept {
|
|||||||
out.persistent_map = storage;
|
out.persistent_map = storage;
|
||||||
|
|
||||||
GLint profile = 0;
|
GLint profile = 0;
|
||||||
if (major >= 3) {
|
if (major > 3 || (major == 3 && minor >= 2)) {
|
||||||
// GL_CONTEXT_PROFILE_MASK only exists from 3.2 on; older contexts would
|
// GL_CONTEXT_PROFILE_MASK only exists from 3.2 on; older contexts would
|
||||||
// raise GL_INVALID_ENUM and leave the value at 0.
|
// raise GL_INVALID_ENUM and leave the value at 0.
|
||||||
glGetIntegerv(GL_CONTEXT_PROFILE_MASK, &profile);
|
glGetIntegerv(GL_CONTEXT_PROFILE_MASK, &profile);
|
||||||
@@ -150,6 +154,9 @@ bool query_caps(Caps& out) noexcept {
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
bool load(ProcLoader loader) noexcept {
|
bool load(ProcLoader loader) noexcept {
|
||||||
|
// A partially resolved table must not be used: the fallible constructors
|
||||||
|
// consult available() and report Unsupported until the whole table is good.
|
||||||
|
g_table_ready = false;
|
||||||
const char* missing = nullptr;
|
const char* missing = nullptr;
|
||||||
|
|
||||||
#define PLOT_GL_RESOLVE(ret, name, args) \
|
#define PLOT_GL_RESOLVE(ret, name, args) \
|
||||||
@@ -173,9 +180,12 @@ bool load(ProcLoader loader) noexcept {
|
|||||||
PLOT_ASSERT(false, missing);
|
PLOT_ASSERT(false, missing);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
g_table_ready = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool available() noexcept { return g_table_ready; }
|
||||||
|
|
||||||
const Caps& caps() noexcept {
|
const Caps& caps() noexcept {
|
||||||
if (!g_caps_valid) {
|
if (!g_caps_valid) {
|
||||||
if (query_caps(g_caps)) {
|
if (query_caps(g_caps)) {
|
||||||
|
|||||||
+17
-11
@@ -10,6 +10,7 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <dlfcn.h>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -65,7 +66,8 @@ PLOT_TEST(headless_context_provides_gl33) {
|
|||||||
PLOT_CHECK(caps.major > 3 || (caps.major == 3 && caps.minor >= 3));
|
PLOT_CHECK(caps.major > 3 || (caps.major == 3 && caps.minor >= 3));
|
||||||
PLOT_CHECK(caps.renderer != nullptr && caps.renderer[0] != '\0');
|
PLOT_CHECK(caps.renderer != nullptr && caps.renderer[0] != '\0');
|
||||||
PLOT_CHECK(caps.vendor != nullptr && caps.vendor[0] != '\0');
|
PLOT_CHECK(caps.vendor != nullptr && caps.vendor[0] != '\0');
|
||||||
PLOT_CHECK(caps.core_profile); // the context was created as 3.3 core
|
// EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR is requested, so this checks our profile-mask parsing.
|
||||||
|
PLOT_CHECK(caps.core_profile);
|
||||||
const char* version = reinterpret_cast<const char*>(plot::gl::glGetString(plot::gl::GL_VERSION));
|
const char* version = reinterpret_cast<const char*>(plot::gl::glGetString(plot::gl::GL_VERSION));
|
||||||
PLOT_CHECK(version != nullptr);
|
PLOT_CHECK(version != nullptr);
|
||||||
const int major = caps.major;
|
const int major = caps.major;
|
||||||
@@ -78,16 +80,20 @@ PLOT_TEST(headless_context_provides_gl33) {
|
|||||||
caps.core_profile ? 1 : 0);
|
caps.core_profile ? 1 : 0);
|
||||||
|
|
||||||
// The dlsym(RTLD_DEFAULT) / libGL.so.1 fallback must resolve the whole table
|
// The dlsym(RTLD_DEFAULT) / libGL.so.1 fallback must resolve the whole table
|
||||||
// too, and its pointers must still dispatch to the current EGL context.
|
// too, and its pointers must still dispatch to the current EGL context. Only
|
||||||
PLOT_CHECK(plot::gl::load(nullptr));
|
// a host that ships desktop libGL can exercise it; on a GLES-only host there
|
||||||
PLOT_CHECK(plot::gl::glGetError() == plot::gl::GL_NO_ERROR);
|
// is nothing to assert.
|
||||||
const char* fallback_version =
|
if (dlopen("libGL.so.1", RTLD_LAZY) != nullptr) {
|
||||||
reinterpret_cast<const char*>(plot::gl::glGetString(plot::gl::GL_VERSION));
|
PLOT_CHECK(plot::gl::load(nullptr));
|
||||||
PLOT_CHECK(fallback_version != nullptr);
|
PLOT_CHECK(plot::gl::glGetError() == plot::gl::GL_NO_ERROR);
|
||||||
PLOT_CHECK(std::strcmp(fallback_version, version) == 0); // same context, same driver
|
const char* fallback_version =
|
||||||
const plot::gl::Caps& reprobed = plot::gl::caps(); // load() invalidated the cache
|
reinterpret_cast<const char*>(plot::gl::glGetString(plot::gl::GL_VERSION));
|
||||||
PLOT_CHECK(reprobed.major == major && reprobed.minor == minor);
|
PLOT_CHECK(fallback_version != nullptr);
|
||||||
PLOT_CHECK(reprobed.renderer != nullptr && reprobed.renderer[0] != '\0');
|
PLOT_CHECK(std::strcmp(fallback_version, version) == 0); // same context, same driver
|
||||||
|
const plot::gl::Caps& reprobed = plot::gl::caps(); // load() invalidated the cache
|
||||||
|
PLOT_CHECK(reprobed.major == major && reprobed.minor == minor);
|
||||||
|
PLOT_CHECK(reprobed.renderer != nullptr && reprobed.renderer[0] != '\0');
|
||||||
|
}
|
||||||
|
|
||||||
// Hand the table back to the context-aware resolver so teardown (deleting the
|
// Hand the table back to the context-aware resolver so teardown (deleting the
|
||||||
// framebuffer) runs through the entry points of the context it belongs to.
|
// framebuffer) runs through the entry points of the context it belongs to.
|
||||||
|
|||||||
Reference in new issue
Block a user