Skip to main content

yog_gfx/
lib.rs

1//! Yog graphics — low-level GPU pipeline access for mods.
2//!
3//! Provides ergonomic wrappers around `YogGfxApi`, the stable C ABI table for
4//! GPU operations (VAO/VBO, GLSL shaders, textures, draw calls, render state).
5//!
6//! # Render contexts
7//!
8//! Mods receive a [`GfxContext`] in two places:
9//!
10//! - `on_hud_render(|ctx| { ... })` — called every frame after the HUD is drawn.
11//!   `ctx.view_proj()` is zeroed; `ctx.draw2d()` works.
12//! - `on_world_render(|ctx| { ... })` — called after world geometry.
13//!   `ctx.view_proj()` holds the camera view-projection matrix in camera-relative
14//!   space; `ctx.camera_pos()` is the world-space camera position.
15//!
16//! # GPU resource lifetime
17//!
18//! GPU handles (`u32`) must be created and destroyed on the render thread.
19//! Store handles between frames; pass `ctx` on every render call.
20//!
21//! ```ignore
22//! use yog_gfx::{GfxContext, gl::{Buffer, ShaderProgram, VertexArray}};
23//!
24//! struct MyRenderer {
25//!     vbo: Option<Buffer>,
26//!     vao: Option<VertexArray>,
27//!     prog: Option<ShaderProgram>,
28//! }
29//!
30//! impl MyRenderer {
31//!     fn ensure_init(&mut self, ctx: &GfxContext) {
32//!         if self.vbo.is_some() { return; }
33//!         let vbo = ctx.create_buffer();
34//!         vbo.upload(ctx, &MY_VERTICES, false);
35//!         let prog = ctx.create_shader(VERT_SRC, FRAG_SRC).unwrap();
36//!         let vao = ctx.create_vao();
37//!         vao.attrib(ctx, &vbo, yog_gfx::gl::Attrib { index: 0, components: 3, dtype: yog_gfx::core::DataType::F32, normalized: false, stride: 24, offset: 0 });
38//!         self.vbo = Some(vbo);
39//!         self.vao = Some(vao);
40//!         self.prog = Some(prog);
41//!     }
42//!
43//!     fn render(&mut self, ctx: &GfxContext) {
44//!         self.ensure_init(ctx);
45//!         let vao = self.vao.as_ref().unwrap();
46//!         let prog = self.prog.as_ref().unwrap();
47//!         prog.uniform_mat4(ctx, "uViewProj", &ctx.view_proj());
48//!         ctx.draw_arrays(vao, prog, yog_gfx::core::DrawMode::Triangles, 0, 3);
49//!     }
50//! }
51//! ```
52
53/// What an indexed draw draws: `count` indices of `ebo`, `width` wide.
54#[derive(Copy, Clone, Debug)]
55pub struct Elements<'a> {
56    pub ebo: &'a gl::Buffer,
57    pub count: u32,
58    pub width: core::IndexWidth,
59}
60
61pub mod core;
62pub mod draw2d;
63pub mod gl;
64pub mod image;
65#[cfg(feature = "testing")]
66pub mod testing;
67
68/// The graphics primitives every Yog crate shares.
69pub use yog_graphics as graphics;
70pub use yog_graphics::{Color, Rect, Vec2};
71
72use yog_abi::YogGfxApi;
73
74/// Handle to the GPU and draw capabilities for a single render frame.
75///
76/// Valid only within an `on_hud_render` or `on_world_render` callback.
77/// Do **not** store across frames — store GPU resource handles (`u32`) instead.
78#[derive(Copy, Clone)]
79pub struct GfxContext(*const YogGfxApi);
80
81unsafe impl Send for GfxContext {}
82unsafe impl Sync for GfxContext {}
83
84/// See [`GfxContext::text_width_fn`]: `(text, font) -> width` in GUI pixels.
85pub type TextWidthFn = unsafe extern "C" fn(text: yog_abi::YogStr, font: yog_abi::YogStr) -> f32;
86
87/// See [`GfxContext::image_size_fn`]: `(source, *w, *h) -> resolved`.
88pub type ImageSizeFn =
89    unsafe extern "C" fn(source: yog_abi::YogStr, w: *mut f32, h: *mut f32) -> bool;
90
91impl GfxContext {
92    #[doc(hidden)]
93    pub unsafe fn from_raw(raw: *const YogGfxApi) -> Self {
94        Self(raw)
95    }
96
97    #[inline]
98    fn api(&self) -> &YogGfxApi {
99        unsafe { &*self.0 }
100    }
101
102    // ── Frame info ────────────────────────────────────────────────────────────
103
104    /// GUI pixel dimensions of the screen for this frame.
105    pub fn screen_size(&self) -> (i32, i32) {
106        let a = self.api();
107        (a.screen_w, a.screen_h)
108    }
109
110    /// Partial-tick interpolation factor (0.0–1.0).
111    pub fn delta_tick(&self) -> f32 {
112        self.api().delta_tick
113    }
114
115    /// View-projection matrix in camera-relative space (column-major, 16 × f32).
116    /// Zeros during `on_hud_render`; filled during `on_world_render`.
117    pub fn view_proj(&self) -> [f32; 16] {
118        self.api().view_proj
119    }
120
121    /// Camera world-space position.  All zeros during `on_hud_render`.
122    pub fn camera_pos(&self) -> [f32; 3] {
123        self.api().camera_pos
124    }
125
126    /// Local player world-space position (eye height).  All zeros during `on_hud_render`.
127    /// Use this to anchor geometry to the player; differs from `camera_pos` in third-person.
128    pub fn player_pos(&self) -> [f32; 3] {
129        self.api().player_pos
130    }
131
132    // ── GPU buffer ───────────────────────────────────────────────────────────
133
134    /// Allocate a new GPU buffer (VBO or EBO). Returns handle 0 on failure.
135    pub fn create_buffer(&self) -> gl::Buffer {
136        gl::Buffer {
137            handle: unsafe { (self.api().buf_create)() },
138        }
139    }
140
141    /// Delete a buffer allocated by `create_buffer`.
142    pub fn delete_buffer(&self, buf: gl::Buffer) {
143        unsafe { (self.api().buf_delete)(buf.handle) }
144    }
145
146    // ── Vertex array ─────────────────────────────────────────────────────────
147
148    /// Allocate a new vertex array object. Returns handle 0 on failure.
149    pub fn create_vao(&self) -> gl::VertexArray {
150        gl::VertexArray {
151            handle: unsafe { (self.api().vao_create)() },
152        }
153    }
154
155    /// Delete a vertex array allocated by `create_vao`.
156    pub fn delete_vao(&self, vao: gl::VertexArray) {
157        unsafe { (self.api().vao_delete)(vao.handle) }
158    }
159
160    // ── Shader program ────────────────────────────────────────────────────────
161
162    /// Compile and link a GLSL shader program.
163    /// Returns `Err(())` and logs on compile/link failure.
164    #[allow(clippy::result_unit_err)] // public API; the log carries the details
165    pub fn create_shader(&self, vert_src: &str, frag_src: &str) -> Result<gl::ShaderProgram, ()> {
166        use yog_abi::YogStr;
167        let mut handle = 0u32;
168        let ok = unsafe {
169            (self.api().prog_create)(
170                YogStr::from_str(vert_src),
171                YogStr::from_str(frag_src),
172                &mut handle,
173            )
174        };
175        if ok && handle != 0 {
176            Ok(gl::ShaderProgram { handle })
177        } else {
178            Err(())
179        }
180    }
181
182    /// Delete a shader program.
183    pub fn delete_shader(&self, prog: gl::ShaderProgram) {
184        unsafe { (self.api().prog_delete)(prog.handle) }
185    }
186
187    // ── Texture ───────────────────────────────────────────────────────────────
188
189    /// Upload RGBA8 pixel data as a new GPU texture.
190    /// `linear`: `true` = bilinear filter, `false` = nearest.
191    pub fn create_texture_rgba(&self, w: u32, h: u32, pixels: &[u8], linear: bool) -> gl::Texture {
192        gl::Texture {
193            handle: unsafe { (self.api().tex_create)(w, h, pixels.as_ptr(), linear) },
194        }
195    }
196
197    /// Get the GL texture handle that Minecraft uses for an identifier
198    /// (e.g. `"minecraft:textures/gui/icons.png"`).  Returns handle 0 if not found.
199    pub fn texture_from_mc(&self, id: &str) -> gl::Texture {
200        use yog_abi::YogStr;
201        gl::Texture {
202            handle: unsafe { (self.api().tex_from_mc)(YogStr::from_str(id)) },
203        }
204    }
205
206    /// Delete a texture.
207    pub fn delete_texture(&self, tex: gl::Texture) {
208        unsafe { (self.api().tex_delete)(tex.handle) }
209    }
210
211    /// Bind a texture to the given sampler unit (0–7).
212    pub fn bind_texture(&self, unit: u32, tex: &gl::Texture) {
213        unsafe { (self.api().tex_bind)(unit, tex.handle) }
214    }
215
216    // ── Draw calls ────────────────────────────────────────────────────────────
217
218    /// Draw primitives using a vertex array (no index buffer).
219    pub fn draw_arrays(
220        &self,
221        vao: &gl::VertexArray,
222        prog: &gl::ShaderProgram,
223        mode: core::DrawMode,
224        first: u32,
225        count: u32,
226    ) {
227        unsafe { (self.api().draw_arrays)(vao.handle, prog.handle, mode as u8, first, count) }
228    }
229
230    /// Draws `elements` (an index buffer's) from `vao` with `prog`.
231    pub fn draw_elements(
232        &self,
233        vao: &gl::VertexArray,
234        prog: &gl::ShaderProgram,
235        mode: core::DrawMode,
236        elements: Elements<'_>,
237    ) {
238        let wide = elements.width == core::IndexWidth::U32;
239        unsafe {
240            (self.api().draw_elements)(
241                vao.handle,
242                elements.ebo.handle,
243                prog.handle,
244                mode as u8,
245                elements.count,
246                wide,
247            )
248        }
249    }
250
251    // ── Render state ──────────────────────────────────────────────────────────
252
253    /// Enable or disable alpha blending.
254    /// `src`/`dst`: GL blend factor constants from [`core::blend`].
255    pub fn set_blend(&self, enabled: bool, src: u32, dst: u32) {
256        unsafe { (self.api().set_blend)(enabled, src, dst) }
257    }
258
259    /// Enable or disable depth testing and writing.
260    pub fn set_depth(&self, test: bool, write: bool) {
261        unsafe { (self.api().set_depth)(test, write) }
262    }
263
264    /// Enable the GL scissor: `x, y, w, h` in framebuffer pixels from the
265    /// bottom-left corner. For GPU drawing (`draw_arrays`…), not
266    /// [`draw2d`](Self::draw2d) — the game batches 2D drawing; clip that by
267    /// hand, as yog-ui does.
268    pub fn set_scissor(&self, x: i32, y: i32, w: i32, h: i32) {
269        unsafe { (self.api().set_scissor)(x, y, w, h) }
270    }
271
272    /// Disable scissor clipping.
273    pub fn clear_scissor(&self) {
274        unsafe { (self.api().clear_scissor)() }
275    }
276
277    /// Set the GL viewport in physical pixels (x, y, width, height).
278    pub fn set_viewport(&self, x: i32, y: i32, w: i32, h: i32) {
279        unsafe { (self.api().set_viewport)(x, y, w, h) }
280    }
281
282    // ── 2D convenience ────────────────────────────────────────────────────────
283
284    /// The host's text-width function (see [`draw2d::Draw2D::text_width`]) as
285    /// a plain function pointer — valid for the life of the process, so it
286    /// can be kept for measuring outside render callbacks (on the render
287    /// thread), e.g. by a layout engine.
288    pub fn text_width_fn(&self) -> TextWidthFn {
289        self.api().text_width
290    }
291
292    /// The host's image-size function (see [`draw2d::Draw2D::image_size`])
293    /// as a plain function pointer, valid for the life of the process — for
294    /// a layout engine sizing images outside render callbacks.
295    pub fn image_size_fn(&self) -> ImageSizeFn {
296        self.api().image_size
297    }
298
299    /// Access the 2D drawing helpers (text, rectangles, MC textures).
300    /// These only work during `on_hud_render`.
301    pub fn draw2d(&self) -> draw2d::Draw2D<'_> {
302        draw2d::Draw2D::new(self)
303    }
304}