https://github.com/fuzzyzilla/glhf
Experimental Zero-Cost Type-State OpenGLES Wrapper
https://github.com/fuzzyzilla/glhf
crate graphics-programming opengl opengl-es opengles opengles3 rust
Last synced: 6 months ago
JSON representation
Experimental Zero-Cost Type-State OpenGLES Wrapper
- Host: GitHub
- URL: https://github.com/fuzzyzilla/glhf
- Owner: Fuzzyzilla
- Created: 2024-08-03T21:54:42.000Z (almost 2 years ago)
- Default Branch: master
- Last Pushed: 2024-08-19T03:53:16.000Z (almost 2 years ago)
- Last Synced: 2025-01-20T23:26:03.310Z (over 1 year ago)
- Topics: crate, graphics-programming, opengl, opengl-es, opengles, opengles3, rust
- Language: Rust
- Homepage:
- Size: 229 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# GL, HF
A quirky lil OpenGL ES 3.X wrapper crate making excessive use of typestate and the borrow checker to ensure correct API
usage at compile time with *zero* runtime cost.
**Why ES 3?** Unfortunately, this is the lingua-franca of legacy graphics. Apps targeting GL ES 3 can compile to
the Web, to Android, and even to iOS. While efforts exist to bridge this gap, for my purposes this is the tool I need.
Along with regular rust-ification of the API (splitting up the monolithic `GLenum` with many disjoint enums and
eliminating invalid argument combinations at the type level), two primary strategies are at play:
## Typestate
Some types in OpenGL require passing certain runtime checks before becoming usable, or have important runtime state
determined by the ways in which they are accessed - for example, when a texture name is first bound to a texture target,
it goes from an uninitialized state to being permanently associated with that texture target type; a framebuffer must
pass "Completeness" checking; a program must be linked.
This is codified in a "Typestate" API, where the types of variables change depending on the statically-known dataflow
they are used in.
Along with this, the bindpoints themselves (`glBindBuffer` and the likes) are written in a typestate manner. For example,
It must be statically known that a *non-null* buffer is bound to the `GL_ELEMENT_ARRAY_BUFFER` buffer in order to call
`gl.draw.elements(...)`.
## Borrow Checker
The relationship between objects in OpenGL is sometimes complex and oftentimes difficult to find documentation for. This
crate expresses those relationships by projecting them as rust borrows.
To execute a command, you must prove (at compile time) that you hold all the necessary resources. If any call is made
that may sneakily change those resources out from under you, the references are invalidated, creating an error at compile
time instead of a hard-to-track bug at runtime.
```rust
// Bind our texture to `TEXTURE_2D`...
let active_texture = gl.texture.d2.bind(&awesome_texture);
// (Whoops! glActiveTexture switches out every texture binding!)
gl.texture.unit(1);
// ...try to update our previously-bound `awesome_texture`
active_texture.mag_filter(glhf::texture::Filter::Linear);
```
```rust
error[E0499]: cannot borrow `gl.texture` as mutable more than once at a time
|
| let active_texture = gl.texture.d2.bind(&self.shadow_texture);
| ------------- first mutable borrow occurs here
| gl.texture.unit(1);
| ^^^^^^^^^^ second mutable borrow occurs here
|
| active_texture.mag_filter(glhf::texture::Filter::Linear);
| -------------- first borrow later used here
```
## Cool tricks
I just think they're neat :3
```rust
let [a, handful, of, textures] = gl.new.textures();
```
## Non-goals
* Automatic resource management - object handles do not have drop glue, and must be manually deleted.
* Preventative error checking - other than the not-unsubstantial compile-time error prevention, runtime state is not
queried to ensure GL calls will not error.
* Full safety - unsafe APIs are required to make use of this library, as even something as simple as `gl.draw.arrays`
may invoke UB. To to greatest extent possible howerver, UB is documented and some is prevented at compile time.
* Object-oriented API - All handles are thin-wrappers around `NonZero`, and all state APIs are ZSTs.