pyxel/
graphics.rs

1use std::collections::HashMap;
2use std::mem::size_of;
3
4use cfg_if::cfg_if;
5use glow::{HasContext, PixelUnpackData};
6
7use crate::font::SharedFont;
8use crate::image::Color;
9use crate::pyxel::Pyxel;
10use crate::settings::{BACKGROUND_COLOR, MAX_COLORS, NUM_SCREEN_TYPES};
11
12cfg_if! {
13    if #[cfg(target_os = "macos")] {
14        const GL_VERSION: &str = include_str!("shaders/gles_version.glsl");
15    } else {
16        const GL_VERSION: &str = include_str!("shaders/gl_version.glsl");
17    }
18}
19
20const GLES_VERSION: &str = include_str!("shaders/gles_version.glsl");
21const COMMON_VERT: &str = include_str!("shaders/common.vert");
22const COMMON_FRAG: &str = include_str!("shaders/common.frag");
23const SCREEN_FRAGS: [&str; NUM_SCREEN_TYPES as usize] = [
24    include_str!("shaders/crisp.frag"),
25    include_str!("shaders/smooth.frag"),
26    include_str!("shaders/retro.frag"),
27];
28
29pub struct ScreenShader {
30    shader_program: glow::Program,
31    uniform_locations: HashMap<String, glow::UniformLocation>,
32    vertex_array: glow::VertexArray,
33}
34
35pub struct Graphics {
36    screen_shaders: Vec<ScreenShader>,
37    screen_texture: glow::NativeTexture,
38    colors_texture: glow::NativeTexture,
39}
40
41impl Graphics {
42    pub fn new() -> Self {
43        unsafe {
44            let gl = pyxel_platform::glow_context();
45            gl.disable(glow::FRAMEBUFFER_SRGB);
46            gl.disable(glow::BLEND);
47
48            let screen_shaders = Self::create_screen_shaders(gl);
49            let screen_texture = Self::create_screen_texture(gl);
50            let colors_texture = Self::create_colors_texture(gl);
51
52            Self {
53                screen_shaders,
54                screen_texture,
55                colors_texture,
56            }
57        }
58    }
59
60    unsafe fn create_screen_shaders(gl: &mut glow::Context) -> Vec<ScreenShader> {
61        let glsl_version = if pyxel_platform::is_gles_enabled() {
62            GLES_VERSION
63        } else {
64            GL_VERSION
65        };
66
67        let mut screen_shaders = Vec::new();
68        for &screen_frag in &SCREEN_FRAGS {
69            // Vertex shader
70            let vertex_shader = gl.create_shader(glow::VERTEX_SHADER).unwrap();
71            gl.shader_source(vertex_shader, &format!("{glsl_version}{COMMON_VERT}"));
72            gl.compile_shader(vertex_shader);
73            assert!(
74                gl.get_shader_compile_status(vertex_shader),
75                "\n[vertex shader]\n{}",
76                gl.get_shader_info_log(vertex_shader)
77            );
78
79            // Fragment shader
80            let fragment_shader = gl.create_shader(glow::FRAGMENT_SHADER).unwrap();
81            gl.shader_source(
82                fragment_shader,
83                &format!("{glsl_version}{COMMON_FRAG}{screen_frag}"),
84            );
85            gl.compile_shader(fragment_shader);
86            assert!(
87                gl.get_shader_compile_status(fragment_shader),
88                "\n[fragment shader]\n{}",
89                gl.get_shader_info_log(fragment_shader)
90            );
91
92            // Shader program
93            let shader_program = gl.create_program().unwrap();
94            gl.attach_shader(shader_program, vertex_shader);
95            gl.attach_shader(shader_program, fragment_shader);
96            gl.link_program(shader_program);
97            assert!(
98                gl.get_program_link_status(shader_program),
99                "{}",
100                gl.get_program_info_log(shader_program)
101            );
102            gl.detach_shader(shader_program, vertex_shader);
103            gl.delete_shader(vertex_shader);
104            gl.detach_shader(shader_program, fragment_shader);
105            gl.delete_shader(fragment_shader);
106
107            // Uniform locations
108            let mut uniform_locations: HashMap<String, glow::UniformLocation> = HashMap::new();
109            let uniform_names = [
110                "u_screenPos",
111                "u_screenSize",
112                "u_screenScale",
113                "u_numColors",
114                "u_backgroundColor",
115                "u_screenTexture",
116                "u_colorsTexture",
117            ];
118
119            for &uniform_name in &uniform_names {
120                if let Some(location) = gl.get_uniform_location(shader_program, uniform_name) {
121                    uniform_locations.insert(uniform_name.to_string(), location);
122                }
123            }
124
125            // Vertex array
126            let vertices: [f32; 8] = [-1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0, -1.0];
127            let vertex_array = gl.create_vertex_array().unwrap();
128            let vertex_buffer = gl.create_buffer().unwrap();
129
130            gl.bind_vertex_array(Some(vertex_array));
131            gl.bind_buffer(glow::ARRAY_BUFFER, Some(vertex_buffer));
132            gl.buffer_data_u8_slice(
133                glow::ARRAY_BUFFER,
134                vertices.align_to::<u8>().1,
135                glow::STATIC_DRAW,
136            );
137
138            let position = gl.get_attrib_location(shader_program, "position").unwrap();
139            gl.vertex_attrib_pointer_f32(
140                position,
141                2,
142                glow::FLOAT,
143                false,
144                2 * size_of::<f32>() as i32,
145                0,
146            );
147            gl.enable_vertex_attrib_array(position);
148
149            // Add screen shader
150            screen_shaders.push(ScreenShader {
151                shader_program,
152                uniform_locations,
153                vertex_array,
154            });
155        }
156
157        screen_shaders
158    }
159
160    unsafe fn create_screen_texture(gl: &mut glow::Context) -> glow::NativeTexture {
161        let screen_texture = gl.create_texture().unwrap();
162        gl.active_texture(glow::TEXTURE0);
163        gl.bind_texture(glow::TEXTURE_2D, Some(screen_texture));
164
165        gl.tex_parameter_i32(
166            glow::TEXTURE_2D,
167            glow::TEXTURE_MIN_FILTER,
168            glow::NEAREST as i32,
169        );
170        gl.tex_parameter_i32(
171            glow::TEXTURE_2D,
172            glow::TEXTURE_MAG_FILTER,
173            glow::NEAREST as i32,
174        );
175        gl.tex_parameter_i32(
176            glow::TEXTURE_2D,
177            glow::TEXTURE_WRAP_S,
178            glow::CLAMP_TO_EDGE as i32,
179        );
180        gl.tex_parameter_i32(
181            glow::TEXTURE_2D,
182            glow::TEXTURE_WRAP_T,
183            glow::CLAMP_TO_EDGE as i32,
184        );
185
186        screen_texture
187    }
188
189    unsafe fn create_colors_texture(gl: &mut glow::Context) -> glow::NativeTexture {
190        let colors_texture = gl.create_texture().unwrap();
191        gl.active_texture(glow::TEXTURE1);
192        gl.bind_texture(glow::TEXTURE_2D, Some(colors_texture));
193
194        gl.tex_parameter_i32(
195            glow::TEXTURE_2D,
196            glow::TEXTURE_MIN_FILTER,
197            glow::NEAREST as i32,
198        );
199        gl.tex_parameter_i32(
200            glow::TEXTURE_2D,
201            glow::TEXTURE_MAG_FILTER,
202            glow::NEAREST as i32,
203        );
204        gl.tex_parameter_i32(
205            glow::TEXTURE_2D,
206            glow::TEXTURE_WRAP_S,
207            glow::CLAMP_TO_EDGE as i32,
208        );
209        gl.tex_parameter_i32(
210            glow::TEXTURE_2D,
211            glow::TEXTURE_WRAP_T,
212            glow::CLAMP_TO_EDGE as i32,
213        );
214
215        colors_texture
216    }
217}
218
219impl Pyxel {
220    pub fn clip(&self, x: f64, y: f64, width: f64, height: f64) {
221        self.screen.lock().clip(x, y, width, height);
222    }
223
224    pub fn clip0(&self) {
225        self.screen.lock().clip0();
226    }
227
228    pub fn camera(&self, x: f64, y: f64) {
229        self.screen.lock().camera(x, y);
230    }
231
232    pub fn camera0(&self) {
233        self.screen.lock().camera0();
234    }
235
236    pub fn pal(&self, src_color: Color, dst_color: Color) {
237        self.screen.lock().pal(src_color, dst_color);
238    }
239
240    pub fn pal0(&self) {
241        self.screen.lock().pal0();
242    }
243
244    pub fn dither(&self, alpha: f32) {
245        self.screen.lock().dither(alpha);
246    }
247
248    pub fn cls(&self, color: Color) {
249        self.screen.lock().cls(color);
250    }
251
252    pub fn pget(&self, x: f64, y: f64) -> Color {
253        self.screen.lock().pget(x, y)
254    }
255
256    pub fn pset(&self, x: f64, y: f64, color: Color) {
257        self.screen.lock().pset(x, y, color);
258    }
259
260    pub fn line(&self, x1: f64, y1: f64, x2: f64, y2: f64, color: Color) {
261        self.screen.lock().line(x1, y1, x2, y2, color);
262    }
263
264    pub fn rect(&self, x: f64, y: f64, width: f64, height: f64, color: Color) {
265        self.screen.lock().rect(x, y, width, height, color);
266    }
267
268    pub fn rectb(&self, x: f64, y: f64, width: f64, height: f64, color: Color) {
269        self.screen.lock().rectb(x, y, width, height, color);
270    }
271
272    pub fn circ(&self, x: f64, y: f64, radius: f64, color: Color) {
273        self.screen.lock().circ(x, y, radius, color);
274    }
275
276    pub fn circb(&self, x: f64, y: f64, radius: f64, color: Color) {
277        self.screen.lock().circb(x, y, radius, color);
278    }
279
280    pub fn elli(&self, x: f64, y: f64, width: f64, height: f64, color: Color) {
281        self.screen.lock().elli(x, y, width, height, color);
282    }
283
284    pub fn ellib(&self, x: f64, y: f64, width: f64, height: f64, color: Color) {
285        self.screen.lock().ellib(x, y, width, height, color);
286    }
287
288    pub fn tri(&self, x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64, color: Color) {
289        self.screen.lock().tri(x1, y1, x2, y2, x3, y3, color);
290    }
291
292    pub fn trib(&self, x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64, color: Color) {
293        self.screen.lock().trib(x1, y1, x2, y2, x3, y3, color);
294    }
295
296    pub fn fill(&self, x: f64, y: f64, color: Color) {
297        self.screen.lock().fill(x, y, color);
298    }
299
300    pub fn blt(
301        &self,
302        x: f64,
303        y: f64,
304        image_index: u32,
305        image_x: f64,
306        image_y: f64,
307        width: f64,
308        height: f64,
309        color_key: Option<Color>,
310        rotate: Option<f64>,
311        scale: Option<f64>,
312    ) {
313        self.screen.lock().blt(
314            x,
315            y,
316            self.images.lock()[image_index as usize].clone(),
317            image_x,
318            image_y,
319            width,
320            height,
321            color_key,
322            rotate,
323            scale,
324        );
325    }
326
327    pub fn bltm(
328        &self,
329        x: f64,
330        y: f64,
331        tilemap_index: u32,
332        tilemap_x: f64,
333        tilemap_y: f64,
334        width: f64,
335        height: f64,
336        color_key: Option<Color>,
337        rotate: Option<f64>,
338        scale: Option<f64>,
339    ) {
340        self.screen.lock().bltm(
341            x,
342            y,
343            self.tilemaps.lock()[tilemap_index as usize].clone(),
344            tilemap_x,
345            tilemap_y,
346            width,
347            height,
348            color_key,
349            rotate,
350            scale,
351        );
352    }
353
354    pub fn text(&self, x: f64, y: f64, string: &str, color: Color, font: Option<SharedFont>) {
355        self.screen.lock().text(x, y, string, color, font);
356    }
357
358    pub(crate) fn render_screen(&mut self) {
359        unsafe {
360            let gl = pyxel_platform::glow_context();
361            self.set_viewport(gl);
362            self.use_screen_shader(gl);
363            self.bind_screen_texture(gl);
364            self.bind_colors_texture(gl);
365            gl.draw_arrays(glow::TRIANGLE_STRIP, 0, 4);
366            pyxel_platform::swap_window();
367        }
368    }
369
370    unsafe fn set_viewport(&self, gl: &mut glow::Context) {
371        let (window_width, window_height) = pyxel_platform::window_size();
372        gl.viewport(0, 0, window_width as i32, window_height as i32);
373    }
374
375    unsafe fn use_screen_shader(&self, gl: &mut glow::Context) {
376        let shader = &self.graphics.screen_shaders[self.system.screen_mode as usize];
377        gl.use_program(Some(shader.shader_program));
378        let uniform_locations = &shader.uniform_locations;
379
380        if let Some(location) = uniform_locations.get("u_screenPos") {
381            let (_, window_height) = pyxel_platform::window_size();
382            gl.uniform_2_f32(
383                Some(location),
384                self.system.screen_x as f32,
385                (window_height as i32
386                    - self.system.screen_y
387                    - (self.height as f64 * self.system.screen_scale) as i32)
388                    as f32,
389            );
390        }
391
392        if let Some(location) = uniform_locations.get("u_screenSize") {
393            gl.uniform_2_f32(
394                Some(location),
395                (self.width as f64 * self.system.screen_scale) as f32,
396                (self.height as f64 * self.system.screen_scale) as f32,
397            );
398        }
399
400        if let Some(location) = uniform_locations.get("u_screenScale") {
401            gl.uniform_1_f32(Some(location), self.system.screen_scale as f32);
402        }
403
404        if let Some(location) = uniform_locations.get("u_numColors") {
405            gl.uniform_1_i32(Some(location), self.colors.lock().len() as i32);
406        }
407
408        if let Some(location) = uniform_locations.get("u_backgroundColor") {
409            gl.uniform_3_f32(
410                Some(location),
411                ((BACKGROUND_COLOR >> 16) as u8) as f32 / 255.0,
412                ((BACKGROUND_COLOR >> 8) as u8) as f32 / 255.0,
413                (BACKGROUND_COLOR as u8) as f32 / 255.0,
414            );
415        }
416
417        if let Some(location) = uniform_locations.get("u_screenTexture") {
418            gl.uniform_1_i32(Some(location), 0);
419        }
420
421        if let Some(location) = uniform_locations.get("u_colorsTexture") {
422            gl.uniform_1_i32(Some(location), 1);
423        }
424
425        gl.bind_vertex_array(Some(shader.vertex_array));
426    }
427
428    unsafe fn bind_screen_texture(&self, gl: &mut glow::Context) {
429        gl.active_texture(glow::TEXTURE0);
430        gl.bind_texture(glow::TEXTURE_2D, Some(self.graphics.screen_texture));
431        gl.pixel_store_i32(glow::UNPACK_ALIGNMENT, 1);
432
433        let texture_format = if pyxel_platform::is_gles_enabled() {
434            glow::LUMINANCE
435        } else {
436            glow::RED
437        };
438
439        gl.tex_image_2d(
440            glow::TEXTURE_2D,
441            0,
442            texture_format as i32,
443            self.width as i32,
444            self.height as i32,
445            0,
446            texture_format,
447            glow::UNSIGNED_BYTE,
448            PixelUnpackData::Slice(Some(&self.screen.lock().canvas.data)),
449        );
450    }
451
452    #[allow(clippy::uninlined_format_args)]
453    unsafe fn bind_colors_texture(&self, gl: &mut glow::Context) {
454        gl.active_texture(glow::TEXTURE1);
455        gl.bind_texture(glow::TEXTURE_2D, Some(self.graphics.colors_texture));
456        gl.pixel_store_i32(glow::UNPACK_ALIGNMENT, 4);
457
458        let colors = self.colors.lock();
459        assert!(
460            !colors.is_empty() && colors.len() <= MAX_COLORS as usize,
461            "Number of colors must be between 1 to {}",
462            MAX_COLORS
463        );
464
465        let mut pixels: Vec<u8> = Vec::with_capacity(colors.len() * 3);
466        for color in &*colors {
467            pixels.push((color >> 16) as u8);
468            pixels.push((color >> 8) as u8);
469            pixels.push(*color as u8);
470        }
471
472        gl.tex_image_2d(
473            glow::TEXTURE_2D,
474            0,
475            glow::RGB as i32,
476            colors.len() as i32,
477            1,
478            0,
479            glow::RGB,
480            glow::UNSIGNED_BYTE,
481            PixelUnpackData::Slice(Some(&pixels)),
482        );
483    }
484}