ursus_core/render/thread/
mod.rs1pub 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#[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#[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}