1
use std::collections::HashMap;
2
use std::sync::atomic::{AtomicBool, Ordering};
3
use std::sync::{Arc, Mutex};
4

            
5
use easygpu::prelude::*;
6
use easygpu::wgpu::{
7
    Buffer, Extent3d, FilterMode, Origin3d, PresentMode, TextureAspect, TextureUsages,
8
    COPY_BYTES_PER_ROW_ALIGNMENT,
9
};
10
use easygpu_lyon::LyonPipeline;
11
use figures::Rectlike;
12
use image::DynamicImage;
13

            
14
use crate::math::{ExtentsRect, Point, Size, Unknown};
15
use crate::scene::SceneEvent;
16
use crate::sprite::{self, VertexShaderSource};
17

            
18
mod frame;
19
use frame::{FontUpdate, Frame, FrameCommand};
20

            
21
/// Renders frames created by a [`Scene`](crate::scene::Scene).
22
pub struct FrameRenderer<T>
23
where
24
    T: VertexShaderSource,
25
{
26
    keep_running: Arc<AtomicBool>,
27
    shutdown: Option<Box<dyn ShutdownCallback>>,
28
    renderer: Renderer,
29
    multisample_texture: Option<Texture>,
30
    destination: Destination,
31
    sprite_pipeline: sprite::Pipeline<T>,
32
    shape_pipeline: LyonPipeline<T::Lyon>,
33
    gpu_state: Mutex<GpuState>,
34
    scene_event_receiver: flume::Receiver<SceneEvent>,
35
}
36

            
37
1
#[derive(Default)]
38
struct GpuState {
39
    textures: HashMap<u64, BindingGroup>,
40
}
41

            
42
#[allow(clippy::large_enum_variant)]
43
enum Destination {
44
    Uninitialized,
45
    Device,
46
    Texture {
47
        color: Texture,
48
        depth: DepthBuffer,
49
        output: Buffer,
50
    },
51
}
52

            
53
#[allow(clippy::large_enum_variant)] // This is an internal type that should be stored on the stack.
54
enum Output<'a> {
55
    SwapChain(RenderFrame),
56
    Texture {
57
        color: &'a Texture,
58
        depth: &'a DepthBuffer,
59
    },
60
}
61

            
62
impl<'a> RenderTarget for Output<'a> {
63
1
    fn color_target(&self) -> &wgpu::TextureView {
64
1
        match self {
65
            Output::SwapChain(swap) => swap.color_target(),
66
1
            Output::Texture { color, .. } => &color.view,
67
        }
68
1
    }
69

            
70
1
    fn zdepth_target(&self) -> &wgpu::TextureView {
71
1
        match self {
72
            Output::SwapChain(swap) => swap.zdepth_target(),
73
1
            Output::Texture { depth, .. } => &depth.texture.view,
74
        }
75
1
    }
76
}
77

            
78
enum RenderCommand {
79
    SpriteBuffer(u64, sprite::BatchBuffers),
80
    FontBuffer(u64, sprite::BatchBuffers),
81
    Shapes(easygpu_lyon::Shape),
82
}
83

            
84
impl<T> FrameRenderer<T>
85
where
86
    T: VertexShaderSource + Send + Sync + 'static,
87
{
88
1
    fn new(
89
1
        renderer: Renderer,
90
1
        keep_running: Arc<AtomicBool>,
91
1
        scene_event_receiver: flume::Receiver<SceneEvent>,
92
1
    ) -> Self {
93
1
        let shape_pipeline = renderer.pipeline(Blending::default(), T::sampler_format());
94
1
        let sprite_pipeline = renderer.pipeline(Blending::default(), T::sampler_format());
95
1
        Self {
96
1
            renderer,
97
1
            keep_running,
98
1
            destination: Destination::Uninitialized,
99
1
            sprite_pipeline,
100
1
            shape_pipeline,
101
1
            scene_event_receiver,
102
1
            shutdown: None,
103
1
            multisample_texture: None,
104
1
            gpu_state: Mutex::new(GpuState::default()),
105
1
        }
106
1
    }
107

            
108
    /// Launches a thread that renders the results of the `SceneEvent`s.
109
    pub fn run<F: ShutdownCallback>(
110
        renderer: Renderer,
111
        keep_running: Arc<AtomicBool>,
112
        scene_event_receiver: flume::Receiver<SceneEvent>,
113
        shutdown_callback: F,
114
    ) {
115
        let mut frame_renderer = Self::new(renderer, keep_running, scene_event_receiver);
116
        frame_renderer.shutdown = Some(Box::new(shutdown_callback));
117
        std::thread::Builder::new()
118
            .name(String::from("kludgine-frame-renderer"))
119
            .spawn(move || frame_renderer.render_loop())
120
            .unwrap();
121
    }
122

            
123
    /// Launches a thread that renders the results of the `SceneEvent`s.
124
1
    pub fn render_one_frame(
125
1
        renderer: Renderer,
126
1
        scene_event_receiver: flume::Receiver<SceneEvent>,
127
1
    ) -> crate::Result<DynamicImage> {
128
1
        let mut frame_renderer = Self::new(renderer, Arc::default(), scene_event_receiver);
129
1
        let mut frame = Frame::default();
130
1
        frame.update(&frame_renderer.scene_event_receiver);
131
1
        frame_renderer.render_frame(&mut frame)?;
132
1
        if let Destination::Texture { output, .. } = frame_renderer.destination {
133
1
            let data = output.slice(..);
134
1
            let result = Arc::new(Mutex::new(None));
135
1
            let callback_result = result.clone();
136
1
            data.map_async(wgpu::MapMode::Read, move |map_result| {
137
1
                let mut result = callback_result.lock().unwrap();
138
1
                *result = Some(map_result);
139
1
            });
140
1
            let wgpu_device = frame_renderer.renderer.device.wgpu;
141
1
            loop {
142
1
                wgpu_device.poll(wgpu::Maintain::Wait);
143
1
                match result.lock().unwrap().take() {
144
1
                    Some(Ok(())) => break,
145
                    Some(Err(err)) => return Err(err.into()),
146
                    None => {}
147
                }
148
            }
149

            
150
1
            let bytes = data.get_mapped_range().to_vec();
151
1

            
152
1
            let frame_size = frame.size.cast::<usize>();
153
1
            let bytes_per_row = size_for_aligned_copy(frame_size.width * 4);
154
1
            Ok(image::DynamicImage::ImageRgba8(
155
1
                image::ImageBuffer::from_fn(
156
1
                    frame_size.width as u32,
157
1
                    frame_size.height as u32,
158
4096
                    move |x, y| {
159
4096
                        let offset = y as usize * bytes_per_row + x as usize * 4;
160
4096
                        image::Rgba([
161
4096
                            bytes[offset + 2],
162
4096
                            bytes[offset + 1],
163
4096
                            bytes[offset],
164
4096
                            bytes[offset + 3],
165
4096
                        ])
166
4096
                    },
167
1
                ),
168
1
            ))
169
        } else {
170
            panic!("render_one_frame only works with an offscreen renderer")
171
        }
172
1
    }
173

            
174
    fn render_loop(mut self) {
175
        let mut frame = Frame::default();
176
        loop {
177
            if !self.keep_running.load(Ordering::SeqCst) {
178
                let shutdown = self.shutdown.take();
179
                // These drops prevents a segfault on exit per. The shutdown method must be
180
                // called after self is dropped. https://github.com/gfx-rs/wgpu/issues/1439
181
                drop(self);
182
                drop(frame);
183
                if let Some(mut shutdown) = shutdown {
184
                    shutdown.shutdown();
185
                }
186
                return;
187
            }
188
            if frame.update(&self.scene_event_receiver) {
189
                self.render_frame(&mut frame)
190
                    .expect("Error rendering window");
191
            } else {
192
                self.keep_running.store(false, Ordering::SeqCst);
193
            }
194
        }
195
    }
196

            
197
1
    fn create_destination(
198
1
        renderer: &mut Renderer,
199
1
        frame_size: Size<u32, ScreenSpace>,
200
1
    ) -> Destination {
201
1
        if renderer.device.surface.is_some() {
202
            renderer.configure(frame_size, PresentMode::Fifo, T::sampler_format());
203
            Destination::Device
204
        } else {
205
1
            let color = renderer.texture(
206
1
                frame_size,
207
1
                T::sampler_format(),
208
1
                TextureUsages::TEXTURE_BINDING
209
1
                    | TextureUsages::COPY_DST
210
1
                    | TextureUsages::COPY_SRC
211
1
                    | TextureUsages::RENDER_ATTACHMENT,
212
1
                false,
213
1
            );
214
1
            let depth = renderer
215
1
                .device
216
1
                .create_zbuffer(frame_size, renderer.sample_count());
217
1
            let output = renderer.device.wgpu.create_buffer(&wgpu::BufferDescriptor {
218
1
                label: Some("output buffer"),
219
1
                size: buffer_size(frame_size) as u64,
220
1
                usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
221
1
                mapped_at_creation: false,
222
1
            });
223
1
            Destination::Texture {
224
1
                color,
225
1
                depth,
226
1
                output,
227
1
            }
228
        }
229
1
    }
230

            
231
    #[allow(clippy::too_many_lines)] // TODO refactor
232
2
    fn render_frame(&mut self, engine_frame: &mut Frame) -> crate::Result<()> {
233
2
        let frame_size = engine_frame.size.cast::<u32>();
234
2
        if frame_size.width == 0 || frame_size.height == 0 {
235
            return Ok(());
236
2
        }
237

            
238
2
        let output = match &mut self.destination {
239
            Destination::Uninitialized => {
240
1
                self.destination = Self::create_destination(&mut self.renderer, frame_size);
241
1
                return self.render_frame(engine_frame);
242
            }
243
            Destination::Device => {
244
                if self.renderer.device.size() != frame_size {
245
                    self.renderer
246
                        .configure(frame_size, PresentMode::Fifo, T::sampler_format());
247
                }
248

            
249
                let output = match self.renderer.current_frame() {
250
                    Ok(texture) => texture,
251
                    Err(wgpu::SurfaceError::Outdated | wgpu::SurfaceError::Timeout) => {
252
                        // Ignore outdated, we'll draw
253
                        // next time.
254
                        return Ok(());
255
                    }
256
                    Err(err) => panic!("Unrecoverable error on swap chain {:?}", err),
257
                };
258
                Output::SwapChain(output)
259
            }
260
            Destination::Texture {
261
1
                color,
262
1
                depth,
263
1
                output,
264
1
            } => {
265
1
                if color.size != frame_size {
266
                    if let Destination::Texture {
267
                        color: new_color,
268
                        depth: new_depth,
269
                        output: new_output,
270
                    } = Self::create_destination(&mut self.renderer, frame_size)
271
                    {
272
                        *color = new_color;
273
                        *depth = new_depth;
274
                        *output = new_output;
275
                    } else {
276
                        unreachable!();
277
                    }
278
1
                }
279
1
                Output::Texture { color, depth }
280
            }
281
        };
282
1
        let mut frame = self.renderer.frame();
283
1

            
284
1
        let ortho = ScreenTransformation::ortho(
285
1
            0.,
286
1
            0.,
287
1
            frame_size.width as f32,
288
1
            frame_size.height as f32,
289
1
            -1.,
290
1
            1.,
291
1
        );
292
1
        self.renderer.update_pipeline(&self.shape_pipeline, ortho);
293
1

            
294
1
        self.renderer.update_pipeline(&self.sprite_pipeline, ortho);
295
1

            
296
1
        {
297
1
            let mut render_commands = Vec::new();
298
1
            let mut gpu_state = self
299
1
                .gpu_state
300
1
                .try_lock()
301
1
                .expect("There should be no contention");
302

            
303
            for FontUpdate {
304
                font_id,
305
                rect,
306
                data,
307
1
            } in &engine_frame.pending_font_updates
308
            {
309
                let mut loaded_font = engine_frame.fonts.get_mut(font_id).unwrap();
310
                if loaded_font.texture.is_none() {
311
                    let texture = self.renderer.texture(
312
                        Size::new(512, 512),
313
                        T::texture_format(),
314
                        TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
315
                        false,
316
                    ); // TODO font texture should be configurable
317
                    let sampler = self
318
                        .renderer
319
                        .sampler(FilterMode::Linear, FilterMode::Linear);
320

            
321
                    let binding = self
322
                        .sprite_pipeline
323
                        .binding(&self.renderer, &texture, &sampler);
324
                    loaded_font.binding = Some(binding);
325
                    loaded_font.texture = Some(texture);
326
                }
327

            
328
                let row_bytes = size_for_aligned_copy(rect.width() as usize * 4);
329
                let mut pixels = Vec::with_capacity(row_bytes * rect.height() as usize);
330
                let mut pixel_iterator = data.iter();
331
                for _ in 0..rect.height() {
332
                    for _ in 0..rect.width() {
333
                        let p = pixel_iterator.next().unwrap();
334
                        pixels.push(255);
335
                        pixels.push(255);
336
                        pixels.push(255);
337
                        pixels.push(*p);
338
                    }
339

            
340
                    pixels.resize_with(size_for_aligned_copy(pixels.len()), Default::default);
341
                }
342

            
343
                let pixels = Rgba8::align(&pixels);
344
                self.renderer.submit(&[Op::Transfer {
345
                    f: loaded_font.texture.as_ref().unwrap(),
346
                    buf: pixels,
347
                    rect: ExtentsRect::new(
348
                        Point::new(rect.min.x, rect.min.y),
349
                        Point::new(rect.max.x, rect.max.y),
350
                    )
351
                    .as_sized()
352
                    .cast::<i32>(),
353
                }]);
354
            }
355
1
            engine_frame.pending_font_updates.clear();
356

            
357
1
            for command in std::mem::take(&mut engine_frame.commands) {
358
1
                match command {
359
                    FrameCommand::LoadTexture(texture) => {
360
                        if !gpu_state.textures.contains_key(&texture.id()) {
361
                            let sampler = self
362
                                .renderer
363
                                .sampler(FilterMode::Nearest, FilterMode::Nearest);
364

            
365
                            let (gpu_texture, texels, texture_id) = {
366
                                let (w, h) = texture.image.dimensions();
367
                                let bytes_per_row = size_for_aligned_copy(w as usize * 4);
368
                                let mut pixels = Vec::with_capacity(bytes_per_row * h as usize);
369
                                for (_, row) in texture.image.enumerate_rows() {
370
                                    for (_, _, pixel) in row {
371
                                        pixels.push(pixel[0]);
372
                                        pixels.push(pixel[1]);
373
                                        pixels.push(pixel[2]);
374
                                        pixels.push(pixel[3]);
375
                                    }
376

            
377
                                    pixels.resize_with(
378
                                        size_for_aligned_copy(pixels.len()),
379
                                        Default::default,
380
                                    );
381
                                }
382
                                let pixels = Rgba8::align(&pixels);
383

            
384
                                (
385
                                    self.renderer.texture(
386
                                        Size::new(w, h).cast::<u32>(),
387
                                        T::texture_format(),
388
                                        TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
389
                                        false,
390
                                    ),
391
                                    pixels.to_owned(),
392
                                    texture.id(),
393
                                )
394
                            };
395

            
396
                            self.renderer
397
                                .submit(&[Op::Fill(&gpu_texture, texels.as_slice())]);
398

            
399
                            gpu_state.textures.insert(
400
                                texture_id,
401
                                self.sprite_pipeline.binding(
402
                                    &self.renderer,
403
                                    &gpu_texture,
404
                                    &sampler,
405
                                ),
406
                            );
407
                        }
408
                    }
409
                    FrameCommand::DrawBatch(batch) => {
410
                        let mut gpu_batch = sprite::GpuBatch::new(
411
                            batch.size.cast_unit(),
412
                            batch.clipping_rect.map(|r| r.as_extents()),
413
                        );
414
                        for sprite_handle in &batch.sprites {
415
                            gpu_batch.add_sprite(sprite_handle.clone());
416
                        }
417
                        render_commands.push(RenderCommand::SpriteBuffer(
418
                            batch.loaded_texture_id,
419
                            gpu_batch.finish(&self.renderer),
420
                        ));
421
                    }
422
1
                    FrameCommand::DrawShapes(batch) => {
423
1
                        render_commands.push(RenderCommand::Shapes(batch.finish(&self.renderer)?));
424
                        // let prepared_shape = batch.finish(&self.renderer)?;
425
                        // pass.set_easy_pipeline(&self.shape_pipeline);
426
                        // prepared_shape.draw(&mut pass);
427
                    }
428
                    FrameCommand::DrawText { text, clip } => {
429
                        if let Some(loaded_font) = engine_frame.fonts.get(&text.font.id()) {
430
                            if let Some(texture) = loaded_font.texture.as_ref() {
431
                                let mut batch = sprite::GpuBatch::new(
432
                                    texture.size,
433
                                    clip.map(|r| r.as_extents()),
434
                                );
435
                                for (uv_rect, screen_rect) in text.glyphs.iter().filter_map(|g| {
436
                                    loaded_font.cache.rect_for(0, &g.glyph).ok().flatten()
437
                                }) {
438
                                    // This is one section that feels like a kludge. gpu_cache is
439
                                    // storing the textures upside down like normal but easywgpu is
440
                                    // automatically flipping textures. Easygpu's texture isn't
441
                                    // exactly the best compatibility with this process
442
                                    // because gpu_cache also produces data that is 1 byte per
443
                                    // pixel, and we have to expand it when we're updating the
444
                                    // texture
445
                                    let source = ExtentsRect::<_, Unknown>::new(
446
                                        Point::new(
447
                                            uv_rect.min.x * 512.0,
448
                                            (1.0 - uv_rect.max.y) * 512.0,
449
                                        ),
450
                                        Point::new(
451
                                            uv_rect.max.x * 512.0,
452
                                            (1.0 - uv_rect.min.y) * 512.0,
453
                                        ),
454
                                    );
455

            
456
                                    let dest = ExtentsRect::new(
457
                                        text.location
458
                                            + figures::Vector::new(
459
                                                screen_rect.min.x as f32,
460
                                                screen_rect.min.y as f32,
461
                                            ),
462
                                        text.location
463
                                            + figures::Vector::new(
464
                                                screen_rect.max.x as f32,
465
                                                screen_rect.max.y as f32,
466
                                            ),
467
                                    );
468
                                    batch.add_box(
469
                                        source.cast_unit().cast(),
470
                                        dest,
471
                                        sprite::SpriteRotation::none(),
472
                                        text.color.into(),
473
                                        1.,
474
                                    );
475
                                }
476
                                render_commands.push(RenderCommand::FontBuffer(
477
                                    loaded_font.font.id(),
478
                                    batch.finish(&self.renderer),
479
                                ));
480
                            }
481
                        }
482
                    }
483
                }
484
            }
485
1
            if self
486
1
                .multisample_texture
487
1
                .as_ref()
488
1
                .map_or(true, |t| t.size != frame_size)
489
1
            {
490
1
                self.multisample_texture = Some(self.renderer.texture(
491
1
                    frame_size,
492
1
                    T::sampler_format(),
493
1
                    TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING,
494
1
                    true,
495
1
                ));
496
1
            }
497
1
            let mut pass = frame.pass(
498
1
                PassOp::Clear(Rgba::TRANSPARENT),
499
1
                &output,
500
1
                Some(&self.multisample_texture.as_ref().unwrap().view),
501
1
            );
502
2
            for command in &render_commands {
503
1
                match command {
504
                    RenderCommand::SpriteBuffer(texture_id, buffer) => {
505
                        pass.set_easy_pipeline(&self.sprite_pipeline);
506
                        let binding = gpu_state.textures.get(texture_id).unwrap();
507
                        pass.easy_draw(buffer, binding);
508
                    }
509
                    RenderCommand::FontBuffer(font_id, buffer) => {
510
                        pass.set_easy_pipeline(&self.sprite_pipeline);
511
                        if let Some(binding) = engine_frame
512
                            .fonts
513
                            .get(font_id)
514
                            .and_then(|f| f.binding.as_ref())
515
                        {
516
                            pass.easy_draw(buffer, binding);
517
                        }
518
                    }
519
1
                    RenderCommand::Shapes(shapes) => {
520
1
                        pass.set_easy_pipeline(&self.shape_pipeline);
521
1
                        shapes.draw(&mut pass);
522
1
                    }
523
                }
524
            }
525
        }
526

            
527
1
        if let Destination::Texture { output, color, .. } = &self.destination {
528
1
            frame.encoder_mut().copy_texture_to_buffer(
529
1
                wgpu::ImageCopyTexture {
530
1
                    texture: &color.wgpu,
531
1
                    mip_level: 0,
532
1
                    origin: Origin3d::ZERO,
533
1
                    aspect: TextureAspect::All,
534
1
                },
535
1
                wgpu::ImageCopyBuffer {
536
1
                    buffer: output,
537
1
                    layout: wgpu::ImageDataLayout {
538
1
                        offset: 0,
539
1
                        bytes_per_row: Some(
540
1
                            size_for_aligned_copy(frame_size.width as usize * 4) as u32
541
1
                        ),
542
1
                        rows_per_image: Some(frame_size.height),
543
1
                    },
544
1
                },
545
1
                Extent3d {
546
1
                    width: frame_size.width,
547
1
                    height: frame_size.height,
548
1
                    depth_or_array_layers: 1,
549
1
                },
550
1
            );
551
1
        }
552

            
553
1
        self.renderer.present(frame);
554
1

            
555
1
        Ok(())
556
2
    }
557
}
558

            
559
3
const fn size_for_aligned_copy(bytes: usize) -> usize {
560
3
    let chunks =
561
3
        (bytes + COPY_BYTES_PER_ROW_ALIGNMENT as usize - 1) / COPY_BYTES_PER_ROW_ALIGNMENT as usize;
562
3
    chunks * COPY_BYTES_PER_ROW_ALIGNMENT as usize
563
3
}
564

            
565
1
const fn buffer_size(size: Size<u32, ScreenSpace>) -> usize {
566
1
    size_for_aligned_copy(size.width as usize * 4) * size.height as usize
567
1
}
568

            
569
/// A callback that can be invoked when a [`FrameRenderer`] is fully shut down.
570
pub trait ShutdownCallback: Send + Sync + 'static {
571
    /// Invoked when the [`FrameRenderer`] is fully shut down.
572
    fn shutdown(&mut self);
573
}
574

            
575
impl<F> ShutdownCallback for F
576
where
577
    F: FnMut() + Send + Sync + 'static,
578
{
579
    fn shutdown(&mut self) {
580
        self();
581
    }
582
}