feat(gl): dependency-free GL loader, RAII handles, caps probe, headless EGL harness
This commit is contained in:
1 parent
ed71719df7
commit
258caa38a7
11 files changed
+1458
-1
No files matched your search
@@ -0,0 +1,192 @@
|
||||
#pragma once
|
||||
#include <plot/core/status.hpp>
|
||||
#include <plot/core/types.hpp>
|
||||
#include <plot/gl/api.hpp>
|
||||
#include <plot/math/vec2.hpp>
|
||||
|
||||
namespace plot::gl {
|
||||
|
||||
/// Move-only ownership of one GL object name (`GLuint`).
|
||||
///
|
||||
/// `Derived` supplies `static void destroy(GLuint) noexcept`. Copying is
|
||||
/// deleted and moving transfers the name, leaving the source empty.
|
||||
/// Destruction and `reset()` are no-ops when `id() == 0`, so an empty handle is
|
||||
/// safe to destroy at any point — including after its context is gone. A
|
||||
/// non-empty handle must not outlive the context that owns the name; call
|
||||
/// `reset()` first when the context is about to be destroyed.
|
||||
template <class Derived>
|
||||
class Handle {
|
||||
public:
|
||||
Handle() noexcept = default;
|
||||
Handle(const Handle&) = delete;
|
||||
Handle& operator=(const Handle&) = delete;
|
||||
~Handle() { reset(); }
|
||||
|
||||
/// GL object name, or 0 when the handle is empty.
|
||||
GLuint id() const noexcept { return id_; }
|
||||
explicit operator bool() const noexcept { return id_ != 0; }
|
||||
|
||||
/// Deletes the GL object, if any, and empties the handle.
|
||||
void reset() noexcept {
|
||||
if (id_ != 0) {
|
||||
Derived::destroy(id_);
|
||||
id_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
Handle(Handle&& other) noexcept : id_(other.id_) { other.id_ = 0; }
|
||||
Handle& operator=(Handle&& other) noexcept {
|
||||
if (this != &other) {
|
||||
reset();
|
||||
id_ = other.id_;
|
||||
other.id_ = 0;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
GLuint id_ = 0;
|
||||
};
|
||||
|
||||
/// Vertex buffer object. All fallible operations report through `Status`;
|
||||
/// the void ones assume that `plot::gl::load()` ran and a context is current.
|
||||
class Buffer final : public Handle<Buffer> {
|
||||
public:
|
||||
/// Allocates a buffer name. Fails with `Unsupported` when the entry points
|
||||
/// are not resolved yet and `GLFailure` when the driver returns name 0.
|
||||
bool create(Status* st = nullptr) noexcept;
|
||||
|
||||
void bind(GLenum target = GL_ARRAY_BUFFER) const noexcept;
|
||||
|
||||
/// Replaces the whole store with `bytes` bytes of `data` (`data` may be null
|
||||
/// to allocate without initialising).
|
||||
void upload(const void* data, usize bytes, GLenum usage) const noexcept;
|
||||
|
||||
/// Overwrites `bytes` bytes starting at `offset`; the store must already be
|
||||
/// at least `offset + bytes` large.
|
||||
void upload_sub(const void* data, usize bytes, usize offset) const noexcept;
|
||||
|
||||
/// Exposed for the CRTP base. Deleting an unknown name is a GL no-op.
|
||||
static void destroy(GLuint id) noexcept;
|
||||
};
|
||||
|
||||
/// Vertex array object holding the attribute layout of the bound buffer.
|
||||
class VertexArray final : public Handle<VertexArray> {
|
||||
public:
|
||||
bool create(Status* st = nullptr) noexcept;
|
||||
|
||||
void bind() const noexcept;
|
||||
|
||||
/// Enables attribute `index` as `components` floats read from the currently
|
||||
/// bound `GL_ARRAY_BUFFER`, advancing `stride` bytes per vertex and starting
|
||||
/// `offset` bytes into it. Requires this vertex array to be bound.
|
||||
void attrib(u32 index, i32 components, usize stride, usize offset) const noexcept;
|
||||
|
||||
/// Exposed for the CRTP base.
|
||||
static void destroy(GLuint id) noexcept;
|
||||
};
|
||||
|
||||
/// Linked shader program. Constructed only through `from_source()`.
|
||||
class Program final : public Handle<Program> {
|
||||
public:
|
||||
/// Compiles and links a vertex/fragment source pair. On failure the returned
|
||||
/// program is empty and `st` (when non-null) carries `GLFailure` plus the
|
||||
/// driver's info log, or `Unsupported` when `load()` was never called.
|
||||
static Program from_source(const char* vertex_src, const char* fragment_src,
|
||||
Status* st = nullptr) noexcept;
|
||||
|
||||
void use() const noexcept;
|
||||
|
||||
/// Sets a float uniform; a name that is not an active uniform of this program
|
||||
/// is a silent no-op.
|
||||
void set_uniform(const char* name, f32 value) const noexcept;
|
||||
|
||||
/// Sets a two-component float uniform.
|
||||
void set_uniform(const char* name, Vec2 value) const noexcept;
|
||||
|
||||
/// Exposed for the CRTP base.
|
||||
static void destroy(GLuint id) noexcept;
|
||||
};
|
||||
|
||||
/// 2D texture. Creation only allocates storage; `upload_*` replaces the whole
|
||||
/// image (`glTexImage2D`), which is what the plot pipelines need for atlases
|
||||
/// and one-shot uploads.
|
||||
class Texture final : public Handle<Texture> {
|
||||
public:
|
||||
/// Allocates `width` x `height` 8-bit RGBA storage (`GL_RGBA8`), linear
|
||||
/// filtering, clamped edges.
|
||||
bool create_rgba(u32 width, u32 height, Status* st = nullptr) noexcept;
|
||||
|
||||
/// Allocates `width` x `height` single-channel 8-bit storage (`GL_R8`) for
|
||||
/// coverage masks and narrow series data.
|
||||
bool create_r8(u32 width, u32 height, Status* st = nullptr) noexcept;
|
||||
|
||||
/// Replaces the whole image with `width * height * 4` bytes of RGBA data.
|
||||
void upload_rgba(u32 width, u32 height, const void* pixels) const noexcept;
|
||||
|
||||
/// Replaces the whole image with `width * height` bytes of single-channel
|
||||
/// data (row padding is disabled, so any width is valid).
|
||||
void upload_r8(u32 width, u32 height, const void* pixels) const noexcept;
|
||||
|
||||
/// Binds to `unit` (e.g. `GL_TEXTURE0`).
|
||||
void bind(GLenum unit = GL_TEXTURE0) const noexcept;
|
||||
|
||||
void set_filter(GLenum min, GLenum mag) const noexcept;
|
||||
|
||||
/// Exposed for the CRTP base.
|
||||
static void destroy(GLuint id) noexcept;
|
||||
};
|
||||
|
||||
/// Framebuffer object, the render target of the off-screen paths.
|
||||
class Framebuffer final : public Handle<Framebuffer> {
|
||||
public:
|
||||
bool create(Status* st = nullptr) noexcept;
|
||||
|
||||
void bind(GLenum target = GL_FRAMEBUFFER) const noexcept;
|
||||
|
||||
/// Attaches `texture`'s level 0 to `GL_COLOR_ATTACHMENT0`.
|
||||
void attach_color(const Texture& texture) const noexcept;
|
||||
|
||||
/// True when the attachment set is `GL_FRAMEBUFFER_COMPLETE`.
|
||||
bool complete() const noexcept;
|
||||
|
||||
/// Exposed for the CRTP base.
|
||||
static void destroy(GLuint id) noexcept;
|
||||
};
|
||||
|
||||
/// Move-only GL sync object (`GLsync`, GL 3.2) used to guard reuse of buffers
|
||||
/// that the GPU may still be reading.
|
||||
class Fence {
|
||||
public:
|
||||
Fence() noexcept = default;
|
||||
Fence(const Fence&) = delete;
|
||||
Fence& operator=(const Fence&) = delete;
|
||||
Fence(Fence&& other) noexcept : id_(other.id_) { other.id_ = nullptr; }
|
||||
Fence& operator=(Fence&& other) noexcept {
|
||||
if (this != &other) {
|
||||
reset();
|
||||
id_ = other.id_;
|
||||
other.id_ = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
/// Deleting no fence is a no-op, so an empty fence is always safe.
|
||||
~Fence() { reset(); }
|
||||
|
||||
/// Inserts a fence in the GL command stream.
|
||||
bool create(Status* st = nullptr) noexcept;
|
||||
|
||||
/// Waits up to `timeout_ns` nanoseconds for the fence to be signalled,
|
||||
/// flushing the command stream first. True when it signalled in time.
|
||||
bool wait(u64 timeout_ns) const noexcept;
|
||||
|
||||
GLsync id() const noexcept { return id_; }
|
||||
explicit operator bool() const noexcept { return id_ != nullptr; }
|
||||
|
||||
void reset() noexcept;
|
||||
|
||||
private:
|
||||
GLsync id_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace plot::gl
|
||||
Reference in new issue
Block a user