use dreamwell_matter::config::BenchmarkConfig;
#[derive(Debug)]
pub struct SafetyCheckResult {
pub passed: bool,
pub adapter_name: String,
pub backend: String,
pub checks: Vec<SafetyCheckItem>,
pub vram_estimate: VramEstimate,
}
#[derive(Debug)]
pub struct SafetyCheckItem {
pub name: &'static str,
pub passed: bool,
pub detail: String,
}
#[derive(Debug, Default)]
pub struct VramEstimate {
pub hdr_framebuffer_bytes: u64,
pub depth_buffer_bytes: u64,
pub bloom_mips_bytes: u64,
pub particle_buffer_bytes: u64,
pub ibl_textures_bytes: u64,
pub pbr_buffers_bytes: u64,
pub mesh_buffers_bytes: u64,
pub dream_lighting_bytes: u64,
}
impl VramEstimate {
pub fn total_bytes(&self) -> u64 {
self.hdr_framebuffer_bytes
+ self.depth_buffer_bytes
+ self.bloom_mips_bytes
+ self.particle_buffer_bytes
+ self.ibl_textures_bytes
+ self.pbr_buffers_bytes
+ self.mesh_buffers_bytes
+ self.dream_lighting_bytes
}
pub fn format_bytes(bytes: u64) -> String {
if bytes >= 1024 * 1024 * 1024 {
format!("{:.2} GB", bytes as f64 / (1024.0 * 1024.0 * 1024.0))
} else if bytes >= 1024 * 1024 {
format!("{:.1} MB", bytes as f64 / (1024.0 * 1024.0))
} else if bytes >= 1024 {
format!("{:.1} KB", bytes as f64 / 1024.0)
} else {
format!("{} bytes", bytes)
}
}
fn compute(config: &BenchmarkConfig) -> Self {
let w = config.width as u64;
let h = config.height as u64;
let particle_count = 4096u64;
Self {
hdr_framebuffer_bytes: w * h * 8,
depth_buffer_bytes: w * h * 4,
bloom_mips_bytes: (w / 2) * (h / 2) * 8 + (w / 4) * (h / 4) * 8,
particle_buffer_bytes: particle_count * 64,
ibl_textures_bytes: 512 * 512 * 8 + 64 * 64 * 6 * 8 + 128 * 128 * 6 * 8 ,
pbr_buffers_bytes: 4 * 32 * 4 + 64 * 32 + 32 * 64 + 7 * 256 ,
mesh_buffers_bytes: 9 * 2048,
dream_lighting_bytes: (w / 2) * (h / 2) * 8 + w * h * 8 + w * h * 8 * 2 + w * h * 4 + w * h * 4 + 160 * 90 * 64 * 8 ,
}
}
}
impl SafetyCheckResult {
pub fn print(&self) {
println!();
println!("=== DreamMatter GPU Pre-Check ===");
println!();
let rt_status = self.checks.iter()
.find(|c| c.name == "RT: EXPERIMENTAL_RAY_QUERY")
.map(|c| c.detail.contains("available (RT"))
.unwrap_or(false);
println!(" Hardware");
println!(" Adapter: {}", self.adapter_name);
println!(" Backend: {}", self.backend);
println!(" Ray Tracing: {}", if rt_status { "On (hardware detected)" } else { "Off" });
println!();
println!(" Compatibility ({}/{})", self.checks.iter().filter(|c| c.passed).count(), self.checks.len());
for item in &self.checks {
let icon = if item.passed { "OK" } else { "!!" };
println!(" [{}] {}: {}", icon, item.name, item.detail);
}
println!();
println!(" VRAM Estimate");
println!(" HDR framebuffer: {}", VramEstimate::format_bytes(self.vram_estimate.hdr_framebuffer_bytes));
println!(" Depth buffer: {}", VramEstimate::format_bytes(self.vram_estimate.depth_buffer_bytes));
println!(" Bloom mips: {}", VramEstimate::format_bytes(self.vram_estimate.bloom_mips_bytes));
println!(" Dreamlets: {}", VramEstimate::format_bytes(self.vram_estimate.particle_buffer_bytes));
println!(" IBL textures: {}", VramEstimate::format_bytes(self.vram_estimate.ibl_textures_bytes));
println!(" PBR buffers: {}", VramEstimate::format_bytes(self.vram_estimate.pbr_buffers_bytes));
println!(" Mesh geometry: {}", VramEstimate::format_bytes(self.vram_estimate.mesh_buffers_bytes));
println!(" Dream Lighting: {}", VramEstimate::format_bytes(self.vram_estimate.dream_lighting_bytes));
println!(" ─────────────────────────");
println!(" Total estimated: {}", VramEstimate::format_bytes(self.vram_estimate.total_bytes()));
println!();
let status = if self.passed { "READY" } else { "NOT READY" };
println!(" Status: {}", status);
println!("=================================");
println!();
}
}
pub fn run_safety_checks(
adapter: &wgpu::Adapter,
device: &wgpu::Device,
config: &BenchmarkConfig,
) -> SafetyCheckResult {
let info = adapter.get_info();
let limits = device.limits();
let features = device.features();
let mut checks = Vec::with_capacity(12);
let max_dim = limits.max_texture_dimension_2d;
let needed = config.width.max(config.height);
checks.push(SafetyCheckItem {
name: "Texture dimensions",
passed: max_dim >= needed,
detail: format!("max {}px, need {}px", max_dim, needed),
});
checks.push(SafetyCheckItem {
name: "Uniform buffer",
passed: limits.max_uniform_buffer_binding_size >= 256,
detail: format!("{} bytes (min 256)", limits.max_uniform_buffer_binding_size),
});
checks.push(SafetyCheckItem {
name: "Storage buffer",
passed: limits.max_storage_buffer_binding_size >= 1024 * 1024,
detail: format!("{} bytes (min 1 MiB)", limits.max_storage_buffer_binding_size),
});
checks.push(SafetyCheckItem {
name: "Bind groups",
passed: limits.max_bind_groups >= 3,
detail: format!("{} (min 3)", limits.max_bind_groups),
});
checks.push(SafetyCheckItem {
name: "Compute workgroup",
passed: limits.max_compute_workgroup_size_x >= 64,
detail: format!("max_x={} (min 64)", limits.max_compute_workgroup_size_x),
});
checks.push(SafetyCheckItem {
name: "Color attachments",
passed: limits.max_color_attachments >= 1,
detail: format!("{} (min 1)", limits.max_color_attachments),
});
checks.push(SafetyCheckItem {
name: "Timestamp queries",
passed: true,
detail: if features.contains(wgpu::Features::TIMESTAMP_QUERY) {
"available".into()
} else {
"unavailable (gpu_ms will read 0.0)".into()
},
});
let fabric_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let format = wgpu::TextureFormat::Bgra8UnormSrgb;
let fabric = dreamwell_fabric::DreamFabric::new(device, format, false);
let scene_mode = fabric.scene_dream_mode();
drop(fabric);
scene_mode
}));
let fabric_ok = fabric_result.is_ok();
checks.push(SafetyCheckItem {
name: "DreamFabric creation",
passed: fabric_ok,
detail: if fabric_ok { "OK".into() } else { "FAILED".into() },
});
let render_ok = fabric_result
.as_ref()
.map(|mode| *mode == dreamwell_engine::material::SceneDreamMode::PbrDefault)
.unwrap_or(false);
checks.push(SafetyCheckItem {
name: "Render pipeline (PBR)",
passed: render_ok || fabric_ok,
detail: if render_ok { "PbrDefault active".into() }
else if fabric_ok { "OK (PBR lazy-init)".into() }
else { "FAILED".into() },
});
checks.push(SafetyCheckItem {
name: "Depth format",
passed: true,
detail: "Depth32Float".into(),
});
let warnings = config.validate();
checks.push(SafetyCheckItem {
name: "Config validation",
passed: warnings.is_empty(),
detail: if warnings.is_empty() { "valid".into() } else { warnings.join("; ") },
});
checks.push(SafetyCheckItem {
name: "Uniform alignment",
passed: limits.min_uniform_buffer_offset_alignment <= 256,
detail: format!("{} bytes", limits.min_uniform_buffer_offset_alignment),
});
let indirect_first = features.contains(wgpu::Features::INDIRECT_FIRST_INSTANCE);
checks.push(SafetyCheckItem {
name: "DVR: INDIRECT_FIRST_INSTANCE",
passed: true, detail: if indirect_first { "available (DVR fast path)" } else { "unavailable (TRP fallback)" }.into(),
});
let max_storage = limits.max_storage_buffer_binding_size as u64;
let dreamlet_1m_bytes = 1_000_000u64 * 128;
checks.push(SafetyCheckItem {
name: "DVR: 1M dreamlet capacity",
passed: max_storage >= dreamlet_1m_bytes,
detail: format!("storage max {} MB, need {} MB",
max_storage / (1024 * 1024), dreamlet_1m_bytes / (1024 * 1024)),
});
let multi_draw = features.contains(wgpu::Features::MULTI_DRAW_INDIRECT_COUNT);
checks.push(SafetyCheckItem {
name: "DVR: MULTI_DRAW_INDIRECT_COUNT",
passed: true, detail: if multi_draw { "available (GPU-driven count)" } else { "unavailable (CPU count)" }.into(),
});
let mesh_shader = features.contains(wgpu::Features::EXPERIMENTAL_MESH_SHADER);
checks.push(SafetyCheckItem {
name: "DVR: Mesh shaders",
passed: true, detail: if mesh_shader { "available (task+mesh dispatch)" } else { "unavailable (vertex fallback)" }.into(),
});
let catalog_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let mut catalog = dreamwell_gpu::dreamlet_catalog::DreamletCatalog::default();
catalog.init(device, 1024); let ok = catalog.initialized;
catalog.destroy();
ok
}));
let catalog_ok = catalog_result.unwrap_or(false);
checks.push(SafetyCheckItem {
name: "DVR: DreamletCatalog init",
passed: catalog_ok,
detail: if catalog_ok { "OK (cull + physics pipelines compiled)" } else { "FAILED" }.into(),
});
let merged_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let mesh = dreamwell_gpu::gpu_driven::MergedMeshBuffer::build(device);
let count = mesh.descriptors.len();
mesh.destroy();
count
}));
let merged_ok = merged_result.is_ok();
let mesh_count = merged_result.unwrap_or(0);
checks.push(SafetyCheckItem {
name: "DVR: Merged mesh buffer",
passed: merged_ok,
detail: format!("{} mesh descriptors (9 shapes × 3 LODs)", mesh_count),
});
let ray_query = features.contains(wgpu::Features::EXPERIMENTAL_RAY_QUERY);
checks.push(SafetyCheckItem {
name: "RT: EXPERIMENTAL_RAY_QUERY",
passed: true, detail: if ray_query {
"available (RT GI + RT shadows)"
} else {
"unavailable (screen-space GI fallback)"
}.into(),
});
let max_tlas = limits.max_tlas_instance_count;
checks.push(SafetyCheckItem {
name: "RT: TLAS instances",
passed: true, detail: if ray_query {
format!("max {} instances", max_tlas)
} else {
"N/A (no RT support)".into()
},
});
let max_blas = limits.max_blas_primitive_count;
checks.push(SafetyCheckItem {
name: "RT: BLAS primitives",
passed: true, detail: if ray_query {
format!("max {} primitives", max_blas)
} else {
"N/A (no RT support)".into()
},
});
if ray_query {
let hash_cap = 1_048_576u64;
let rt_vram = hash_cap * 40 + config.width as u64 * config.height as u64 * 9 + 64 * 1024 * 1024; checks.push(SafetyCheckItem {
name: "RT: VRAM estimate",
passed: true,
detail: format!("{} (hash grid + output textures + TLAS)", VramEstimate::format_bytes(rt_vram)),
});
}
let all_passed = checks.iter().all(|c| c.passed);
let vram_estimate = VramEstimate::compute(config);
SafetyCheckResult {
passed: all_passed,
adapter_name: info.name.clone(),
backend: format!("{:?}", info.backend),
checks,
vram_estimate,
}
}