Skip to main content

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::{manager::ResourceManager, untyped::ResourceKind},
25    core::{
26        algebra::{Matrix4, Vector2, Vector4},
27        arrayvec::ArrayVec,
28        color::Color,
29        math::Rect,
30        some_or_continue,
31        sstorage::ImmutableString,
32    },
33    graphics::{
34        buffer::BufferUsage,
35        error::FrameworkError,
36        framebuffer::{GpuFrameBuffer, ResourceBindGroup, ResourceBinding},
37        geometry_buffer::{
38            AttributeDefinition, AttributeKind, ElementsDescriptor, GpuGeometryBuffer,
39            GpuGeometryBufferDescriptor, VertexBufferData, VertexBufferDescriptor,
40        },
41        gpu_program::ShaderResourceKind,
42        server::GraphicsServer,
43        uniform::StaticUniformBuffer,
44        BlendFactor, BlendFunc, BlendParameters, ColorMask, CompareFunc, DrawParameters,
45        ElementRange, ScissorBox, StencilFunc,
46    },
47    gui::{
48        brush::Brush,
49        draw::Command,
50        draw::{CommandTexture, DrawingContext},
51    },
52    renderer::{
53        bundle::{self, make_texture_binding},
54        cache::{
55            shader::{binding, property, PropertyGroup, RenderMaterial, ShaderCache},
56            uniform::{UniformBlockLocation, UniformBufferCache, UniformMemoryAllocator},
57        },
58        resources::RendererResources,
59        RenderPassStatistics, TextureCache,
60    },
61    resource::texture::{Texture, TextureKind, TexturePixelKind, TextureResource},
62};
63use fyrox_ui::UserInterface;
64use uuid::Uuid;
65
66/// User interface renderer allows you to render drawing context in specified render target.
67pub struct UiRenderer {
68    geometry_buffer: GpuGeometryBuffer,
69    clipping_geometry_buffer: GpuGeometryBuffer,
70}
71
72/// A set of parameters to render a specified user interface drawing context.
73pub struct UiRenderContext<'a, 'b, 'c> {
74    /// Graphics server.
75    pub server: &'a dyn GraphicsServer,
76    /// Viewport to where render the user interface.
77    pub viewport: Rect<i32>,
78    /// Frame buffer to where render the user interface.
79    pub frame_buffer: &'b GpuFrameBuffer,
80    /// Width of the frame buffer to where render the user interface.
81    pub frame_width: f32,
82    /// Height of the frame buffer to where render the user interface.
83    pub frame_height: f32,
84    /// Drawing context of a user interface.
85    pub drawing_context: &'c DrawingContext,
86    /// Renderer resources.
87    pub renderer_resources: &'a RendererResources,
88    /// GPU texture cache.
89    pub texture_cache: &'a mut TextureCache,
90    /// A reference to the cache of uniform buffers.
91    pub uniform_buffer_cache: &'a mut UniformBufferCache,
92    /// A reference to the render pass cache.
93    pub render_pass_cache: &'a mut ShaderCache,
94    /// A reference to the uniform memory allocator.
95    pub uniform_memory_allocator: &'a mut UniformMemoryAllocator,
96    /// A reference to the resource manager.
97    pub resource_manager: &'a ResourceManager,
98}
99
100/// Contains all the info required to render a user interface.
101pub struct UiRenderInfo<'a> {
102    /// A reference to a user interface that needs to be rendered.
103    pub ui: &'a UserInterface,
104    /// A render target to render a user interface (UI) to. If [`None`], then the UI will be rendered
105    /// to the screen directly.
106    pub render_target: Option<TextureResource>,
107    /// A color that will be used to fill a render target before rendering of the UI. Ignored if the
108    /// render target is [`None`] and nothing will be cleared.
109    pub clear_color: Color,
110    /// A reference to the resource manager.
111    pub resource_manager: &'a ResourceManager,
112}
113
114fn write_uniform_blocks(
115    ortho: &Matrix4<f32>,
116    resolution: Vector2<f32>,
117    commands: &[Command],
118    uniform_memory_allocator: &mut UniformMemoryAllocator,
119) -> Vec<ArrayVec<(usize, UniformBlockLocation), 8>> {
120    let mut block_locations = Vec::with_capacity(commands.len());
121
122    for cmd in commands {
123        let mut command_block_locations = ArrayVec::<(usize, UniformBlockLocation), 8>::new();
124
125        let material_data_guard = cmd.material.data_ref();
126        let material = some_or_continue!(material_data_guard.as_loaded_ref());
127        let shader_data_guard = material.shader().data_ref();
128        let shader = some_or_continue!(shader_data_guard.as_loaded_ref());
129
130        for resource in shader.definition.resources.iter() {
131            if resource.name.as_str() == "fyrox_widgetData" {
132                let mut raw_stops = [0.0; 16];
133                let mut raw_colors = [Vector4::default(); 16];
134                let bounds_max = cmd.bounds.right_bottom_corner();
135
136                let (gradient_origin, gradient_end) = match cmd.brush {
137                    Brush::Solid(_) => (Vector2::default(), Vector2::default()),
138                    Brush::LinearGradient { from, to, .. } => (from, to),
139                    Brush::RadialGradient { center, .. } => (center, Vector2::default()),
140                };
141
142                let solid_color = match cmd.brush {
143                    Brush::Solid(color) => color,
144                    _ => Color::WHITE,
145                };
146                let gradient_colors = match cmd.brush {
147                    Brush::Solid(_) => &raw_colors,
148                    Brush::LinearGradient { ref stops, .. }
149                    | Brush::RadialGradient { ref stops, .. } => {
150                        for (i, point) in stops.iter().enumerate() {
151                            raw_colors[i] = point.color.as_frgba();
152                        }
153                        &raw_colors
154                    }
155                };
156                let gradient_stops = match cmd.brush {
157                    Brush::Solid(_) => &raw_stops,
158                    Brush::LinearGradient { ref stops, .. }
159                    | Brush::RadialGradient { ref stops, .. } => {
160                        for (i, point) in stops.iter().enumerate() {
161                            raw_stops[i] = point.stop;
162                        }
163                        &raw_stops
164                    }
165                };
166                let brush_type = match cmd.brush {
167                    Brush::Solid(_) => 0,
168                    Brush::LinearGradient { .. } => 1,
169                    Brush::RadialGradient { .. } => 2,
170                };
171                let gradient_point_count = match cmd.brush {
172                    Brush::Solid(_) => 0,
173                    Brush::LinearGradient { ref stops, .. }
174                    | Brush::RadialGradient { ref stops, .. } => stops.len() as i32,
175                };
176
177                let is_font_texture = matches!(cmd.texture, CommandTexture::Font { .. });
178
179                let buffer = StaticUniformBuffer::<2048>::new()
180                    .with(ortho)
181                    .with(&cmd.transform)
182                    .with(&solid_color)
183                    .with(gradient_colors.as_slice())
184                    .with(gradient_stops.as_slice())
185                    .with(&gradient_origin)
186                    .with(&gradient_end)
187                    .with(&resolution)
188                    .with(&cmd.bounds.position)
189                    .with(&bounds_max)
190                    .with(&is_font_texture)
191                    .with(&cmd.opacity)
192                    .with(&brush_type)
193                    .with(&gradient_point_count);
194
195                command_block_locations
196                    .push((resource.binding, uniform_memory_allocator.allocate(buffer)));
197            } else if let ShaderResourceKind::PropertyGroup(ref shader_property_group) =
198                resource.kind
199            {
200                let mut buf = StaticUniformBuffer::<16384>::new();
201
202                if let Some(material_property_group) =
203                    material.property_group_ref(resource.name.clone())
204                {
205                    bundle::write_with_material(
206                        shader_property_group,
207                        material_property_group,
208                        |c, n| c.property_ref(n.clone()).map(|p| p.as_ref()),
209                        &mut buf,
210                    );
211                } else {
212                    bundle::write_shader_values(shader_property_group, &mut buf)
213                }
214
215                command_block_locations
216                    .push((resource.binding, uniform_memory_allocator.allocate(buf)));
217            }
218        }
219
220        block_locations.push(command_block_locations);
221    }
222
223    block_locations
224}
225
226impl UiRenderer {
227    pub(in crate::renderer) fn new(server: &dyn GraphicsServer) -> Result<Self, FrameworkError> {
228        let geometry_buffer_desc = GpuGeometryBufferDescriptor {
229            name: "UiGeometryBuffer",
230            elements: ElementsDescriptor::Triangles(&[]),
231            buffers: &[VertexBufferDescriptor {
232                usage: BufferUsage::DynamicDraw,
233                attributes: &[
234                    AttributeDefinition {
235                        location: 0,
236                        kind: AttributeKind::Float,
237                        component_count: 2,
238                        normalized: false,
239                        divisor: 0,
240                    },
241                    AttributeDefinition {
242                        location: 1,
243                        kind: AttributeKind::Float,
244                        component_count: 2,
245                        normalized: false,
246                        divisor: 0,
247                    },
248                    AttributeDefinition {
249                        location: 2,
250                        kind: AttributeKind::UnsignedByte,
251                        component_count: 4,
252                        normalized: true, // Make sure [0; 255] -> [0; 1]
253                        divisor: 0,
254                    },
255                ],
256                data: VertexBufferData::new::<crate::gui::draw::Vertex>(None),
257            }],
258            usage: BufferUsage::DynamicDraw,
259        };
260
261        let clipping_geometry_buffer_desc = GpuGeometryBufferDescriptor {
262            name: "UiClippingGeometryBuffer",
263            elements: ElementsDescriptor::Triangles(&[]),
264            buffers: &[VertexBufferDescriptor {
265                usage: BufferUsage::DynamicDraw,
266                attributes: &[
267                    // We're interested only in position. Fragment shader won't run for clipping geometry anyway.
268                    AttributeDefinition {
269                        location: 0,
270                        kind: AttributeKind::Float,
271                        component_count: 2,
272                        normalized: false,
273                        divisor: 0,
274                    },
275                ],
276                data: VertexBufferData::new::<crate::gui::draw::Vertex>(None),
277            }],
278            usage: BufferUsage::DynamicDraw,
279        };
280
281        Ok(Self {
282            geometry_buffer: server.create_geometry_buffer(geometry_buffer_desc)?,
283            clipping_geometry_buffer: server
284                .create_geometry_buffer(clipping_geometry_buffer_desc)?,
285        })
286    }
287
288    /// Renders given UI's drawing context to specified frame buffer.
289    pub fn render(
290        &mut self,
291        args: UiRenderContext,
292    ) -> Result<RenderPassStatistics, FrameworkError> {
293        let UiRenderContext {
294            server,
295            viewport,
296            frame_buffer,
297            frame_width,
298            frame_height,
299            drawing_context,
300            renderer_resources,
301            texture_cache,
302            uniform_buffer_cache,
303            render_pass_cache,
304            uniform_memory_allocator,
305            resource_manager,
306        } = args;
307
308        let mut statistics = RenderPassStatistics::default();
309
310        self.geometry_buffer
311            .set_buffer_data_of_type(0, drawing_context.get_vertices());
312        self.geometry_buffer
313            .set_triangles(drawing_context.get_triangles());
314
315        let ortho = Matrix4::new_orthographic(0.0, frame_width, frame_height, 0.0, -1.0, 1.0);
316        let resolution = Vector2::new(frame_width, frame_height);
317
318        let uniform_blocks = write_uniform_blocks(
319            &ortho,
320            resolution,
321            drawing_context.get_commands(),
322            uniform_memory_allocator,
323        );
324
325        uniform_memory_allocator.upload(server)?;
326
327        for (cmd, command_uniform_blocks) in
328            drawing_context.get_commands().iter().zip(uniform_blocks)
329        {
330            let mut clip_bounds = cmd.clip_bounds;
331            clip_bounds.position.x = clip_bounds.position.x.floor();
332            clip_bounds.position.y = clip_bounds.position.y.floor();
333            clip_bounds.size.x = clip_bounds.size.x.ceil();
334            clip_bounds.size.y = clip_bounds.size.y.ceil();
335
336            let scissor_box = Some(ScissorBox {
337                x: clip_bounds.position.x as i32,
338                // Because OpenGL was designed for mathematicians, it has origin at lower left corner.
339                y: viewport.size.y - (clip_bounds.position.y + clip_bounds.size.y) as i32,
340                width: clip_bounds.size.x as i32,
341                height: clip_bounds.size.y as i32,
342            });
343
344            let mut stencil_test = None;
345
346            // Draw clipping geometry first if we have any. This is optional, because complex
347            // clipping is very rare and in most cases scissor test will do the job.
348            if let Some(clipping_geometry) = cmd.clipping_geometry.as_ref() {
349                frame_buffer.clear(viewport, None, None, Some(0));
350
351                self.clipping_geometry_buffer
352                    .set_buffer_data_of_type(0, &clipping_geometry.vertex_buffer);
353                self.clipping_geometry_buffer
354                    .set_triangles(&clipping_geometry.triangle_buffer);
355
356                // Draw
357                let properties = PropertyGroup::from([
358                    property("projectionMatrix", &ortho),
359                    property("worldMatrix", &cmd.transform),
360                ]);
361                let material = RenderMaterial::from([binding("properties", &properties)]);
362                statistics += renderer_resources.shaders.ui.run_pass(
363                    1,
364                    &ImmutableString::new("Clip"),
365                    frame_buffer,
366                    &self.geometry_buffer,
367                    viewport,
368                    &material,
369                    uniform_buffer_cache,
370                    Default::default(),
371                    None,
372                )?;
373
374                // Make sure main geometry will be drawn only on marked pixels.
375                stencil_test = Some(StencilFunc {
376                    func: CompareFunc::Equal,
377                    ref_value: 1,
378                    ..Default::default()
379                });
380            }
381
382            let params = DrawParameters {
383                cull_face: None,
384                color_write: ColorMask::all(true),
385                depth_write: false,
386                stencil_test,
387                depth_test: None,
388                blend: Some(BlendParameters {
389                    func: BlendFunc::new(BlendFactor::SrcAlpha, BlendFactor::OneMinusSrcAlpha),
390                    ..Default::default()
391                }),
392                stencil_op: Default::default(),
393                scissor_box,
394            };
395
396            let element_range = ElementRange::Specific {
397                offset: cmd.triangles.start,
398                count: cmd.triangles.end - cmd.triangles.start,
399            };
400
401            let material_data_guard = cmd.material.data_ref();
402            let material = some_or_continue!(material_data_guard.as_loaded_ref());
403
404            if let Some(render_pass_container) = render_pass_cache.get(server, material.shader()) {
405                let shader_data_guard = material.shader().data_ref();
406                let shader = some_or_continue!(shader_data_guard.as_loaded_ref());
407
408                let render_pass = render_pass_container.get(&ImmutableString::new("Forward"))?;
409
410                let mut resource_bindings = ArrayVec::<ResourceBinding, 32>::new();
411
412                for resource in shader.definition.resources.iter() {
413                    match resource.kind {
414                        ShaderResourceKind::Texture { fallback, .. } => {
415                            if resource.name.as_str() == "fyrox_widgetTexture" {
416                                let mut diffuse_texture = (
417                                    &renderer_resources.white_dummy,
418                                    &renderer_resources.linear_wrap_sampler,
419                                );
420
421                                match &cmd.texture {
422                                    CommandTexture::Font {
423                                        font,
424                                        page_index,
425                                        height,
426                                    } => {
427                                        if let Some(font) = font.state().data() {
428                                            let page_size = font.page_size() as u32;
429                                            if let Some(page) = font
430                                                .atlases
431                                                .get_mut(height)
432                                                .and_then(|atlas| atlas.pages.get_mut(*page_index))
433                                            {
434                                                if page.texture.is_none() || page.modified {
435                                                    if let Some(details) = Texture::from_bytes(
436                                                        TextureKind::Rectangle {
437                                                            width: page_size,
438                                                            height: page_size,
439                                                        },
440                                                        TexturePixelKind::R8,
441                                                        page.pixels.clone(),
442                                                    ) {
443                                                        page.texture = Some(
444                                                            TextureResource::new_ok(
445                                                                Uuid::new_v4(),
446                                                                ResourceKind::Embedded,
447                                                                details,
448                                                            )
449                                                            .into(),
450                                                        );
451                                                        page.modified = false;
452                                                    }
453                                                }
454                                                if let Some(texture) = texture_cache.get(
455                                                    server,
456                                                    resource_manager,
457                                                    &page
458                                                        .texture
459                                                        .as_ref()
460                                                        .unwrap()
461                                                        .try_cast::<Texture>()
462                                                        .unwrap(),
463                                                ) {
464                                                    diffuse_texture = (
465                                                        &texture.gpu_texture,
466                                                        &texture.gpu_sampler,
467                                                    );
468                                                }
469                                            }
470                                        }
471                                    }
472                                    CommandTexture::Texture(texture) => {
473                                        if let Some(texture) =
474                                            texture_cache.get(server, resource_manager, texture)
475                                        {
476                                            diffuse_texture =
477                                                (&texture.gpu_texture, &texture.gpu_sampler);
478                                        }
479                                    }
480                                    _ => (),
481                                }
482
483                                resource_bindings.push(ResourceBinding::texture(
484                                    diffuse_texture.0,
485                                    diffuse_texture.1,
486                                    resource.binding,
487                                ))
488                            } else {
489                                resource_bindings.push(make_texture_binding(
490                                    server,
491                                    material,
492                                    resource,
493                                    renderer_resources,
494                                    fallback,
495                                    resource_manager,
496                                    texture_cache,
497                                ))
498                            }
499                        }
500                        ShaderResourceKind::PropertyGroup(_) => {
501                            if let Some((_, block_location)) = command_uniform_blocks
502                                .iter()
503                                .find(|(binding, _)| *binding == resource.binding)
504                            {
505                                resource_bindings.push(
506                                    uniform_memory_allocator
507                                        .block_to_binding(*block_location, resource.binding),
508                                );
509                            }
510                        }
511                    }
512                }
513
514                statistics += frame_buffer.draw(
515                    &self.geometry_buffer,
516                    viewport,
517                    &render_pass.program,
518                    &params,
519                    &[ResourceBindGroup {
520                        bindings: &resource_bindings,
521                    }],
522                    element_range,
523                )?;
524            }
525        }
526
527        Ok(statistics)
528    }
529}