Skip to main content

ursus_core/render/thread/
mod.rs

1pub mod command;
2
3use std::sync::mpsc::Receiver;
4use std::sync::{mpsc, Arc};
5use std::time::Instant;
6
7use self::command::{PipelineFactory, RenderCommand};
8use crate::assets::storage::GpuAssetServer;
9use crate::assets::upload::GpuUploadRequest;
10use crate::render::frame_stats::FrameStats;
11use crate::render::triple_buffer::TripleBuffer;
12use crate::render::world::RWorld;
13use crate::vulkan::resources::texture::TextureSource;
14use crate::vulkan::{DynRenderer, VulkanContext};
15use crate::EngineFlags;
16use raw_window_handle::{RawDisplayHandle, RawWindowHandle};
17
18pub struct WindowHandles {
19    pub display: RawDisplayHandle,
20    pub window: RawWindowHandle,
21}
22
23unsafe impl Send for WindowHandles {}
24
25//todo: this function has too many arguments (8/7)
26#[allow(clippy::too_many_arguments)]
27pub fn render_thread_main(
28    handles: WindowHandles,
29    flags: EngineFlags,
30    initial_pipeline: PipelineFactory,
31    triple_buf: Arc<TripleBuffer<RWorld>>,
32    frame_stats: FrameStats,
33    cmd_rx: Receiver<RenderCommand>,
34    upload_rx: Receiver<GpuUploadRequest>,
35    ready_tx: mpsc::SyncSender<()>,
36) {
37    if let Err(e) = render_loop(handles, flags, initial_pipeline, triple_buf, frame_stats, cmd_rx, upload_rx, ready_tx)
38    {
39        log::error!("Render thread exited with error: {e}");
40    }
41}
42
43//todo: this function has too many arguments (8/7)
44#[allow(clippy::too_many_arguments)]
45fn render_loop(
46    handles: WindowHandles,
47    flags: EngineFlags,
48    initial_pipeline: PipelineFactory,
49    triple_buf: Arc<TripleBuffer<RWorld>>,
50    frame_stats: FrameStats,
51    cmd_rx: Receiver<RenderCommand>,
52    upload_rx: Receiver<GpuUploadRequest>,
53    ready_tx: mpsc::SyncSender<()>,
54) -> anyhow::Result<()> {
55    let mut vk = VulkanContext::from_handles(handles.display, handles.window, flags)?;
56
57    let temp_pool = crate::app::create_temp_pool(&vk)?;
58
59    let mut gpu_assets = GpuAssetServer::new(
60        vk.device.handle.clone(),
61        vk.device.physical,
62        vk.instance.handle.clone(),
63        temp_pool,
64        vk.device.graphics_queue,
65    )?;
66
67    let mut renderer = initial_pipeline.build(&vk, &mut gpu_assets, 0.5, 0.2)?;
68
69    let mut render_idx: usize = 2;
70    let mut initialized = false;
71    let mut paused = false;
72    let mut last_present = Instant::now();
73
74    let _ = ready_tx.send(());
75
76    'outer: loop {
77        puffin::GlobalProfiler::lock().new_frame();
78
79        if paused {
80            match cmd_rx.recv() {
81                Ok(cmd) => {
82                    if !handle_render_command(cmd, &mut vk, &mut renderer, &mut gpu_assets, &mut paused, temp_pool)? {
83                        return Ok(());
84                    }
85                    if paused {
86                        continue 'outer;
87                    }
88                }
89                Err(_) => {
90                    log::warn!("Render thread: cmd channel closed while paused, shutting down");
91                    return Ok(());
92                }
93            }
94        }
95
96        loop {
97            match cmd_rx.try_recv() {
98                Ok(cmd) => {
99                    if !handle_render_command(cmd, &mut vk, &mut renderer, &mut gpu_assets, &mut paused, temp_pool)? {
100                        return Ok(());
101                    }
102                    if paused {
103                        continue 'outer;
104                    }
105                }
106                Err(mpsc::TryRecvError::Empty) => break,
107                Err(mpsc::TryRecvError::Disconnected) => {
108                    log::warn!("Render thread: cmd channel closed, shutting down");
109                    return Ok(());
110                }
111            }
112        }
113
114        flush_uploads_gpu(&upload_rx, &mut gpu_assets)?;
115
116        let got_new = triple_buf.consume(&mut render_idx);
117        if got_new {
118            initialized = true;
119        }
120
121        if !initialized {
122            std::thread::yield_now();
123            continue;
124        }
125
126        let render_world = triple_buf.render_slot(render_idx);
127
128        let needs_recreate = renderer.draw_frame(&vk, render_world, &mut gpu_assets)?;
129
130        let now = Instant::now();
131        let frame_ms = now.duration_since(last_present).as_secs_f32() * 1000.0;
132        last_present = now;
133        frame_stats.record_cpu_frame(frame_ms);
134
135        if let Some(times) = renderer.last_frame_times() {
136            frame_stats.record_gpu_times(times.clone());
137        }
138
139        if needs_recreate {
140            let sw = vk.swapchain.as_ref().unwrap();
141            let (w, h) = (sw.extent.width, sw.extent.height);
142            unsafe { vk.device.handle.device_wait_idle().ok() };
143            vk.recreate_swapchain(w, h, false)?;
144            renderer.resize_output(w, h, &gpu_assets)?;
145        }
146    }
147}
148
149fn handle_render_command(
150    cmd: RenderCommand,
151    vk: &mut VulkanContext,
152    renderer: &mut Box<dyn DynRenderer>,
153    gpu_assets: &mut GpuAssetServer,
154    paused: &mut bool,
155    temp_pool: ash::vk::CommandPool,
156) -> anyhow::Result<bool> {
157    match cmd {
158        RenderCommand::Shutdown => {
159            log::info!("Render thread: received Shutdown");
160            unsafe { vk.device.handle.device_wait_idle().ok() };
161            unsafe { vk.device.handle.destroy_command_pool(temp_pool, None) };
162            return Ok(false);
163        }
164        RenderCommand::Resize { width, height } => {
165            unsafe { vk.device.handle.device_wait_idle().ok() };
166            vk.recreate_swapchain(width, height, false)?;
167            renderer.resize_output(width, height, gpu_assets)?;
168            log::debug!("Render thread: resize {width}x{height}");
169        }
170        RenderCommand::SetInternalScale(scale) => {
171            let sw = vk.swapchain.as_ref().unwrap();
172            let w = (sw.extent.width as f32 * scale) as u32;
173            let h = (sw.extent.height as f32 * scale) as u32;
174            renderer.resize_internal(w.max(1), h.max(1), gpu_assets)?;
175        }
176        RenderCommand::SetExposure(v) => renderer.set_exposure(v),
177        RenderCommand::SetFsrSharpness(v) => renderer.set_fsr_sharpness(v),
178        RenderCommand::SetPipeline(factory) => {
179            unsafe { vk.device.handle.device_wait_idle().ok() };
180            let prev_exp = renderer.exposure();
181            let prev_fsr = renderer.fsr_sharpness();
182            *renderer = factory.build(vk, gpu_assets, prev_exp, prev_fsr)?;
183            log::info!("Render thread: frame_pipeline switched");
184        }
185        RenderCommand::SetPaused(p) => {
186            *paused = p;
187            log::info!("Render thread: paused={p}");
188        }
189    }
190    Ok(true)
191}
192
193fn flush_uploads_gpu(rx: &Receiver<GpuUploadRequest>, gpu: &mut GpuAssetServer) -> anyhow::Result<()> {
194    loop {
195        match rx.try_recv() {
196            Ok(req) => match req {
197                GpuUploadRequest::Mesh { handle, vertices, indices, name } => {
198                    use crate::assets::mesh::CpuMesh;
199                    let cpu = CpuMesh::new(name, vertices, indices);
200                    if let Err(e) = gpu.meshes.upload(handle, &cpu) {
201                        log::error!("GPU upload mesh failed: {e}");
202                    }
203                }
204                GpuUploadRequest::Texture { handle, pixels, width, height, format, name } => {
205                    if let Err(e) = gpu.textures.upload(
206                        &gpu.descriptors,
207                        handle,
208                        TextureSource { pixels: &pixels, width, height, format, name: &name },
209                    ) {
210                        log::error!("GPU upload texture failed: {e}");
211                    }
212                }
213                GpuUploadRequest::Material { handle, bytes } => {
214                    if let Err(e) = gpu.materials.upload(handle, &bytes, &gpu.descriptors) {
215                        log::error!("GPU upload material data failed for {:?}: {e}", handle);
216                    }
217                }
218            },
219            Err(mpsc::TryRecvError::Empty) => break,
220            Err(mpsc::TryRecvError::Disconnected) => break,
221        }
222    }
223    Ok(())
224}