dreamwell-matter 1.0.0

DreamMatter benchmark — GPU physics materialization demo and profiler
Documentation
// GPU safety pre-check — validates adapter capabilities, device limits,
// render pipeline readiness, resource budgets, and VRAM cost estimation.

use dreamwell_matter::config::BenchmarkConfig;

/// Result of the GPU safety pre-check.
#[derive(Debug)]
pub struct SafetyCheckResult {
    pub passed: bool,
    pub adapter_name: String,
    pub backend: String,
    pub checks: Vec<SafetyCheckItem>,
    pub vram_estimate: VramEstimate,
}

/// Individual safety check item.
#[derive(Debug)]
pub struct SafetyCheckItem {
    pub name: &'static str,
    pub passed: bool,
    pub detail: String,
}

/// Estimated VRAM usage breakdown for human-readable reporting.
#[derive(Debug, Default)]
pub struct VramEstimate {
    /// HDR framebuffer (Rgba16Float at resolution)
    pub hdr_framebuffer_bytes: u64,
    /// Depth buffer (Depth32Float at resolution)
    pub depth_buffer_bytes: u64,
    /// Bloom mip chain (2 levels at half/quarter resolution)
    pub bloom_mips_bytes: u64,
    /// DreamMatter particle buffer (64 bytes × capacity)
    pub particle_buffer_bytes: u64,
    /// IBL textures (BRDF LUT + irradiance cube + specular cube)
    pub ibl_textures_bytes: u64,
    /// PBR uniform + light buffers
    pub pbr_buffers_bytes: u64,
    /// Scene mesh vertex + index buffers (estimated)
    pub mesh_buffers_bytes: u64,
    /// Dream Lighting: RT GI hash grid + screen-space effect textures
    pub dream_lighting_bytes: u64,
}

impl VramEstimate {
    /// Total estimated VRAM in bytes.
    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
    }

    /// Format bytes as human-readable MB or GB.
    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; // DEFAULT_DREAMLET_CAPACITY

        Self {
            // Rgba16Float = 8 bytes/pixel
            hdr_framebuffer_bytes: w * h * 8,
            // Depth32Float = 4 bytes/pixel
            depth_buffer_bytes: w * h * 4,
            // Bloom: half-res + quarter-res, Rgba16Float
            bloom_mips_bytes: (w / 2) * (h / 2) * 8 + (w / 4) * (h / 4) * 8,
            // Particle buffer: 64 bytes/particle
            particle_buffer_bytes: particle_count * 64,
            // IBL: BRDF LUT 512x512 Rgba16Float + irradiance 64x64x6 + specular 128x128x6 (6 mips)
            ibl_textures_bytes: 512 * 512 * 8 // BRDF LUT
                + 64 * 64 * 6 * 8            // irradiance cube
                + 128 * 128 * 6 * 8          // specular base (mip 0 only for estimate)
                ,
            // PBR: 3 light buffers + per-object uniforms (7 objects × 256 bytes)
            pbr_buffers_bytes: 4 * 32 * 4  // directional: 4 × 32 bytes
                + 64 * 32                   // point: 64 × 32 bytes
                + 32 * 64                   // spot: 32 × 64 bytes
                + 7 * 256                   // per-object uniforms
                ,
            // Mesh buffers: 9 shape types × ~2KB average
            mesh_buffers_bytes: 9 * 2048,
            // Dream Lighting: SSAO (half-res), SSR (full-res), TAA (2× history),
            // Hi-Z mip chain, motion vectors, volumetric fog froxels
            dream_lighting_bytes: (w / 2) * (h / 2) * 8 // SSAO half-res
                + w * h * 8                              // SSR full-res
                + w * h * 8 * 2                          // TAA ping-pong history
                + w * h * 4                              // Hi-Z mip chain (approx)
                + w * h * 4                              // Motion vectors
                + 160 * 90 * 64 * 8                      // Volumetric fog froxels
                ,
        }
    }
}

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!();
    }
}

/// Run all GPU safety pre-checks. Does not panic — all failures captured in result.
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);

    // 1. Texture dimensions
    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),
    });

    // 2. Uniform buffer
    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),
    });

    // 3. Storage buffer
    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),
    });

    // 4. Bind groups
    checks.push(SafetyCheckItem {
        name: "Bind groups",
        passed: limits.max_bind_groups >= 3,
        detail: format!("{} (min 3)", limits.max_bind_groups),
    });

    // 5. Compute workgroup
    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),
    });

    // 6. Color attachments
    checks.push(SafetyCheckItem {
        name: "Color attachments",
        passed: limits.max_color_attachments >= 1,
        detail: format!("{} (min 1)", limits.max_color_attachments),
    });

    // 7. Timestamp queries
    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()
        },
    });

    // 8. DreamFabric creation
    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() },
    });

    // 9. Render pipeline
    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() },
    });

    // 10. Depth format
    checks.push(SafetyCheckItem {
        name: "Depth format",
        passed: true,
        detail: "Depth32Float".into(),
    });

    // 11. Config
    let warnings = config.validate();
    checks.push(SafetyCheckItem {
        name: "Config validation",
        passed: warnings.is_empty(),
        detail: if warnings.is_empty() { "valid".into() } else { warnings.join("; ") },
    });

    // 12. Uniform alignment
    checks.push(SafetyCheckItem {
        name: "Uniform alignment",
        passed: limits.min_uniform_buffer_offset_alignment <= 256,
        detail: format!("{} bytes", limits.min_uniform_buffer_offset_alignment),
    });

    // ── DVR (Dreamwell Render Pipeline) checks ──────────────────────

    // 13. INDIRECT_FIRST_INSTANCE (required for DVR instance_index addressing)
    let indirect_first = features.contains(wgpu::Features::INDIRECT_FIRST_INSTANCE);
    checks.push(SafetyCheckItem {
        name: "DVR: INDIRECT_FIRST_INSTANCE",
        passed: true, // non-blocking — fallback available
        detail: if indirect_first { "available (DVR fast path)" } else { "unavailable (TRP fallback)" }.into(),
    });

    // 14. Storage buffer size for 1M dreamlets (128 MB)
    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)),
    });

    // 15. Multi-draw indirect support
    let multi_draw = features.contains(wgpu::Features::MULTI_DRAW_INDIRECT_COUNT);
    checks.push(SafetyCheckItem {
        name: "DVR: MULTI_DRAW_INDIRECT_COUNT",
        passed: true, // non-blocking — plain multi_draw_indexed_indirect always available
        detail: if multi_draw { "available (GPU-driven count)" } else { "unavailable (CPU count)" }.into(),
    });

    // 16. Mesh shader support
    let mesh_shader = features.contains(wgpu::Features::EXPERIMENTAL_MESH_SHADER);
    checks.push(SafetyCheckItem {
        name: "DVR: Mesh shaders",
        passed: true, // non-blocking — vertex fallback available
        detail: if mesh_shader { "available (task+mesh dispatch)" } else { "unavailable (vertex fallback)" }.into(),
    });

    // 17. DreamletCatalog creation
    let catalog_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
        let mut catalog = dreamwell_gpu::dreamlet_catalog::DreamletCatalog::default();
        catalog.init(device, 1024); // test with 1K dreamlets
        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(),
    });

    // 18. Merged mesh buffer
    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),
    });

    // ── RT (Ray Tracing) checks ────────────────────────────────────

    // 19. Ray query support
    let ray_query = features.contains(wgpu::Features::EXPERIMENTAL_RAY_QUERY);
    checks.push(SafetyCheckItem {
        name: "RT: EXPERIMENTAL_RAY_QUERY",
        passed: true, // non-blocking — fallback to raster
        detail: if ray_query {
            "available (RT GI + RT shadows)"
        } else {
            "unavailable (screen-space GI fallback)"
        }.into(),
    });

    // 20. TLAS instance capacity
    let max_tlas = limits.max_tlas_instance_count;
    checks.push(SafetyCheckItem {
        name: "RT: TLAS instances",
        passed: true, // non-blocking
        detail: if ray_query {
            format!("max {} instances", max_tlas)
        } else {
            "N/A (no RT support)".into()
        },
    });

    // 21. BLAS primitive capacity
    let max_blas = limits.max_blas_primitive_count;
    checks.push(SafetyCheckItem {
        name: "RT: BLAS primitives",
        passed: true, // non-blocking
        detail: if ray_query {
            format!("max {} primitives", max_blas)
        } else {
            "N/A (no RT support)".into()
        },
    });

    // 22. RT VRAM estimate
    if ray_query {
        let hash_cap = 1_048_576u64;
        let rt_vram = hash_cap * 40 // hash grid (8 + 16 + 16 bytes/entry)
            + config.width as u64 * config.height as u64 * 9 // gi_output + shadow_mask
            + 64 * 1024 * 1024; // TLAS scratch
        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,
    }
}