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)), ¤t_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)), ¤t_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 | |);