1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
//! Contains the managing code for pigeon as well as the draw functions. It comes with a basic
//! version of [`Pigeon`] able to draw the basic primatives but its recommended that you create your
//! own using the [`pigeon!`] macro.
//!
//! ## Usage
//! I highly recommend looking at the examples to get a good idea of how pigeon operates and how to use it.
//!
//! ## Drawing
//! To draw in pigeon, call the [`draw`] function. It takes in a reference to a function that you'll
//! create that takes a [`Container`] as an input. In your function you can use the other functions
//! generated by the [`pigeon!`] macro for each pipeline that add any shapes that can be broken down
//! into that pipelines vertices and add them to the [`Container`]. Everything in the [`Container`]
//! gets drawn to the screen.


use crate::{
    graphics::{Breakdown, Drawable},
    pipeline::{QuadPipe, Render, RenderInformation, TrianglePipe},
};
use euclid::{Size2D, Transform3D};
use itertools::Itertools;
use parrot::{
    painter::PassOp,
    pipeline::Blending,
    transform::{ScreenSpace, WorldSpace},
    Painter,
};
use pollster::FutureExt;
use std::cmp::Ordering;
use std::time::Instant;

pub const OPENGL_TO_WGPU_MATRIX: Transform3D<f32, WorldSpace, WorldSpace> = Transform3D::new(
    1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0.5, 1.0,
);


/// Macro to create a pigeon, the manager, and various draw functions.
/// the pigoen struct as input.
///
/// A default [`Pigeon`] is generated from this in the library, but this marco allows you to extend pigeon with your own pipelines.
///
/// ## Pipelines
/// Pipelines must implement [`parrot::Plumber`] and [`crate::pipeline::Render`]
///
/// ## Usage
/// The primary use for this is to create an instance of pigeon which contains all the pipelines you specified.
/// The macro also creates a [`Container`] to contain the [`Breakdown`] generated by the shapes for the pipelines
/// Finally, it creates the relevent funcitons to append a graphic to the dbg!(container)
///
/// you assign each pipeline a "name", which are what the field names for the pipelines in [Pigeon]
/// and the sets of [`Breakdown`] in [`Container`] are called.
/// You can also use [`parrot`] custom pipeline feature
///
/// There are sometimes occurances where you want to handle the preparation and rendering
/// yourself. You can specify a custom prepare function that returns your pipeline and pigeon will
/// call render.
///
/// ## Format
/// ### In genral:
/// ```rust
/// pigeon!(PipelineType => name, AnotherPipeline => another_name | CustomPipeline : func_for_pipeline => name, AnotherCustom : func => another_custom_name | SpecialPipeline : setupfn() : prepare)
/// ```
/// ### With just customs:
/// ```rust
/// pigeon!(| CustomPipeline : func_for_pipeline => name, AnotherCustom : func => another_custom_name |)
/// ```
/// ### With just normal
/// ```rust
/// pigeon!(PipelineType => name, AnotherPipeline => another_name | |)
/// ```
///
/// ### With just specialised
/// ```rust
/// pigeon!(| | SpecPipelineType : setupfn() : preparefn() => name,)
/// ```
///
/// ## Example
/// ```rust
/// pigeon!(QuadPipe => quad, TrianglePipe => triangle);
/// ```
///
/// This creates a [Pigeon] and [Container] with the [`QuadPipe`] and [`TrianglePipe`] pipelines, with the field names quad and triangle.
/// It also creates the fn draw_quad and draw_triangle to append graphics that breakdown for [`QuadPipe`] and [`TrianglePipe`]
#[macro_export]
macro_rules! pigeon {
    ($( $pipe:ty => $name:ident ),* | $($cust_pipe:ty >>  $func:ident => $cust_name:ident),* | $($spec_pipe:ty >> $setup:ident => $spec_name:ident),* ) => {

        
        /// The manager for pigeon. Contains the important things like the pipelines and the [`Painter`]
        #[derive(Debug)]
        pub struct Pigeon {
            pub paint: Painter,
            pub screen: Size2D<f32, WorldSpace>,
            pub frame_time: u128,
            $(pub $name: $pipe,
            )*
            $(pub $cust_name: $cust_pipe,
            )*
            $(pub $spec_name: $spec_pipe,
            )*
        }

        impl Pigeon {
            pub fn new(surface: wgpu::Surface, instance: &wgpu::Instance, size: Size2D<f32, WorldSpace>, sample_count: u32) -> Self {
                let paint = Painter::for_surface(surface, instance, sample_count).block_on().unwrap();
                $(let $name = paint.pipeline::<$pipe>(Blending::default(), paint.preferred_format(), Some(&format!("{} shader", stringify!($name))));
                )*
                $(
                    let $cust_name = paint.custom_pipeline::<$cust_pipe, pigeon_parrot::painter::PipelineFunction>(
                        Some(&format!("{} shader",
                        stringify!($cust_name))),
                        $func
                    );
                )*
                $(
                    let $spec_name = $setup(&paint);
                )*

                Self {
                    paint,
                    screen: size,
                    frame_time: 0,
                    $($name,
                    )*
                    $($cust_name,
                    )*
                    $($spec_name,
                    )*
                }
            }

            pub fn update_size(&mut self, size: impl Into<Size2D<f32, WorldSpace>>) {
                self.screen = size.into();
            }
        }

        /// Used as an intermediate, it contains the breakdowns for various pipelines
        #[derive(Debug)]
        pub struct Container {
            $(pub $name: Vec<Breakdown<<$pipe as Render>::Vertex>>,
            )*
            $(pub $cust_name: Vec<Breakdown<<$cust_pipe as Render>::Vertex>>,
            )*
        }

        impl Container {
            pub fn new() -> Self {
                Self {
                    $($name: vec![],
                    )*
                    $($cust_name: vec![],
                    )*
                }
            }

            pub fn is_updates(&self) -> bool {
                if $(self.$name.len() > 0 || )* $(self.$cust_name.len() > 0)* false {
                    true
                } else {
                    false
                }
            }
        }

        /// Sorts the container so shapes are grouped by texture to minimise texture swapping
        fn sort_container(mut cont: Container) -> Container {
            let st = Instant::now();
            log::debug!("Sorting container");
            // sort container contents by texture.
            $(
                cont.$name = cont.$name.into_iter().sorted_by(|b1, b2| {
                    if let Some(t1) = &b1.texture {
                        if let Some(t2) = &b2.texture {
                            // Some, Some
                            if t1.id == t2.id {
                                Ordering::Equal
                            } else if t1.id > t2.id{
                                Ordering::Greater
                            } else {
                                Ordering::Less
                            }
                        } else {
                            // Some, None
                            Ordering::Greater
                        }
                    } else if b2.texture.is_some() {
                        // None, Some
                        Ordering::Less
                    } else {
                        // None, None
                        Ordering::Equal
                    }
                }).collect();
            )*
            $(
                cont.$cust_name = cont.$cust_name.into_iter().sorted_by(|b1, b2| {
                    if let Some(t1) = &b1.texture {
                        if let Some(t2) = &b2.texture {
                            // Some, Some
                            if t1.id == t2.id {
                                Ordering::Equal
                            } else if t1.id > t2.id{
                                Ordering::Greater
                            } else {
                                Ordering::Less
                            }
                        } else {
                            // Some, None
                            Ordering::Greater
                        }
                    } else if b2.texture.is_some() {
                        // None, Some
                        Ordering::Less
                    } else {
                        // None, None
                        Ordering::Equal
                    }
                }).collect();
            )*
            log::debug!("Sort time >> {}ms", st.elapsed().as_millis());
            log::trace!("Sorted container >> {:?}", cont);
            cont
        }

        /// Used to draw you shapes in pigeon. Takes in your draw function which will fill a [`Container`] with whatever you want
        /// drawn this pass.
        pub fn draw<F>(pigeon: &mut Pigeon, draw_fn: F)
        where
        F: FnOnce(&mut Container),
        {
            log::info!("Performing draw");
            let mut cont = Container::new();

            log::debug!("Calling user's draw function");

            // Allow the user to populate the container
            draw_fn(&mut cont);

            // Sort container
            cont = sort_container(cont);

            // Generate appropriate matrix info
            let ortho: Transform3D<f32, WorldSpace, ScreenSpace> = Transform3D::ortho(-pigeon.screen.width/2.0, pigeon.screen.width/2.0, -pigeon.screen.height/2.0, pigeon.screen.height/2.0, 50.0, -50.0);
            let ortho = OPENGL_TO_WGPU_MATRIX.then(&ortho);
            log::debug!("Transform matrix >> {:?}", ortho);

            let ft = Instant::now();

            // Only render if there are any updates
            if cont.is_updates() {
                // Setup frame
                let mut frame = pigeon.paint.frame();
                let current_surface = pigeon.paint.current_frame().unwrap();
                {
                    let mut pass = frame.pass(pigeon_parrot::painter::PassOp::Clear(pigeon_parrot::color::Rgba::new(0.1, 0.2, 0.3, 1.0)), &current_surface, None);
                    // call pipelines render function
                    $(
                        // Only render if we have something to render
                        if cont.$name.len() > 0 {
                            log::info!("Rendering for pipeline >> {}", stringify!($pipe));
                            let prep: RenderInformation<<$pipe as Render>::Vertex> = (cont.$name, ortho);
                            pigeon.paint.update_pipeline(&mut pigeon.$name, prep);
                            pigeon.$name.render(&mut pigeon.paint, &mut pass);
                        }
                    )*
                    $(
                        // Only render if we have something to render
                        if cont.$cust_name.len() > 0 {
                            log::info!("Rendering for custom pipeline >> {}", stringify!($cust_pipe));
                            let prep: RenderInformation<<$cust_pipe as Render>::Vertex> = (cont.$cust_name, ortho);
                            pigeon.paint.update_pipeline(&mut pigeon.$cust_name, prep);
                            pigeon.$cust_name.render(&mut pigeon.paint, &mut pass);
                        }
                    )*
                    $(
                        // Always call a special pipeline's render function
                        log::info!("Rendering for special pipeline >> {}", stringify!($spec_pipe));
                        #[allow(unused_variables)]
                        pigeon.$spec_name.render(&mut pigeon.paint, &mut pass);
                    )*
                }

                pigeon.paint.present(frame);
            }

            pigeon.frame_time = ft.elapsed().as_millis();
            log::info!("Frame time >> {}ms", pigeon.frame_time);
        }

        paste::paste! {
            $(
                pub fn [<add_$name>](cont: &mut Container, graphics: Vec<&dyn Drawable<Pipeline = $pipe>>) {
                    let mut graphics = graphics.iter().map(|g| g.breakdown()).collect();
                    cont.$name.append(&mut graphics)
                }
            )*
            $(
                pub fn [<add_$cust_name>](cont: &mut Container, graphics: Vec<&dyn Drawable<Pipeline = $cust_pipe>>) {
                    let mut graphics = graphics.iter().map(|g| g.breakdown()).collect();
                    cont.$cust_name.append(&mut graphics)
                }
            )*
        }

        /// Specialised draw functions allowing you direct access to the [`wgpu::RenderPass`]
        pub mod custom_render {
            use super::*;

            /// Simmilar to [`draw`] except it allows you to determine how the rendering stage will proceed, giving you access to the [`wgpu::RenderPass`].
            pub fn draw_cust<F, T>(pigeon: &mut Pigeon, depth: bool, add_fn: F, render_fn: T)
            where
            F: FnOnce(&mut Container),
            T: for <'a> FnOnce(
                &'a mut Pigeon,
                Container,
                &mut wgpu::RenderPass<'a>,
                Transform3D<f32, WorldSpace, ScreenSpace>
            )
            {
                log::info!("Performing custom draw");
                let mut cont = Container::new();

                log::debug!("Calling user's draw function");

                // Allow the user to populate the container
                add_fn(&mut cont);

                // Sort the container
                cont = sort_container(cont);

                // Generate appropriate matrix info
                let ortho: Transform3D<f32, WorldSpace, ScreenSpace> = Transform3D::ortho(-pigeon.screen.width/2.0, pigeon.screen.width/2.0, -pigeon.screen.height/2.0, pigeon.screen.height/2.0, 50.0, -50.0);
                let ortho = OPENGL_TO_WGPU_MATRIX.then(&ortho);
                log::debug!("Transform matrix >> {:?}", ortho);

                let ft = Instant::now();

                // Setup frame
                let mut frame = pigeon.paint.frame();
                let current_surface = if !depth {
                    pigeon.paint.current_frame_no_depth().unwrap()
                } else {
                    pigeon.paint.current_frame().unwrap()
                };

                {
                    let mut pass = frame.pass(PassOp::Clear(pigeon_parrot::color::Rgba::new(0.1, 0.2, 0.3, 1.0)), &current_surface, None);
                    render_fn(pigeon, cont, &mut pass, ortho)
                }
                pigeon.paint.present(frame);

                pigeon.frame_time = ft.elapsed().as_millis();
                log::info!("Frame time >> {}ms", pigeon.frame_time);
            }

            /// Renders all the custom and standard pipelines in the container (not specialised).
            pub fn render_norm<'a>(
                pigeon: &'a mut Pigeon,
                cont: Container,
                pass: &mut wgpu::RenderPass<'a>,
                ortho: &Transform3D<f32, WorldSpace, ScreenSpace>
            ) {
                $(
                    // Only render if we have something to render
                    if cont.$name.len() > 0 {
                        log::info!("Rendering for pipeline >> {}", stringify!($pipe));
                        let prep: RenderInformation<<$pipe as Render>::Vertex> = (cont.$name, *ortho);
                        pigeon.paint.update_pipeline(&mut pigeon.$name, prep);
                        pigeon.$name.render(&mut pigeon.paint, pass);
                    }
                )*
                $(
                    // Only render if we have something to render
                    if cont.$cust_name.len() > 0 {
                        log::info!("Rendering for custom pipeline >> {}", stringify!($cust_pipe));
                        let prep: RenderInformation<<$cust_pipe as Render>::Vertex> = (cont.$cust_name, ortho);
                        pigeon.paint.update_pipeline(&mut pigeon.$cust_name, prep);
                        pigeon.$cust_name.render(&mut pigeon.paint, pass);
                    }
                )*
            }
        }

    };
}

pigeon!(TrianglePipe => triangle, QuadPipe => quad | |);