notan_glow/
lib.rs

1use glow::*;
2use hashbrown::HashMap;
3use notan_graphics::prelude::*;
4use notan_graphics::DeviceBackend;
5use std::any::Any;
6
7mod buffer;
8mod pipeline;
9mod render_target;
10mod texture;
11mod to_glow;
12mod utils;
13
14pub mod prelude;
15pub mod texture_source;
16
17#[cfg(target_arch = "wasm32")]
18mod html_image;
19
20use crate::buffer::Kind;
21use crate::pipeline::get_inner_attrs;
22use crate::texture::{texture_format, texture_type, TextureKey};
23use crate::texture_source::{add_empty_texture, add_texture_from_bytes, add_texture_from_image};
24use crate::to_glow::ToGlow;
25use buffer::InnerBuffer;
26use pipeline::{InnerPipeline, VertexAttributes};
27use render_target::InnerRenderTexture;
28use texture::InnerTexture;
29
30pub struct GlowBackend {
31    pub gl: Context,
32    buffer_count: u64,
33    texture_count: u64,
34    pipeline_count: u64,
35    render_target_count: u64,
36    size: (u32, u32),
37    dpi: f32,
38    pipelines: HashMap<u64, InnerPipeline>,
39    buffers: HashMap<u64, InnerBuffer>,
40    textures: HashMap<u64, InnerTexture>,
41    render_targets: HashMap<u64, InnerRenderTexture>,
42    using_indices: Option<IndexFormat>,
43    api_name: String,
44    current_pipeline: u64,
45    limits: Limits,
46    stats: GpuStats,
47    current_uniforms: Vec<UniformLocation>,
48    target_render_texture: Option<u64>,
49    render_texture_mipmaps: bool,
50}
51
52impl GlowBackend {
53    #[cfg(target_arch = "wasm32")]
54    pub fn new(
55        canvas: &web_sys::HtmlCanvasElement,
56        antialias: bool,
57        transparent: bool,
58    ) -> Result<Self, String> {
59        let (gl, api) = utils::create_gl_context(canvas, antialias, transparent)?;
60        Self::from(gl, &api)
61    }
62
63    #[cfg(all(
64        not(target_arch = "wasm32"),
65        not(target_os = "ios"),
66        not(target_os = "android")
67    ))]
68    pub fn new<F>(loader_function: F) -> Result<Self, String>
69    where
70        F: FnMut(&str) -> *const std::os::raw::c_void,
71    {
72        let gl = unsafe { Context::from_loader_function(loader_function) };
73
74        Self::from(gl, "opengl")
75    }
76
77    #[cfg(any(target_os = "ios", target_os = "android"))]
78    pub fn new<F>(mut loader_function: F) -> Result<Self, String>
79    where
80        F: FnMut(&str) -> *const std::os::raw::c_void,
81    {
82        let gl = unsafe { Context::from_loader_function(loader_function) };
83
84        Self::from(gl, "opengl_es")
85    }
86
87    fn from(gl: Context, api: &str) -> Result<Self, String> {
88        unsafe {
89            let version = gl.get_parameter_string(glow::VERSION);
90            let renderer = gl.get_parameter_string(glow::RENDERER);
91            let vendor = gl.get_parameter_string(glow::VENDOR);
92            log::info!(
93                "OpenGL Info: \nVersion: {version}\nRenderer: {renderer}\nVendor: {vendor}\n---"
94            );
95        }
96
97        let limits = unsafe {
98            Limits {
99                max_texture_size: gl.get_parameter_i32(glow::MAX_TEXTURE_SIZE) as _,
100                max_uniform_blocks: gl.get_parameter_i32(glow::MAX_UNIFORM_BLOCK_SIZE) as _,
101            }
102        };
103
104        let stats = GpuStats::default();
105
106        Ok(Self {
107            pipeline_count: 0,
108            buffer_count: 0,
109            texture_count: 0,
110            render_target_count: 0,
111            gl,
112            size: (0, 0),
113            dpi: 1.0,
114            pipelines: HashMap::new(),
115            buffers: HashMap::new(),
116            textures: HashMap::new(),
117            render_targets: HashMap::new(),
118            using_indices: None,
119            api_name: api.to_string(),
120            current_pipeline: 0,
121            limits,
122            stats,
123            current_uniforms: vec![],
124            target_render_texture: None,
125            render_texture_mipmaps: false,
126        })
127    }
128}
129
130impl GlowBackend {
131    #[inline(always)]
132    fn clear(&mut self, color: &Option<Color>, depth: &Option<f32>, stencil: &Option<i32>) {
133        clear(&self.gl, color, depth, stencil);
134        self.stats.misc += 1;
135    }
136
137    fn begin(
138        &mut self,
139        target: Option<u64>,
140        color: &Option<Color>,
141        depth: &Option<f32>,
142        stencil: &Option<i32>,
143    ) {
144        let render_target = match target {
145            Some(id) => self.render_targets.get(&id),
146            _ => None,
147        };
148
149        let (width, height, dpi) = match render_target {
150            Some(rt) => {
151                rt.bind(&self.gl);
152                self.target_render_texture = Some(rt.texture_id);
153                self.render_texture_mipmaps = rt.use_mipmaps;
154                (rt.size.0, rt.size.1, 1.0)
155            }
156            None => {
157                unsafe {
158                    self.gl.bind_framebuffer(glow::FRAMEBUFFER, None);
159                }
160                self.render_texture_mipmaps = false;
161                (self.size.0, self.size.1, self.dpi)
162            }
163        };
164
165        self.viewport(0.0, 0.0, width as _, height as _, dpi);
166
167        self.clear(color, depth, stencil);
168    }
169
170    #[inline]
171    fn viewport(&mut self, mut x: f32, mut y: f32, width: f32, height: f32, dpi: f32) {
172        if self.target_render_texture.is_none() {
173            y = (self.size.1 as f32 - (height + y)) * dpi;
174            x *= dpi;
175        }
176        let ww = width * dpi;
177        let hh = height * dpi;
178
179        unsafe {
180            self.gl.viewport(x as _, y as _, ww as _, hh as _);
181        }
182
183        self.stats.misc += 1;
184    }
185
186    #[inline]
187    fn scissors(&mut self, x: f32, y: f32, width: f32, height: f32, dpi: f32) {
188        let canvas_height = ((self.size.1 - (height + y) as u32) as f32 * dpi) as _;
189        let x = x * dpi;
190        let width = width * dpi;
191        let height = height * dpi;
192
193        unsafe {
194            self.gl.enable(glow::SCISSOR_TEST);
195            self.gl
196                .scissor(x as _, canvas_height, width as _, height as _);
197        }
198
199        self.stats.misc += 1;
200    }
201
202    fn end(&mut self) {
203        unsafe {
204            // generate mipmap for the framebuffer texture if needed
205            if self.render_texture_mipmaps {
206                if let Some(render_texture) = self
207                    .target_render_texture
208                    .and_then(|id| self.textures.get(&id))
209                {
210                    self.gl
211                        .bind_texture(glow::TEXTURE_2D, Some(render_texture.texture));
212                    self.gl.generate_mipmap(glow::TEXTURE_2D);
213                    self.gl.bind_texture(glow::TEXTURE_2D, None);
214                }
215            }
216            self.gl.disable(glow::SCISSOR_TEST);
217            self.gl.bind_buffer(glow::ARRAY_BUFFER, None);
218            self.gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None);
219            self.gl.bind_buffer(glow::UNIFORM_BUFFER, None);
220            self.gl.bind_vertex_array(None);
221            self.gl.bind_framebuffer(glow::FRAMEBUFFER, None);
222        }
223
224        self.using_indices = None;
225        self.target_render_texture = None;
226        self.render_texture_mipmaps = false;
227    }
228
229    fn clean_pipeline(&mut self, id: u64) {
230        if let Some(pip) = self.pipelines.remove(&id) {
231            pip.clean(&self.gl);
232        }
233    }
234
235    fn set_pipeline(&mut self, id: u64, options: &PipelineOptions) {
236        if let Some(pip) = self.pipelines.get(&id) {
237            pip.bind(&self.gl, options);
238            self.using_indices = None;
239            self.current_pipeline = id;
240            self.current_uniforms.clone_from(&pip.uniform_locations);
241        }
242    }
243
244    fn bind_buffer(&mut self, id: u64) {
245        if let Some(buffer) = self.buffers.get_mut(&id) {
246            #[cfg(debug_assertions)]
247            {
248                debug_assert!(
249                    buffer.initialized,
250                    "Buffer {} -> id({}) is doesn't contain data. This can cause Undefined behavior.",
251                    buffer.kind,
252                    id
253                )
254            }
255            let reset_attrs = match &buffer.kind {
256                Kind::Index(format) => {
257                    self.using_indices = Some(*format);
258                    false
259                }
260                Kind::Uniform(_slot, _name) => {
261                    buffer.bind_ubo_block(
262                        &self.gl,
263                        self.current_pipeline,
264                        self.pipelines.get(&self.current_pipeline).as_ref().unwrap(),
265                    );
266                    false
267                }
268                Kind::Vertex(attrs) => match self.pipelines.get_mut(&self.current_pipeline) {
269                    Some(pip) => pip.use_attrs(id, attrs),
270                    _ => false,
271                },
272            };
273
274            buffer.bind(&self.gl, Some(self.current_pipeline), reset_attrs);
275        }
276    }
277
278    fn bind_texture(&mut self, id: u64, slot: u32, location: u32) {
279        if let Some(pip) = self.pipelines.get(&self.current_pipeline) {
280            if let Some(texture) = self.textures.get(&id) {
281                #[cfg(debug_assertions)]
282                if !pip.texture_locations.contains_key(&location) {
283                    log::warn!("Uniform location {} for texture {} should be declared when the pipeline is created.", location, id);
284                }
285
286                let loc = pip
287                    .texture_locations
288                    .get(&location)
289                    .unwrap_or_else(|| self.get_texture_uniform_loc(&location));
290                texture.bind(&self.gl, slot, loc);
291            }
292        }
293    }
294
295    #[inline(always)]
296    fn get_texture_uniform_loc<'a>(&'a self, location: &'a u32) -> &'a UniformLocation {
297        if cfg!(debug_assertions) {
298            self.current_uniforms.get(*location as usize)
299                .as_ref()
300                .ok_or_else(|| format!("Invalid uniform location {location}, this could means that you're trying to access a uniform not used in the shader code."))
301                .unwrap()
302        } else {
303            &self.current_uniforms[*location as usize]
304        }
305    }
306
307    fn clean_buffer(&mut self, id: u64) {
308        if let Some(buffer) = self.buffers.remove(&id) {
309            buffer.clean(&self.gl);
310        }
311    }
312
313    fn clean_texture(&mut self, id: u64) {
314        if let Some(texture) = self.textures.remove(&id) {
315            texture.clean(&self.gl);
316        }
317    }
318
319    fn clean_render_target(&mut self, id: u64) {
320        if let Some(rt) = self.render_targets.remove(&id) {
321            rt.clean(&self.gl);
322        }
323    }
324
325    fn draw(&mut self, primitive: &DrawPrimitive, offset: i32, count: i32) {
326        unsafe {
327            self.stats.draw_calls += 1;
328            match self.using_indices {
329                None => self.gl.draw_arrays(primitive.to_glow(), offset, count),
330                Some(format) => {
331                    self.gl
332                        .draw_elements(primitive.to_glow(), count, format.to_glow(), offset * 4)
333                }
334            }
335        }
336    }
337    fn draw_instanced(&mut self, primitive: &DrawPrimitive, offset: i32, count: i32, length: i32) {
338        unsafe {
339            self.stats.draw_calls += 1;
340            match self.using_indices {
341                None => self
342                    .gl
343                    .draw_arrays_instanced(primitive.to_glow(), offset, count, length),
344                Some(format) => self.gl.draw_elements_instanced(
345                    primitive.to_glow(),
346                    count,
347                    format.to_glow(),
348                    offset,
349                    length,
350                ),
351            }
352        }
353    }
354
355    pub fn add_inner_texture(
356        &mut self,
357        tex: TextureKey,
358        info: &TextureInfo,
359    ) -> Result<u64, String> {
360        let inner_texture = InnerTexture::new(tex, info)?;
361        self.texture_count += 1;
362        self.textures.insert(self.texture_count, inner_texture);
363        Ok(self.texture_count)
364    }
365}
366
367impl DeviceBackend for GlowBackend {
368    fn api_name(&self) -> &str {
369        &self.api_name
370    }
371
372    fn limits(&self) -> Limits {
373        self.limits
374    }
375
376    fn stats(&self) -> GpuStats {
377        self.stats
378    }
379
380    fn reset_stats(&mut self) {
381        self.stats = GpuStats::default();
382    }
383
384    fn create_pipeline(
385        &mut self,
386        vertex_source: &[u8],
387        fragment_source: &[u8],
388        vertex_attrs: &[VertexAttr],
389        texture_locations: &[(u32, String)],
390        options: PipelineOptions,
391    ) -> Result<u64, String> {
392        let vertex_source = std::str::from_utf8(vertex_source).map_err(|e| e.to_string())?;
393        let fragment_source = std::str::from_utf8(fragment_source).map_err(|e| e.to_string())?;
394
395        let inner_pipeline = InnerPipeline::new(
396            &self.gl,
397            vertex_source,
398            fragment_source,
399            vertex_attrs,
400            texture_locations,
401        )?;
402        inner_pipeline.bind(&self.gl, &options);
403
404        self.pipeline_count += 1;
405        self.pipelines.insert(self.pipeline_count, inner_pipeline);
406
407        self.set_pipeline(self.pipeline_count, &options);
408        self.stats.misc += 1;
409        Ok(self.pipeline_count)
410    }
411
412    fn create_vertex_buffer(
413        &mut self,
414        attrs: &[VertexAttr],
415        step_mode: VertexStepMode,
416    ) -> Result<u64, String> {
417        let (stride, inner_attrs) = get_inner_attrs(attrs);
418        let kind = Kind::Vertex(VertexAttributes::new(stride, inner_attrs, step_mode));
419        let mut inner_buffer = InnerBuffer::new(&self.gl, kind, true)?;
420        inner_buffer.bind(&self.gl, Some(self.current_pipeline), false);
421        self.buffer_count += 1;
422        self.buffers.insert(self.buffer_count, inner_buffer);
423        self.stats.buffer_creation += 1;
424        Ok(self.buffer_count)
425    }
426
427    fn create_index_buffer(&mut self, format: IndexFormat) -> Result<u64, String> {
428        let mut inner_buffer = InnerBuffer::new(&self.gl, Kind::Index(format), true)?;
429        inner_buffer.bind(&self.gl, Some(self.current_pipeline), false);
430        self.buffer_count += 1;
431        self.buffers.insert(self.buffer_count, inner_buffer);
432        self.stats.buffer_creation += 1;
433        Ok(self.buffer_count)
434    }
435
436    fn create_uniform_buffer(&mut self, slot: u32, name: &str) -> Result<u64, String> {
437        let mut inner_buffer =
438            InnerBuffer::new(&self.gl, Kind::Uniform(slot, name.to_string()), true)?;
439        inner_buffer.bind(&self.gl, Some(self.current_pipeline), false);
440        self.buffer_count += 1;
441        self.buffers.insert(self.buffer_count, inner_buffer);
442        self.stats.buffer_creation += 1;
443        Ok(self.buffer_count)
444    }
445
446    fn set_buffer_data(&mut self, id: u64, data: &[u8]) {
447        if let Some(buffer) = self.buffers.get_mut(&id) {
448            buffer.bind(&self.gl, None, false);
449            buffer.update(&self.gl, data);
450            self.stats.buffer_updates += 1;
451        }
452    }
453
454    fn render(&mut self, commands: &[Commands], target: Option<u64>) {
455        commands.iter().for_each(|cmd| {
456            use Commands::*;
457            // println!("Render cmd: {:?}", cmd);
458
459            match cmd {
460                Begin {
461                    color,
462                    depth,
463                    stencil,
464                } => self.begin(target, color, depth, stencil),
465                End => self.end(),
466                Pipeline { id, options } => self.set_pipeline(*id, options),
467                BindBuffer { id } => self.bind_buffer(*id),
468                Draw {
469                    primitive,
470                    offset,
471                    count,
472                } => self.draw(primitive, *offset, *count),
473                DrawInstanced {
474                    primitive,
475                    offset,
476                    count,
477                    length,
478                } => self.draw_instanced(primitive, *offset, *count, *length),
479                BindTexture { id, slot, location } => self.bind_texture(*id, *slot, *location),
480                Size { width, height } => self.set_size(*width, *height),
481                Viewport {
482                    x,
483                    y,
484                    width,
485                    height,
486                } => self.viewport(*x, *y, *width, *height, self.dpi),
487                Scissors {
488                    x,
489                    y,
490                    width,
491                    height,
492                } => self.scissors(*x, *y, *width, *height, self.dpi),
493            }
494        });
495    }
496
497    fn clean(&mut self, to_clean: &[ResourceId]) {
498        log::trace!("gpu resources to_clean {:?}", to_clean);
499        to_clean.iter().for_each(|res| match &res {
500            ResourceId::Pipeline(id) => self.clean_pipeline(*id),
501            ResourceId::Buffer(id) => self.clean_buffer(*id),
502            ResourceId::Texture(id) => self.clean_texture(*id),
503            ResourceId::RenderTexture(id) => self.clean_render_target(*id),
504        });
505    }
506
507    fn set_size(&mut self, width: u32, height: u32) {
508        self.size = (width, height);
509    }
510
511    fn set_dpi(&mut self, scale_factor: f64) {
512        self.dpi = scale_factor as _;
513    }
514
515    fn create_texture(
516        &mut self,
517        source: TextureSourceKind,
518        info: TextureInfo,
519    ) -> Result<(u64, TextureInfo), String> {
520        let (id, info) = match source {
521            TextureSourceKind::Empty => add_empty_texture(self, info)?,
522            TextureSourceKind::Image(buffer) => add_texture_from_image(self, buffer, info)?,
523            TextureSourceKind::Bytes(bytes) => add_texture_from_bytes(self, bytes, info)?,
524            TextureSourceKind::Raw(raw) => raw.create(self, info)?,
525        };
526        self.stats.texture_creation += 1;
527        Ok((id, info))
528    }
529
530    fn create_render_texture(
531        &mut self,
532        texture_id: u64,
533        info: &TextureInfo,
534    ) -> Result<u64, String> {
535        let texture = self.textures.get(&texture_id).ok_or(format!(
536            "Error creating render target: texture id '{texture_id}' not found.",
537        ))?;
538
539        let inner_rt = InnerRenderTexture::new(&self.gl, texture, texture_id, info)?;
540        self.render_target_count += 1;
541        self.render_targets
542            .insert(self.render_target_count, inner_rt);
543
544        self.stats.texture_creation += 1;
545
546        Ok(self.render_target_count)
547    }
548
549    fn update_texture(
550        &mut self,
551        texture: u64,
552        source: TextureUpdaterSourceKind,
553        opts: TextureUpdate,
554    ) -> Result<(), String> {
555        match self.textures.get(&texture) {
556            Some(texture) => {
557                let use_mipmaps = texture.use_mipmaps;
558
559                unsafe {
560                    self.gl
561                        .bind_texture(glow::TEXTURE_2D, Some(texture.texture));
562                    self.gl.pixel_store_i32(
563                        glow::UNPACK_ALIGNMENT,
564                        opts.format.bytes_per_pixel().min(8) as _,
565                    );
566
567                    match source {
568                        TextureUpdaterSourceKind::Bytes(bytes) => {
569                            self.gl.tex_sub_image_2d(
570                                glow::TEXTURE_2D,
571                                0,
572                                opts.x_offset as _,
573                                opts.y_offset as _,
574                                opts.width as _,
575                                opts.height as _,
576                                texture_format(&opts.format),
577                                texture_type(&opts.format),
578                                PixelUnpackData::Slice(Some(bytes)),
579                            );
580                        }
581                        TextureUpdaterSourceKind::Raw(source) => source.update(self, opts)?,
582                    }
583
584                    // if texture has mipmaps enabled re-generate them after the update
585                    if use_mipmaps {
586                        self.gl.generate_mipmap(glow::TEXTURE_2D);
587                    }
588
589                    self.stats.texture_updates += 1;
590
591                    Ok(())
592                }
593            }
594            _ => Err("Invalid texture id".to_string()),
595        }
596    }
597
598    fn read_pixels(
599        &mut self,
600        texture: u64,
601        bytes: &mut [u8],
602        opts: &TextureRead,
603    ) -> Result<(), String> {
604        match self.textures.get(&texture) {
605            Some(texture) => unsafe {
606                let fbo = self.gl.create_framebuffer()?;
607                self.gl.bind_framebuffer(glow::FRAMEBUFFER, Some(fbo));
608                self.gl.framebuffer_texture_2d(
609                    glow::FRAMEBUFFER,
610                    glow::COLOR_ATTACHMENT0,
611                    glow::TEXTURE_2D,
612                    Some(texture.texture),
613                    0,
614                );
615
616                let status = self.gl.check_framebuffer_status(glow::FRAMEBUFFER);
617                let can_read = status == glow::FRAMEBUFFER_COMPLETE;
618
619                let clean = || {
620                    self.gl.bind_framebuffer(glow::FRAMEBUFFER, None);
621                    self.gl.delete_framebuffer(fbo);
622                };
623
624                if can_read {
625                    self.gl.read_pixels(
626                        opts.x_offset as _,
627                        opts.y_offset as _,
628                        opts.width as _,
629                        opts.height as _,
630                        texture_format(&opts.format),
631                        texture_type(&opts.format),
632                        glow::PixelPackData::Slice(Some(bytes)),
633                    );
634
635                    clean();
636                    self.stats.read_pixels += 1;
637                    Ok(())
638                } else {
639                    clean();
640                    Err("Framebuffer incomplete...".to_string())
641                }
642            },
643            None => Err("Invalid texture id".to_string()),
644        }
645    }
646
647    fn as_any_mut(&mut self) -> &mut dyn Any {
648        self
649    }
650}
651
652#[inline]
653pub(crate) fn clear(
654    gl: &Context,
655    color: &Option<Color>,
656    depth: &Option<f32>,
657    stencil: &Option<i32>,
658) {
659    let mut mask = 0;
660    unsafe {
661        if let Some(color) = color {
662            mask |= glow::COLOR_BUFFER_BIT;
663            gl.clear_color(color.r, color.g, color.b, color.a);
664        }
665
666        if let Some(depth) = *depth {
667            mask |= glow::DEPTH_BUFFER_BIT;
668            gl.enable(glow::DEPTH_TEST);
669            gl.depth_mask(true);
670            gl.clear_depth_f32(depth);
671        }
672
673        if let Some(stencil) = *stencil {
674            mask |= glow::STENCIL_BUFFER_BIT;
675            gl.enable(glow::STENCIL_TEST);
676            gl.stencil_mask(0xff);
677            gl.clear_stencil(stencil);
678        }
679
680        if mask != 0 {
681            gl.clear(mask);
682        }
683    }
684}