beamterm_renderer/gl/
renderer.rs1use beamterm_core::gl::{Drawable, GlState, RenderContext};
2use glow::HasContext;
3use web_sys::HtmlCanvasElement;
4
5use crate::{error::Error, js};
6
7pub struct Renderer {
13 gl: glow::Context,
14 raw_gl: web_sys::WebGl2RenderingContext, canvas: web_sys::HtmlCanvasElement,
16 state: GlState,
17 canvas_padding_color: (f32, f32, f32),
18 logical_size_px: (i32, i32),
19 pixel_ratio: f32,
20 auto_resize_canvas_css: bool,
21}
22
23impl std::fmt::Debug for Renderer {
24 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25 f.debug_struct("Renderer")
26 .field("canvas_padding_color", &self.canvas_padding_color)
27 .field("logical_size_px", &self.logical_size_px)
28 .field("pixel_ratio", &self.pixel_ratio)
29 .field("auto_resize_canvas_css", &self.auto_resize_canvas_css)
30 .finish_non_exhaustive()
31 }
32}
33
34impl Renderer {
35 pub fn create(canvas_id: &str, auto_resize_canvas_css: bool) -> Result<Self, Error> {
37 let canvas = js::get_canvas_by_id(canvas_id)?;
38 Self::create_with_canvas(canvas, auto_resize_canvas_css)
39 }
40
41 #[must_use]
43 pub fn canvas_padding_color(mut self, color: u32) -> Self {
44 let r = ((color >> 16) & 0xFF) as f32 / 255.0;
45 let g = ((color >> 8) & 0xFF) as f32 / 255.0;
46 let b = (color & 0xFF) as f32 / 255.0;
47 self.canvas_padding_color = (r, g, b);
48 self
49 }
50
51 pub fn create_with_canvas(
53 canvas: HtmlCanvasElement,
54 auto_resize_canvas_css: bool,
55 ) -> Result<Self, Error> {
56 let (width, height) = (canvas.width() as i32, canvas.height() as i32);
57
58 let (gl, raw_gl) = js::create_glow_context(&canvas)?;
60 let state = GlState::new(&gl);
61
62 let mut renderer = Self {
63 gl,
64 raw_gl,
65 canvas,
66 state,
67 canvas_padding_color: (0.0, 0.0, 0.0),
68 logical_size_px: (width, height),
69 pixel_ratio: 1.0,
70 auto_resize_canvas_css,
71 };
72 renderer.resize(width as _, height as _);
73 Ok(renderer)
74 }
75
76 pub fn resize(&mut self, width: i32, height: i32) {
78 self.logical_size_px = (width, height);
79 let (w, h) = self.physical_size();
80
81 self.canvas.set_width(w as u32);
82 self.canvas.set_height(h as u32);
83
84 if self.auto_resize_canvas_css {
85 let _ = self
86 .canvas
87 .style()
88 .set_property("width", &format!("{width}px"));
89 let _ = self
90 .canvas
91 .style()
92 .set_property("height", &format!("{height}px"));
93 }
94
95 self.state.viewport(&self.gl, 0, 0, w, h);
96 }
97
98 pub fn clear(&mut self, r: f32, g: f32, b: f32) {
100 self.state.clear_color(&self.gl, r, g, b, 1.0);
101 unsafe {
102 self.gl
103 .clear(glow::COLOR_BUFFER_BIT | glow::DEPTH_BUFFER_BIT)
104 };
105 }
106
107 pub fn begin_frame(&mut self) {
109 let (r, g, b) = self.canvas_padding_color;
110 self.clear(r, g, b);
111 }
112
113 pub fn render(&mut self, drawable: &impl Drawable) -> Result<(), crate::Error> {
115 let mut context = RenderContext { gl: &self.gl, state: &mut self.state };
116
117 drawable.prepare(&mut context)?;
118 drawable.draw(&mut context);
119 drawable.cleanup(&mut context);
120 Ok(())
121 }
122
123 pub fn end_frame(&mut self) {
125 }
127
128 pub fn gl(&self) -> &glow::Context {
130 &self.gl
131 }
132
133 pub fn canvas(&self) -> &HtmlCanvasElement {
135 &self.canvas
136 }
137
138 pub fn canvas_size(&self) -> (i32, i32) {
140 self.logical_size()
141 }
142
143 pub fn logical_size(&self) -> (i32, i32) {
145 self.logical_size_px
146 }
147
148 pub fn physical_size(&self) -> (i32, i32) {
151 let (w, h) = self.logical_size_px;
152 (
153 (w as f32 * self.pixel_ratio).round() as i32,
154 (h as f32 * self.pixel_ratio).round() as i32,
155 )
156 }
157
158 pub fn is_context_lost(&self) -> bool {
160 self.raw_gl.is_context_lost()
161 }
162
163 pub fn restore_context(&mut self) -> Result<(), Error> {
165 let (gl, raw_gl) = js::create_glow_context(&self.canvas)?;
166 self.state = GlState::new(&gl);
167 self.gl = gl;
168 self.raw_gl = raw_gl;
169
170 let (width, height) = self.physical_size();
172 self.state.viewport(&self.gl, 0, 0, width, height);
173
174 Ok(())
175 }
176
177 pub(crate) fn set_pixel_ratio(&mut self, pixel_ratio: f32) {
179 self.pixel_ratio = pixel_ratio;
180 }
181}