widgetry/
backend_glow.rs

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
73// Represents one frame that's gonna be drawn
74pub 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        // The scissor rectangle is in units of physical pixles, as opposed to logical pixels
127        let left = (rect.x1 * scale_factor) as i32;
128        // Y-inversion
129        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
167/// Geometry that's been uploaded to the GPU once and can be quickly redrawn many times. Create by
168/// creating a `GeomBatch` and calling `ctx.upload(batch)`.
169pub 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    /// This has no effect when drawn.
188    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    // TODO Prerender doesn't know what things are temporary and permanent. Could make the API more
272    // detailed.
273    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                // TODO Use permanent
330                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, // position is vec2
343                4, // color is vec4
344                1, // texture_id is float
345            ];
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            // Safety?
362            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            /*self.total_bytes_uploaded.set(
372                self.total_bytes_uploaded.get()
373                    + vertex_buffer.get_size()
374                    + index_buffer.get_size(),
375            );*/
376        }
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            // I think it's safe to assume there's not a clip right now.
417            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            // TODO This starts at lower-left, I think we need to use window height here
451            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
502/// Uploads a sprite sheet of textures to the GPU so they can be used by Fill::Texture and
503/// friends to paint shapes.
504///
505/// `path` - image file which is a grid of images.
506/// `sprite_length` - the width and height of an individual cell in the image grid
507///
508/// The image file can have any number of sprites, but they must all be the same size.
509///
510/// Once uploaded, textures are addressed by their id, starting from 1, from left to right, top to
511/// bottom, like so:
512///
513///   ┌─┬─┬─┐
514///   │1│2│3│
515///   ├─┼─┼─┤
516///   │4│5│6│
517///   ├─┼─┼─┤
518///   │7│8│9│
519///   └─┴─┴─┘
520///
521/// Texture(0) is reserved for a pure white (no-op) texture.
522///
523/// Implementation is based on the the description of ArrayTextures from:
524/// https://www.khronos.org/opengl/wiki/Array_Texture.
525///
526/// OpenGL texture arrays expect each texture's bytes to be contiguous, but it's conventional to
527/// store textures in a grid within a single spritesheet image, where a row and column traverses
528/// multiple sprites.
529///
530/// For example, if we had 6 textures, A-F, the input spritesheet bytes would be like:
531/// [[AAA, BBB, CCC],
532///  [AAA, BBB, CCC]
533///  [AAA, BBB, CCC],
534///  [DDD, EEE, FFF],
535///  [DDD, EEE, FFF],
536///  [DDD, EEE, FFF]]
537///
538/// Which we need to convert to:
539/// [[AAAAAAAAA],
540///  [BBBBBBBBB],
541///  [CCCCCCCCC],
542///  [DDDDDDDDD],
543///  [EEEEEEEEE],
544///  [FFFFFFFFF]]
545pub 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        // In order to avoid branching in our shader logic, all shapes are rendered with a texture.
599        // Even "non-textured" styles like Fill::Color, use a "default" no-op (pure white) texture,
600        // which we generate here.
601        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    // Utilizes `tex_storage_3d` which isn't supported by WebGL 1.0.
631    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        // Allocate the storage.
647        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            // Upload pixel data for each mipmap level
660            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}