209 lines
6.9 KiB
C++
209 lines
6.9 KiB
C++
#include <plot/gl/loader.hpp>
|
|
|
|
#include <plot/gl/caps.hpp>
|
|
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <dlfcn.h>
|
|
|
|
// Debug-only diagnostic for the first entry point the loader cannot resolve;
|
|
// the failure itself is reported through load()'s return value, so this never
|
|
// aborts a release build or a test run.
|
|
#ifndef NDEBUG
|
|
#define PLOT_ASSERT(cond, message) \
|
|
do { \
|
|
if (!(cond)) { \
|
|
std::fprintf(stderr, "plot::gl::load: cannot resolve %s (%s:%d)\n", (message), \
|
|
__FILE__, __LINE__); \
|
|
} \
|
|
} while (false)
|
|
#else
|
|
#define PLOT_ASSERT(cond, message) ((void)0)
|
|
#endif
|
|
|
|
namespace plot::gl {
|
|
|
|
// The one definition of the entry point table declared in api.hpp.
|
|
#define PLOT_GL_DEF(ret, name, args) ret (*name) args = nullptr;
|
|
PLOT_GL_FUNCS(PLOT_GL_DEF)
|
|
PLOT_GL_OPTIONAL_FUNCS(PLOT_GL_DEF)
|
|
#undef PLOT_GL_DEF
|
|
|
|
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
|
|
/// resolver nor the global namespace can supply. Never closed: the entry
|
|
/// points handed out must stay valid for the whole process.
|
|
void* system_gl() noexcept {
|
|
static void* lib = nullptr;
|
|
static bool opened = false;
|
|
if (!opened) {
|
|
opened = true;
|
|
lib = dlopen("libGL.so.1", RTLD_LAZY | RTLD_LOCAL);
|
|
}
|
|
return lib;
|
|
}
|
|
|
|
void* resolve(ProcLoader loader, const char* name) noexcept {
|
|
if (loader != nullptr) {
|
|
if (void* symbol = loader(name)) {
|
|
return symbol;
|
|
}
|
|
}
|
|
if (void* symbol = dlsym(RTLD_DEFAULT, name)) {
|
|
return symbol;
|
|
}
|
|
if (void* lib = system_gl()) {
|
|
return dlsym(lib, name);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
// ---------------------------------------------------------------- caps probe
|
|
|
|
Caps g_caps;
|
|
bool g_caps_valid = false;
|
|
|
|
const char* gl_string(GLenum name) noexcept {
|
|
const char* text = reinterpret_cast<const char*>(glGetString(name));
|
|
return text != nullptr ? text : "";
|
|
}
|
|
|
|
/// True when `all` (the legacy space-separated GL_EXTENSIONS string) contains
|
|
/// `name` as a whole token.
|
|
bool has_token(const char* all, const char* name) noexcept {
|
|
if (all == nullptr || name == nullptr) {
|
|
return false;
|
|
}
|
|
const usize len = std::strlen(name);
|
|
for (const char* hit = std::strstr(all, name); hit != nullptr; hit = std::strstr(hit + len, name)) {
|
|
const bool at_start = hit == all || hit[-1] == ' ';
|
|
const bool at_end = hit[len] == '\0' || hit[len] == ' ';
|
|
if (at_start && at_end) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// Tests for an extension in the form the current context exposes it: the
|
|
/// indexed list on 3.0+, the legacy string before that (where
|
|
/// `glGetString(GL_EXTENSIONS)` is still legal).
|
|
bool has_extension(int major, const char* name) noexcept {
|
|
if (major >= 3) {
|
|
if (glGetStringi == nullptr) {
|
|
return false; // indexed list unavailable: the legacy string is illegal on 3.0+ core
|
|
}
|
|
GLint count = 0;
|
|
glGetIntegerv(GL_NUM_EXTENSIONS, &count);
|
|
for (GLint i = 0; i < count; ++i) {
|
|
const char* ext =
|
|
reinterpret_cast<const char*>(glGetStringi(GL_EXTENSIONS, static_cast<GLuint>(i)));
|
|
if (ext != nullptr && std::strcmp(ext, name) == 0) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
// Before 3.0 the legacy space-separated string is the only enumeration.
|
|
return has_token(reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS)), name);
|
|
}
|
|
|
|
/// Fills `out` from the context that is current on this thread. Returns false
|
|
/// when there is no usable context, leaving `out` untouched.
|
|
bool query_caps(Caps& out) noexcept {
|
|
if (glGetString == nullptr || glGetIntegerv == nullptr) {
|
|
return false;
|
|
}
|
|
const char* version = reinterpret_cast<const char*>(glGetString(GL_VERSION));
|
|
if (version == nullptr) {
|
|
return false; // no current context
|
|
}
|
|
int major = 0;
|
|
int minor = 0;
|
|
// Drivers report e.g. "4.6 (Compatibility Profile) Mesa 25.2.0"; only the
|
|
// leading "major.minor" is part of the specification.
|
|
if (std::sscanf(version, "%d.%d", &major, &minor) != 2) {
|
|
return false;
|
|
}
|
|
|
|
out.major = major;
|
|
out.minor = minor;
|
|
out.renderer = gl_string(GL_RENDERER);
|
|
out.vendor = gl_string(GL_VENDOR);
|
|
|
|
const bool has_44 = major > 4 || (major == 4 && minor >= 4);
|
|
out.compute = major > 4 || (major == 4 && minor >= 3);
|
|
const bool storage = has_44 || has_extension(major, "GL_ARB_buffer_storage");
|
|
out.buffer_storage = storage;
|
|
out.persistent_map = storage;
|
|
|
|
GLint profile = 0;
|
|
if (major > 3 || (major == 3 && minor >= 2)) {
|
|
// GL_CONTEXT_PROFILE_MASK only exists from 3.2 on; older contexts would
|
|
// raise GL_INVALID_ENUM and leave the value at 0.
|
|
glGetIntegerv(GL_CONTEXT_PROFILE_MASK, &profile);
|
|
}
|
|
out.core_profile = (static_cast<GLuint>(profile) & GL_CONTEXT_CORE_PROFILE_BIT) != 0u;
|
|
return true;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
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;
|
|
|
|
#define PLOT_GL_RESOLVE(ret, name, args) \
|
|
do { \
|
|
void* symbol = resolve(loader, #name); \
|
|
if (symbol != nullptr) { \
|
|
name = reinterpret_cast<ret (*) args>(symbol); \
|
|
} else if (missing == nullptr) { \
|
|
missing = #name; \
|
|
} \
|
|
} while (false);
|
|
|
|
PLOT_GL_FUNCS(PLOT_GL_RESOLVE)
|
|
|
|
// Above-baseline entries: resolved the same way, but a name the driver does not
|
|
// have leaves its pointer null instead of failing the load, so a 3.3-only
|
|
// context keeps working. Callers gate them on caps() (see api.hpp).
|
|
PLOT_GL_OPTIONAL_FUNCS(PLOT_GL_RESOLVE)
|
|
|
|
#undef PLOT_GL_RESOLVE
|
|
|
|
// The table may have changed, so whatever caps() cached for the old one is
|
|
// stale — including when this load() fails.
|
|
g_caps_valid = false;
|
|
if (missing != nullptr) {
|
|
PLOT_ASSERT(false, missing);
|
|
return false;
|
|
}
|
|
g_table_ready = true;
|
|
return true;
|
|
}
|
|
|
|
bool available() noexcept { return g_table_ready; }
|
|
|
|
const Caps& caps() noexcept {
|
|
if (!g_caps_valid) {
|
|
if (query_caps(g_caps)) {
|
|
g_caps_valid = true;
|
|
} else {
|
|
// No context (or no resolved entry points): report an empty probe rather
|
|
// than stale or uninitialised values.
|
|
g_caps = Caps{};
|
|
}
|
|
}
|
|
return g_caps;
|
|
}
|
|
|
|
} // namespace plot::gl
|