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