1
use easygpu::prelude::*;
2
use figures::{Rectlike, Round};
3

            
4
use crate::math::{ExtentsRect, Pixels, Point, Rect, Size, Unknown};
5
use crate::sprite::pipeline::Vertex;
6
use crate::sprite::{RenderedSprite, SpriteRotation, SpriteSourceLocation};
7

            
8
pub struct GpuBatch {
9
    pub size: Size<u32, ScreenSpace>,
10
    pub clip: Option<ExtentsRect<u32, Pixels>>,
11

            
12
    items: Vec<Vertex>,
13
    indicies: Vec<u16>,
14
}
15

            
16
impl GpuBatch {
17
    pub fn new(size: Size<u32, ScreenSpace>, clip: Option<ExtentsRect<u32, Pixels>>) -> Self {
18
        Self {
19
            size,
20
            clip,
21
            items: Vec::default(),
22
            indicies: Vec::default(),
23
        }
24
    }
25

            
26
    pub fn add_sprite(&mut self, sprite: RenderedSprite) {
27
        let sprite = sprite.data;
28
        let white_transparent = Rgba8 {
29
            r: 255,
30
            g: 255,
31
            b: 255,
32
            a: 0,
33
        };
34
        match &sprite.source.location {
35
            SpriteSourceLocation::Rect(location) => self.add_box(
36
                location.as_extents(),
37
                sprite.render_at,
38
                sprite.rotation,
39
                white_transparent,
40
                sprite.alpha,
41
            ),
42
            SpriteSourceLocation::Joined(locations) => {
43
                let source_bounds = sprite.source.location.bounds();
44
                let scale_x = sprite.render_at.width().get() / source_bounds.size.width as f32;
45
                let scale_y = sprite.render_at.height().get() / source_bounds.size.height as f32;
46
                for location in locations {
47
                    let x =
48
                        scale_x.mul_add(location.destination.x as f32, sprite.render_at.origin.x);
49
                    let y =
50
                        scale_y.mul_add(location.destination.y as f32, sprite.render_at.origin.y);
51
                    let width = location.source.width().get() as f32 * scale_x;
52
                    let height = location.source.height().get() as f32 * scale_y;
53
                    let destination = Rect::new(Point::new(x, y), Size::new(width, height));
54
                    self.add_box(
55
                        location.source.as_extents(),
56
                        destination.as_extents(),
57
                        sprite.rotation,
58
                        white_transparent,
59
                        sprite.alpha,
60
                    );
61
                }
62
            }
63
        }
64
    }
65

            
66
    pub fn vertex(
67
        &self,
68
        src: Point<f32, Unknown>,
69
        dest: Point<f32, Pixels>,
70
        color: Rgba8,
71
        alpha: f32,
72
    ) -> Vertex {
73
        Vertex {
74
            position: [dest.x, dest.y, 0.],
75
            uv: [
76
                src.x / self.size.width as f32,
77
                src.y / self.size.height as f32,
78
            ],
79
            color,
80
            alpha,
81
        }
82
    }
83

            
84
    pub fn add_box(
85
        &mut self,
86
        src: ExtentsRect<u32, Unknown>,
87
        mut dest: ExtentsRect<f32, Pixels>,
88
        rotation: SpriteRotation<Pixels>,
89
        color: Rgba8,
90
        alpha: f32,
91
    ) {
92
        let mut src = src.cast::<f32>();
93
        if let Some(clip) = &self.clip {
94
            // Convert to i32 because the destination could have negative coordinates.
95
            let clip_signed = clip.cast::<i32>();
96
            let dest_rounded = dest.round().cast::<i32>();
97

            
98
            if !(clip_signed.origin.x <= dest_rounded.origin.x
99
                && clip_signed.origin.y <= dest_rounded.origin.y
100
                && clip_signed.extent.x >= dest_rounded.extent.x
101
                && clip_signed.extent.y >= dest_rounded.extent.y)
102
            {
103
                if let Some(clipped_destination) = dest.intersection(&clip.cast::<f32>()) {
104
                    if rotation.angle.is_some() {
105
                        // To properly apply clipping on a rotated quad requires tessellating the
106
                        // remaining polygon, and the easygpu-lyon layer doesn't support uv
107
                        // coordinate extrapolation at this moment. We could use lyon directly to
108
                        // generate these vertexes.
109
                        eprintln!(
110
                            "Kludgine Error: Need to implement partial occlusion for sprites. Not \
111
                             clipping."
112
                        );
113
                    } else {
114
                        // Adjust the src box based on how much was clipped
115
                        let source_size = src.size();
116
                        let dest_size = dest.size();
117
                        let x_scale = source_size.width / dest_size.width;
118
                        let y_scale = source_size.height / dest_size.height;
119
                        src = ExtentsRect::new(
120
                            Point::new(
121
                                x_scale.mul_add(
122
                                    clipped_destination.origin.x - dest.origin.x,
123
                                    src.origin.x,
124
                                ),
125
                                y_scale.mul_add(
126
                                    clipped_destination.origin.y - dest.origin.y,
127
                                    src.origin.y,
128
                                ),
129
                            ),
130
                            Point::new(
131
                                src.extent.x
132
                                    - (dest.extent.x - clipped_destination.extent.x) * x_scale,
133
                                src.extent.y
134
                                    - (dest.extent.y - clipped_destination.extent.y) * y_scale,
135
                            ),
136
                        );
137
                        dest = clipped_destination;
138
                    }
139
                } else {
140
                    // Full clipping, just skip the drawing entirely
141
                    return;
142
                }
143
            }
144
        }
145

            
146
        let origin = rotation.location.unwrap_or_else(|| dest.center());
147
        let top_left = self
148
            .vertex(src.origin, dest.origin, color, alpha)
149
            .rotate_by(rotation.angle, origin);
150
        let top_right = self
151
            .vertex(
152
                Point::from_figures(src.extent.x(), src.origin.y()),
153
                Point::from_figures(dest.extent.x(), dest.origin.y()),
154
                color,
155
                alpha,
156
            )
157
            .rotate_by(rotation.angle, origin);
158
        let bottom_left = self
159
            .vertex(
160
                Point::from_figures(src.origin.x(), src.extent.y()),
161
                Point::from_figures(dest.origin.x(), dest.extent.y()),
162
                color,
163
                alpha,
164
            )
165
            .rotate_by(rotation.angle, origin);
166
        let bottom_right = self
167
            .vertex(
168
                Point::from_figures(src.extent.x(), src.extent.y()),
169
                Point::from_figures(dest.extent.x(), dest.extent.y()),
170
                color,
171
                alpha,
172
            )
173
            .rotate_by(rotation.angle, origin);
174

            
175
        self.add_quad(top_left, top_right, bottom_left, bottom_right);
176
    }
177

            
178
    pub fn add_quad(
179
        &mut self,
180
        top_left: Vertex,
181
        top_right: Vertex,
182
        bottom_left: Vertex,
183
        bottom_right: Vertex,
184
    ) {
185
        let top_left_index = self.items.len() as u16;
186
        self.items.push(top_left);
187
        let top_right_index = self.items.len() as u16;
188
        self.items.push(top_right);
189
        let bottom_left_index = self.items.len() as u16;
190
        self.items.push(bottom_left);
191
        let bottom_right_index = self.items.len() as u16;
192
        self.items.push(bottom_right);
193

            
194
        self.indicies.push(top_left_index);
195
        self.indicies.push(top_right_index);
196
        self.indicies.push(bottom_left_index);
197

            
198
        self.indicies.push(top_right_index);
199
        self.indicies.push(bottom_right_index);
200
        self.indicies.push(bottom_left_index);
201
    }
202

            
203
    // pub fn add_triangle(&mut self, a: Vertex, b: Vertex, c: Vertex) {
204
    //     self.indicies.push(self.indicies.len() as u16);
205
    //     self.items.push(a);
206
    //     self.indicies.push(self.indicies.len() as u16);
207
    //     self.items.push(b);
208
    //     self.indicies.push(self.indicies.len() as u16);
209
    //     self.items.push(c);
210
    // }
211

            
212
    pub(crate) fn finish(&self, renderer: &Renderer) -> BatchBuffers {
213
        let vertices = renderer.device.create_buffer(&self.items);
214
        let indices = renderer.device.create_index(&self.indicies);
215
        BatchBuffers {
216
            vertices,
217
            indices,
218
            index_count: self.indicies.len() as u32,
219
        }
220
    }
221
}
222

            
223
pub struct BatchBuffers {
224
    pub vertices: VertexBuffer,
225
    pub indices: IndexBuffer,
226
    pub index_count: u32,
227
}
228

            
229
impl Draw for BatchBuffers {
230
    fn draw<'a>(&'a self, binding: &'a BindingGroup, pass: &mut wgpu::RenderPass<'a>) {
231
        if self.index_count > 0 {
232
            pass.set_binding(binding, &[]);
233
            pass.set_easy_vertex_buffer(&self.vertices);
234
            pass.set_easy_index_buffer(&self.indices);
235
            pass.draw_indexed(0..self.index_count, 0, 0..1);
236
        }
237
    }
238
}