realms 4.5.5

A powerful and lightweight graphics library for making Rust games
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
//! The `shader` module stores structs and functions used to manage opengl
//! shaders used to draw vertices.
//! It has two primary structs: `Shader` and `ShaderProgram`.

extern crate alloc;
use alloc::ffi::CString;
use core::ptr;

use gl::types::{GLchar, GLint};

/// Enum storing the type of shader and its associated opengl integer.
/// You should pass a variant of `ShaderType` when creating a `Shader`.
#[expect(
    clippy::module_name_repetitions,
    reason = "ShaderType is more descriptive; and may be used without absolute path."
)]
#[repr(u32)]
#[non_exhaustive]
pub enum ShaderType {
    /// `Vertex` shaders alter the *position* of vertices.
    /// For example, a vertex shader could double the size of a shape by
    /// doubling each coordinate, or be used to animate objects (e.g. trees
    /// swaying in the wind).
    ///
    /// The code for a basic vertex shader which simply outputs the 2D
    /// coordinates stored the `VertexBuffer` (but as a 3D vertex, a `vec4`)
    /// can be found in example 3, at
    /// <https://github.com/dylanopen/realms/tree/main/examples/example3_colorful_triangle/src/vertex.glsl>.
    ///
    /// Read more: <https://www.khronos.org/opengl/wiki/Vertex_Shader>.
    Vertex = gl::VERTEX_SHADER,

    /// `Vertex` shaders determine the *color of each pixel* on the screen.
    /// This allows you to set the color of shapes or apply a texture to some
    /// vertices.
    ///
    /// The code for a basic fragment shader which applies the color stored in
    /// the `VertexBuffer` can be found in example 3, at
    /// <https://github.com/dylanopen/realms/tree/main/examples/example3_colorful_triangle/src/fragment.glsl>.
    ///
    /// Read more: <https://www.khronos.org/opengl/wiki/Fragment_Shader>.
    Fragment = gl::FRAGMENT_SHADER,
}

/// A wrapper around an opengl shader.
/// This struct currently only stores the opengl reference to the shader, but
/// is provided as a convenient wrapper for creating shaders from their source.
///
/// NOTE: `Shader`s do nothing unless added to a `ShaderProgram`.
///
/// NOTE: When the shader is added to a `ShaderProgram`, the shader is deleted.
/// This isn't necessary, but it helps free up a little memory as the shader
/// itself is useless without an attached program.
/// If you want to use the same shader in a different program, you MUST create
/// fully a new `Shader`. *Do not* just copy the `gl_id` into a new `Shader`,
/// as the shader is DELETED. This will either throw an error or just not
/// render anything at all.
///
/// ## Example usage:
///
/// ``` rust
/// let v_shader_src = include_str!("default.vert.glsl");
/// let f_shader_src = include_str!("default.frag.glsl");
/// let v_shader = Shader::load_str(ShaderType::Vertex, v_shader_src).unwrap();         
/// let f_shader = Shader::load_str(ShaderType::Fragment, f_shader_src).unwrap();
/// let program = ShaderProgram::new(vec![v_shader, f_shader]).unwrap();
/// ```
#[non_exhaustive]
pub struct Shader {
    /// The opengl reference id to the shader (as a standard C integer).
    /// This is used to link the vertex, fragment, etc. shaders together using
    /// an opengl function by a `ShaderProgram` class.
    ///
    /// You usually don't need to access this. However, it is made public so
    /// that libraries extending the low-level functionality of Realms can
    /// interoperate with the Realms `Shader` class.
    pub gl_id: u32,
}

impl Shader {
    /// Load and compile an opengl shader from the given source string and
    /// shader type.
    ///
    /// ## Errors
    ///
    /// If the shader compilation failed (i.e. your shader has a syntax error),
    /// the error information is provided in the `Err` variant as a string.
    /// If you `.unwrap()` or `.expect(...)` the return value, it will print
    /// the GLSL error.
    ///
    /// ## Example usage
    ///
    /// ``` rust
    /// let v_shader_src = include_str!("default.vert.glsl");
    /// let f_shader_src = include_str!("default.frag.glsl");                
    /// let v_shader = Shader::load_str(ShaderType::Vertex, v_shader_src).unwrap();         
    /// let f_shader = Shader::load_str(ShaderType::Fragment, f_shader_src).unwrap();
    /// let program = ShaderProgram::new(vec![v_shader, f_shader]).unwrap();
    /// ```
    ///
    /// ## Migrating from 1.3.3 to 2.3.3
    ///
    /// As of version `2.3.3` (major) `Shader::load_str` now takes the source
    /// code for the shader (`source` parameter) as `&str` rather than `String`.
    /// To fix this, you likely just need to remove the `.to_string()` call to
    /// the shader source string, or add a `.as_str()` call.
    #[expect(
        clippy::uninit_vec,
        reason = "I can't find a way to fix this lint, please make a PR/issue if you know the solution"
    )]
    #[inline]
    pub fn load_str(shader_type: ShaderType, source: &str) -> Result<Shader, String> {
        #[expect(
            clippy::as_conversions,
            reason = "no other way to get integral value of enum variant"
        )]
        let gl_id = unsafe { gl::CreateShader(shader_type as u32) };
        let c_source = CString::new(source.as_bytes())
            .map_err(|err| format!("Realms: failed to create CString from shader source: {err}"))?;
        unsafe {
            gl::ShaderSource(gl_id, 1, &c_source.as_ptr(), ptr::null());
        }
        unsafe {
            gl::CompileShader(gl_id);
        }

        let mut success = GLint::from(gl::FALSE);
        let mut info_log: Vec<u8> = Vec::with_capacity(1024);
        unsafe {
            info_log.set_len(1024 - 1);
        } // -1 to skip trailing \0
        unsafe {
            gl::GetShaderiv(gl_id, gl::COMPILE_STATUS, &raw mut success);
        }
        if success != GLint::from(gl::TRUE) {
            unsafe {
                gl::GetShaderInfoLog(
                    gl_id,
                    1024,
                    ptr::null_mut(),
                    info_log.as_mut_ptr().cast::<GLchar>(),
                );
            };
            let gl_error = String::from_utf8_lossy(&info_log);
            let gl_error = gl_error
                .split_once('\0')
                .ok_or("Realms: received malformed shader compile error info from opengl")?
                .0;

            return Err(format!("Realms: failed to compile shader: {gl_error}"));
        }

        Ok(Shader { gl_id })
    }
}

/// A wrapper around an opengl shader *program*.
///
/// This struct currently only stores the opengl reference to the shader
/// program.
/// It is provided as a convenient wrapper for creating a shader program from
/// a vector of `Shader` objects.
#[expect(
    clippy::module_name_repetitions,
    reason = "ShaderProgram is more descriptive; and may be used without absolute path."
)]
#[non_exhaustive]
pub struct ShaderProgram {
    /// The opengl reference id to the shader program (as a standard C integer).
    /// This is used to `use` the shader program on each frame with the
    /// `gl::UseProgram` function called by the `new_frame` function.
    ///
    /// You usually don't need to access this. However, it is made public so
    /// that libraries extending the low-level functionality of Realms can
    /// interoperate with the Realms `Shader` class.
    pub gl_id: u32,
}

impl ShaderProgram {
    /// A default `ShaderProgram` for when you want to *unbind* the current
    /// shader.
    /// Its uses are to unbind a shader (if you don't want to use any shader)
    /// or if you haven't yet written a shader and want to use realms without
    /// one.
    ///
    /// For an example of using the `NONE` shader, see *example 1: window*:
    /// <https://github.com/dylanopen/realms/tree/main/examples/example1_window>.
    pub const NONE: ShaderProgram = ShaderProgram { gl_id: 0 };

    /// Load and compile an opengl shader **program** from the given `Vec` of
    /// `Shader`s.
    ///
    /// NOTE: When the shader is added to a `ShaderProgram`, the shader is deleted.
    /// This isn't necessary, but it helps free up a little memory as the shader
    /// itself is useless without an attached program.
    /// If you want to use the same shader in a different program, you MUST create
    /// fully a new `Shader`. *Do not* just copy the `gl_id` into a new `Shader`,
    /// as the shader is DELETED. This will either throw an error or just not
    /// render anything at all.
    ///
    /// ## Errors
    ///
    /// If linking the shader program fails, the error will be returned in the
    /// `Err` variant of the result.
    /// If you `.unwrap()` or `.expect(...)` the return value, it will print
    /// the GLSL error.
    ///
    /// ## Panics
    ///
    /// In the rare but possible case that opengl returns a malformed response
    /// upon request for the compile error of the shader, this function will
    /// PANIC.
    ///
    /// ## Example usage:
    ///
    /// ``` rust
    /// let v_shader_src = include_str!("default.vert.glsl");
    /// let f_shader_src = include_str!("default.frag.glsl");
    /// let v_shader = Shader::load_str(ShaderType::Vertex, v_shader_src).unwrap();         
    /// let f_shader = Shader::load_str(ShaderType::Fragment, f_shader_src).unwrap();
    /// let program = ShaderProgram::new(vec![v_shader, f_shader]).unwrap();
    /// ```
    #[expect(
        clippy::needless_pass_by_value,
        reason = "the caller shouldn't be able to mutate the shaders after building the shader program"
    )]
    #[inline]
    pub fn new(shaders: Vec<Shader>) -> Result<ShaderProgram, String> {
        #[expect(
            clippy::multiple_unsafe_ops_per_block,
            reason = "will fix when I have time; feel free to open a PR"
        )]
        #[expect(
            clippy::uninit_vec,
            reason = "we should add MaybeUninit wrapper to info_log in the future"
        )]
        let gl_id = unsafe {
            let gl_id = gl::CreateProgram();
            for shader in &shaders {
                gl::AttachShader(gl_id, shader.gl_id);
            }
            gl::LinkProgram(gl_id);

            let mut success = GLint::from(gl::FALSE);
            let mut info_log = Vec::with_capacity(1024);
            info_log.set_len(1024 - 1); // subtract 1 to skip the trailing null character
            gl::GetProgramiv(gl_id, gl::LINK_STATUS, &raw mut success);
            if success != GLint::from(gl::TRUE) {
                #[expect(clippy::as_conversions, reason = "need to cast mut ptr -> *mut GLchar")]
                #[expect(clippy::ptr_as_ptr, reason = "need to cast mut ptr -> *mut GLchar")]
                gl::GetProgramInfoLog(
                    gl_id,
                    1024,
                    ptr::null_mut(),
                    info_log.as_mut_ptr() as *mut GLchar,
                );
                #[expect(
                    clippy::absolute_paths,
                    reason = "importing the `str` module would clash with the `str` type"
                )]
                return Err(format!(
                    "Realms: failed to link shader program: {}",
                    core::str::from_utf8(&info_log).map_err(|e| e.to_string())?
                ));
            }
            for shader in &shaders {
                gl::DeleteShader(shader.gl_id);
            }
            gl_id
        };

        Ok(ShaderProgram { gl_id })
    }

    /// Bind (use) the shader.
    /// If you pass this shader program to the `VertexBuffer::new_frame()`
    /// method, this method is automatically run before drawing the vertices,
    /// so this method does not need to be manually called.
    ///
    /// In short, calling this funtion tells opengl to use this shader program
    /// to draw the vertices.
    ///
    /// ## Example usage
    ///
    /// ``` rust
    /// let shader_program = ...;
    /// let vertex_buffer = ...;
    /// while w.is_running() {
    ///     w.new_frame();
    ///     shader_program.bind(); // manually
    ///     vertex_buffer.draw(&shader_program); // automatically calls bind
    /// }
    /// ```
    ///
    /// ## Migrating from 2.3.4 to 3.3.4
    ///
    /// The name of this method changed in version `3.3.4` from `new_frame` to
    /// `bind`. Nothing else about this method has changed, only its name.
    ///
    /// However, the usage has changed. While this method used to be called by
    /// the `Window::new_frame` method, it is no longer called by that method
    /// but instead is called by the `VertexBuffer::draw` method.
    #[inline]
    pub fn bind(&self) {
        unsafe {
            gl::UseProgram(self.gl_id);
        }
    }

    /// Upload a single float to the shader as a uniform.
    /// Use this uniform in the shader using `uniform float <name>`.
    ///
    /// ## Panics
    ///
    /// If the `uniform_name` passed could not be converted into a `CString`,
    /// this function will PANIC as it unwraps a `Result`.
    #[inline]
    pub fn uniform_1f(&self, uniform_name: &str, data: f32) {
        #[expect(
            clippy::unwrap_used,
            reason = "it is very rare that the library user will pass a `&str` that cannot be converted to a `CString`"
        )]
        let location = unsafe {
            gl::GetUniformLocation(self.gl_id, CString::new(uniform_name).unwrap().as_ptr())
        };
        unsafe {
            gl::Uniform1f(location, data);
        }
    }

    /// Upload a vec2 of floats to the shader as a uniform.
    /// Use this uniform in the shader using `uniform vec2 <name>`.
    ///
    /// ## Panics
    ///
    /// If the `uniform_name` passed could not be converted into a `CString`,
    /// this function will PANIC as it unwraps a `Result`.
    #[inline]
    pub fn uniform_2f(&self, uniform_name: &str, data: (f32, f32)) {
        #[expect(
            clippy::unwrap_used,
            reason = "it is very rare that the library user will pass a `&str` that cannot be converted to a `CString`"
        )]
        let location = unsafe {
            gl::GetUniformLocation(self.gl_id, CString::new(uniform_name).unwrap().as_ptr())
        };
        unsafe {
            gl::Uniform2f(location, data.0, data.1);
        }
    }

    /// Upload a vec3 of floats to the shader as a uniform.
    /// Use this uniform in the shader using `uniform vec3 <name>`.
    ///
    /// ## Panics
    ///
    /// If the `uniform_name` passed could not be converted into a `CString`,
    /// this function will PANIC as it unwraps a `Result`.
    #[inline]
    pub fn uniform_3f(&self, uniform_name: &str, data: (f32, f32, f32)) {
        #[expect(
            clippy::unwrap_used,
            reason = "it is very rare that the library user will pass a `&str` that cannot be converted to a `CString`"
        )]
        let location = unsafe {
            gl::GetUniformLocation(self.gl_id, CString::new(uniform_name).unwrap().as_ptr())
        };
        unsafe {
            gl::Uniform3f(location, data.0, data.1, data.2);
        }
    }

    /// Upload a vec4 of floats to the shader as a uniform.
    /// Use this uniform in the shader using `uniform vec4 <name>`.
    ///
    /// ## Panics
    ///
    /// If the `uniform_name` passed could not be converted into a `CString`,
    /// this function will PANIC as it unwraps a `Result`.
    #[inline]
    pub fn uniform_4f(&self, uniform_name: &str, data: (f32, f32, f32, f32)) {
        #[expect(
            clippy::unwrap_used,
            reason = "it is very rare that the library user will pass a `&str` that cannot be converted to a `CString`"
        )]
        let location = unsafe {
            gl::GetUniformLocation(self.gl_id, CString::new(uniform_name).unwrap().as_ptr())
        };
        unsafe {
            gl::Uniform4f(location, data.0, data.1, data.2, data.3);
        }
    }

    /// Upload a single integer (i32) to the shader as a uniform.
    /// Use this uniform in the shader using `uniform int <name>`.
    ///
    /// ## Panics
    ///
    /// If the `uniform_name` passed could not be converted into a `CString`,
    /// this function will PANIC as it unwraps a `Result`.
    #[inline]
    pub fn uniform_1i(&self, uniform_name: &str, data: i32) {
        #[expect(
            clippy::unwrap_used,
            reason = "it is very rare that the library user will pass a `&str` that cannot be converted to a `CString`"
        )]
        let location = unsafe {
            gl::GetUniformLocation(self.gl_id, CString::new(uniform_name).unwrap().as_ptr())
        };
        unsafe {
            gl::Uniform1i(location, data);
        }
    }

    /// Upload a vec2 of integers (i32s) to the shader as a uniform.
    /// Use this uniform in the shader using `uniform ivec2 <name>`.
    ///
    /// ## Panics
    ///
    /// If the `uniform_name` passed could not be converted into a `CString`,
    /// this function will PANIC as it unwraps a `Result`.
    #[inline]
    pub fn uniform_2i(&self, uniform_name: &str, data: (i32, i32)) {
        #[expect(
            clippy::unwrap_used,
            reason = "it is very rare that the library user will pass a `&str` that cannot be converted to a `CString`"
        )]
        let location = unsafe {
            gl::GetUniformLocation(self.gl_id, CString::new(uniform_name).unwrap().as_ptr())
        };
        unsafe {
            gl::Uniform2i(location, data.0, data.1);
        }
    }

    /// Upload a vec3 of integers (i32s) to the shader as a uniform.
    /// Use this uniform in the shader using `uniform ivec3 <name>`.
    ///
    /// ## Panics
    ///
    /// If the `uniform_name` passed could not be converted into a `CString`,
    /// this function will PANIC as it unwraps a `Result`.
    #[inline]
    pub fn uniform_3i(&self, uniform_name: &str, data: (i32, i32, i32)) {
        #[expect(
            clippy::unwrap_used,
            reason = "it is very rare that the library user will pass a `&str` that cannot be converted to a `CString`"
        )]
        let location = unsafe {
            gl::GetUniformLocation(self.gl_id, CString::new(uniform_name).unwrap().as_ptr())
        };
        unsafe {
            gl::Uniform3i(location, data.0, data.1, data.2);
        }
    }

    /// Upload a vec4 of integers (i32s) to the shader as a uniform.
    /// Use this uniform in the shader using `uniform ivec4 <name>`.
    ///
    /// ## Panics
    ///
    /// If the `uniform_name` passed could not be converted into a `CString`,
    /// this function will PANIC as it unwraps a `Result`.
    #[inline]
    pub fn uniform_4i(&self, uniform_name: &str, data: (i32, i32, i32, i32)) {
        #[expect(
            clippy::unwrap_used,
            reason = "it is very rare that the library user will pass a `&str` that cannot be converted to a `CString`"
        )]
        let location = unsafe {
            gl::GetUniformLocation(self.gl_id, CString::new(uniform_name).unwrap().as_ptr())
        };
        unsafe {
            gl::Uniform4i(location, data.0, data.1, data.2, data.3);
        }
    }
}