beamterm_renderer/gl/
renderer.rs

1use web_sys::HtmlCanvasElement;
2
3use crate::{
4    error::Error,
5    gl::{context::GlState, GL},
6    js,
7};
8
9/// Rendering context that provides access to WebGL state.
10pub(super) struct RenderContext<'a> {
11    pub gl: &'a web_sys::WebGl2RenderingContext,
12    pub state: &'a mut GlState,
13}
14
15/// High-level WebGL2 renderer for terminal-style applications.
16///
17/// The `Renderer` manages the WebGL2 rendering context, canvas, and provides
18/// a simplified interface for rendering drawable objects. It handles frame
19/// management, viewport setup, and coordinate system transformations.
20#[derive(Debug)]
21pub struct Renderer {
22    gl: web_sys::WebGl2RenderingContext,
23    canvas: web_sys::HtmlCanvasElement,
24    state: GlState,
25    canvas_padding_color: (f32, f32, f32),
26}
27
28impl Renderer {
29    /// Creates a new renderer by querying for a canvas element with the given ID.
30    ///
31    /// This method searches the DOM for a canvas element with the specified ID,
32    /// initializes a WebGL2 context, and sets up the renderer with orthographic
33    /// projection matching the canvas dimensions.
34    ///
35    /// # Parameters
36    /// * `canvas_id` - CSS selector for the canvas element (e.g., "canvas" or "#my-canvas")
37    ///
38    /// # Returns
39    /// * `Ok(Renderer)` - Successfully created renderer
40    /// * `Err(Error)` - Failed to find canvas, create WebGL context, or initialize renderer
41    ///
42    /// # Errors
43    /// * `Error::UnableToRetrieveCanvas` - Canvas element not found
44    /// * `Error::FailedToRetrieveWebGl2RenderingContext` - WebGL2 not supported or failed to initialize
45    pub fn create(canvas_id: &str) -> Result<Self, Error> {
46        let canvas = js::get_canvas_by_id(canvas_id)?;
47        Self::create_with_canvas(canvas)
48    }
49
50    /// Sets the background color for the canvas area outside the terminal grid.
51    ///
52    /// When the canvas dimensions don't align perfectly with the terminal cell grid,
53    /// there may be unused pixels around the edges. This color fills those padding
54    /// areas to maintain a consistent appearance.
55    pub fn canvas_padding_color(mut self, color: u32) -> Self {
56        let r = ((color >> 16) & 0xFF) as f32 / 255.0;
57        let g = ((color >> 8) & 0xFF) as f32 / 255.0;
58        let b = (color & 0xFF) as f32 / 255.0;
59        self.canvas_padding_color = (r, g, b);
60        self
61    }
62
63    /// Creates a new renderer from an existing HTML canvas element.
64    ///
65    /// This method takes ownership of an existing canvas element and initializes
66    /// the WebGL2 context and renderer state. Useful when you already have a
67    /// reference to the canvas element.
68    ///
69    /// # Parameters
70    /// * `canvas` - HTML canvas element to use for rendering
71    ///
72    /// # Returns
73    /// * `Ok(Renderer)` - Successfully created renderer
74    /// * `Err(Error)` - Failed to create WebGL context or initialize renderer
75    pub fn create_with_canvas(canvas: HtmlCanvasElement) -> Result<Self, Error> {
76        let (width, height) = (canvas.width(), canvas.height());
77
78        // initialize WebGL context
79        let gl = js::get_webgl2_context(&canvas)?;
80        let state = GlState::new(&gl);
81
82        let mut renderer = Self {
83            gl,
84            canvas,
85            state,
86            canvas_padding_color: (0.0, 0.0, 0.0),
87        };
88        renderer.resize(width as _, height as _);
89        Ok(renderer)
90    }
91
92    /// Resizes the canvas and updates the viewport.
93    ///
94    /// This method changes the canvas resolution and adjusts the WebGL viewport
95    /// to match. The projection matrix is automatically updated to maintain
96    /// proper coordinate mapping.
97    ///
98    /// # Parameters
99    /// * `width` - New canvas width in pixels
100    /// * `height` - New canvas height in pixels
101    pub fn resize(&mut self, width: i32, height: i32) {
102        self.canvas.set_width(width as u32);
103        self.canvas.set_height(height as u32);
104        self.state.viewport(&self.gl, 0, 0, width, height);
105    }
106
107    /// Clears the framebuffer with the specified color.
108    ///
109    /// Sets the clear color and clears both the color and depth buffers.
110    /// Color components should be in the range [0.0, 1.0].
111    ///
112    /// # Parameters
113    /// * `r` - Red component (0.0 to 1.0)
114    /// * `g` - Green component (0.0 to 1.0)
115    /// * `b` - Blue component (0.0 to 1.0)
116    pub fn clear(&mut self, r: f32, g: f32, b: f32) {
117        self.state.clear_color(&self.gl, r, g, b, 1.0);
118        self.gl
119            .clear(GL::COLOR_BUFFER_BIT | GL::DEPTH_BUFFER_BIT);
120    }
121
122    /// Begins a new rendering frame.
123    pub fn begin_frame(&mut self) {
124        let (r, g, b) = self.canvas_padding_color;
125        self.clear(r, g, b);
126    }
127
128    /// Renders a drawable object.
129    ///
130    /// This method calls the drawable's prepare, draw, and cleanup methods
131    /// in sequence, providing it with a render context containing.
132    ///
133    /// # Parameters
134    /// * `drawable` - Object implementing the `Drawable` trait
135    #[allow(private_bounds)]
136    pub fn render(&mut self, drawable: &impl Drawable) {
137        let mut context = RenderContext { gl: &self.gl, state: &mut self.state };
138
139        drawable.prepare(&mut context);
140        drawable.draw(&mut context);
141        drawable.cleanup(&mut context);
142    }
143
144    /// Ends the current rendering frame.
145    ///
146    /// This method finalizes the frame rendering. In future versions, this
147    /// may handle buffer swapping or other post-rendering operations.
148    pub fn end_frame(&mut self) {
149        // swap buffers (todo)
150    }
151
152    /// Returns a reference to the WebGL2 rendering context.
153    pub fn gl(&self) -> &GL {
154        &self.gl
155    }
156
157    /// Returns a mutable reference to the WebGL2 rendering context.
158    pub fn canvas(&self) -> &HtmlCanvasElement {
159        &self.canvas
160    }
161
162    /// Returns the current canvas dimensions as a tuple.
163    ///
164    /// # Returns
165    /// Tuple containing (width, height) in pixels
166    pub fn canvas_size(&self) -> (i32, i32) {
167        (self.canvas.width() as i32, self.canvas.height() as i32)
168    }
169}
170
171/// Trait for objects that can be rendered by the renderer.
172pub(super) trait Drawable {
173    /// Prepares the object for rendering.
174    ///
175    /// This method should set up all necessary OpenGL state, bind shaders,
176    /// textures, and vertex data required for rendering.
177    ///
178    /// # Parameters
179    /// * `context` - Mutable reference to the render context
180    fn prepare(&self, context: &mut RenderContext);
181
182    /// Performs the actual rendering.
183    ///
184    /// This method should issue draw calls to render the object. All necessary
185    /// state should already be set up from the `prepare()` call.
186    ///
187    /// # Parameters
188    /// * `context` - Mutable reference to the render context
189    fn draw(&self, context: &mut RenderContext);
190
191    /// Cleans up after rendering.
192    ///
193    /// This method should restore OpenGL state and unbind any resources
194    /// that were bound during `prepare()`. This ensures proper cleanup
195    /// for subsequent rendering operations.
196    ///
197    /// # Parameters
198    /// * `context` - Mutable reference to the render context
199    fn cleanup(&self, context: &mut RenderContext);
200}