veloren_voxygen/ui/ice/renderer/
mod.rs

1mod defaults;
2mod primitive;
3pub mod style;
4mod widget;
5
6pub use defaults::Defaults;
7
8use primitive::Primitive;
9
10use super::{
11    super::graphic::{self, Graphic, TexId},
12    Font, FontId, RawFont, Rotation,
13    cache::Cache,
14    widget::image,
15};
16use crate::{
17    error::Error,
18    render::{
19        DynamicModel, Mesh, Renderer, UiBoundLocals, UiDrawer, UiLocals, UiMode, UiVertex,
20        create_ui_quad, create_ui_quad_vert_gradient,
21    },
22};
23use common::{slowjob::SlowJobPool, util::srgba_to_linear};
24use common_base::span;
25use std::{convert::TryInto, ops::Range};
26use vek::*;
27
28enum DrawKind {
29    Image(TexId),
30    // Text and non-textured geometry
31    Plain,
32}
33
34#[expect(dead_code)] // TODO: remove once WorldPos is used
35enum DrawCommand {
36    Draw { kind: DrawKind, verts: Range<u32> },
37    Scissor(Aabr<u16>),
38    WorldPos(Option<usize>),
39}
40impl DrawCommand {
41    fn image(verts: Range<usize>, id: TexId) -> DrawCommand {
42        DrawCommand::Draw {
43            kind: DrawKind::Image(id),
44            // TODO: move conversion into helper method so we don't have to write it out so many
45            // times
46            verts: verts
47                .start
48                .try_into()
49                .expect("Vertex count for UI rendering does not fit in a u32!")
50                ..verts
51                    .end
52                    .try_into()
53                    .expect("Vertex count for UI rendering does not fit in a u32!"),
54        }
55    }
56
57    fn plain(verts: Range<usize>) -> DrawCommand {
58        DrawCommand::Draw {
59            kind: DrawKind::Plain,
60            verts: verts
61                .start
62                .try_into()
63                .expect("Vertex count for UI rendering does not fit in a u32!")
64                ..verts
65                    .end
66                    .try_into()
67                    .expect("Vertex count for UI rendering does not fit in a u32!"),
68        }
69    }
70}
71
72#[derive(PartialEq)]
73enum State {
74    Image(TexId),
75    Plain,
76}
77
78// Optimization idea inspired by what I think iced wgpu renderer may be doing:
79// Could have layers of things which don't intersect and thus can be reordered
80// arbitrarily
81
82pub struct IcedRenderer {
83    //image_map: Map<(Image, Rotation)>,
84    cache: Cache,
85    // Model for drawing the ui
86    model: DynamicModel<UiVertex>,
87    // Consts to specify positions of ingame elements (e.g. Nametags)
88    ingame_locals: Vec<UiBoundLocals>,
89    // Consts for default ui drawing position (ie the interface)
90    interface_locals: UiBoundLocals,
91
92    // Used to delay cache resizing until after current frame is drawn
93    //need_cache_resize: bool,
94    // Half of physical resolution
95    half_res: Vec2<f32>,
96    // Pixel perfection alignment
97    align: Vec2<f32>,
98    // Scale factor between physical and win dims
99    p_scale: f32,
100    // Pretend dims :) (i.e. scaled)
101    win_dims: Vec2<f32>,
102    // Scissor for the whole window
103    window_scissor: Aabr<u16>,
104
105    // Per-frame/update
106    current_state: State,
107    mesh: Mesh<UiVertex>,
108    glyphs: Vec<(usize, usize, Rgba<f32>, Vec2<u32>)>,
109    // Output from glyph_brush in the previous frame
110    // It can sometimes ask you to redraw with these instead (idk if that is done with
111    // pre-positioned glyphs)
112    last_glyph_verts: Vec<(Aabr<f32>, Aabr<f32>)>,
113    start: usize,
114    // Draw commands for the next render
115    draw_commands: Vec<DrawCommand>,
116}
117impl IcedRenderer {
118    pub fn new(
119        renderer: &mut Renderer,
120        scaled_resolution: Vec2<f32>,
121        physical_resolution: Vec2<u32>,
122        default_font: Font,
123    ) -> Result<Self, Error> {
124        let (half_res, align, p_scale) =
125            Self::calculate_resolution_dependents(physical_resolution, scaled_resolution);
126
127        let interface_locals = renderer.create_ui_bound_locals(&[UiLocals::default()]);
128
129        Ok(Self {
130            cache: Cache::new(renderer, default_font)?,
131            draw_commands: Vec::new(),
132            model: renderer.create_dynamic_model(100),
133            interface_locals,
134            ingame_locals: Vec::new(),
135            mesh: Mesh::new(),
136            glyphs: Vec::new(),
137            last_glyph_verts: Vec::new(),
138            current_state: State::Plain,
139            half_res,
140            align,
141            p_scale,
142            win_dims: scaled_resolution,
143            window_scissor: default_scissor(physical_resolution),
144            start: 0,
145        })
146    }
147
148    pub fn add_font(&mut self, font: RawFont) -> FontId { self.cache.add_font(font) }
149
150    /// Allows clearing out the fonts when switching languages
151    pub fn clear_fonts(&mut self, default_font: Font) { self.cache.clear_fonts(default_font); }
152
153    pub fn add_graphic(&mut self, graphic: Graphic) -> graphic::Id {
154        self.cache.add_graphic(graphic)
155    }
156
157    pub fn replace_graphic(&mut self, id: graphic::Id, graphic: Graphic) {
158        self.cache.replace_graphic(id, graphic);
159    }
160
161    fn image_dims(&self, handle: image::Handle) -> (u32, u32) {
162        self
163            .cache
164            .graphic_cache()
165            .get_graphic_dims((handle, Rotation::None))
166            // TODO: don't unwrap
167            .unwrap()
168    }
169
170    pub fn resize(
171        &mut self,
172        scaled_resolution: Vec2<f32>,
173        physical_resolution: Vec2<u32>,
174        renderer: &mut Renderer,
175    ) {
176        self.win_dims = scaled_resolution;
177        self.window_scissor = default_scissor(physical_resolution);
178
179        self.update_resolution_dependents(physical_resolution);
180
181        // Resize graphic cache
182        self.cache.resize_graphic_cache(renderer);
183        // Resize glyph cache
184        self.cache.resize_glyph_cache(renderer).unwrap();
185    }
186
187    pub fn draw(
188        &mut self,
189        primitive: Primitive,
190        renderer: &mut Renderer,
191        pool: Option<&SlowJobPool>,
192    ) {
193        span!(_guard, "draw", "IcedRenderer::draw");
194        // Re-use memory
195        self.draw_commands.clear();
196        self.mesh.clear();
197        self.glyphs.clear();
198
199        self.current_state = State::Plain;
200        self.start = 0;
201
202        self.draw_primitive(primitive, Vec2::zero(), 1.0, renderer, pool);
203
204        // Enter the final command.
205        self.draw_commands.push(match self.current_state {
206            State::Plain => DrawCommand::plain(self.start..self.mesh.vertices().len()),
207            State::Image(id) => DrawCommand::image(self.start..self.mesh.vertices().len(), id),
208        });
209
210        // Draw glyph cache (use for debugging).
211        /*self.draw_commands
212            .push(DrawCommand::Scissor(self.window_scissor));
213        self.start = self.mesh.vertices().len();
214        self.mesh.push_quad(create_ui_quad(
215            Aabr {
216                min: (-1.0, -1.0).into(),
217                max: (1.0, 1.0).into(),
218            },
219            Aabr {
220                min: (0.0, 1.0).into(),
221                max: (1.0, 0.0).into(),
222            },
223            Rgba::new(1.0, 1.0, 1.0, 0.3),
224            UiMode::Text,
225        ));
226        self.draw_commands
227            .push(DrawCommand::plain(self.start..self.mesh.vertices().len()));*/
228
229        // Fill in placeholder glyph quads
230        let (glyph_cache, (cache_tex, _)) = self.cache.glyph_cache_mut_and_tex();
231        let half_res = self.half_res;
232
233        let brush_result = glyph_cache.process_queued(
234            |rect, tex_data| {
235                let offset = rect.min;
236                let size = [rect.width(), rect.height()];
237
238                let new_data = tex_data
239                    .iter()
240                    .map(|x| [255, 255, 255, *x])
241                    .collect::<Vec<[u8; 4]>>();
242
243                renderer.update_texture(cache_tex, offset, size, &new_data);
244            },
245            // Urgh more allocation we don't need
246            |vertex_data| {
247                let uv_rect = vertex_data.tex_coords;
248                let uv = Aabr {
249                    min: Vec2::new(uv_rect.min.x, uv_rect.max.y),
250                    max: Vec2::new(uv_rect.max.x, uv_rect.min.y),
251                };
252                let pixel_coords = vertex_data.pixel_coords;
253                let rect = Aabr {
254                    min: Vec2::new(
255                        pixel_coords.min.x / half_res.x - 1.0,
256                        1.0 - pixel_coords.max.y / half_res.y,
257                    ),
258                    max: Vec2::new(
259                        pixel_coords.max.x / half_res.x - 1.0,
260                        1.0 - pixel_coords.min.y / half_res.y,
261                    ),
262                };
263                (uv, rect)
264            },
265        );
266
267        match brush_result {
268            Ok(brush_action) => {
269                match brush_action {
270                    glyph_brush::BrushAction::Draw(verts) => self.last_glyph_verts = verts,
271                    glyph_brush::BrushAction::ReDraw => {},
272                }
273
274                let glyphs = &self.glyphs;
275                let mesh = &mut self.mesh;
276                let p_scale = self.p_scale;
277                let half_res = self.half_res;
278
279                glyphs
280                    .iter()
281                    .flat_map(|(mesh_index, glyph_count, linear_color, offset)| {
282                        let mesh_index = *mesh_index;
283                        let linear_color = *linear_color;
284                        // Could potentially pass this in as part of the extras
285                        let offset =
286                            offset.map(|e| e as f32 * p_scale) / half_res * Vec2::new(-1.0, 1.0);
287                        (0..*glyph_count).map(move |i| (mesh_index + i * 6, linear_color, offset))
288                    })
289                    .zip(self.last_glyph_verts.iter())
290                    .for_each(|((mesh_index, linear_color, offset), (uv, rect))| {
291                        // TODO: add function to vek for this
292                        let rect = Aabr {
293                            min: rect.min + offset,
294                            max: rect.max + offset,
295                        };
296
297                        mesh.replace_quad(
298                            mesh_index,
299                            create_ui_quad(rect, *uv, linear_color, UiMode::Text),
300                        )
301                    });
302            },
303            Err(glyph_brush::BrushError::TextureTooSmall { suggested: (x, y) }) => {
304                tracing::error!(
305                    "Texture to small for all glyphs, would need one of the size: ({}, {})",
306                    x,
307                    y
308                );
309            },
310        }
311
312        // Create a larger dynamic model if the mesh is larger than the current model
313        // size.
314        if self.model.len() < self.mesh.vertices().len() {
315            self.model = renderer.create_dynamic_model(self.mesh.vertices().len() * 4 / 3);
316        }
317        // Update model with new mesh.
318        renderer.update_model(&self.model, &self.mesh, 0);
319    }
320
321    // Returns (half_res, align)
322    fn calculate_resolution_dependents(
323        res: Vec2<u32>,
324        win_dims: Vec2<f32>,
325    ) -> (Vec2<f32>, Vec2<f32>, f32) {
326        let half_res = res.map(|e| e as f32 / 2.0);
327        let align = align(res);
328        // Assume to be the same in x and y for now...
329        let p_scale = res.x as f32 / win_dims.x;
330
331        (half_res, align, p_scale)
332    }
333
334    fn update_resolution_dependents(&mut self, res: Vec2<u32>) {
335        let (half_res, align, p_scale) = Self::calculate_resolution_dependents(res, self.win_dims);
336        self.half_res = half_res;
337        self.align = align;
338        self.p_scale = p_scale;
339    }
340
341    fn gl_aabr(&self, bounds: iced::Rectangle) -> Aabr<f32> {
342        let flipped_y = self.win_dims.y - bounds.y;
343        let half_win_dims = self.win_dims.map(|e| e / 2.0);
344        let half_res = self.half_res;
345        let min = (((Vec2::new(bounds.x, flipped_y - bounds.height) - half_win_dims)
346            / half_win_dims
347            * half_res
348            + self.align)
349            .map(|e| e.round())
350            - self.align)
351            / half_res;
352        let max = (((Vec2::new(bounds.x + bounds.width, flipped_y) - half_win_dims)
353            / half_win_dims
354            * half_res
355            + self.align)
356            .map(|e| e.round())
357            - self.align)
358            / half_res;
359        Aabr { min, max }
360    }
361
362    fn position_glyphs(
363        &mut self,
364        bounds: iced::Rectangle,
365        horizontal_alignment: iced::HorizontalAlignment,
366        vertical_alignment: iced::VerticalAlignment,
367        text: &str,
368        size: u16,
369        font: FontId,
370    ) -> Vec<glyph_brush::SectionGlyph> {
371        use glyph_brush::{GlyphCruncher, HorizontalAlign, VerticalAlign};
372        // TODO: add option to align based on the geometry of the rendered glyphs
373        // instead of all possible glyphs
374        let (x, h_align) = match horizontal_alignment {
375            iced::HorizontalAlignment::Left => (bounds.x, HorizontalAlign::Left),
376            iced::HorizontalAlignment::Center => (bounds.center_x(), HorizontalAlign::Center),
377            iced::HorizontalAlignment::Right => (bounds.x + bounds.width, HorizontalAlign::Right),
378        };
379
380        let (y, v_align) = match vertical_alignment {
381            iced::VerticalAlignment::Top => (bounds.y, VerticalAlign::Top),
382            iced::VerticalAlignment::Center => (bounds.center_y(), VerticalAlign::Center),
383            iced::VerticalAlignment::Bottom => (bounds.y + bounds.height, VerticalAlign::Bottom),
384        };
385
386        let p_scale = self.p_scale;
387
388        let section = glyph_brush::Section {
389            screen_position: (x * p_scale, y * p_scale),
390            bounds: (bounds.width * p_scale, bounds.height * p_scale),
391            layout: glyph_brush::Layout::Wrap {
392                line_breaker: Default::default(),
393                h_align,
394                v_align,
395            },
396            text: vec![glyph_brush::Text {
397                text,
398                scale: (size as f32 * p_scale).into(),
399                font_id: font.0,
400                extra: (),
401            }],
402        };
403
404        self
405            .cache
406            .glyph_cache_mut()
407            .glyphs(section)
408            // We would still have to generate vertices for these even if they have no pixels
409            // Note: this is somewhat hacky and could fail if there is a non-whitespace character
410            // that is not visible (to solve this we could use the extra values in
411            // queue_pre_positioned to keep track of which glyphs are actually returned by
412            // proccess_queued)
413            .filter(|g| {
414                !text[g.byte_index..]
415                    .chars()
416                    .next()
417                    .unwrap()
418                    .is_whitespace()
419            })
420            .cloned()
421            .collect()
422    }
423
424    fn draw_primitive(
425        &mut self,
426        primitive: Primitive,
427        offset: Vec2<u32>,
428        alpha: f32,
429        renderer: &mut Renderer,
430        pool: Option<&SlowJobPool>,
431    ) {
432        match primitive {
433            Primitive::Group { primitives } => {
434                primitives
435                    .into_iter()
436                    .for_each(|p| self.draw_primitive(p, offset, alpha, renderer, pool));
437            },
438            Primitive::Image {
439                handle,
440                bounds,
441                color,
442                source_rect,
443            } => {
444                let color = srgba_to_linear(color.map(|e| e as f32 / 255.0));
445                let color = apply_alpha(color, alpha);
446                // Don't draw a transparent image.
447                if color.a == 0.0 {
448                    return;
449                }
450
451                let (graphic_id, rotation) = handle;
452                let gl_aabr = self.gl_aabr(iced::Rectangle {
453                    x: bounds.x - offset.x as f32,
454                    y: bounds.y - offset.y as f32,
455                    ..bounds
456                });
457
458                let graphic_cache = self.cache.graphic_cache_mut();
459                let half_res = self.half_res; // Make borrow checker happy by avoiding self in closure
460                let (source_aabr, gl_size) = {
461                    // Transform the source rectangle into uv coordinate.
462                    // TODO: Make sure this is right.  Especially the conversions.
463                    let ((uv_l, uv_r, uv_b, uv_t), gl_size) = match graphic_cache
464                        .get_graphic(graphic_id)
465                    {
466                        Some(Graphic::Blank) | None => return,
467                        Some(Graphic::Image(image, ..)) => {
468                            source_rect.and_then(|src_rect| {
469                                #[rustfmt::skip] use ::image::GenericImageView;
470                                let (image_w, image_h) = image.dimensions();
471                                let (source_w, source_h) = src_rect.size().into_tuple();
472                                let gl_size = gl_aabr.size();
473                                if image_w == 0
474                                    || image_h == 0
475                                    || source_w < 1.0
476                                    || source_h < 1.0
477                                    || gl_size.reduce_partial_min() < f32::EPSILON
478                                {
479                                    None
480                                } else {
481                                    // TODO: do this earlier
482                                    // Multiply drawn image size by ratio of original image
483                                    // size to
484                                    // source rectangle size (since as the proportion of the
485                                    // image gets
486                                    // smaller, the drawn size should get bigger), up to the
487                                    // actual
488                                    // size of the original image.
489                                    let ratio_x = (image_w as f32 / source_w)
490                                        .min((image_w as f32 / (gl_size.w * half_res.x)).max(1.0));
491                                    let ratio_y = (image_h as f32 / source_h)
492                                        .min((image_h as f32 / (gl_size.h * half_res.y)).max(1.0));
493                                    let (l, b) = src_rect.min.into_tuple();
494                                    let (r, t) = src_rect.max.into_tuple();
495                                    Some((
496                                        (
497                                            l / image_w as f32, /* * ratio_x */
498                                            r / image_w as f32, /* * ratio_x */
499                                            b / image_h as f32, /* * ratio_y */
500                                            t / image_h as f32, /* * ratio_y */
501                                        ),
502                                        Extent2::new(gl_size.w * ratio_x, gl_size.h * ratio_y),
503                                    ))
504                                    /* ((l / image_w as f32),
505                                    (r / image_w as f32),
506                                    (b / image_h as f32),
507                                    (t / image_h as f32)) */
508                                }
509                            })
510                        },
511                        // No easy way to interpret source_rect for voxels...
512                        Some(Graphic::Voxel(..)) => None,
513                    }
514                    .unwrap_or_else(|| ((0.0, 1.0, 0.0, 1.0), gl_aabr.size()));
515                    (
516                        Aabr {
517                            min: Vec2::new(uv_l, uv_b),
518                            max: Vec2::new(uv_r, uv_t),
519                        },
520                        gl_size,
521                    )
522                };
523
524                let resolution = Vec2::new(
525                    (gl_size.w * self.half_res.x).round() as u16,
526                    (gl_size.h * self.half_res.y).round() as u16,
527                );
528
529                // Don't do anything if resolution is zero
530                if resolution.map(|e| e == 0).reduce_or() {
531                    return;
532                    // TODO: consider logging uneeded elements
533                }
534
535                // Cache graphic at particular resolution.
536                let (uv_aabr, scale, tex_id) = match graphic_cache.cache_res(
537                    renderer,
538                    pool,
539                    graphic_id,
540                    resolution,
541                    // TODO: take f32 here
542                    source_aabr.map(|e| e as f64),
543                    rotation,
544                ) {
545                    // TODO: get dims from graphic_cache (or have it return floats directly)
546                    Some(((aabr, scale), tex_id)) => {
547                        let cache_dims = graphic_cache
548                            .get_tex(tex_id)
549                            .0
550                            .get_dimensions()
551                            .xy()
552                            .map(|e| e as f32);
553                        let min = Vec2::new(aabr.min.x as f32, aabr.max.y as f32) / cache_dims;
554                        let max = Vec2::new(aabr.max.x as f32, aabr.min.y as f32) / cache_dims;
555                        (Aabr { min, max }, scale, tex_id)
556                    },
557                    None => return,
558                };
559
560                // Switch to the image state if we are not in it already or if a different
561                // texture id was being used.
562                self.switch_state(State::Image(tex_id));
563
564                self.mesh
565                    .push_quad(create_ui_quad(gl_aabr, uv_aabr, color, UiMode::Image {
566                        scale,
567                    }));
568            },
569            Primitive::Gradient {
570                bounds,
571                top_linear_color,
572                bottom_linear_color,
573            } => {
574                // Don't draw a transparent rectangle.
575                let top_linear_color = apply_alpha(top_linear_color, alpha);
576                let bottom_linear_color = apply_alpha(bottom_linear_color, alpha);
577                if top_linear_color.a == 0.0 && bottom_linear_color.a == 0.0 {
578                    return;
579                }
580
581                self.switch_state(State::Plain);
582
583                let gl_aabr = self.gl_aabr(iced::Rectangle {
584                    x: bounds.x - offset.x as f32,
585                    y: bounds.y - offset.y as f32,
586                    ..bounds
587                });
588
589                self.mesh.push_quad(create_ui_quad_vert_gradient(
590                    gl_aabr,
591                    Aabr {
592                        min: Vec2::zero(),
593                        max: Vec2::zero(),
594                    },
595                    top_linear_color,
596                    bottom_linear_color,
597                    UiMode::Geometry,
598                ));
599            },
600
601            Primitive::Rectangle {
602                bounds,
603                linear_color,
604            } => {
605                let linear_color = apply_alpha(linear_color, alpha);
606                // Don't draw a transparent rectangle.
607                if linear_color.a == 0.0 {
608                    return;
609                }
610
611                self.switch_state(State::Plain);
612
613                let gl_aabr = self.gl_aabr(iced::Rectangle {
614                    x: bounds.x - offset.x as f32,
615                    y: bounds.y - offset.y as f32,
616                    ..bounds
617                });
618
619                self.mesh.push_quad(create_ui_quad(
620                    gl_aabr,
621                    Aabr {
622                        min: Vec2::zero(),
623                        max: Vec2::zero(),
624                    },
625                    linear_color,
626                    UiMode::Geometry,
627                ));
628            },
629            Primitive::Text {
630                glyphs,
631                bounds: _, // iced::Rectangle
632                linear_color,
633            } => {
634                let linear_color = apply_alpha(linear_color, alpha);
635                self.switch_state(State::Plain);
636
637                // TODO: makes sure we are not doing all this work for hidden text
638                // e.g. in chat
639                let glyph_cache = self.cache.glyph_cache_mut();
640
641                // Count glyphs
642                let glyph_count = glyphs.len();
643
644                // Queue the glyphs to be cached.
645                glyph_cache.queue_pre_positioned(
646                    glyphs,
647                    // TODO: glyph_brush should document that these need to be the same length
648                    vec![(); glyph_count],
649                    // Since we already passed in `bounds` to position the glyphs some of this
650                    // seems redundant...
651                    // Note: we can't actually use this because dropping glyphs messeses up the
652                    // counting and there is not a method provided to drop out of bounds
653                    // glyphs while positioning them
654                    // Note: keeping commented code in case how we handle text changes
655                    glyph_brush::ab_glyph::Rect {
656                        min: glyph_brush::ab_glyph::point(
657                            -10000.0, //bounds.x * self.p_scale,
658                            -10000.0, //bounds.y * self.p_scale,
659                        ),
660                        max: glyph_brush::ab_glyph::point(
661                            10000.0, //(bounds.x + bounds.width) * self.p_scale,
662                            10000.0, //(bounds.y + bounds.height) * self.p_scale,
663                        ),
664                    },
665                );
666
667                // Leave ui and verts blank to fill in when processing cached glyphs
668                let zero_aabr = Aabr {
669                    min: Vec2::broadcast(0.0),
670                    max: Vec2::broadcast(0.0),
671                };
672                self.glyphs.push((
673                    self.mesh.vertices().len(),
674                    glyph_count,
675                    linear_color,
676                    offset,
677                ));
678                for _ in 0..glyph_count {
679                    // Push placeholder quad
680                    // Note: moving to some sort of layering / z based system would be an
681                    // alternative to this (and might help with reducing draw
682                    // calls)
683                    self.mesh.push_quad(create_ui_quad(
684                        zero_aabr,
685                        zero_aabr,
686                        linear_color,
687                        UiMode::Text,
688                    ));
689                }
690            },
691            Primitive::Clip {
692                bounds,
693                offset: clip_offset,
694                content,
695            } => {
696                let new_scissor = {
697                    // TODO: incorporate current offset for nested Clips
698                    let intersection = Aabr {
699                        min: Vec2 {
700                            x: (bounds.x * self.p_scale) as u16,
701                            y: (bounds.y * self.p_scale) as u16,
702                        },
703                        max: Vec2 {
704                            x: ((bounds.x + bounds.width) * self.p_scale) as u16,
705                            y: ((bounds.y + bounds.height) * self.p_scale) as u16,
706                        },
707                    }
708                    .intersection(self.window_scissor);
709
710                    if intersection.is_valid() && intersection.size().map(|s| s > 0).reduce_and() {
711                        intersection
712                    } else {
713                        // If the intersection is invalid or zero sized we don't need to process
714                        // the content primitive
715                        return;
716                    }
717                };
718                // Not expecting this case: new_scissor == current_scissor
719                // So not optimizing for it
720
721                // Finish the current command.
722                // TODO: ensure we never push empty commands
723                self.draw_commands.push(match self.current_state {
724                    State::Plain => DrawCommand::plain(self.start..self.mesh.vertices().len()),
725                    State::Image(id) => {
726                        DrawCommand::image(self.start..self.mesh.vertices().len(), id)
727                    },
728                });
729                self.start = self.mesh.vertices().len();
730
731                self.draw_commands.push(DrawCommand::Scissor(new_scissor));
732
733                // TODO: support nested clips?
734                // TODO: if previous command was a clip changing back to the default replace it
735                // with this
736                // TODO: cull primitives outside the current scissor
737
738                // Renderer child
739                self.draw_primitive(*content, offset + clip_offset, alpha, renderer, pool);
740
741                // Reset scissor
742                self.draw_commands.push(match self.current_state {
743                    State::Plain => DrawCommand::plain(self.start..self.mesh.vertices().len()),
744                    State::Image(id) => {
745                        DrawCommand::image(self.start..self.mesh.vertices().len(), id)
746                    },
747                });
748                self.start = self.mesh.vertices().len();
749
750                self.draw_commands
751                    .push(DrawCommand::Scissor(self.window_scissor));
752            },
753            Primitive::Opacity { alpha: a, content } => {
754                self.draw_primitive(*content, offset, alpha * a, renderer, pool);
755            },
756            Primitive::Nothing => {},
757        }
758    }
759
760    // Switches to the specified state if not already in it
761    // If switch occurs current state is converted into a draw command
762    fn switch_state(&mut self, state: State) {
763        if self.current_state != state {
764            let vert_range = self.start..self.mesh.vertices().len();
765            let draw_command = match self.current_state {
766                State::Plain => DrawCommand::plain(vert_range),
767                State::Image(id) => DrawCommand::image(vert_range, id),
768            };
769            self.draw_commands.push(draw_command);
770            self.start = self.mesh.vertices().len();
771            self.current_state = state;
772        }
773    }
774
775    pub fn render<'a>(&'a self, drawer: &mut UiDrawer<'_, 'a>) {
776        span!(_guard, "render", "IcedRenderer::render");
777        let mut drawer = drawer.prepare(&self.interface_locals, &self.model, self.window_scissor);
778        for draw_command in self.draw_commands.iter() {
779            match draw_command {
780                DrawCommand::Scissor(new_scissor) => {
781                    drawer.set_scissor(*new_scissor);
782                },
783                DrawCommand::WorldPos(index) => {
784                    drawer.set_locals(
785                        index.map_or(&self.interface_locals, |i| &self.ingame_locals[i]),
786                    );
787                },
788                DrawCommand::Draw { kind, verts } => {
789                    // TODO: don't make these: assert!(!verts.is_empty());
790                    let tex = match kind {
791                        DrawKind::Image(tex_id) => self.cache.graphic_cache().get_tex(*tex_id),
792                        DrawKind::Plain => self.cache.glyph_cache_tex(),
793                    };
794                    drawer.draw(tex.1, verts.clone()); // Note: trivial clone
795                },
796            }
797        }
798    }
799}
800
801// Given the the resolution determines the offset needed to align integer
802// offsets from the center of the sceen to pixels
803#[inline(always)]
804fn align(res: Vec2<u32>) -> Vec2<f32> {
805    // TODO: does this logic still apply in iced's coordinate system?
806    // If the resolution is odd then the center of the screen will be within the
807    // middle of a pixel so we need to offset by 0.5 pixels to be on the edge of
808    // a pixel
809    res.map(|e| (e & 1) as f32 * 0.5)
810}
811
812fn default_scissor(physical_resolution: Vec2<u32>) -> Aabr<u16> {
813    let (screen_w, screen_h) = physical_resolution.into_tuple();
814    Aabr {
815        min: Vec2 { x: 0, y: 0 },
816        max: Vec2 {
817            x: screen_w as u16,
818            y: screen_h as u16,
819        },
820    }
821}
822
823impl iced::Renderer for IcedRenderer {
824    // Default styling
825    type Defaults = Defaults;
826    // TODO: use graph of primitives to enable diffing???
827    type Output = (Primitive, iced::mouse::Interaction);
828
829    fn layout<M>(
830        &mut self,
831        element: &iced::Element<'_, M, Self>,
832        limits: &iced::layout::Limits,
833    ) -> iced::layout::Node {
834        span!(_guard, "layout", "IcedRenderer::layout");
835
836        // Trim text measurements cache?
837
838        element.layout(self, limits)
839    }
840
841    fn overlay(
842        &mut self,
843        (base_primitive, base_interaction): Self::Output,
844        (overlay_primitive, overlay_interaction): Self::Output,
845        overlay_bounds: iced::Rectangle,
846    ) -> Self::Output {
847        span!(_guard, "overlay", "IcedRenderer::overlay");
848        (
849            Primitive::Group {
850                primitives: vec![base_primitive, Primitive::Clip {
851                    bounds: iced::Rectangle {
852                        // TODO: do we need this + 0.5?
853                        width: overlay_bounds.width + 0.5,
854                        height: overlay_bounds.height + 0.5,
855                        ..overlay_bounds
856                    },
857                    offset: Vec2::new(0, 0),
858                    content: Box::new(overlay_primitive),
859                }],
860            },
861            base_interaction.max(overlay_interaction),
862        )
863    }
864}
865
866fn apply_alpha(color: Rgba<f32>, alpha: f32) -> Rgba<f32> {
867    Rgba {
868        a: alpha * color.a,
869        ..color
870    }
871}
872// TODO: impl Debugger