fyrox_impl/renderer/
ui_renderer.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21//! See [`UiRenderer`] docs.
22
23use crate::{
24    asset::untyped::ResourceKind,
25    core::{
26        algebra::{Matrix4, Vector2, Vector4},
27        color::Color,
28        math::Rect,
29        sstorage::ImmutableString,
30    },
31    gui::{
32        brush::Brush,
33        draw::{CommandTexture, DrawingContext},
34    },
35    renderer::{
36        cache::uniform::UniformBufferCache,
37        flat_shader::FlatShader,
38        framework::{
39            buffer::BufferUsage,
40            error::FrameworkError,
41            framebuffer::{FrameBuffer, ResourceBindGroup, ResourceBinding},
42            geometry_buffer::{
43                AttributeDefinition, AttributeKind, GeometryBuffer, GeometryBufferDescriptor,
44                VertexBufferData, VertexBufferDescriptor,
45            },
46            gpu_program::{GpuProgram, UniformLocation},
47            server::GraphicsServer,
48            uniform::StaticUniformBuffer,
49            BlendFactor, BlendFunc, BlendParameters, ColorMask, CompareFunc, DrawParameters,
50            ElementRange, ScissorBox, StencilAction, StencilFunc, StencilOp,
51        },
52        FallbackResources, RenderPassStatistics, TextureCache,
53    },
54    resource::texture::{Texture, TextureKind, TexturePixelKind, TextureResource},
55};
56use fyrox_graphics::{framebuffer::BufferLocation, geometry_buffer::ElementsDescriptor};
57
58struct UiShader {
59    program: Box<dyn GpuProgram>,
60    diffuse_texture: UniformLocation,
61    uniform_block_index: usize,
62}
63
64impl UiShader {
65    pub fn new(server: &dyn GraphicsServer) -> Result<Self, FrameworkError> {
66        let fragment_source = include_str!("shaders/ui_fs.glsl");
67        let vertex_source = include_str!("shaders/ui_vs.glsl");
68        let program = server.create_program("UIShader", vertex_source, fragment_source)?;
69        Ok(Self {
70            diffuse_texture: program.uniform_location(&ImmutableString::new("diffuseTexture"))?,
71            uniform_block_index: program.uniform_block_index(&ImmutableString::new("Uniforms"))?,
72            program,
73        })
74    }
75}
76
77/// User interface renderer allows you to render drawing context in specified render target.
78pub struct UiRenderer {
79    shader: UiShader,
80    geometry_buffer: Box<dyn GeometryBuffer>,
81    clipping_geometry_buffer: Box<dyn GeometryBuffer>,
82}
83
84/// A set of parameters to render a specified user interface drawing context.
85pub struct UiRenderContext<'a, 'b, 'c> {
86    /// Graphics server.
87    pub server: &'a dyn GraphicsServer,
88    /// Viewport to where render the user interface.
89    pub viewport: Rect<i32>,
90    /// Frame buffer to where render the user interface.
91    pub frame_buffer: &'b mut dyn FrameBuffer,
92    /// Width of the frame buffer to where render the user interface.
93    pub frame_width: f32,
94    /// Height of the frame buffer to where render the user interface.
95    pub frame_height: f32,
96    /// Drawing context of a user interface.
97    pub drawing_context: &'c DrawingContext,
98    /// Fallback textures.
99    pub fallback_resources: &'a FallbackResources,
100    /// GPU texture cache.
101    pub texture_cache: &'a mut TextureCache,
102    /// A reference to the cache of uniform buffers.
103    pub uniform_buffer_cache: &'a mut UniformBufferCache,
104    /// A reference to the shader that will be used to draw clipping geometry.
105    pub flat_shader: &'a FlatShader,
106}
107
108impl UiRenderer {
109    pub(in crate::renderer) fn new(server: &dyn GraphicsServer) -> Result<Self, FrameworkError> {
110        let geometry_buffer_desc = GeometryBufferDescriptor {
111            elements: ElementsDescriptor::Triangles(&[]),
112            buffers: &[VertexBufferDescriptor {
113                usage: BufferUsage::DynamicDraw,
114                attributes: &[
115                    AttributeDefinition {
116                        location: 0,
117                        kind: AttributeKind::Float,
118                        component_count: 2,
119                        normalized: false,
120                        divisor: 0,
121                    },
122                    AttributeDefinition {
123                        location: 1,
124                        kind: AttributeKind::Float,
125                        component_count: 2,
126                        normalized: false,
127                        divisor: 0,
128                    },
129                    AttributeDefinition {
130                        location: 2,
131                        kind: AttributeKind::UnsignedByte,
132                        component_count: 4,
133                        normalized: true, // Make sure [0; 255] -> [0; 1]
134                        divisor: 0,
135                    },
136                ],
137                data: VertexBufferData::new::<crate::gui::draw::Vertex>(None),
138            }],
139            usage: BufferUsage::DynamicDraw,
140        };
141
142        let clipping_geometry_buffer_desc = GeometryBufferDescriptor {
143            elements: ElementsDescriptor::Triangles(&[]),
144            buffers: &[VertexBufferDescriptor {
145                usage: BufferUsage::DynamicDraw,
146                attributes: &[
147                    // We're interested only in position. Fragment shader won't run for clipping geometry anyway.
148                    AttributeDefinition {
149                        location: 0,
150                        kind: AttributeKind::Float,
151                        component_count: 2,
152                        normalized: false,
153                        divisor: 0,
154                    },
155                ],
156                data: VertexBufferData::new::<crate::gui::draw::Vertex>(None),
157            }],
158            usage: BufferUsage::DynamicDraw,
159        };
160
161        Ok(Self {
162            geometry_buffer: server.create_geometry_buffer(geometry_buffer_desc)?,
163            clipping_geometry_buffer: server
164                .create_geometry_buffer(clipping_geometry_buffer_desc)?,
165            shader: UiShader::new(server)?,
166        })
167    }
168
169    /// Renders given UI's drawing context to specified frame buffer.
170    pub fn render(
171        &mut self,
172        args: UiRenderContext,
173    ) -> Result<RenderPassStatistics, FrameworkError> {
174        let UiRenderContext {
175            server,
176            viewport,
177            frame_buffer,
178            frame_width,
179            frame_height,
180            drawing_context,
181            fallback_resources,
182            texture_cache,
183            uniform_buffer_cache,
184            flat_shader,
185        } = args;
186
187        let mut statistics = RenderPassStatistics::default();
188
189        self.geometry_buffer
190            .set_buffer_data_of_type(0, drawing_context.get_vertices());
191        self.geometry_buffer
192            .set_triangles(drawing_context.get_triangles());
193
194        let ortho = Matrix4::new_orthographic(0.0, frame_width, frame_height, 0.0, -1.0, 1.0);
195        let resolution = Vector2::new(frame_width, frame_height);
196
197        for cmd in drawing_context.get_commands() {
198            let mut diffuse_texture = &fallback_resources.white_dummy;
199            let mut is_font_texture = false;
200
201            let mut clip_bounds = cmd.clip_bounds;
202            clip_bounds.position.x = clip_bounds.position.x.floor();
203            clip_bounds.position.y = clip_bounds.position.y.floor();
204            clip_bounds.size.x = clip_bounds.size.x.ceil();
205            clip_bounds.size.y = clip_bounds.size.y.ceil();
206
207            let scissor_box = Some(ScissorBox {
208                x: clip_bounds.position.x as i32,
209                // Because OpenGL was designed for mathematicians, it has origin at lower left corner.
210                y: viewport.size.y - (clip_bounds.position.y + clip_bounds.size.y) as i32,
211                width: clip_bounds.size.x as i32,
212                height: clip_bounds.size.y as i32,
213            });
214
215            let mut stencil_test = None;
216
217            // Draw clipping geometry first if we have any. This is optional, because complex
218            // clipping is very rare and in most cases scissor test will do the job.
219            if let Some(clipping_geometry) = cmd.clipping_geometry.as_ref() {
220                frame_buffer.clear(viewport, None, None, Some(0));
221
222                self.clipping_geometry_buffer
223                    .set_buffer_data_of_type(0, &clipping_geometry.vertex_buffer);
224                self.clipping_geometry_buffer
225                    .set_triangles(&clipping_geometry.triangle_buffer);
226
227                let uniform_buffer =
228                    uniform_buffer_cache.write(StaticUniformBuffer::<256>::new().with(&ortho))?;
229
230                // Draw
231                statistics += frame_buffer.draw(
232                    &*self.clipping_geometry_buffer,
233                    viewport,
234                    &*flat_shader.program,
235                    &DrawParameters {
236                        cull_face: None,
237                        color_write: ColorMask::all(false),
238                        depth_write: false,
239                        stencil_test: None,
240                        depth_test: None,
241                        blend: None,
242                        stencil_op: StencilOp {
243                            zpass: StencilAction::Incr,
244                            ..Default::default()
245                        },
246                        scissor_box,
247                    },
248                    &[ResourceBindGroup {
249                        bindings: &[ResourceBinding::Buffer {
250                            buffer: uniform_buffer,
251                            binding: BufferLocation::Auto {
252                                shader_location: flat_shader.uniform_buffer_binding,
253                            },
254                            data_usage: Default::default(),
255                        }],
256                    }],
257                    ElementRange::Full,
258                )?;
259
260                // Make sure main geometry will be drawn only on marked pixels.
261                stencil_test = Some(StencilFunc {
262                    func: CompareFunc::Equal,
263                    ref_value: 1,
264                    ..Default::default()
265                });
266            }
267
268            match &cmd.texture {
269                CommandTexture::Font {
270                    font,
271                    page_index,
272                    height,
273                } => {
274                    if let Some(font) = font.state().data() {
275                        let page_size = font.page_size() as u32;
276                        if let Some(page) = font
277                            .atlases
278                            .get_mut(height)
279                            .and_then(|atlas| atlas.pages.get_mut(*page_index))
280                        {
281                            if page.texture.is_none() || page.modified {
282                                if let Some(details) = Texture::from_bytes(
283                                    TextureKind::Rectangle {
284                                        width: page_size,
285                                        height: page_size,
286                                    },
287                                    TexturePixelKind::R8,
288                                    page.pixels.clone(),
289                                ) {
290                                    page.texture = Some(
291                                        TextureResource::new_ok(ResourceKind::Embedded, details)
292                                            .into(),
293                                    );
294                                    page.modified = false;
295                                }
296                            }
297                            if let Some(texture) = texture_cache.get(
298                                server,
299                                &page
300                                    .texture
301                                    .as_ref()
302                                    .unwrap()
303                                    .try_cast::<Texture>()
304                                    .unwrap(),
305                            ) {
306                                diffuse_texture = texture;
307                            }
308                            is_font_texture = true;
309                        }
310                    }
311                }
312                CommandTexture::Texture(texture) => {
313                    if let Some(texture) = texture_cache.get(server, texture) {
314                        diffuse_texture = texture;
315                    }
316                }
317                _ => (),
318            }
319
320            let mut raw_stops = [0.0; 16];
321            let mut raw_colors = [Vector4::default(); 16];
322            let bounds_max = cmd.bounds.right_bottom_corner();
323
324            let (gradient_origin, gradient_end) = match cmd.brush {
325                Brush::Solid(_) => (Vector2::default(), Vector2::default()),
326                Brush::LinearGradient { from, to, .. } => (from, to),
327                Brush::RadialGradient { center, .. } => (center, Vector2::default()),
328            };
329
330            let params = DrawParameters {
331                cull_face: None,
332                color_write: ColorMask::all(true),
333                depth_write: false,
334                stencil_test,
335                depth_test: None,
336                blend: Some(BlendParameters {
337                    func: BlendFunc::new(BlendFactor::SrcAlpha, BlendFactor::OneMinusSrcAlpha),
338                    ..Default::default()
339                }),
340                stencil_op: Default::default(),
341                scissor_box,
342            };
343
344            let solid_color = match cmd.brush {
345                Brush::Solid(color) => color,
346                _ => Color::WHITE,
347            };
348            let gradient_colors = match cmd.brush {
349                Brush::Solid(_) => &raw_colors,
350                Brush::LinearGradient { ref stops, .. }
351                | Brush::RadialGradient { ref stops, .. } => {
352                    for (i, point) in stops.iter().enumerate() {
353                        raw_colors[i] = point.color.as_frgba();
354                    }
355                    &raw_colors
356                }
357            };
358            let gradient_stops = match cmd.brush {
359                Brush::Solid(_) => &raw_stops,
360                Brush::LinearGradient { ref stops, .. }
361                | Brush::RadialGradient { ref stops, .. } => {
362                    for (i, point) in stops.iter().enumerate() {
363                        raw_stops[i] = point.stop;
364                    }
365                    &raw_stops
366                }
367            };
368            let brush_type = match cmd.brush {
369                Brush::Solid(_) => 0,
370                Brush::LinearGradient { .. } => 1,
371                Brush::RadialGradient { .. } => 2,
372            };
373            let gradient_point_count = match cmd.brush {
374                Brush::Solid(_) => 0,
375                Brush::LinearGradient { ref stops, .. }
376                | Brush::RadialGradient { ref stops, .. } => stops.len() as i32,
377            };
378
379            let uniform_buffer = uniform_buffer_cache.write(
380                StaticUniformBuffer::<1024>::new()
381                    .with(&ortho)
382                    .with(&solid_color)
383                    .with_slice(gradient_colors)
384                    .with_slice(gradient_stops)
385                    .with(&gradient_origin)
386                    .with(&gradient_end)
387                    .with(&resolution)
388                    .with(&cmd.bounds.position)
389                    .with(&bounds_max)
390                    .with(&is_font_texture)
391                    .with(&cmd.opacity)
392                    .with(&brush_type)
393                    .with(&gradient_point_count),
394            )?;
395
396            let shader = &self.shader;
397            statistics += frame_buffer.draw(
398                &*self.geometry_buffer,
399                viewport,
400                &*self.shader.program,
401                &params,
402                &[ResourceBindGroup {
403                    bindings: &[
404                        ResourceBinding::texture(diffuse_texture, &shader.diffuse_texture),
405                        ResourceBinding::Buffer {
406                            buffer: uniform_buffer,
407                            binding: BufferLocation::Auto {
408                                shader_location: self.shader.uniform_block_index,
409                            },
410                            data_usage: Default::default(),
411                        },
412                    ],
413                }],
414                ElementRange::Specific {
415                    offset: cmd.triangles.start,
416                    count: cmd.triangles.end - cmd.triangles.start,
417                },
418            )?;
419        }
420
421        Ok(statistics)
422    }
423}