thyme/gl_backend/
mod.rs

1use std::error::Error;
2
3use crate::font::{Font, FontSource, FontTextureWriter, FontDrawParams};
4use crate::image::ImageDrawParams;
5use crate::render::{
6    view_matrix, DrawList, DrawMode, FontHandle, Renderer, TexCoord, TextureData, TextureHandle,
7};
8use crate::theme_definition::CharacterRange;
9use crate::{Color, Frame, Point, Rect};
10
11mod program;
12use program::Program;
13
14mod texture;
15use texture::GLTexture;
16
17mod vertex_buffer;
18use vertex_buffer::VAO;
19
20/// A Thyme [`Renderer`](trait.Renderer.html) for raw [`OpenGL`](https://github.com/brendanzab/gl-rs/).
21///
22/// This adapter registers image and font data as OpenGL textures using gl-rs, and renders each frame.
23/// After the UI has been built, the [`Frame`](struct.Frame.html) should be passed to the renderer
24/// for drawing.
25///
26/// Fonts are prerendered to a texture on the GPU, based on the ttf
27/// font data and the theme specified size.
28///
29/// Data is structured to minimize number of draw calls, with one to three draw calls per render group
30/// (created with [`WidgetBuilder.new_render_group`](struct.WidgetBuilder.html#method.new_render_group))
31/// being typical.  Unless you need UI groups where different widgets may overlap and change draw
32/// ordering frame-by-frame, a single render group will usually be enough for most of your UI.
33///
34/// Widget clipping is handled using `gl::CLIP_DISTANCE0` to `gl::CLIP_DISTANCE3`, again to minimize draw calls.  Since the data to send
35/// to the GPU is constructed each frame in the immediate mode UI model, the amount of data is minimized
36/// by sending only a single `Vertex` for each Image, with the vertex components including the rectangular position and
37/// texture coordinates.  The actual individual on-screen vertices are then constructed with a Geometry shader.
38pub struct GLRenderer {
39    base_program: Program,
40    font_program: Program,
41
42    // assets loaded from the context
43    textures: Vec<GLTexture>,
44    fonts: Vec<GLTexture>,
45
46    // per frame data
47    draw_list: GLDrawList,
48    groups: Vec<DrawGroup>,
49    matrix: [[f32; 4]; 4],
50}
51
52impl Default for GLRenderer {
53    fn default() -> Self {
54        Self::new()
55    }
56}
57
58impl GLRenderer {
59    /// Creates a GLRenderer
60    pub fn new() -> GLRenderer {
61        let base_program = Program::new(VERT_SHADER_SRC, GEOM_SHADER_SRC, FRAGMENT_SHADER_SRC);
62
63        let font_program = Program::new(VERT_SHADER_SRC, GEOM_SHADER_SRC, FONT_FRAGMENT_SHADER_SRC);
64
65        GLRenderer {
66            base_program,
67            font_program,
68            fonts: Vec::new(),
69            textures: Vec::new(),
70            draw_list: GLDrawList::new(),
71            groups: Vec::new(),
72            matrix: view_matrix(Point::default(), Point { x: 100.0, y: 100.0 }),
73        }
74    }
75
76    fn font(&self, font: FontHandle) -> &GLTexture {
77        &self.fonts[font.id()]
78    }
79
80    fn texture(&self, texture: TextureHandle) -> &GLTexture {
81        &self.textures[texture.id()]
82    }
83
84    /// Clears the screen with this color.
85    pub fn clear_color(&self, r: f32, g: f32, b: f32, a: f32) {
86        unsafe {
87            gl::ClearColor(r, g, b, a);
88            gl::Clear(gl::COLOR_BUFFER_BIT);
89        }
90    }
91
92    /// Draws the specified [`Frame`](struct.Frame.html) to the Glium surface, usually the Glium Frame.
93    pub fn draw_frame(&mut self, frame: Frame) {
94        let mouse_cursor = frame.mouse_cursor();
95        let (context, widgets, render_groups) = frame.finish_frame();
96        let context = context.internal().borrow();
97
98        let time_millis = context.time_millis();
99        let display_pos = Point::default();
100        let display_size = context.display_size();
101        let scale = context.scale_factor();
102        self.matrix = view_matrix(display_pos, display_size);
103
104        self.draw_list.clear();
105        self.groups.clear();
106
107        unsafe {
108            gl::Enable(gl::BLEND);
109            gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
110            gl::Enable(gl::CLIP_DISTANCE0);
111            gl::Enable(gl::CLIP_DISTANCE1);
112            gl::Enable(gl::CLIP_DISTANCE2);
113            gl::Enable(gl::CLIP_DISTANCE3);
114        }
115
116        for render_group in render_groups.into_iter().rev() {
117            let mut draw_mode = None;
118
119            // render backgrounds
120            for widget in render_group.iter(&widgets) {
121                if !widget.visible() {
122                    continue;
123                }
124                let image_handle = match widget.background() {
125                    None => continue,
126                    Some(handle) => handle,
127                };
128                let time_millis = time_millis - context.base_time_millis_for(widget.id());
129                let image = context.themes().image(image_handle);
130
131                self.write_group_if_changed(&mut draw_mode, DrawMode::Image(image.texture()));
132
133                image.draw(
134                    &mut self.draw_list,
135                    ImageDrawParams {
136                        pos: widget.pos().into(),
137                        size: widget.size().into(),
138                        anim_state: widget.anim_state(),
139                        clip: widget.clip(),
140                        time_millis,
141                        scale,
142                        color: widget.image_color(),
143                    },
144                );
145            }
146
147            // render foregrounds & text
148            for widget in render_group.iter(&widgets) {
149                if !widget.visible() {
150                    continue;
151                }
152
153                let border = widget.border();
154                let fg_pos = widget.pos() + border.tl();
155                let fg_size = widget.inner_size();
156
157                if let Some(image_handle) = widget.foreground() {
158                    let time_millis = time_millis - context.base_time_millis_for(widget.id());
159                    let image = context.themes().image(image_handle);
160                    self.write_group_if_changed(&mut draw_mode, DrawMode::Image(image.texture()));
161
162                    image.draw(
163                        &mut self.draw_list,
164                        ImageDrawParams {
165                            pos: fg_pos.into(),
166                            size: fg_size.into(),
167                            anim_state: widget.anim_state(),
168                            clip: widget.clip(),
169                            time_millis,
170                            scale,
171                            color: widget.image_color(),
172                        },
173                    );
174                }
175
176                if let Some(text) = widget.text() {
177                    if let Some(font_sum) = widget.font() {
178                        self.write_group_if_changed(
179                            &mut draw_mode,
180                            DrawMode::Font(font_sum.handle),
181                        );
182                        let font = context.themes().font(font_sum.handle);
183
184                        let params = FontDrawParams {
185                            area_size: fg_size * scale,
186                            pos: fg_pos * scale,
187                            indent: widget.text_indent(),
188                            align: widget.text_align(),
189                        };
190
191                        font.draw(
192                            &mut self.draw_list,
193                            params,
194                            text,
195                            widget.text_color(),
196                            widget.clip() * scale,
197                        )
198                    }
199                }
200            }
201
202            // render anything from the final draw calls
203            if let Some(mode) = draw_mode {
204                self.write_group(mode);
205            }
206        }
207
208        if let Some((mouse_cursor, align, anim_state)) = mouse_cursor {
209            let image = context.themes().image(mouse_cursor);
210            let mouse_pos = context.mouse_pos();
211            let size = image.base_size();
212            let pos = mouse_pos - align.adjust_for(size);
213            let clip = Rect::new(pos, size);
214
215            let params = ImageDrawParams {
216                pos: pos.into(),
217                size: size.into(),
218                anim_state,
219                clip,
220                time_millis,
221                scale,
222                color: Color::white(),
223            };
224
225            image.draw(&mut self.draw_list, params);
226            self.write_group(DrawMode::Image(image.texture()));
227        }
228
229        unsafe {
230            gl::Enable(gl::FRAMEBUFFER_SRGB);
231        }
232        // create the vertex buffer and draw all groups
233        let vao = VAO::new(&self.draw_list.vertices);
234        vao.bind();
235
236        let font_uniform_tex = self.font_program.get_uniform_location("tex");
237        let font_uniform_matrix = self.font_program.get_uniform_location("matrix");
238
239        let base_uniform_tex = self.base_program.get_uniform_location("tex");
240        let base_uniform_matrix = self.base_program.get_uniform_location("matrix");
241
242        for group in &self.groups {
243            match group.mode {
244                DrawMode::Font(font_handle) => {
245                    let font = self.font(font_handle);
246
247                    font.bind(0);
248                    self.font_program.use_program();
249
250                    self.font_program
251                        .uniform_matrix4fv(font_uniform_matrix, false, &self.matrix);
252                    self.font_program.uniform1i(font_uniform_tex, 0);
253
254                    unsafe {
255                        gl::DrawArrays(gl::POINTS, group.start as _, (group.end - group.start) as _)
256                    };
257                }
258                DrawMode::Image(tex_handle) => {
259                    let texture = self.texture(tex_handle);
260
261                    texture.bind(0);
262                    self.base_program.use_program();
263
264                    self.base_program.uniform1i(base_uniform_tex, 0);
265                    self.base_program
266                        .uniform_matrix4fv(base_uniform_matrix, false, &self.matrix);
267
268                    unsafe {
269                        gl::Disable(gl::FRAMEBUFFER_SRGB);
270                    }
271                    unsafe {
272                        gl::DrawArrays(gl::POINTS, group.start as _, (group.end - group.start) as _)
273                    };
274                }
275            };
276        }
277    }
278
279    fn write_group_if_changed(&mut self, mode: &mut Option<DrawMode>, desired_mode: DrawMode) {
280        match mode {
281            None => *mode = Some(desired_mode),
282            Some(cur_mode) => {
283                if *cur_mode != desired_mode {
284                    self.write_group(*cur_mode);
285                    *mode = Some(desired_mode);
286                }
287            }
288        }
289    }
290
291    fn write_group(&mut self, mode: DrawMode) {
292        let end = self.draw_list.vertices.len();
293        // if this is the first draw group, start at 0
294        let start = match self.groups.last() {
295            None => 0,
296            Some(group) => group.end,
297        };
298        self.groups.push(DrawGroup { start, end, mode });
299    }
300}
301
302impl Renderer for GLRenderer {
303    fn register_texture(
304        &mut self,
305        handle: TextureHandle,
306        image_data: &[u8],
307        dimensions: (u32, u32),
308    ) -> Result<TextureData, crate::Error> {
309        let gl_texture = GLTexture::new(
310            image_data,
311            dimensions,
312            gl::LINEAR,
313            gl::CLAMP_TO_EDGE,
314            gl::RGBA,
315            gl::RGBA8,
316        );
317
318        assert!(handle.id() <= self.textures.len());
319        if handle.id() == self.textures.len() {
320            self.textures.push(gl_texture);
321        } else {
322            self.textures[handle.id()] = gl_texture;
323        }
324
325        Ok(TextureData::new(handle, dimensions.0, dimensions.1))
326    }
327
328    fn register_font(
329        &mut self,
330        handle: FontHandle,
331        source: &FontSource,
332        ranges: &[CharacterRange],
333        size: f32,
334        scale: f32,
335    ) -> Result<Font, crate::Error> {
336        let font = &source.font;
337
338        let writer = FontTextureWriter::new(font, ranges, size, scale);
339
340        let writer_out = writer.write(handle, ranges)?;
341
342        let font_texture = GLTexture::new(
343            &writer_out.data,
344            (writer_out.tex_width, writer_out.tex_height),
345            gl::NEAREST,
346            gl::CLAMP_TO_BORDER,
347            gl::RED,
348            gl::R8,
349        );
350
351        assert!(handle.id() <= self.fonts.len());
352        if handle.id() == self.fonts.len() {
353            self.fonts.push(font_texture);
354        } else {
355            self.fonts[handle.id()] = font_texture;
356        }
357
358        Ok(writer_out.font)
359    }
360}
361
362struct DrawGroup {
363    start: usize,
364    end: usize,
365    mode: DrawMode,
366}
367
368// Pass through the vertex to the geometry shader where the rectangle is built
369const VERT_SHADER_SRC: &str = r#"
370  #version 330
371
372  layout(location = 0) in vec2 position;
373  layout(location = 1) in vec2 size;
374  layout(location = 2) in vec2 tex0;
375  layout(location = 3) in vec2 tex1;
376  layout(location = 4) in vec4 color;
377  layout(location = 5) in vec2 clip_pos;
378  layout(location = 6) in vec2 clip_size;
379
380  out vec2 g_size;
381  out vec2 g_tex0;
382  out vec2 g_tex1;
383  out vec4 g_color;
384  out vec2 g_clip_pos;
385  out vec2 g_clip_size;
386
387  void main() {
388    gl_Position = vec4(position, 0.0, 1.0);
389	
390	g_size = size;
391	g_tex0 = tex0;
392	g_tex1 = tex1;
393	g_color = color;
394	g_clip_pos = clip_pos;
395	g_clip_size = clip_size;
396  }
397"#;
398
399const GEOM_SHADER_SRC: &str = r#"
400  #version 150
401
402  layout (points) in;
403  layout (triangle_strip, max_vertices = 4) out;
404
405  in vec2 g_size[];
406  in vec2 g_tex0[];
407  in vec2 g_tex1[];
408  in vec4 g_color[];
409  in vec2 g_clip_pos[];
410  in vec2 g_clip_size[];
411
412  out vec2 v_tex_coords;
413  out vec4 v_color;
414
415  uniform mat4 matrix;
416
417  void main() {
418	vec4 base = gl_in[0].gl_Position;
419    
420    vec2 clip_pos = g_clip_pos[0];
421    vec2 clip_size = g_clip_size[0];
422
423    // draw the rectangle using 2 triangles in triangle_strip
424
425    // [0, 0] vertex
426    vec4 position = base;
427    gl_ClipDistance[0] = position.x - clip_pos.x;
428    gl_ClipDistance[1] = clip_pos.x + clip_size.x - position.x;
429    gl_ClipDistance[2] = position.y - clip_pos.y;
430    gl_ClipDistance[3] = clip_pos.y + clip_size.y - position.y;
431	gl_Position = matrix * position;
432	v_tex_coords = g_tex0[0];
433	v_color = g_color[0];
434	EmitVertex();
435    
436    // [0, 1] vertex
437    position = base + vec4(0.0, g_size[0].y, 0.0, 0.0);
438    gl_ClipDistance[0] = position.x - clip_pos.x;
439    gl_ClipDistance[1] = clip_pos.x + clip_size.x - position.x;
440    gl_ClipDistance[2] = position.y - clip_pos.y;
441    gl_ClipDistance[3] = clip_pos.y + clip_size.y - position.y;
442	gl_Position = matrix * position;
443	v_tex_coords = vec2(g_tex0[0].x, g_tex1[0].y);
444	v_color = g_color[0];
445    EmitVertex();
446    
447    // [1, 0] vertex
448    position = base + vec4(g_size[0].x, 0.0, 0.0, 0.0);
449	gl_ClipDistance[0] = position.x - clip_pos.x;
450    gl_ClipDistance[1] = clip_pos.x + clip_size.x - position.x;
451    gl_ClipDistance[2] = position.y - clip_pos.y;
452    gl_ClipDistance[3] = clip_pos.y + clip_size.y - position.y;
453	gl_Position = matrix * position;
454	v_tex_coords = vec2(g_tex1[0].x, g_tex0[0].y);
455	v_color = g_color[0];
456    EmitVertex();
457    
458    // [1, 1] vertex
459    position = base + vec4(g_size[0].x, g_size[0].y, 0.0, 0.0);
460    gl_ClipDistance[0] = position.x - clip_pos.x;
461    gl_ClipDistance[1] = clip_pos.x + clip_size.x - position.x;
462    gl_ClipDistance[2] = position.y - clip_pos.y;
463    gl_ClipDistance[3] = clip_pos.y + clip_size.y - position.y;
464    gl_Position = matrix * position;
465    v_tex_coords = g_tex1[0];
466    v_color = g_color[0];
467    EmitVertex();
468
469    EndPrimitive();
470  }
471"#;
472
473const FRAGMENT_SHADER_SRC: &str = r#"
474  #version 150
475
476  in vec2 v_tex_coords;
477  in vec4 v_color;
478
479  out vec4 color;
480
481  uniform sampler2D tex;
482
483  void main() {
484    color = v_color * texture(tex, v_tex_coords);
485  }
486"#;
487
488const FONT_FRAGMENT_SHADER_SRC: &str = r#"
489    #version 150
490
491    in vec2 v_tex_coords;
492    in vec4 v_color;
493
494    out vec4 color;
495
496    uniform sampler2D tex;
497    
498    void main() {
499        color = vec4(v_color.rgb, texture(tex, v_tex_coords).r);
500    }
501"#;
502
503struct GLDrawList {
504    vertices: Vec<GLVertex>,
505}
506
507impl GLDrawList {
508    fn new() -> Self {
509        GLDrawList {
510            vertices: Vec::new(),
511        }
512    }
513
514    fn clear(&mut self) {
515        self.vertices.clear();
516    }
517}
518
519impl DrawList for GLDrawList {
520    fn len(&self) -> usize {
521        self.vertices.len()
522    }
523
524    fn back_adjust_positions(&mut self, since_index: usize, amount: Point) {
525        for vert in self.vertices.iter_mut().skip(since_index) {
526            vert.position[0] += amount.x;
527            vert.position[1] += amount.y;
528        }
529    }
530
531    fn push_rect(
532        &mut self,
533        pos: [f32; 2],
534        size: [f32; 2],
535        tex: [TexCoord; 2],
536        color: Color,
537        clip: Rect,
538    ) {
539        let vert = GLVertex {
540            position: pos,
541            size,
542            tex0: [tex[0].x(), tex[0].y()],
543            tex1: [tex[1].x(), tex[1].y()],
544            color: color.into(),
545            clip_pos: clip.pos.into(),
546            clip_size: clip.size.into(),
547        };
548
549        self.vertices.push(vert);
550    }
551}
552
553#[derive(Copy, Clone)]
554#[repr(C)]
555pub(crate) struct GLVertex {
556    pub position: [f32; 2],
557    pub size: [f32; 2],
558    pub tex0: [f32; 2],
559    pub tex1: [f32; 2],
560    pub color: [f32; 4],
561    pub clip_pos: [f32; 2],
562    pub clip_size: [f32; 2],
563}
564
565/// An error originating from the [`GLRenderer`](struct.GLRenderer.html)
566#[derive(Debug)]
567pub enum GlError {
568    /// An error creating the glutin context
569    GlutinCreation(glutin::CreationError),
570
571    /// An error using or creating the OpenGL context
572    GlutinContext(glutin::ContextError),
573}
574
575impl std::fmt::Display for GlError {
576    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
577        use self::GlError::*;
578        match self {
579            GlutinCreation(e) => write!(f, "Error creating Glutin context: {}", e),
580            GlutinContext(e) => write!(f, "Error in OpenGL context: {}", e),
581        }
582    }
583}
584
585impl Error for GlError {
586    fn source(&self) -> Option<&(dyn Error + 'static)> {
587        use self::GlError::*;
588        match self {
589            GlutinCreation(e) => Some(e),
590            GlutinContext(e) => Some(e),
591        }
592    }
593}
594
595impl From<glutin::CreationError> for GlError {
596    fn from(e: glutin::CreationError) -> GlError {
597        GlError::GlutinCreation(e)
598    }
599}
600
601impl From<glutin::ContextError> for GlError {
602    fn from(e: glutin::ContextError) -> GlError {
603        GlError::GlutinContext(e)
604    }
605}