use crate::scene::Scene;
use crate::window::WindowState;
use dreamwell_engine::material::SceneDreamMode;
use dreamwell_engine::TopologyLayer;
use dreamwell_fabric::entanglement::CausalEntanglement;
use dreamwell_fabric::DreamFabric;
use dreamwell_fabric::{CausalObserverLaneSender, CausalObserverRecorder, FrameGate, FrameMemory, FrameSeal};
use dreamwell_gpu::formats::DEPTH_FORMAT;
use dreamwell_gpu::post::PostProcessConfig;
#[derive(Clone, Debug)]
pub struct FrameContract {
pub frame: u64,
pub cpu_ms: f32,
pub gpu_ms: f32,
pub budget_ms: f32,
pub budget_exceeded: bool,
pub quality_tier: u32,
pub particle_count: u32,
pub post_process_enabled: bool,
pub frame_skipped: bool,
pub seal_digest: [u8; 32],
pub chain_depth: u64,
pub presented: bool,
}
impl Default for FrameContract {
fn default() -> Self {
Self {
frame: 0,
cpu_ms: 0.0,
gpu_ms: 0.0,
budget_ms: 16.67,
budget_exceeded: false,
quality_tier: 0,
particle_count: 0,
post_process_enabled: true,
frame_skipped: false,
seal_digest: [0u8; 32],
chain_depth: 0,
presented: true,
}
}
}
pub struct SceneRenderer {
width: u32,
height: u32,
depth_texture: Option<wgpu::Texture>,
depth_view: Option<wgpu::TextureView>,
msaa_texture: Option<wgpu::Texture>,
msaa_view: Option<wgpu::TextureView>,
msaa_samples: u32,
pub fabric: Box<DreamFabric>,
total_time: f32,
last_cpu_ms: f32,
query_set: Option<wgpu::QuerySet>,
resolve_buf: Option<wgpu::Buffer>,
readback_buf: Option<wgpu::Buffer>,
last_gpu_ms: f32,
timestamp_period: f32,
frame_gate: FrameGate,
frame_seal: FrameSeal,
frame_memory: FrameMemory,
causal_observer_recorder: CausalObserverRecorder,
causal_observer_tx: Option<CausalObserverLaneSender>,
last_bridge_seal: [u8; 32],
pub last_frame_contract: FrameContract,
frame_count: u64,
}
impl SceneRenderer {
pub fn new(ws: &WindowState) -> Self {
Self::with_msaa(ws, 1)
}
pub fn with_msaa(ws: &WindowState, msaa_samples: u32) -> Self {
let samples = match msaa_samples {
1 | 4 => msaa_samples,
_ => {
log::warn!("msaa_samples={msaa_samples} not supported, falling back to 1");
1
}
};
let fabric = Box::new(DreamFabric::new(&ws.device, ws.surface_config.format, false));
let has_timestamps = ws.device.features().contains(wgpu::Features::TIMESTAMP_QUERY)
&& ws
.device
.features()
.contains(wgpu::Features::TIMESTAMP_QUERY_INSIDE_ENCODERS);
let (query_set, resolve_buf, readback_buf, timestamp_period) = if has_timestamps {
let qs = ws.device.create_query_set(&wgpu::QuerySetDescriptor {
label: Some("gpu_timestamp_queries"),
ty: wgpu::QueryType::Timestamp,
count: 2,
});
let resolve = ws.device.create_buffer(&wgpu::BufferDescriptor {
label: Some("gpu_timestamp_resolve"),
size: 16, usage: wgpu::BufferUsages::QUERY_RESOLVE | wgpu::BufferUsages::COPY_SRC,
mapped_at_creation: false,
});
let readback = ws.device.create_buffer(&wgpu::BufferDescriptor {
label: Some("gpu_timestamp_readback"),
size: 16,
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
mapped_at_creation: false,
});
let period = ws.queue.get_timestamp_period();
log::info!("GPU timestamps enabled (period={period:.2}ns)");
(Some(qs), Some(resolve), Some(readback), period)
} else {
log::info!("GPU timestamps unavailable (adapter lacks TIMESTAMP_QUERY)");
(None, None, None, 0.0)
};
let mut renderer = Self {
width: ws.surface_config.width,
height: ws.surface_config.height,
depth_texture: None,
depth_view: None,
msaa_texture: None,
msaa_view: None,
msaa_samples: samples,
fabric,
total_time: 0.0,
last_cpu_ms: 0.0,
query_set,
resolve_buf,
readback_buf,
last_gpu_ms: 0.0,
timestamp_period,
frame_gate: FrameGate::default(),
frame_seal: FrameSeal::new(),
frame_memory: FrameMemory::default(),
causal_observer_recorder: CausalObserverRecorder::default(),
causal_observer_tx: None,
last_bridge_seal: [0u8; 32],
last_frame_contract: FrameContract::default(),
frame_count: 0,
};
renderer.create_depth_texture(&ws.device);
renderer
.fabric
.resize(&ws.device, ws.surface_config.width, ws.surface_config.height);
renderer
}
pub fn resize(&mut self, width: u32, height: u32, device: &wgpu::Device) {
if width == 0 || height == 0 {
return;
}
if self.width == width && self.height == height {
return; }
self.width = width;
self.height = height;
self.create_depth_texture(device);
self.fabric.resize(device, width, height);
}
fn create_depth_texture(&mut self, device: &wgpu::Device) {
if self.width == 0 || self.height == 0 {
return;
}
self.depth_view = None;
self.depth_texture = None;
self.msaa_view = None;
self.msaa_texture = None;
let texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("runtime_depth"),
size: wgpu::Extent3d {
width: self.width,
height: self.height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: self.msaa_samples,
dimension: wgpu::TextureDimension::D2,
format: DEPTH_FORMAT,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
view_formats: &[],
});
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
self.depth_texture = Some(texture);
self.depth_view = Some(view);
if self.msaa_samples > 1 {
let surface_format = wgpu::TextureFormat::Bgra8UnormSrgb;
let msaa_tex = device.create_texture(&wgpu::TextureDescriptor {
label: Some("runtime_msaa_color"),
size: wgpu::Extent3d {
width: self.width,
height: self.height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: self.msaa_samples,
dimension: wgpu::TextureDimension::D2,
format: surface_format,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
view_formats: &[],
});
let msaa_v = msaa_tex.create_view(&wgpu::TextureViewDescriptor::default());
self.msaa_texture = Some(msaa_tex);
self.msaa_view = Some(msaa_v);
}
}
pub fn render(
&mut self,
ws: &WindowState,
scene: &Scene,
dt: f32,
player_pos: glam::Vec3,
active_layer: TopologyLayer,
) {
self.last_cpu_ms = dt * 1000.0;
self.total_time += dt;
self.read_gpu_timestamp(&ws.device);
let output = match ws.surface.get_current_texture() {
wgpu::CurrentSurfaceTexture::Success(t) => t,
wgpu::CurrentSurfaceTexture::Suboptimal(t) => {
log::warn!("surface suboptimal — reconfiguring");
ws.surface.configure(&ws.device, &ws.surface_config);
t
}
wgpu::CurrentSurfaceTexture::Outdated | wgpu::CurrentSurfaceTexture::Lost => {
log::warn!("surface lost/outdated — reconfiguring");
ws.surface.configure(&ws.device, &ws.surface_config);
return;
}
wgpu::CurrentSurfaceTexture::Timeout | wgpu::CurrentSurfaceTexture::Occluded => {
return;
}
wgpu::CurrentSurfaceTexture::Validation => {
log::error!("surface validation error");
return;
}
};
let view = output.texture.create_view(&wgpu::TextureViewDescriptor::default());
let mut encoder = ws.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("runtime_render"),
});
if let Some(ref qs) = self.query_set {
encoder.write_timestamp(qs, 0);
}
let fc = self.fabric.begin_frame(&ws.queue, dt, player_pos, active_layer);
if self.fabric.gpu_scene().is_dirty() {
self.fabric.upload_scene(&ws.device, &scene.game_objects);
} else {
self.fabric.sync_scene_transforms(&scene.game_objects);
}
self.fabric.extract(&scene.game_objects, &fc.gpu_observer, dt);
self.fabric.prepare(&scene.game_objects);
self.fabric.queue_draw_items(&scene.camera);
if let Some(dv) = &self.depth_view {
self.fabric.end_frame(
&ws.device,
&ws.queue,
&mut encoder,
&view,
dv,
&scene.camera,
&fc,
self.msaa_view.as_ref(),
);
} else {
log::warn!("depth_view is None — rendering fallback clear pass");
let _pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("fallback_clear"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &view,
resolve_target: None,
depth_slice: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
r: 1.0,
g: 0.0,
b: 0.0,
a: 1.0,
}),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
multiview_mask: None,
});
}
if let (Some(ref qs), Some(ref resolve), Some(ref readback)) =
(&self.query_set, &self.resolve_buf, &self.readback_buf)
{
encoder.write_timestamp(qs, 1);
encoder.resolve_query_set(qs, 0..2, resolve, 0);
encoder.copy_buffer_to_buffer(resolve, 0, readback, 0, 16);
}
let render_cmds = encoder.finish();
ws.queue.submit(std::iter::once(render_cmds));
output.present();
self.frame_count += 1;
let quality = self
.frame_gate
.evaluate(self.last_gpu_ms, self.fabric.matter().dreammatter_capacity());
let quality_tier = self.frame_gate.quality_tier();
let timestamp_ns = std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0);
let entry = self.frame_seal.seal_frame(
self.frame_count,
self.last_gpu_ms,
self.last_cpu_ms,
quality.particle_count,
quality_tier,
self.last_bridge_seal,
timestamp_ns,
true,
);
let chain_depth = self.frame_seal.chain_depth();
self.frame_memory.capture_seal(entry.clone());
let dispatch_ns = self.causal_observer_recorder.now_ns();
self.causal_observer_recorder.record(
self.frame_count,
dispatch_ns,
self.last_gpu_ms,
quality.particle_count,
quality_tier,
true,
);
if let Some(ref tx) = self.causal_observer_tx {
tx.send_frame(&entry, self.frame_count, chain_depth);
}
self.last_frame_contract = FrameContract {
frame: self.frame_count,
cpu_ms: self.last_cpu_ms,
gpu_ms: self.last_gpu_ms,
budget_ms: 16.67,
budget_exceeded: self.last_gpu_ms > 16.67 * 0.85,
quality_tier,
particle_count: quality.particle_count,
post_process_enabled: quality.post_process_enabled,
frame_skipped: false,
seal_digest: entry.seal_digest,
chain_depth,
presented: true,
};
}
pub fn render_entangled(
&mut self,
ws: &WindowState,
scene: &Scene,
dt: f32,
entanglement: &CausalEntanglement,
active_layer: TopologyLayer,
) {
self.last_cpu_ms = dt * 1000.0;
self.total_time += dt;
self.read_gpu_timestamp(&ws.device);
let output = match ws.surface.get_current_texture() {
wgpu::CurrentSurfaceTexture::Success(t) => t,
wgpu::CurrentSurfaceTexture::Suboptimal(t) => {
log::warn!("surface suboptimal — reconfiguring");
ws.surface.configure(&ws.device, &ws.surface_config);
t
}
wgpu::CurrentSurfaceTexture::Outdated | wgpu::CurrentSurfaceTexture::Lost => {
log::warn!("surface lost/outdated — reconfiguring");
ws.surface.configure(&ws.device, &ws.surface_config);
return;
}
wgpu::CurrentSurfaceTexture::Timeout | wgpu::CurrentSurfaceTexture::Occluded => {
return;
}
wgpu::CurrentSurfaceTexture::Validation => {
log::error!("surface validation error");
return;
}
};
let view = output.texture.create_view(&wgpu::TextureViewDescriptor::default());
let mut encoder = ws.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("runtime_render_entangled"),
});
if let Some(ref qs) = self.query_set {
encoder.write_timestamp(qs, 0);
}
let fc = self
.fabric
.begin_frame_entangled(&ws.queue, dt, entanglement, active_layer);
if self.fabric.gpu_scene().is_dirty() {
self.fabric.upload_scene(&ws.device, &scene.game_objects);
} else {
self.fabric.sync_scene_transforms(&scene.game_objects);
}
self.fabric.extract(&scene.game_objects, &fc.gpu_observer, dt);
self.fabric.prepare(&scene.game_objects);
self.fabric.queue_draw_items(&scene.camera);
if let Some(dv) = &self.depth_view {
self.fabric.end_frame(
&ws.device,
&ws.queue,
&mut encoder,
&view,
dv,
&scene.camera,
&fc,
self.msaa_view.as_ref(),
);
}
if let (Some(ref qs), Some(ref resolve), Some(ref readback)) =
(&self.query_set, &self.resolve_buf, &self.readback_buf)
{
encoder.write_timestamp(qs, 1);
encoder.resolve_query_set(qs, 0..2, resolve, 0);
encoder.copy_buffer_to_buffer(resolve, 0, readback, 0, 16);
}
let render_cmds = encoder.finish();
ws.queue.submit(std::iter::once(render_cmds));
output.present();
self.frame_count += 1;
let quality = self
.frame_gate
.evaluate(self.last_gpu_ms, self.fabric.matter().dreammatter_capacity());
let quality_tier = self.frame_gate.quality_tier();
let timestamp_ns = std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0);
let entry = self.frame_seal.seal_frame(
self.frame_count,
self.last_gpu_ms,
self.last_cpu_ms,
quality.particle_count,
quality_tier,
self.last_bridge_seal,
timestamp_ns,
true,
);
let chain_depth = self.frame_seal.chain_depth();
self.frame_memory.capture_seal(entry.clone());
let dispatch_ns = self.causal_observer_recorder.now_ns();
self.causal_observer_recorder.record(
self.frame_count,
dispatch_ns,
self.last_gpu_ms,
quality.particle_count,
quality_tier,
true,
);
if let Some(ref tx) = self.causal_observer_tx {
tx.send_frame(&entry, self.frame_count, chain_depth);
}
self.last_frame_contract = FrameContract {
frame: self.frame_count,
cpu_ms: self.last_cpu_ms,
gpu_ms: self.last_gpu_ms,
budget_ms: 16.67,
budget_exceeded: self.last_gpu_ms > 16.67 * 0.85,
quality_tier,
particle_count: quality.particle_count,
post_process_enabled: quality.post_process_enabled,
frame_skipped: false,
seal_digest: entry.seal_digest,
chain_depth,
presented: true,
};
}
fn read_gpu_timestamp(&mut self, device: &wgpu::Device) {
let Some(ref readback) = self.readback_buf else { return };
if self.timestamp_period == 0.0 {
return;
}
let slice = readback.slice(..);
let (tx, rx) = std::sync::mpsc::sync_channel(1);
slice.map_async(wgpu::MapMode::Read, move |result| {
let _ = tx.send(result);
});
let _ = device.poll(wgpu::PollType::Poll);
if let Ok(Ok(())) = rx.try_recv() {
let data = slice.get_mapped_range();
if data.len() >= 16 {
let timestamps: &[u64] = bytemuck::cast_slice(&data[..16]);
let start = timestamps[0];
let end = timestamps[1];
if end > start {
self.last_gpu_ms = (end - start) as f32 * self.timestamp_period / 1_000_000.0;
}
}
drop(data);
readback.unmap();
}
}
pub fn dimensions(&self) -> (u32, u32) {
(self.width, self.height)
}
pub fn msaa_samples(&self) -> u32 {
self.msaa_samples
}
pub fn msaa_view(&self) -> Option<&wgpu::TextureView> {
self.msaa_view.as_ref()
}
pub fn last_picked_id(&self) -> u32 {
self.fabric.last_picked_id()
}
pub fn last_frame_stats(&self) -> dreamwell_fabric::FrameStats {
dreamwell_fabric::FrameStats {
cpu_ms: self.last_cpu_ms,
gpu_ms: self.last_gpu_ms,
visible_meshlets: self.fabric.gpu_scene().object_count() as u32,
particle_count: self.fabric.matter().dreammatter_capacity(),
}
}
pub fn set_post_process_config(&mut self, config: PostProcessConfig) {
self.fabric.set_post_process_config(config);
}
pub fn post_process_config(&self) -> &PostProcessConfig {
self.fabric.post_process_config()
}
pub fn set_scene_dream_mode(&mut self, mode: SceneDreamMode) {
self.fabric.set_scene_dream_mode(mode);
}
pub fn scene_dream_mode(&self) -> SceneDreamMode {
self.fabric.scene_dream_mode()
}
pub fn set_causal_observer_sender(&mut self, tx: CausalObserverLaneSender) {
self.causal_observer_tx = Some(tx);
}
pub fn set_bridge_seal(&mut self, seal: [u8; 32]) {
self.last_bridge_seal = seal;
}
pub fn frame_gate(&self) -> &FrameGate {
&self.frame_gate
}
pub fn frame_seal(&self) -> &FrameSeal {
&self.frame_seal
}
pub fn frame_memory(&self) -> &FrameMemory {
&self.frame_memory
}
pub fn causal_observer_recorder(&self) -> &CausalObserverRecorder {
&self.causal_observer_recorder
}
}