1use std::cell::Cell;
2use std::rc::Rc;
3
4use glow::HasContext;
5
6use crate::drawing::Uniforms;
7use crate::{Canvas, Color, EventCtx, GeomBatch, GfxCtx, ScreenDims, ScreenRectangle};
8
9#[cfg(feature = "native-backend")]
10pub use crate::backend_glow_native::setup;
11
12#[cfg(feature = "wasm-backend")]
13pub use crate::backend_glow_wasm::setup;
14
15pub(crate) unsafe fn build_program(
16 gl: &glow::Context,
17 vertex_shader_src: &str,
18 fragment_shader_src: &str,
19) -> anyhow::Result<glow::Program> {
20 let program = gl.create_program().expect("Cannot create program");
21
22 let shaders = [
23 compile_shader(gl, glow::VERTEX_SHADER, vertex_shader_src)?,
24 compile_shader(gl, glow::FRAGMENT_SHADER, fragment_shader_src)?,
25 ];
26
27 for shader in &shaders {
28 gl.attach_shader(program, *shader);
29 }
30
31 gl.link_program(program);
32 if !gl.get_program_link_status(program) {
33 error!("Linking error: {}", gl.get_program_info_log(program));
34 panic!("{}", gl.get_program_info_log(program));
35 }
36
37 for shader in &shaders {
38 gl.detach_shader(program, *shader);
39 gl.delete_shader(*shader);
40 }
41
42 gl.use_program(Some(program));
43
44 gl.enable(glow::SCISSOR_TEST);
45 gl.enable(glow::DEPTH_TEST);
46 gl.depth_func(glow::LEQUAL);
47 gl.enable(glow::BLEND);
48 gl.blend_func_separate(
49 glow::ONE,
50 glow::ONE_MINUS_SRC_ALPHA,
51 glow::ONE_MINUS_DST_ALPHA,
52 glow::ONE,
53 );
54
55 Ok(program)
56}
57
58unsafe fn compile_shader(
59 gl: &glow::Context,
60 shader_type: u32,
61 shader_source: &str,
62) -> anyhow::Result<glow::Shader> {
63 let shader = gl.create_shader(shader_type).map_err(|e| anyhow!(e))?;
64 gl.shader_source(shader, shader_source);
65 gl.compile_shader(shader);
66 if !gl.get_shader_compile_status(shader) {
67 bail!("error compiling shader: {}", gl.get_shader_info_log(shader));
68 }
69
70 Ok(shader)
71}
72
73pub struct GfxCtxInnards<'a> {
75 gl: &'a glow::Context,
76 current_clip: Option<[i32; 4]>,
77 transform_location: <glow::Context as glow::HasContext>::UniformLocation,
78 window_location: <glow::Context as glow::HasContext>::UniformLocation,
79}
80
81impl<'a> GfxCtxInnards<'a> {
82 pub fn new(
83 gl: &'a glow::Context,
84 program: &'a <glow::Context as glow::HasContext>::Program,
85 ) -> Self {
86 let (transform_location, window_location) = unsafe {
87 (
88 gl.get_uniform_location(*program, "transform").unwrap(),
89 gl.get_uniform_location(*program, "window").unwrap(),
90 )
91 };
92 GfxCtxInnards {
93 gl,
94 current_clip: None,
95 transform_location,
96 window_location,
97 }
98 }
99
100 pub fn clear(&mut self, color: Color) {
101 unsafe {
102 self.gl.clear_color(color.r, color.g, color.b, color.a);
103 self.gl.clear(glow::COLOR_BUFFER_BIT);
104
105 self.gl.clear_depth_f32(1.0);
106 self.gl.clear(glow::DEPTH_BUFFER_BIT);
107 }
108 }
109
110 pub fn redraw(&mut self, obj: &Drawable, uniforms: &Uniforms, _: &PrerenderInnards) {
111 unsafe {
112 self.gl
113 .uniform_3_f32_slice(Some(&self.transform_location), &uniforms.transform);
114 self.gl
115 .uniform_3_f32_slice(Some(&self.window_location), &uniforms.window);
116
117 self.gl.bind_vertex_array(Some(obj.vert_array.id));
118 self.gl
119 .draw_elements(glow::TRIANGLES, obj.num_indices, glow::UNSIGNED_INT, 0);
120 self.gl.bind_vertex_array(None);
121 }
122 }
123
124 pub fn enable_clipping(&mut self, rect: ScreenRectangle, scale_factor: f64, canvas: &Canvas) {
125 assert!(self.current_clip.is_none());
126 let left = (rect.x1 * scale_factor) as i32;
128 let bottom = ((canvas.window_height - rect.y2) * scale_factor) as i32;
130 let width = ((rect.x2 - rect.x1) * scale_factor) as i32;
131 let height = ((rect.y2 - rect.y1) * scale_factor) as i32;
132 unsafe {
133 self.gl.scissor(left, bottom, width, height);
134 }
135 self.current_clip = Some([left, bottom, width, height]);
136 }
137
138 pub fn disable_clipping(&mut self, scale_factor: f64, canvas: &Canvas) {
139 assert!(self.current_clip.is_some());
140 self.current_clip = None;
141 unsafe {
142 self.gl.scissor(
143 0,
144 0,
145 (canvas.window_width * scale_factor) as i32,
146 (canvas.window_height * scale_factor) as i32,
147 );
148 }
149 }
150
151 pub fn take_clip(&mut self, scale_factor: f64, canvas: &Canvas) -> Option<[i32; 4]> {
152 let clip = self.current_clip?;
153 self.disable_clipping(scale_factor, canvas);
154 Some(clip)
155 }
156
157 pub fn restore_clip(&mut self, clip: Option<[i32; 4]>) {
158 self.current_clip = clip;
159 if let Some(c) = clip {
160 unsafe {
161 self.gl.scissor(c[0], c[1], c[2], c[3]);
162 }
163 }
164 }
165}
166
167pub struct Drawable {
170 vert_array: VertexArray,
171 vert_buffer: Buffer,
172 elem_buffer: Buffer,
173 num_indices: i32,
174 gl: Rc<glow::Context>,
175}
176
177impl Drop for Drawable {
178 #[inline]
179 fn drop(&mut self) {
180 self.elem_buffer.destroy(&self.gl);
181 self.vert_buffer.destroy(&self.gl);
182 self.vert_array.destroy(&self.gl);
183 }
184}
185
186impl Drawable {
187 pub fn empty(ctx: &EventCtx) -> Drawable {
189 ctx.upload(GeomBatch::new())
190 }
191
192 pub fn draw(&self, g: &mut GfxCtx) {
193 g.redraw(self);
194 }
195}
196
197struct VertexArray {
198 id: <glow::Context as glow::HasContext>::VertexArray,
199 was_destroyed: bool,
200}
201
202impl VertexArray {
203 fn new(gl: &glow::Context) -> VertexArray {
204 let id = unsafe { gl.create_vertex_array().unwrap() };
205 VertexArray {
206 id,
207 was_destroyed: false,
208 }
209 }
210
211 fn destroy(&mut self, gl: &glow::Context) {
212 assert!(!self.was_destroyed, "already destroyed");
213 self.was_destroyed = true;
214 unsafe {
215 gl.delete_vertex_array(self.id);
216 }
217 }
218}
219
220impl Drop for VertexArray {
221 fn drop(&mut self) {
222 assert!(
223 self.was_destroyed,
224 "failed to call `destroy` before dropped. Memory leaked."
225 );
226 }
227}
228
229struct Buffer {
230 id: <glow::Context as glow::HasContext>::Buffer,
231 was_destroyed: bool,
232}
233
234impl Buffer {
235 fn new(gl: &glow::Context) -> Buffer {
236 let id = unsafe { gl.create_buffer().unwrap() };
237 Buffer {
238 id,
239 was_destroyed: false,
240 }
241 }
242
243 fn destroy(&mut self, gl: &glow::Context) {
244 assert!(!self.was_destroyed, "already destroyed");
245 self.was_destroyed = true;
246 unsafe { gl.delete_buffer(self.id) };
247 }
248}
249
250impl Drop for Buffer {
251 fn drop(&mut self) {
252 assert!(
253 self.was_destroyed,
254 "failed to call `destroy` before dropped. Memory leaked."
255 );
256 }
257}
258
259#[cfg(feature = "wasm-backend")]
260type WindowAdapter = crate::backend_glow_wasm::WindowAdapter;
261
262#[cfg(feature = "native-backend")]
263type WindowAdapter = crate::backend_glow_native::WindowAdapter;
264
265pub struct PrerenderInnards {
266 gl: Rc<glow::Context>,
267 is_gl2: bool,
268 window_adapter: Option<WindowAdapter>,
269 program: <glow::Context as glow::HasContext>::Program,
270
271 pub total_bytes_uploaded: Cell<usize>,
274}
275
276impl PrerenderInnards {
277 pub fn new(
278 gl: glow::Context,
279 is_gl2: bool,
280 program: <glow::Context as glow::HasContext>::Program,
281 window_adapter: Option<WindowAdapter>,
282 ) -> PrerenderInnards {
283 PrerenderInnards {
284 gl: Rc::new(gl),
285 is_gl2,
286 program,
287 window_adapter,
288 total_bytes_uploaded: Cell::new(0),
289 }
290 }
291
292 pub fn actually_upload(&self, permanent: bool, batch: GeomBatch) -> Drawable {
293 let mut vertices: Vec<[f32; 8]> = Vec::new();
294 let mut indices: Vec<u32> = Vec::new();
295
296 for (color, poly, z) in batch.consume() {
297 let idx_offset = vertices.len() as u32;
298 let (pts, raw_indices) = poly.consume();
299 for pt in pts {
300 let style = color.shader_style(pt);
301 vertices.push([
302 pt.x() as f32,
303 pt.y() as f32,
304 z as f32,
305 style[0],
306 style[1],
307 style[2],
308 style[3],
309 style[4],
310 ]);
311 }
312 for idx in raw_indices {
313 indices.push(idx_offset + (idx as u32));
314 }
315 }
316
317 let (vert_buffer, vert_array, elem_buffer) = unsafe {
318 let vert_array = VertexArray::new(&self.gl);
319 let vert_buffer = Buffer::new(&self.gl);
320 let elem_buffer = Buffer::new(&self.gl);
321
322 self.gl.bind_vertex_array(Some(vert_array.id));
323
324 self.gl
325 .bind_buffer(glow::ARRAY_BUFFER, Some(vert_buffer.id));
326 self.gl.buffer_data_u8_slice(
327 glow::ARRAY_BUFFER,
328 vertices.align_to::<u8>().1,
329 glow::STATIC_DRAW,
331 );
332
333 self.gl
334 .bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(elem_buffer.id));
335 self.gl.buffer_data_u8_slice(
336 glow::ELEMENT_ARRAY_BUFFER,
337 indices.align_to::<u8>().1,
338 glow::STATIC_DRAW,
339 );
340
341 let vertex_attributes: [i32; 3] = [
342 3, 4, 1, ];
346 let stride = vertex_attributes.iter().sum::<i32>() * std::mem::size_of::<f32>() as i32;
347 let mut offset = 0;
348 for (i, size) in vertex_attributes.iter().enumerate() {
349 self.gl.enable_vertex_attrib_array(i as u32);
350 self.gl.vertex_attrib_pointer_f32(
351 i as u32,
352 *size,
353 glow::FLOAT,
354 false,
355 stride,
356 offset,
357 );
358 offset += size * std::mem::size_of::<f32>() as i32;
359 }
360
361 self.gl.bind_vertex_array(None);
363 self.gl.bind_buffer(glow::ARRAY_BUFFER, None);
364 self.gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None);
365
366 (vert_buffer, vert_array, elem_buffer)
367 };
368 let num_indices = indices.len() as i32;
369
370 if permanent {
371 }
377
378 Drawable {
379 vert_array,
380 vert_buffer,
381 elem_buffer,
382 num_indices,
383 gl: self.gl.clone(),
384 }
385 }
386
387 pub(crate) fn window(&self) -> &winit::window::Window {
388 self.window_adapter.as_ref().expect("no window").window()
389 }
390
391 pub fn request_redraw(&self) {
392 self.window().request_redraw();
393 }
394
395 pub fn set_cursor_icon(&self, icon: winit::window::CursorIcon) {
396 self.window().set_cursor_icon(icon);
397 }
398
399 pub fn set_cursor_visible(&self, visible: bool) {
400 self.window().set_cursor_visible(visible);
401 }
402
403 pub fn draw_new_frame(&self) -> GfxCtxInnards {
404 GfxCtxInnards::new(&self.gl, &self.program)
405 }
406
407 pub fn window_resized(&self, new_size: ScreenDims, scale_factor: f64) {
408 let physical_size = winit::dpi::LogicalSize::from(new_size).to_physical(scale_factor);
409 self.window_adapter
410 .as_ref()
411 .expect("no window")
412 .window_resized(new_size, scale_factor);
413 unsafe {
414 self.gl
415 .viewport(0, 0, physical_size.width, physical_size.height);
416 self.gl
418 .scissor(0, 0, physical_size.width, physical_size.height);
419 }
420 }
421
422 pub fn window_size(&self, scale_factor: f64) -> ScreenDims {
423 self.window().inner_size().to_logical(scale_factor).into()
424 }
425
426 pub fn set_window_icon(&self, icon: winit::window::Icon) {
427 self.window().set_window_icon(Some(icon));
428 }
429
430 pub fn monitor_scale_factor(&self) -> f64 {
431 self.window().scale_factor()
432 }
433
434 pub fn draw_finished(&self, gfx_ctx_innards: GfxCtxInnards) {
435 self.window_adapter
436 .as_ref()
437 .expect("no window")
438 .draw_finished(gfx_ctx_innards)
439 }
440
441 pub(crate) fn screencap(&self, dims: ScreenDims, filename: String) -> anyhow::Result<()> {
442 let width = dims.width as u32;
443 let height = dims.height as u32;
444
445 let mut img = image::DynamicImage::new_rgba8(width, height);
446 let pixels = img.as_mut_rgba8().unwrap();
447
448 unsafe {
449 self.gl.pixel_store_i32(glow::PACK_ALIGNMENT, 1);
450 self.gl.read_pixels(
452 0,
453 0,
454 width as i32,
455 height as i32,
456 glow::RGBA,
457 glow::UNSIGNED_BYTE,
458 glow::PixelPackData::Slice(pixels),
459 );
460 }
461
462 image::save_buffer(
463 &filename,
464 &image::imageops::flip_vertical(&img),
465 width,
466 height,
467 image::ColorType::Rgba8,
468 )?;
469 Ok(())
470 }
471
472 #[allow(unused)]
473 pub fn use_program_for_renderonly(&self) {
474 unsafe {
475 self.gl.use_program(Some(self.program));
476 }
477 }
478
479 pub fn upload_texture(&self, texture: SpriteTexture, scale: (f32, f32)) {
480 if self.is_gl2 {
481 texture
482 .upload_gl2(&self.gl)
483 .expect("failed to upload textures");
484
485 unsafe {
486 let location = self
487 .gl
488 .get_uniform_location(self.program, "texture_scale")
489 .unwrap();
490 self.gl
491 .uniform_2_f32_slice(Some(&location), &[scale.0, scale.1]);
492 }
493 } else {
494 warn!(
495 "texture uploading for WebGL 1.0 is not yet supported. Enable WebGL 2.0 on your \
496 browser."
497 );
498 }
499 }
500}
501
502pub struct SpriteTexture {
546 texture_bytes: Vec<u8>,
547 sprite_width: u32,
548 sprite_height: u32,
549 sprite_count: u32,
550}
551
552impl SpriteTexture {
553 pub fn new(
554 sprite_bytes: Vec<u8>,
555 sprite_width: u32,
556 sprite_height: u32,
557 ) -> anyhow::Result<Self> {
558 let dynamic_img = image::load_from_memory(&sprite_bytes)?;
559
560 let img = if let image::DynamicImage::ImageRgba8(img) = dynamic_img {
561 img
562 } else {
563 todo!("support other image formats");
564 };
565
566 let bytes_per_pixel = 4;
567
568 let (img_width, img_height) = img.dimensions();
569 let sprites_per_row = img_width / sprite_width;
570 let sprites_per_column = img_height / sprite_height;
571 let sprite_count = sprites_per_row * sprites_per_column;
572
573 assert_eq!(
574 sprites_per_row * sprite_width,
575 img_width,
576 "sprites must align exactly"
577 );
578 assert_eq!(
579 sprites_per_column * sprite_height,
580 img_height,
581 "sprites must align exactly"
582 );
583
584 info!(
585 "img_size: {}x{}px ({} px), sprite_size: {}x{}px, sprites: {}x{} ({} sprites)",
586 img_width,
587 img_height,
588 img.pixels().len(),
589 sprite_width,
590 sprite_height,
591 sprites_per_row,
592 sprites_per_column,
593 sprite_count
594 );
595
596 let mut texture_bytes: Vec<u8> = Vec::with_capacity(img.pixels().len() * bytes_per_pixel);
597
598 texture_bytes.append(&mut vec![
602 255;
603 (sprite_width * sprite_height) as usize
604 * bytes_per_pixel
605 ]);
606
607 use image::GenericImageView;
608 for y in 0..sprites_per_column {
609 for x in 0..sprites_per_row {
610 let sprite_cell = img.view(
611 x * sprite_width,
612 y * sprite_height,
613 sprite_width,
614 sprite_height,
615 );
616 for p in sprite_cell.pixels() {
617 texture_bytes.extend_from_slice(&p.2 .0);
618 }
619 }
620 }
621
622 Ok(Self {
623 texture_bytes,
624 sprite_width,
625 sprite_height,
626 sprite_count,
627 })
628 }
629
630 fn upload_gl2(&self, gl: &glow::Context) -> anyhow::Result<()> {
632 let texture_id = unsafe {
633 gl.create_texture()
634 .map_err(|err| anyhow!("error creating texture: {}", err))?
635 };
636
637 let format = glow::RGBA;
638 let target = glow::TEXTURE_2D_ARRAY;
639 let mipmap_levels: u32 = 2;
640 let internal_format = glow::RGBA;
641
642 unsafe {
643 gl.bind_texture(target, Some(texture_id));
644 }
645
646 unsafe {
648 gl.tex_storage_3d(
649 target,
650 mipmap_levels as i32,
651 internal_format,
652 self.sprite_width as i32,
653 self.sprite_height as i32,
654 self.sprite_count as i32,
655 );
656 }
657
658 unsafe {
659 for mipmap_level in 0..mipmap_levels {
661 let width = self.sprite_width as i32 / 2i32.pow(mipmap_level);
662 let height = self.sprite_height as i32 / 2i32.pow(mipmap_level);
663 gl.tex_image_3d(
664 target,
665 mipmap_level as i32,
666 format as i32,
667 width,
668 height,
669 self.sprite_count as i32,
670 0,
671 format,
672 glow::UNSIGNED_BYTE,
673 Some(&self.texture_bytes),
674 );
675 }
676 gl.generate_mipmap(target);
677 }
678
679 Ok(())
680 }
681}