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 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 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 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 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 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 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}