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