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, 0, 3, yog_gfx::core::DataType::F32, false, 24, 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
53pub mod core;
54pub mod draw2d;
55pub mod gl;
56
57use yog_abi::YogGfxApi;
58
59/// One inventory slot's content, snapshotted for this render frame — see
60/// [`GfxContext::inv_slot`].
61#[derive(Debug, Clone, Default)]
62pub struct InvSlotData {
63    /// `"namespace:item_id"` — empty string if the slot is empty.
64    pub item_id: String,
65    pub count: u32,
66    /// Screen-space pixel position.
67    pub x: i32,
68    pub y: i32,
69}
70
71/// Handle to the GPU and draw capabilities for a single render frame.
72///
73/// Valid only within an `on_hud_render` or `on_world_render` callback.
74/// Do **not** store across frames — store GPU resource handles (`u32`) instead.
75#[derive(Copy, Clone)]
76pub struct GfxContext(*const YogGfxApi);
77
78unsafe impl Send for GfxContext {}
79unsafe impl Sync for GfxContext {}
80
81impl GfxContext {
82    #[doc(hidden)]
83    pub unsafe fn from_raw(raw: *const YogGfxApi) -> Self {
84        Self(raw)
85    }
86
87    #[inline]
88    fn api(&self) -> &YogGfxApi {
89        unsafe { &*self.0 }
90    }
91
92    // ── Frame info ────────────────────────────────────────────────────────────
93
94    /// GUI pixel dimensions of the screen for this frame.
95    pub fn screen_size(&self) -> (i32, i32) {
96        let a = self.api();
97        (a.screen_w, a.screen_h)
98    }
99
100    /// Partial-tick interpolation factor (0.0–1.0).
101    pub fn delta_tick(&self) -> f32 {
102        self.api().delta_tick
103    }
104
105    /// View-projection matrix in camera-relative space (column-major, 16 × f32).
106    /// Zeros during `on_hud_render`; filled during `on_world_render`.
107    pub fn view_proj(&self) -> [f32; 16] {
108        self.api().view_proj
109    }
110
111    /// Camera world-space position.  All zeros during `on_hud_render`.
112    pub fn camera_pos(&self) -> [f32; 3] {
113        self.api().camera_pos
114    }
115
116    /// Local player world-space position (eye height).  All zeros during `on_hud_render`.
117    /// Use this to anchor geometry to the player; differs from `camera_pos` in third-person.
118    pub fn player_pos(&self) -> [f32; 3] {
119        self.api().player_pos
120    }
121
122    // ── Inventory slots (for `yog-ui`'s `inv_slot` widget) ───────────────────
123
124    /// Number of slots snapshotted this frame from the currently-open
125    /// inventory screen (0 if none is open).
126    pub fn inv_slot_count(&self) -> usize {
127        self.api().inv_slot_count as usize
128    }
129
130    /// Slot `index`'s content this frame, or `None` if out of range.
131    /// `item_id` is empty if the slot itself is empty.
132    pub fn inv_slot(&self, index: usize) -> Option<InvSlotData> {
133        if index >= self.inv_slot_count() {
134            return None;
135        }
136        let raw = &self.api().inv_slots[index];
137        let item_id = if raw.item_id.is_empty() {
138            String::new()
139        } else {
140            unsafe { raw.item_id.as_str() }.to_owned()
141        };
142        Some(InvSlotData {
143            item_id,
144            count: raw.count,
145            x: raw.x,
146            y: raw.y,
147        })
148    }
149
150    // ── GPU buffer ───────────────────────────────────────────────────────────
151
152    /// Allocate a new GPU buffer (VBO or EBO). Returns handle 0 on failure.
153    pub fn create_buffer(&self) -> gl::Buffer {
154        gl::Buffer {
155            handle: unsafe { (self.api().buf_create)() },
156        }
157    }
158
159    /// Delete a buffer allocated by `create_buffer`.
160    pub fn delete_buffer(&self, buf: gl::Buffer) {
161        unsafe { (self.api().buf_delete)(buf.handle) }
162    }
163
164    // ── Vertex array ─────────────────────────────────────────────────────────
165
166    /// Allocate a new vertex array object. Returns handle 0 on failure.
167    pub fn create_vao(&self) -> gl::VertexArray {
168        gl::VertexArray {
169            handle: unsafe { (self.api().vao_create)() },
170        }
171    }
172
173    /// Delete a vertex array allocated by `create_vao`.
174    pub fn delete_vao(&self, vao: gl::VertexArray) {
175        unsafe { (self.api().vao_delete)(vao.handle) }
176    }
177
178    // ── Shader program ────────────────────────────────────────────────────────
179
180    /// Compile and link a GLSL shader program.
181    /// Returns `Err(())` and logs on compile/link failure.
182    pub fn create_shader(&self, vert_src: &str, frag_src: &str) -> Result<gl::ShaderProgram, ()> {
183        use yog_abi::YogStr;
184        let mut handle = 0u32;
185        let ok = unsafe {
186            (self.api().prog_create)(
187                YogStr::from_str(vert_src),
188                YogStr::from_str(frag_src),
189                &mut handle,
190            )
191        };
192        if ok && handle != 0 {
193            Ok(gl::ShaderProgram { handle })
194        } else {
195            Err(())
196        }
197    }
198
199    /// Delete a shader program.
200    pub fn delete_shader(&self, prog: gl::ShaderProgram) {
201        unsafe { (self.api().prog_delete)(prog.handle) }
202    }
203
204    // ── Texture ───────────────────────────────────────────────────────────────
205
206    /// Upload RGBA8 pixel data as a new GPU texture.
207    /// `linear`: `true` = bilinear filter, `false` = nearest.
208    pub fn create_texture_rgba(&self, w: u32, h: u32, pixels: &[u8], linear: bool) -> gl::Texture {
209        gl::Texture {
210            handle: unsafe { (self.api().tex_create)(w, h, pixels.as_ptr(), linear) },
211        }
212    }
213
214    /// Get the GL texture handle that Minecraft uses for an identifier
215    /// (e.g. `"minecraft:textures/gui/icons.png"`).  Returns handle 0 if not found.
216    pub fn texture_from_mc(&self, id: &str) -> gl::Texture {
217        use yog_abi::YogStr;
218        gl::Texture {
219            handle: unsafe { (self.api().tex_from_mc)(YogStr::from_str(id)) },
220        }
221    }
222
223    /// Delete a texture.
224    pub fn delete_texture(&self, tex: gl::Texture) {
225        unsafe { (self.api().tex_delete)(tex.handle) }
226    }
227
228    /// Bind a texture to the given sampler unit (0–7).
229    pub fn bind_texture(&self, unit: u32, tex: &gl::Texture) {
230        unsafe { (self.api().tex_bind)(unit, tex.handle) }
231    }
232
233    // ── Draw calls ────────────────────────────────────────────────────────────
234
235    /// Draw primitives using a vertex array (no index buffer).
236    pub fn draw_arrays(
237        &self,
238        vao: &gl::VertexArray,
239        prog: &gl::ShaderProgram,
240        mode: core::DrawMode,
241        first: u32,
242        count: u32,
243    ) {
244        unsafe { (self.api().draw_arrays)(vao.handle, prog.handle, mode as u8, first, count) }
245    }
246
247    /// Draw primitives via an index buffer.
248    /// `u32_indices`: `true` = `u32` indices, `false` = `u16` indices.
249    pub fn draw_elements(
250        &self,
251        vao: &gl::VertexArray,
252        ebo: &gl::Buffer,
253        prog: &gl::ShaderProgram,
254        mode: core::DrawMode,
255        count: u32,
256        u32_indices: bool,
257    ) {
258        unsafe {
259            (self.api().draw_elements)(
260                vao.handle,
261                ebo.handle,
262                prog.handle,
263                mode as u8,
264                count,
265                u32_indices,
266            )
267        }
268    }
269
270    // ── Render state ──────────────────────────────────────────────────────────
271
272    /// Enable or disable alpha blending.
273    /// `src`/`dst`: GL blend factor constants from [`core::blend`].
274    pub fn set_blend(&self, enabled: bool, src: u32, dst: u32) {
275        unsafe { (self.api().set_blend)(enabled, src, dst) }
276    }
277
278    /// Enable or disable depth testing and writing.
279    pub fn set_depth(&self, test: bool, write: bool) {
280        unsafe { (self.api().set_depth)(test, write) }
281    }
282
283    /// Enable scissor clipping to a GUI-pixel rectangle.
284    pub fn set_scissor(&self, x: i32, y: i32, w: i32, h: i32) {
285        unsafe { (self.api().set_scissor)(x, y, w, h) }
286    }
287
288    /// Disable scissor clipping.
289    pub fn clear_scissor(&self) {
290        unsafe { (self.api().clear_scissor)() }
291    }
292
293    /// Set the GL viewport in physical pixels (x, y, width, height).
294    pub fn set_viewport(&self, x: i32, y: i32, w: i32, h: i32) {
295        unsafe { (self.api().set_viewport)(x, y, w, h) }
296    }
297
298    // ── 2D convenience ────────────────────────────────────────────────────────
299
300    /// Access the 2D drawing helpers (text, rectangles, MC textures).
301    /// These only work during `on_hud_render`.
302    pub fn draw2d(&self) -> draw2d::Draw2D<'_> {
303        draw2d::Draw2D::new(self)
304    }
305}