1use crate::scene::Scene;
11use crate::window::WindowState;
12use dreamwell_engine::material::SceneDreamMode;
13use dreamwell_engine::TopologyLayer;
14use dreamwell_fabric::entanglement::CausalEntanglement;
15use dreamwell_fabric::DreamFabric;
16use dreamwell_fabric::{CausalObserverLaneSender, CausalObserverRecorder, FrameGate, FrameMemory, FrameSeal};
17use dreamwell_gpu::formats::DEPTH_FORMAT;
18use dreamwell_gpu::post::PostProcessConfig;
19
20#[derive(Clone, Debug)]
22pub struct FrameContract {
23 pub frame: u64,
24 pub cpu_ms: f32,
25 pub gpu_ms: f32,
26 pub budget_ms: f32,
27 pub budget_exceeded: bool,
28 pub quality_tier: u32,
29 pub particle_count: u32,
30 pub post_process_enabled: bool,
31 pub frame_skipped: bool,
32 pub seal_digest: [u8; 32],
33 pub chain_depth: u64,
34 pub presented: bool,
35}
36
37impl Default for FrameContract {
38 fn default() -> Self {
39 Self {
40 frame: 0,
41 cpu_ms: 0.0,
42 gpu_ms: 0.0,
43 budget_ms: 16.67,
44 budget_exceeded: false,
45 quality_tier: 0,
46 particle_count: 0,
47 post_process_enabled: true,
48 frame_skipped: false,
49 seal_digest: [0u8; 32],
50 chain_depth: 0,
51 presented: true,
52 }
53 }
54}
55
56pub struct SceneRenderer {
64 width: u32,
65 height: u32,
66 depth_texture: Option<wgpu::Texture>,
67 depth_view: Option<wgpu::TextureView>,
68 msaa_texture: Option<wgpu::Texture>,
71 msaa_view: Option<wgpu::TextureView>,
72 msaa_samples: u32,
74 pub fabric: Box<DreamFabric>,
75 total_time: f32,
76 last_cpu_ms: f32,
78 query_set: Option<wgpu::QuerySet>,
80 resolve_buf: Option<wgpu::Buffer>,
82 readback_buf: Option<wgpu::Buffer>,
84 last_gpu_ms: f32,
86 timestamp_period: f32,
88 frame_gate: FrameGate,
90 frame_seal: FrameSeal,
92 frame_memory: FrameMemory,
94 causal_observer_recorder: CausalObserverRecorder,
96 causal_observer_tx: Option<CausalObserverLaneSender>,
98 last_bridge_seal: [u8; 32],
100 pub last_frame_contract: FrameContract,
102 frame_count: u64,
104}
105
106impl SceneRenderer {
107 pub fn new(ws: &WindowState) -> Self {
108 Self::with_msaa(ws, 1)
109 }
110
111 pub fn with_msaa(ws: &WindowState, msaa_samples: u32) -> Self {
113 let samples = match msaa_samples {
114 1 | 4 => msaa_samples,
115 _ => {
116 log::warn!("msaa_samples={msaa_samples} not supported, falling back to 1");
117 1
118 }
119 };
120 let fabric = Box::new(DreamFabric::new(&ws.device, ws.surface_config.format, false));
121
122 let has_timestamps = ws.device.features().contains(wgpu::Features::TIMESTAMP_QUERY)
127 && ws
128 .device
129 .features()
130 .contains(wgpu::Features::TIMESTAMP_QUERY_INSIDE_ENCODERS);
131 let (query_set, resolve_buf, readback_buf, timestamp_period) = if has_timestamps {
132 let qs = ws.device.create_query_set(&wgpu::QuerySetDescriptor {
133 label: Some("gpu_timestamp_queries"),
134 ty: wgpu::QueryType::Timestamp,
135 count: 2,
136 });
137 let resolve = ws.device.create_buffer(&wgpu::BufferDescriptor {
138 label: Some("gpu_timestamp_resolve"),
139 size: 16, usage: wgpu::BufferUsages::QUERY_RESOLVE | wgpu::BufferUsages::COPY_SRC,
141 mapped_at_creation: false,
142 });
143 let readback = ws.device.create_buffer(&wgpu::BufferDescriptor {
144 label: Some("gpu_timestamp_readback"),
145 size: 16,
146 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
147 mapped_at_creation: false,
148 });
149 let period = ws.queue.get_timestamp_period();
150 log::info!("GPU timestamps enabled (period={period:.2}ns)");
151 (Some(qs), Some(resolve), Some(readback), period)
152 } else {
153 log::info!("GPU timestamps unavailable (adapter lacks TIMESTAMP_QUERY)");
154 (None, None, None, 0.0)
155 };
156
157 let mut renderer = Self {
158 width: ws.surface_config.width,
159 height: ws.surface_config.height,
160 depth_texture: None,
161 depth_view: None,
162 msaa_texture: None,
163 msaa_view: None,
164 msaa_samples: samples,
165 fabric,
166 total_time: 0.0,
167 last_cpu_ms: 0.0,
168 query_set,
169 resolve_buf,
170 readback_buf,
171 last_gpu_ms: 0.0,
172 timestamp_period,
173 frame_gate: FrameGate::default(),
174 frame_seal: FrameSeal::new(),
175 frame_memory: FrameMemory::default(),
176 causal_observer_recorder: CausalObserverRecorder::default(),
177 causal_observer_tx: None,
178 last_bridge_seal: [0u8; 32],
179 last_frame_contract: FrameContract::default(),
180 frame_count: 0,
181 };
182 renderer.create_depth_texture(&ws.device);
183 renderer
184 .fabric
185 .resize(&ws.device, ws.surface_config.width, ws.surface_config.height);
186 renderer
187 }
188
189 pub fn resize(&mut self, width: u32, height: u32, device: &wgpu::Device) {
190 if width == 0 || height == 0 {
191 return;
192 }
193 if self.width == width && self.height == height {
194 return; }
196 self.width = width;
197 self.height = height;
198 self.create_depth_texture(device);
199 self.fabric.resize(device, width, height);
200 }
201
202 fn create_depth_texture(&mut self, device: &wgpu::Device) {
204 if self.width == 0 || self.height == 0 {
205 return;
206 }
207 self.depth_view = None;
209 self.depth_texture = None;
210 self.msaa_view = None;
211 self.msaa_texture = None;
212
213 let texture = device.create_texture(&wgpu::TextureDescriptor {
214 label: Some("runtime_depth"),
215 size: wgpu::Extent3d {
216 width: self.width,
217 height: self.height,
218 depth_or_array_layers: 1,
219 },
220 mip_level_count: 1,
221 sample_count: self.msaa_samples,
222 dimension: wgpu::TextureDimension::D2,
223 format: DEPTH_FORMAT,
224 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
225 view_formats: &[],
226 });
227 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
228 self.depth_texture = Some(texture);
229 self.depth_view = Some(view);
230
231 if self.msaa_samples > 1 {
233 let surface_format = wgpu::TextureFormat::Bgra8UnormSrgb;
234 let msaa_tex = device.create_texture(&wgpu::TextureDescriptor {
235 label: Some("runtime_msaa_color"),
236 size: wgpu::Extent3d {
237 width: self.width,
238 height: self.height,
239 depth_or_array_layers: 1,
240 },
241 mip_level_count: 1,
242 sample_count: self.msaa_samples,
243 dimension: wgpu::TextureDimension::D2,
244 format: surface_format,
245 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
246 view_formats: &[],
247 });
248 let msaa_v = msaa_tex.create_view(&wgpu::TextureViewDescriptor::default());
249 self.msaa_texture = Some(msaa_tex);
250 self.msaa_view = Some(msaa_v);
251 }
252 }
253
254 pub fn render(
263 &mut self,
264 ws: &WindowState,
265 scene: &Scene,
266 dt: f32,
267 player_pos: glam::Vec3,
268 active_layer: TopologyLayer,
269 ) {
270 self.last_cpu_ms = dt * 1000.0;
272 self.total_time += dt;
273
274 self.read_gpu_timestamp(&ws.device);
276
277 let output = match ws.surface.get_current_texture() {
279 wgpu::CurrentSurfaceTexture::Success(t) => t,
280 wgpu::CurrentSurfaceTexture::Suboptimal(t) => {
281 log::warn!("surface suboptimal — reconfiguring");
282 ws.surface.configure(&ws.device, &ws.surface_config);
283 t
284 }
285 wgpu::CurrentSurfaceTexture::Outdated | wgpu::CurrentSurfaceTexture::Lost => {
286 log::warn!("surface lost/outdated — reconfiguring");
287 ws.surface.configure(&ws.device, &ws.surface_config);
288 return;
289 }
290 wgpu::CurrentSurfaceTexture::Timeout | wgpu::CurrentSurfaceTexture::Occluded => {
291 return;
292 }
293 wgpu::CurrentSurfaceTexture::Validation => {
294 log::error!("surface validation error");
295 return;
296 }
297 };
298
299 let view = output.texture.create_view(&wgpu::TextureViewDescriptor::default());
300
301 let mut encoder = ws.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
302 label: Some("runtime_render"),
303 });
304
305 if let Some(ref qs) = self.query_set {
307 encoder.write_timestamp(qs, 0);
308 }
309
310 let fc = self.fabric.begin_frame(&ws.queue, dt, player_pos, active_layer);
312
313 if self.fabric.gpu_scene().is_dirty() {
315 self.fabric.upload_scene(&ws.device, &scene.game_objects);
316 } else {
317 self.fabric.sync_scene_transforms(&scene.game_objects);
318 }
319
320 self.fabric.extract(&scene.game_objects, &fc.gpu_observer, dt);
322 self.fabric.prepare(&scene.game_objects);
323 self.fabric.queue_draw_items(&scene.camera);
324
325 if let Some(dv) = &self.depth_view {
329 self.fabric.end_frame(
330 &ws.device,
331 &ws.queue,
332 &mut encoder,
333 &view,
334 dv,
335 &scene.camera,
336 &fc,
337 self.msaa_view.as_ref(),
338 );
339 } else {
340 log::warn!("depth_view is None — rendering fallback clear pass");
343 let _pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
344 label: Some("fallback_clear"),
345 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
346 view: &view,
347 resolve_target: None,
348 depth_slice: None,
349 ops: wgpu::Operations {
350 load: wgpu::LoadOp::Clear(wgpu::Color {
351 r: 1.0,
352 g: 0.0,
353 b: 0.0,
354 a: 1.0,
355 }),
356 store: wgpu::StoreOp::Store,
357 },
358 })],
359 depth_stencil_attachment: None,
360 timestamp_writes: None,
361 occlusion_query_set: None,
362 multiview_mask: None,
363 });
364 }
365
366 if let (Some(ref qs), Some(ref resolve), Some(ref readback)) =
368 (&self.query_set, &self.resolve_buf, &self.readback_buf)
369 {
370 encoder.write_timestamp(qs, 1);
371 encoder.resolve_query_set(qs, 0..2, resolve, 0);
372 encoder.copy_buffer_to_buffer(resolve, 0, readback, 0, 16);
373 }
374
375 let render_cmds = encoder.finish();
377 ws.queue.submit(std::iter::once(render_cmds));
378 output.present();
379
380 self.frame_count += 1;
382
383 let quality = self
385 .frame_gate
386 .evaluate(self.last_gpu_ms, self.fabric.matter().dreammatter_capacity());
387 let quality_tier = self.frame_gate.quality_tier();
388
389 let timestamp_ns = std::time::SystemTime::now()
391 .duration_since(std::time::SystemTime::UNIX_EPOCH)
392 .map(|d| d.as_nanos() as u64)
393 .unwrap_or(0);
394 let entry = self.frame_seal.seal_frame(
395 self.frame_count,
396 self.last_gpu_ms,
397 self.last_cpu_ms,
398 quality.particle_count,
399 quality_tier,
400 self.last_bridge_seal,
401 timestamp_ns,
402 true,
403 );
404
405 let chain_depth = self.frame_seal.chain_depth();
407 self.frame_memory.capture_seal(entry.clone());
408
409 let dispatch_ns = self.causal_observer_recorder.now_ns();
411 self.causal_observer_recorder.record(
412 self.frame_count,
413 dispatch_ns,
414 self.last_gpu_ms,
415 quality.particle_count,
416 quality_tier,
417 true,
418 );
419
420 if let Some(ref tx) = self.causal_observer_tx {
422 tx.send_frame(&entry, self.frame_count, chain_depth);
423 }
424
425 self.last_frame_contract = FrameContract {
427 frame: self.frame_count,
428 cpu_ms: self.last_cpu_ms,
429 gpu_ms: self.last_gpu_ms,
430 budget_ms: 16.67,
431 budget_exceeded: self.last_gpu_ms > 16.67 * 0.85,
432 quality_tier,
433 particle_count: quality.particle_count,
434 post_process_enabled: quality.post_process_enabled,
435 frame_skipped: false,
436 seal_digest: entry.seal_digest,
437 chain_depth,
438 presented: true,
439 };
440 }
441
442 pub fn render_entangled(
446 &mut self,
447 ws: &WindowState,
448 scene: &Scene,
449 dt: f32,
450 entanglement: &CausalEntanglement,
451 active_layer: TopologyLayer,
452 ) {
453 self.last_cpu_ms = dt * 1000.0;
454 self.total_time += dt;
455
456 self.read_gpu_timestamp(&ws.device);
457
458 let output = match ws.surface.get_current_texture() {
459 wgpu::CurrentSurfaceTexture::Success(t) => t,
460 wgpu::CurrentSurfaceTexture::Suboptimal(t) => {
461 log::warn!("surface suboptimal — reconfiguring");
462 ws.surface.configure(&ws.device, &ws.surface_config);
463 t
464 }
465 wgpu::CurrentSurfaceTexture::Outdated | wgpu::CurrentSurfaceTexture::Lost => {
466 log::warn!("surface lost/outdated — reconfiguring");
467 ws.surface.configure(&ws.device, &ws.surface_config);
468 return;
469 }
470 wgpu::CurrentSurfaceTexture::Timeout | wgpu::CurrentSurfaceTexture::Occluded => {
471 return;
472 }
473 wgpu::CurrentSurfaceTexture::Validation => {
474 log::error!("surface validation error");
475 return;
476 }
477 };
478
479 let view = output.texture.create_view(&wgpu::TextureViewDescriptor::default());
480
481 let mut encoder = ws.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
482 label: Some("runtime_render_entangled"),
483 });
484
485 if let Some(ref qs) = self.query_set {
486 encoder.write_timestamp(qs, 0);
487 }
488
489 let fc = self
491 .fabric
492 .begin_frame_entangled(&ws.queue, dt, entanglement, active_layer);
493
494 if self.fabric.gpu_scene().is_dirty() {
495 self.fabric.upload_scene(&ws.device, &scene.game_objects);
496 } else {
497 self.fabric.sync_scene_transforms(&scene.game_objects);
498 }
499
500 self.fabric.extract(&scene.game_objects, &fc.gpu_observer, dt);
501 self.fabric.prepare(&scene.game_objects);
502 self.fabric.queue_draw_items(&scene.camera);
503
504 if let Some(dv) = &self.depth_view {
505 self.fabric.end_frame(
506 &ws.device,
507 &ws.queue,
508 &mut encoder,
509 &view,
510 dv,
511 &scene.camera,
512 &fc,
513 self.msaa_view.as_ref(),
514 );
515 }
516
517 if let (Some(ref qs), Some(ref resolve), Some(ref readback)) =
518 (&self.query_set, &self.resolve_buf, &self.readback_buf)
519 {
520 encoder.write_timestamp(qs, 1);
521 encoder.resolve_query_set(qs, 0..2, resolve, 0);
522 encoder.copy_buffer_to_buffer(resolve, 0, readback, 0, 16);
523 }
524
525 let render_cmds = encoder.finish();
526 ws.queue.submit(std::iter::once(render_cmds));
527 output.present();
528
529 self.frame_count += 1;
531
532 let quality = self
534 .frame_gate
535 .evaluate(self.last_gpu_ms, self.fabric.matter().dreammatter_capacity());
536 let quality_tier = self.frame_gate.quality_tier();
537
538 let timestamp_ns = std::time::SystemTime::now()
540 .duration_since(std::time::SystemTime::UNIX_EPOCH)
541 .map(|d| d.as_nanos() as u64)
542 .unwrap_or(0);
543 let entry = self.frame_seal.seal_frame(
544 self.frame_count,
545 self.last_gpu_ms,
546 self.last_cpu_ms,
547 quality.particle_count,
548 quality_tier,
549 self.last_bridge_seal,
550 timestamp_ns,
551 true,
552 );
553
554 let chain_depth = self.frame_seal.chain_depth();
556 self.frame_memory.capture_seal(entry.clone());
557
558 let dispatch_ns = self.causal_observer_recorder.now_ns();
560 self.causal_observer_recorder.record(
561 self.frame_count,
562 dispatch_ns,
563 self.last_gpu_ms,
564 quality.particle_count,
565 quality_tier,
566 true,
567 );
568
569 if let Some(ref tx) = self.causal_observer_tx {
571 tx.send_frame(&entry, self.frame_count, chain_depth);
572 }
573
574 self.last_frame_contract = FrameContract {
576 frame: self.frame_count,
577 cpu_ms: self.last_cpu_ms,
578 gpu_ms: self.last_gpu_ms,
579 budget_ms: 16.67,
580 budget_exceeded: self.last_gpu_ms > 16.67 * 0.85,
581 quality_tier,
582 particle_count: quality.particle_count,
583 post_process_enabled: quality.post_process_enabled,
584 frame_skipped: false,
585 seal_digest: entry.seal_digest,
586 chain_depth,
587 presented: true,
588 };
589 }
590
591 fn read_gpu_timestamp(&mut self, device: &wgpu::Device) {
594 let Some(ref readback) = self.readback_buf else { return };
595 if self.timestamp_period == 0.0 {
596 return;
597 }
598
599 let slice = readback.slice(..);
600 let (tx, rx) = std::sync::mpsc::sync_channel(1);
601 slice.map_async(wgpu::MapMode::Read, move |result| {
602 let _ = tx.send(result);
603 });
604
605 let _ = device.poll(wgpu::PollType::Poll);
608
609 if let Ok(Ok(())) = rx.try_recv() {
610 let data = slice.get_mapped_range();
611 if data.len() >= 16 {
612 let timestamps: &[u64] = bytemuck::cast_slice(&data[..16]);
613 let start = timestamps[0];
614 let end = timestamps[1];
615 if end > start {
616 self.last_gpu_ms = (end - start) as f32 * self.timestamp_period / 1_000_000.0;
617 }
618 }
619 drop(data);
620 readback.unmap();
621 }
622 }
623
624 pub fn dimensions(&self) -> (u32, u32) {
626 (self.width, self.height)
627 }
628
629 pub fn msaa_samples(&self) -> u32 {
631 self.msaa_samples
632 }
633
634 pub fn msaa_view(&self) -> Option<&wgpu::TextureView> {
637 self.msaa_view.as_ref()
638 }
639
640 pub fn last_picked_id(&self) -> u32 {
642 self.fabric.last_picked_id()
643 }
644
645 pub fn last_frame_stats(&self) -> dreamwell_fabric::FrameStats {
648 dreamwell_fabric::FrameStats {
649 cpu_ms: self.last_cpu_ms,
650 gpu_ms: self.last_gpu_ms,
651 visible_meshlets: self.fabric.gpu_scene().object_count() as u32,
652 particle_count: self.fabric.matter().dreammatter_capacity(),
653 }
654 }
655
656 pub fn set_post_process_config(&mut self, config: PostProcessConfig) {
658 self.fabric.set_post_process_config(config);
659 }
660
661 pub fn post_process_config(&self) -> &PostProcessConfig {
663 self.fabric.post_process_config()
664 }
665
666 pub fn set_scene_dream_mode(&mut self, mode: SceneDreamMode) {
668 self.fabric.set_scene_dream_mode(mode);
669 }
670
671 pub fn scene_dream_mode(&self) -> SceneDreamMode {
673 self.fabric.scene_dream_mode()
674 }
675
676 pub fn set_causal_observer_sender(&mut self, tx: CausalObserverLaneSender) {
678 self.causal_observer_tx = Some(tx);
679 }
680
681 pub fn set_bridge_seal(&mut self, seal: [u8; 32]) {
683 self.last_bridge_seal = seal;
684 }
685
686 pub fn frame_gate(&self) -> &FrameGate {
688 &self.frame_gate
689 }
690
691 pub fn frame_seal(&self) -> &FrameSeal {
693 &self.frame_seal
694 }
695
696 pub fn frame_memory(&self) -> &FrameMemory {
698 &self.frame_memory
699 }
700
701 pub fn causal_observer_recorder(&self) -> &CausalObserverRecorder {
703 &self.causal_observer_recorder
704 }
705}