comfy_wgpu/
renderer.rs

1use std::sync::mpsc::{channel, Receiver, Sender};
2
3use crate::*;
4
5use image::Rgba;
6
7pub enum RenderPipeline<'a> {
8    User(&'a UserRenderPipeline),
9    Wgpu(&'a wgpu::RenderPipeline),
10}
11
12pub struct UserRenderPipeline {
13    pub pipeline: wgpu::RenderPipeline,
14    pub layout: wgpu::BindGroupLayout,
15    pub bind_group: wgpu::BindGroup,
16    pub buffers: HashMap<String, wgpu::Buffer>,
17}
18
19pub type PipelineMap = HashMap<String, wgpu::RenderPipeline>;
20pub type UserPipelineMap = HashMap<String, UserRenderPipeline>;
21pub type TextureMap = HashMap<TextureHandle, BindableTexture>;
22pub type RenderTargetMap = HashMap<RenderTargetId, UserRenderTarget>;
23
24#[repr(C)]
25#[derive(Copy, Clone, Debug, Default, bytemuck::Pod, bytemuck::Zeroable)]
26pub struct QuadUniform {
27    pub clip_position: [f32; 2],
28    pub size: [f32; 2],
29}
30
31pub fn shader_to_wgpu(shader: &Shader) -> wgpu::ShaderModuleDescriptor<'_> {
32    wgpu::ShaderModuleDescriptor {
33        label: Some(&shader.name),
34        source: wgpu::ShaderSource::Wgsl(shader.source.as_str().into()),
35    }
36}
37
38// TODO: reducing number of Arc's?
39#[derive(Clone)]
40pub struct GraphicsContext {
41    pub surface: Arc<wgpu::Surface<'static>>,
42    pub instance: Arc<wgpu::Instance>,
43    pub adapter: Arc<wgpu::Adapter>,
44    pub device: Arc<wgpu::Device>,
45    pub queue: Arc<wgpu::Queue>,
46    // Shared for all regular textures/sprites
47    pub texture_layout: Arc<wgpu::BindGroupLayout>,
48    pub config: Arc<AtomicRefCell<wgpu::SurfaceConfiguration>>,
49    // TODO: maybe some sharing can be reduced with this
50    pub texture_creator: Arc<AtomicRefCell<WgpuTextureCreator>>,
51    // TODO: atomic refcell?
52    pub textures: Arc<Mutex<TextureMap>>,
53}
54
55pub struct WgpuRenderer {
56    pub context: GraphicsContext,
57
58    pub pipelines: PipelineMap,
59    pub user_pipelines: UserPipelineMap,
60    pub shaders: RefCell<ShaderMap>,
61    pub render_targets: RefCell<RenderTargetMap>,
62
63    pub text: RefCell<TextRasterizer>,
64
65    pub egui_winit: egui_winit::State,
66    pub egui_render_routine: RefCell<EguiRenderRoutine>,
67
68    pub screenshot_buffer: SizedBuffer,
69
70    pub vertex_buffer: SizedBuffer,
71    pub index_buffer: SizedBuffer,
72
73    pub quad_ubg: UniformBindGroup,
74
75    pub texture_layout: Arc<wgpu::BindGroupLayout>,
76
77    pub window: &'static Window,
78
79    pub depth_texture: Arc<Texture>,
80
81    pub first_pass_texture: BindableTexture,
82
83    pub lights_buffer: wgpu::Buffer,
84    pub global_lighting_params_buffer: wgpu::Buffer,
85
86    pub bloom: Bloom,
87    pub post_processing_effects: RefCell<Vec<PostProcessingEffect>>,
88
89    pub render_texture_format: wgpu::TextureFormat,
90
91    pub tonemapping_texture: BindableTexture,
92
93    pub camera_uniform: CameraUniform,
94    pub camera_buffer: wgpu::Buffer,
95    pub camera_bind_group: Arc<wgpu::BindGroup>,
96    pub camera_bind_group_layout: wgpu::BindGroupLayout,
97
98    pub color: Color,
99
100    pub enable_z_buffer: bool,
101
102    // TODO: remove this in favor of the context one
103    pub texture_creator: Arc<AtomicRefCell<WgpuTextureCreator>>,
104
105    #[cfg(not(target_arch = "wasm32"))]
106    pub thread_pool: rayon::ThreadPool,
107    pub loaded_image_recv: Receiver<LoadedImage>,
108    pub loaded_image_send: Sender<LoadedImage>,
109
110    pub textures: Arc<Mutex<TextureMap>>,
111
112    pub sprite_shader_id: ShaderId,
113    pub error_shader_id: ShaderId,
114
115    pub screenshot_params: ScreenshotParams,
116    pub screenshot_history_buffer: VecDeque<ScreenshotItem>,
117}
118
119impl WgpuRenderer {
120    pub async fn new(
121        window: &'static Window,
122        egui_winit: egui_winit::State,
123    ) -> Self {
124        let context = create_graphics_context(window).await;
125
126        trace!("Loading builtin engine textures");
127
128        {
129            let textures = &mut context.textures.lock();
130
131            macro_rules! load_engine_tex {
132                ($name: literal) => {{
133                    load_texture_from_engine_bytes(
134                        &context,
135                        $name,
136                        include_bytes!(concat!(
137                            env!("CARGO_MANIFEST_DIR"),
138                            "/assets/",
139                            $name,
140                            ".png"
141                        )),
142                        textures,
143                        wgpu::AddressMode::Repeat,
144                    );
145                }};
146            }
147
148            load_engine_tex!("error");
149            load_engine_tex!("1px");
150            load_engine_tex!("test-grid");
151            load_engine_tex!("_builtin-comfy");
152        }
153
154        let mut camera_uniform = CameraUniform::new();
155        camera_uniform.update_view_proj(&main_camera());
156
157        let camera_buffer = context.device.create_buffer_init(
158            &wgpu::util::BufferInitDescriptor {
159                label: Some("Camera Buffer"),
160                contents: bytemuck::cast_slice(&[camera_uniform]),
161                usage: wgpu::BufferUsages::UNIFORM |
162                    wgpu::BufferUsages::COPY_DST,
163            },
164        );
165
166        let camera_bind_group_layout = context.device.create_bind_group_layout(
167            &wgpu::BindGroupLayoutDescriptor {
168                entries: &[
169                    wgpu::BindGroupLayoutEntry {
170                        binding: 0,
171                        visibility: wgpu::ShaderStages::VERTEX |
172                            wgpu::ShaderStages::FRAGMENT,
173                        ty: wgpu::BindingType::Buffer {
174                            ty: wgpu::BufferBindingType::Uniform,
175                            has_dynamic_offset: false,
176                            min_binding_size: None,
177                        },
178                        count: None,
179                    },
180                    wgpu::BindGroupLayoutEntry {
181                        binding: 1,
182                        visibility: wgpu::ShaderStages::VERTEX |
183                            wgpu::ShaderStages::FRAGMENT,
184                        ty: wgpu::BindingType::Buffer {
185                            ty: wgpu::BufferBindingType::Uniform,
186                            has_dynamic_offset: false,
187                            min_binding_size: None,
188                        },
189                        count: None,
190                    },
191                    wgpu::BindGroupLayoutEntry {
192                        binding: 2,
193                        visibility: wgpu::ShaderStages::FRAGMENT,
194                        ty: wgpu::BindingType::Buffer {
195                            ty: wgpu::BufferBindingType::Uniform,
196                            has_dynamic_offset: false,
197                            min_binding_size: None,
198                        },
199                        count: None,
200                    },
201                    wgpu::BindGroupLayoutEntry {
202                        binding: 3,
203                        visibility: wgpu::ShaderStages::FRAGMENT,
204                        ty: wgpu::BindingType::Texture {
205                            sample_type: wgpu::TextureSampleType::Float {
206                                filterable: true,
207                            },
208                            view_dimension: wgpu::TextureViewDimension::D2,
209                            multisampled: false,
210                        },
211                        count: None,
212                    },
213                    wgpu::BindGroupLayoutEntry {
214                        binding: 4,
215                        visibility: wgpu::ShaderStages::FRAGMENT,
216                        ty: wgpu::BindingType::Sampler(
217                            wgpu::SamplerBindingType::Filtering,
218                        ),
219                        count: None,
220                    },
221                ],
222                label: Some("camera_bind_group_layout"),
223            },
224        );
225
226        let lights_buffer = context.device.create_buffer_init(
227            &wgpu::util::BufferInitDescriptor {
228                label: Some("Lights Buffer"),
229                contents: bytemuck::cast_slice(&[LightUniform::default()]),
230                usage: wgpu::BufferUsages::UNIFORM |
231                    wgpu::BufferUsages::COPY_DST,
232            },
233        );
234
235        let global_lighting_params_buffer = context.device.create_buffer_init(
236            &wgpu::util::BufferInitDescriptor {
237                label: Some("Global Lighting Params Buffer"),
238                contents: bytemuck::cast_slice(&[
239                    GlobalLightingParams::default(),
240                ]),
241                usage: wgpu::BufferUsages::UNIFORM |
242                    wgpu::BufferUsages::COPY_DST,
243            },
244        );
245
246        let lut_dim = 2;
247
248        let color_lut_texture = Texture::create_uninit(
249            &context.device,
250            lut_dim,
251            lut_dim,
252            Some("LUT"),
253        )
254        .unwrap();
255
256        let lut_image =
257            image::ImageBuffer::from_fn(lut_dim, lut_dim, |x, y| {
258                if x == 0 && y == 0 {
259                    Rgba([255, 0, 0, 255]) // Red
260                } else if x == 1 && y == 0 {
261                    Rgba([0, 255, 0, 255]) // Green
262                } else if x == 0 && y == 1 {
263                    Rgba([0, 0, 255, 255]) // Blue
264                } else {
265                    Rgba([255, 255, 255, 255]) // White
266                }
267            });
268
269        let lut_width = lut_image.width();
270        let lut_height = lut_image.height();
271
272        let color_lut_data: Vec<f32> = lut_image
273            .pixels()
274            .flat_map(|rgba| {
275                let r = rgba[0] as f32 / 255.0;
276                let g = rgba[1] as f32 / 255.0;
277                let b = rgba[2] as f32 / 255.0;
278                let a = rgba[3] as f32 / 255.0;
279                vec![r, g, b, a]
280            })
281            .collect();
282
283        context.queue.write_texture(
284            wgpu::ImageCopyTexture {
285                texture: &color_lut_texture.texture,
286                mip_level: 0,
287                origin: wgpu::Origin3d::ZERO,
288                aspect: wgpu::TextureAspect::All,
289            },
290            bytemuck::cast_slice(color_lut_data.as_slice()),
291            wgpu::ImageDataLayout {
292                offset: 0,
293                bytes_per_row: Some(
294                    lut_width * 4 * std::mem::size_of::<f32>() as u32,
295                ),
296                rows_per_image: None,
297            },
298            wgpu::Extent3d {
299                width: lut_width,
300                height: lut_height,
301                depth_or_array_layers: 1,
302            },
303        );
304
305        let camera_bind_group =
306            context.device.create_bind_group(&wgpu::BindGroupDescriptor {
307                layout: &camera_bind_group_layout,
308                entries: &[
309                    wgpu::BindGroupEntry {
310                        binding: 0,
311                        resource: camera_buffer.as_entire_binding(),
312                    },
313                    wgpu::BindGroupEntry {
314                        binding: 1,
315                        resource: lights_buffer.as_entire_binding(),
316                    },
317                    wgpu::BindGroupEntry {
318                        binding: 2,
319                        resource: global_lighting_params_buffer
320                            .as_entire_binding(),
321                    },
322                    wgpu::BindGroupEntry {
323                        binding: 3,
324                        resource: wgpu::BindingResource::TextureView(
325                            &color_lut_texture.view,
326                        ),
327                    },
328                    wgpu::BindGroupEntry {
329                        binding: 4,
330                        resource: wgpu::BindingResource::Sampler(
331                            &color_lut_texture.sampler,
332                        ),
333                    },
334                ],
335                label: Some("camera_bind_group"),
336            });
337
338        let camera_bind_group = Arc::new(camera_bind_group);
339
340        // let global_lighting_params_bind_group =
341        //     context.device.create_bind_group(&wgpu::BindGroupDescriptor {
342        //         label: Some("Global Lighting Params Bind Group"),
343        //         layout: &global_lighting_params_bind_group_layout,
344        //         entries: &[
345        //         ],
346        //     });
347
348        // let global_lighting_params_bind_group =
349        //     Arc::new(global_lighting_params_bind_group);
350
351        trace!("Initializing egui");
352
353        let (width, height, format) = {
354            let config = context.config.borrow();
355            (config.width, config.height, config.format)
356        };
357
358        let scale_factor = game_config()
359            .scale_factor_override
360            .unwrap_or(window.scale_factor() as f32);
361
362        let egui_render_routine = EguiRenderRoutine::new(
363            &context.device,
364            format,
365            1,
366            width,
367            height,
368            scale_factor,
369        );
370
371        let screenshot_buffer = SizedBuffer::new(
372            "screenshot_buffer",
373            &context.device,
374            (width * height) as usize * std::mem::size_of::<u32>(),
375            BufferType::Read,
376        );
377
378        info!("Initializing with scale factor: {}", window.scale_factor());
379
380        BLOOD_CANVAS
381            .set(AtomicRefCell::new(BloodCanvas::new(
382                context.texture_creator.clone(),
383            )))
384            .expect("failed to create glow blood canvas");
385
386        let (width, height) = {
387            let config = context.config.borrow();
388            (config.width, config.height)
389        };
390
391        let first_pass_texture = BindableTexture::new(
392            &context.device,
393            &context.texture_layout,
394            &TextureCreationParams {
395                label: Some("First Pass Texture"),
396                width,
397                height,
398                ..Default::default()
399            },
400        );
401
402        let tonemapping_texture = BindableTexture::new(
403            &context.device,
404            &context.texture_layout,
405            &TextureCreationParams {
406                label: Some("Tonemapping"),
407                width,
408                height,
409                ..Default::default()
410            },
411        );
412
413        let quad = QuadUniform { clip_position: [0.0, 0.0], size: [0.2, 0.2] };
414
415        let quad_ubg = UniformBindGroup::simple(
416            "Debug Quad",
417            &context.device,
418            bytemuck::cast_slice(&[quad]),
419        );
420
421        let render_texture_format = wgpu::TextureFormat::Rgba16Float;
422
423        let mut shaders = ShaderMap::new();
424
425        let bloom = Bloom::new(
426            &context,
427            &mut shaders,
428            render_texture_format,
429            camera_bind_group.clone(),
430            &camera_bind_group_layout,
431        );
432
433        let vertex_buffer = SizedBuffer::new(
434            "Mesh Vertex Buffer",
435            &context.device,
436            1024 * 1024,
437            BufferType::Vertex,
438        );
439
440        let index_buffer = SizedBuffer::new(
441            "Mesh Index Buffer",
442            &context.device,
443            1024 * 1024,
444            BufferType::Index,
445        );
446
447
448        let (tx_texture, rx_texture) = channel::<LoadedImage>();
449
450        // TODO: resize
451
452        let depth_texture = Texture::create_depth_texture(
453            &context.device,
454            &context.config.borrow(),
455            "Depth Texture",
456        );
457
458        let sprite_shader_id = create_shader(
459            &mut shaders,
460            "sprite",
461            &sprite_shader_from_fragment(engine_shader_source!("sprite")),
462            HashMap::new(),
463        )
464        .unwrap();
465
466        let error_shader_id = create_shader(
467            &mut shaders,
468            "error",
469            &sprite_shader_from_fragment(engine_shader_source!("error")),
470            HashMap::new(),
471        )
472        .unwrap();
473
474        let renderer = Self {
475            #[cfg(not(target_arch = "wasm32"))]
476            thread_pool: rayon::ThreadPoolBuilder::new().build().unwrap(),
477
478            text: RefCell::new(TextRasterizer::new(context.clone())),
479
480            sprite_shader_id,
481            error_shader_id,
482
483            loaded_image_recv: rx_texture,
484            loaded_image_send: tx_texture,
485
486            depth_texture: Arc::new(depth_texture),
487
488            pipelines: HashMap::new(),
489            user_pipelines: HashMap::new(),
490
491            shaders: RefCell::new(shaders),
492            render_targets: RefCell::new(HashMap::new()),
493
494            screenshot_buffer,
495
496            vertex_buffer,
497            index_buffer,
498
499            post_processing_effects: RefCell::new(Vec::new()),
500            bloom,
501
502            egui_winit,
503            egui_render_routine: RefCell::new(egui_render_routine),
504
505            first_pass_texture,
506
507            lights_buffer,
508
509            quad_ubg,
510
511            global_lighting_params_buffer,
512
513            texture_layout: context.texture_layout.clone(),
514
515            tonemapping_texture,
516
517            camera_uniform,
518            camera_buffer,
519            camera_bind_group,
520            camera_bind_group_layout,
521
522            textures: context.textures.clone(),
523            render_texture_format,
524
525            color: Color::new(0.1, 0.2, 0.3, 1.0),
526
527            enable_z_buffer: false,
528
529            texture_creator: context.texture_creator.clone(),
530
531            window,
532
533            context,
534            screenshot_history_buffer: VecDeque::new(),
535            screenshot_params: screenshot::ScreenshotParams::default(),
536        };
537
538        {
539            let copy_shader_id = create_shader(
540                &mut renderer.shaders.borrow_mut(),
541                "copy",
542                &post_process_shader_from_fragment(COPY_SHADER_SRC),
543                HashMap::new(),
544            )
545            .expect("copy shader creation failed");
546
547            insert_post_processing_effect(&renderer, 0, "copy", copy_shader_id);
548        }
549
550        renderer
551    }
552
553    pub fn window(&self) -> &Window {
554        &self.window
555    }
556
557    pub fn render_post_processing(
558        &mut self,
559        screen_view: &wgpu::TextureView,
560        game_config: &GameConfig,
561    ) {
562        span_with_timing!("render_post_processing");
563
564        let mut encoder =
565            self.context.device.simple_encoder("Post Processing Encoder");
566
567        let mut input_bind_group = &self.first_pass_texture.bind_group;
568
569        if game_config.bloom_enabled {
570            self.bloom.draw(
571                &self.context.device,
572                &self.texture_layout,
573                input_bind_group,
574                &mut encoder,
575            );
576        }
577
578        let post_processing_effects = self.post_processing_effects.borrow();
579        let surface_texture_format = self.context.config.borrow().format;
580
581        let (last_effect_view, last_effect_format) = if game_config
582            .tonemapping_enabled
583        {
584            (&self.tonemapping_texture.texture.view, self.render_texture_format)
585        } else {
586            (screen_view, surface_texture_format)
587        };
588
589        let enabled_effects =
590            post_processing_effects.iter().filter(|x| x.enabled).collect_vec();
591
592        for (i, effect) in enabled_effects.iter().enumerate() {
593            let (output_texture_view, output_texture_format) =
594                if i == enabled_effects.len() - 1 {
595                    (last_effect_view, last_effect_format)
596                } else {
597                    (&effect.render_texture.view, self.render_texture_format)
598                };
599
600            let pipeline_key =
601                format!("{}-{:?}", effect.name, output_texture_format);
602
603            let maybe_pipeline = if self.pipelines.contains_key(&pipeline_key) {
604                Some(self.pipelines.get(&pipeline_key).unwrap())
605            } else {
606                info!("Loading EFFECT: {}", effect.name);
607
608                if let Some(shader) = self.shaders.borrow().get(effect.id) {
609                    let pipeline = create_post_processing_pipeline(
610                        &effect.name,
611                        &self.context.device,
612                        output_texture_format,
613                        &[&self.texture_layout, &self.camera_bind_group_layout],
614                        shader.clone(),
615                        // &effect.shader,
616                        wgpu::BlendState::REPLACE,
617                    );
618
619                    self.pipelines.insert(pipeline_key.clone(), pipeline);
620                    self.pipelines.get(&pipeline_key)
621                } else {
622                    warn!(
623                        "NO SHADER FOR EFFECT: {} ... {}",
624                        effect.name, effect.id
625                    );
626                    None
627                }
628            };
629
630            if let Some(pipeline) = maybe_pipeline {
631                draw_post_processing_output(
632                    &effect.name,
633                    &mut encoder,
634                    pipeline,
635                    input_bind_group,
636                    &self.camera_bind_group,
637                    // &effect.bind_group,
638                    output_texture_view,
639                    true,
640                    None,
641                );
642
643                input_bind_group = &effect.bind_group;
644            } else {
645                error!("Missing pipeline for {}", effect.name);
646            }
647        }
648
649        if game_config.bloom_enabled {
650            self.bloom.blit_final(
651                &mut encoder,
652                &mut self.shaders.borrow_mut(),
653                &mut self.pipelines,
654                last_effect_view,
655                last_effect_format,
656                &game_config.lighting,
657            );
658        }
659
660        if game_config.tonemapping_enabled {
661            let tonemapping_pipeline = self
662                .pipelines
663                .entry("tonemapping".into())
664                .or_insert_with(|| {
665                    // TODO: texture format?
666                    let shaders = &mut self.shaders.borrow_mut();
667
668                    create_post_processing_pipeline(
669                        "Tonemapping",
670                        &self.context.device,
671                        self.context.config.borrow().format,
672                        &[&self.texture_layout, &self.camera_bind_group_layout],
673                        create_engine_post_processing_shader!(
674                            shaders,
675                            "tonemapping"
676                        ),
677                        wgpu::BlendState::REPLACE,
678                    )
679                });
680
681
682            {
683                let should_clear = false;
684
685                let mut render_pass =
686                    encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
687                        label: Some("Tonemapping"),
688                        color_attachments: &[Some(
689                            wgpu::RenderPassColorAttachment {
690                                view: screen_view,
691                                resolve_target: None,
692                                ops: wgpu::Operations {
693                                    load: if should_clear {
694                                        wgpu::LoadOp::Clear(wgpu::Color {
695                                            r: 0.0,
696                                            g: 0.0,
697                                            b: 0.0,
698                                            a: 1.0,
699                                        })
700                                    } else {
701                                        wgpu::LoadOp::Load
702                                    },
703                                    store: wgpu::StoreOp::Store,
704                                },
705                            },
706                        )],
707                        depth_stencil_attachment: None,
708                        timestamp_writes: None,
709                        occlusion_query_set: None,
710                    });
711
712                render_pass.set_pipeline(tonemapping_pipeline);
713                render_pass.set_bind_group(
714                    0,
715                    &self.tonemapping_texture.bind_group,
716                    &[],
717                );
718                render_pass.set_bind_group(1, &self.camera_bind_group, &[]);
719
720                render_pass.draw(0..3, 0..1);
721            }
722        }
723
724        self.context.queue.submit(std::iter::once(encoder.finish()));
725    }
726
727    pub fn render_egui(&self, view: &wgpu::TextureView, egui: &egui::Context) {
728        span_with_timing!("render_egui");
729
730        let mut encoder =
731            self.context.device.simple_encoder("egui Render Encoder");
732
733        let paint_jobs =
734            self.egui_render_routine.borrow_mut().end_frame_and_render(
735                egui,
736                &self.context.device,
737                &self.context.queue,
738                &mut encoder,
739                egui_scale_factor(),
740            );
741
742        let egui_render = self.egui_render_routine.borrow();
743        let egui_render = &egui_render;
744
745        {
746            let mut render_pass =
747                encoder.simple_render_pass("egui Render Pass", None, view);
748            egui_render.render_pass.render(
749                &mut render_pass,
750                &paint_jobs,
751                &egui_render.screen_descriptor,
752            );
753        }
754
755        self.context.queue.submit(std::iter::once(encoder.finish()));
756    }
757
758    pub fn on_event(
759        &mut self,
760        event: &winit::event::WindowEvent,
761        _egui_ctx: &egui::Context,
762    ) -> bool {
763        self.egui_winit.on_window_event(self.window, event).consumed
764    }
765
766    pub fn as_mut_any(&mut self) -> &mut dyn Any {
767        self
768    }
769
770    pub fn update(&mut self, params: &mut DrawParams) {
771        let _span = span!("renderer update");
772
773        let mut changed_recording_mode = false;
774
775        if is_key_pressed(KeyCode::F3) {
776            params.config.dev.recording_mode =
777                match params.config.dev.recording_mode {
778                    RecordingMode::None => RecordingMode::Landscape,
779                    RecordingMode::Tiktok => RecordingMode::Landscape,
780                    RecordingMode::Landscape => RecordingMode::None,
781                };
782
783            changed_recording_mode = true;
784        }
785
786        if is_key_pressed(KeyCode::F9) {
787            self.enable_z_buffer = !self.enable_z_buffer;
788            info!("Z Buffer: {}", self.enable_z_buffer);
789        }
790
791        if is_key_pressed(KeyCode::F4) {
792            params.config.dev.recording_mode =
793                match params.config.dev.recording_mode {
794                    RecordingMode::None => RecordingMode::Tiktok,
795                    RecordingMode::Tiktok => RecordingMode::None,
796                    RecordingMode::Landscape => RecordingMode::Tiktok,
797                };
798
799            changed_recording_mode = true;
800        }
801
802        // Load textures
803        {
804            let _span = span!("wgpu texture load");
805
806            while let Ok(loaded_image) = self.loaded_image_recv.try_recv() {
807                let context = self.context.clone();
808                let textures = self.textures.clone();
809                let layout = self.texture_layout.clone();
810
811                let load_image_texture = move || {
812                    let texture = Texture::from_image(
813                        &context.device,
814                        &context.queue,
815                        &loaded_image.image,
816                        Some(&loaded_image.path),
817                        false,
818                    )
819                    .unwrap();
820
821                    let bind_group = context.device.simple_bind_group(
822                        Some(&format!("{}_bind_group", loaded_image.path)),
823                        &texture,
824                        &layout,
825                    );
826
827                    textures.lock().insert(
828                        loaded_image.handle,
829                        BindableTexture { bind_group, texture },
830                    );
831                };
832
833                #[cfg(target_arch = "wasm32")]
834                load_image_texture();
835
836                #[cfg(not(target_arch = "wasm32"))]
837                self.thread_pool.spawn(load_image_texture);
838            }
839        }
840
841        if changed_recording_mode {
842            info!("Recording Mode: {:?}", params.config.dev.recording_mode);
843
844            self.window.set_title(&format!(
845                "{} {}(COMFY ENGINE)",
846                params.config.game_name,
847                if params.config.dev.recording_mode == RecordingMode::Tiktok {
848                    "Portrait "
849                } else {
850                    ""
851                },
852            ));
853
854            let ratio = 1920.0 / 1080.0;
855
856            let size = 600.0;
857            let portrait_res = winit::dpi::PhysicalSize::new(
858                size as i32,
859                (size * ratio) as i32,
860            );
861
862            let landscape_res = winit::dpi::PhysicalSize::new(1920, 1080);
863
864            let (resolution, _ui_toggle) =
865                match params.config.dev.recording_mode {
866                    RecordingMode::None => (landscape_res, true),
867                    RecordingMode::Tiktok => (portrait_res, false),
868                    RecordingMode::Landscape => (landscape_res, false),
869                };
870
871            self.window.request_inner_size(resolution).log_err();
872            // self.window.center();
873        }
874
875        #[cfg(not(any(feature = "ci-release", target_arch = "wasm32")))]
876        maybe_reload_shaders(&mut self.shaders.borrow_mut());
877
878        self.camera_uniform.update_view_proj(&main_camera());
879
880        params.config.lighting.time = get_time() as f32;
881
882        {
883            let camera = main_camera();
884            params.config.lighting.chromatic_aberration =
885                (camera.shake_amount * 200.0 * camera.shake_timer)
886                    .clamp(0.0, 200.0);
887        }
888
889        self.context.queue.write_buffer(
890            &self.global_lighting_params_buffer,
891            0,
892            bytemuck::cast_slice(&[params.config.lighting]),
893        );
894
895        self.context.queue.write_buffer(
896            &self.camera_buffer,
897            0,
898            bytemuck::cast_slice(&[self.camera_uniform]),
899        );
900
901        let mut light_uniform = LightUniform::default();
902
903        for (i, light) in params.lights.iter().enumerate() {
904            if i >= 128 {
905                break;
906            }
907
908            light_uniform.lights[i] = *light;
909            light_uniform.num_lights += 1;
910        }
911
912        self.context.queue.write_buffer(
913            &self.lights_buffer,
914            0,
915            bytemuck::cast_slice(&[light_uniform]),
916        );
917    }
918
919    pub fn draw(&mut self, params: DrawParams, egui: &egui::Context) {
920        span_with_timing!("render");
921
922        let output = {
923            let _span = span!("get current surface");
924
925            match self.context.surface.get_current_texture() {
926                Ok(texture) => texture,
927                Err(_) => {
928                    return;
929                }
930            }
931        };
932
933        let surface_view = {
934            let _span = span!("create surface view");
935            output.texture.create_view(&wgpu::TextureViewDescriptor::default())
936        };
937
938        let config = params.config.clone();
939
940        run_batched_render_passes(
941            self,
942            &surface_view,
943            params,
944            self.sprite_shader_id,
945            self.error_shader_id,
946        );
947
948        self.render_post_processing(&surface_view, &config);
949        self.render_egui(&surface_view, egui);
950
951        if config.dev.show_buffers {
952            span_with_timing!("render_debug");
953
954            let pp = self.post_processing_effects.borrow();
955
956            let mut bind_groups = vec![&self.first_pass_texture.bind_group];
957            bind_groups.push(&self.bloom.threshold.bind_group);
958            bind_groups.push(&self.bloom.blur_texture.bind_group);
959
960            for effect in pp.iter() {
961                if effect.enabled {
962                    bind_groups.push(&effect.bind_group);
963                }
964            }
965
966            render_debug(
967                &self.context,
968                &mut self.shaders.borrow_mut(),
969                self.enable_z_buffer,
970                &self.quad_ubg,
971                &self.texture_layout,
972                &self.depth_texture,
973                bind_groups,
974                &mut self.pipelines,
975                &surface_view,
976            );
977        }
978
979        {
980            let config = self.context.config.borrow();
981            let screen = uvec2(config.width, config.height);
982
983            if self.screenshot_params.record_screenshots {
984                screenshot::record_screenshot_history(
985                    screen,
986                    &self.context,
987                    &self.screenshot_buffer,
988                    &output,
989                    &mut self.screenshot_params,
990                    &mut self.screenshot_history_buffer,
991                );
992            }
993
994            #[cfg(feature = "record-pngs")]
995            screenshot::record_pngs(
996                screen,
997                &self.context,
998                &self.screenshot_buffer,
999                &output,
1000            );
1001        }
1002
1003        {
1004            span_with_timing!("present");
1005            output.present();
1006        }
1007    }
1008
1009    pub fn scale_factor(&self) -> f32 {
1010        // self.window.scale_factor() as f32
1011        1.0
1012    }
1013
1014    pub fn resize(&mut self, new_size: UVec2) {
1015        let _span = span!("resize");
1016
1017        let scale_factor = game_config()
1018            .scale_factor_override
1019            .unwrap_or(self.window.scale_factor() as f32);
1020
1021        let size = winit::dpi::PhysicalSize::<u32>::new(new_size.x, new_size.y);
1022
1023        {
1024            let mut config = self.context.config.borrow_mut();
1025
1026            config.width = size.width;
1027            config.height = size.height;
1028
1029            self.context.surface.configure(&self.context.device, &config);
1030        }
1031
1032        self.egui_render_routine.borrow_mut().resize(
1033            size.width,
1034            size.height,
1035            scale_factor,
1036        );
1037
1038        // self.egui_winit.set_pixels_per_point(scale_factor);
1039    }
1040
1041    pub fn width(&self) -> f32 {
1042        self.context.config.borrow().width as f32
1043    }
1044
1045    pub fn height(&self) -> f32 {
1046        self.context.config.borrow().height as f32
1047    }
1048
1049    pub fn end_frame(&mut self) {}
1050}
1051
1052pub fn depth_stencil_attachment(
1053    enabled: bool,
1054    view: &wgpu::TextureView,
1055    is_first: bool,
1056) -> Option<wgpu::RenderPassDepthStencilAttachment> {
1057    let clear_depth =
1058        if is_first { wgpu::LoadOp::Clear(1.0) } else { wgpu::LoadOp::Load };
1059
1060    if enabled {
1061        Some(wgpu::RenderPassDepthStencilAttachment {
1062            view,
1063            depth_ops: Some(wgpu::Operations {
1064                load: clear_depth,
1065                store: wgpu::StoreOp::Store,
1066            }),
1067            stencil_ops: None,
1068        })
1069    } else {
1070        None
1071    }
1072}