Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/facett

facett-graph3d/src/logo3d/gpu.rs

//! GPU render path for the 3D znippy rabbit logo — a small, **depth-tested**,
//! lit `egui_wgpu` paint callback. Mirrors the structure of facett-map's GPU
//! kernel (a `*Renderer` stored in `callback_resources`, installed once via
//! [`install_renderer`], driven by a [`CallbackTrait`](egui_wgpu::CallbackTrait)),
//! but draws an indexed triangle mesh with a real Z-buffer and Lambert shading
//! instead of culled line ways.
//!
//! Upload-once: the rabbit mesh is uploaded to VRAM on the first paint; per-frame
//! only the rotation uniform (yaw/tilt) + accent colour change.
//!
//! CONS-CORE Phase C (step 10): the logo now renders into the **shared
//! [`OffscreenColorDepth`](facett_core::render::gpu::OffscreenColorDepth)** colour +
//! depth32 target (a real GPU Z-buffer — `CompareFunction::Less`) and **blits** that
//! into egui's colour-only pass, exactly like `facett-map3d`. This is the parity
//! fold: before, the logo drew straight into egui's pass and relied on back-face
//! culling + CCW winding for occlusion (egui supplies no depth attachment); now it
//! shares map3d's offscreen+depth+blit helper, so a concave/self-occluding mesh gets
//! true per-pixel occlusion. The CPU painter remains the headless / no-GPU fallback
//! (the snapshot golden path), unchanged.

#![allow(clippy::too_many_lines, reason = "one self-contained lit pipeline")]

use egui_wgpu::wgpu;
use facett_core::rabbit::RabbitMesh;
use facett_core::render::gpu::offscreen::{OffscreenColorDepth, DEPTH_FORMAT, OFFSCREEN_FORMAT};

/// One mesh vertex: position + normal. Matches `VsIn` in `logo.wgsl`.
#[repr(C)]
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
pub struct Vertex {
    pub pos: [f32; 3],
    pub nrm: [f32; 3],
}

/// Per-frame uniform: yaw/tilt rotation + accent colour. 32 bytes.
#[repr(C)]
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
struct Uniforms {
    yaw: f32,
    tilt: f32,
    scale: f32,
    _pad0: f32,
    accent: [f32; 3],
    _pad1: f32,
}

/// CPU-side mesh payload uploaded once.
pub struct GpuGeometry {
    pub vertices: Vec<Vertex>,
    pub indices: Vec<u32>,
}

/// Build the upload payload from the extruded [`RabbitMesh`].
pub fn build_gpu_geometry(mesh: &RabbitMesh) -> GpuGeometry {
    let vertices = mesh
        .positions
        .iter()
        .zip(mesh.normals.iter())
        .map(|(&pos, &nrm)| Vertex { pos, nrm })
        .collect();
    GpuGeometry { vertices, indices: mesh.indices.clone() }
}

/// Persistent GPU resources for the logo.
pub struct LogoRenderer {
    pipeline: wgpu::RenderPipeline,
    uniform: wgpu::Buffer,
    bind: wgpu::BindGroup,
    vbuf: Option<wgpu::Buffer>,
    ibuf: Option<wgpu::Buffer>,
    index_count: u32,
    /// Shared offscreen colour+depth target + blit (CONS-CORE L0; the parity fold
    /// with facett-map3d). The mesh draws into this with a real depth test, then it
    /// blits into egui's pass.
    offscreen: OffscreenColorDepth,
}

impl LogoRenderer {
    pub fn new(device: &wgpu::Device, target_format: wgpu::TextureFormat) -> Self {
        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
            label: Some("facett_logo3d"),
            source: wgpu::ShaderSource::Wgsl(include_str!("logo.wgsl").into()),
        });
        let uniform = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("logo_uniforms"),
            size: std::mem::size_of::<Uniforms>() as u64,
            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });
        let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
            label: Some("logo_bgl"),
            entries: &[wgpu::BindGroupLayoutEntry {
                binding: 0,
                visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
                ty: wgpu::BindingType::Buffer {
                    ty: wgpu::BufferBindingType::Uniform,
                    has_dynamic_offset: false,
                    min_binding_size: None,
                },
                count: None,
            }],
        });
        let bind = device.create_bind_group(&wgpu::BindGroupDescriptor {
            label: Some("logo_bind"),
            layout: &bgl,
            entries: &[wgpu::BindGroupEntry { binding: 0, resource: uniform.as_entire_binding() }],
        });
        let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
            label: Some("logo_pl"),
            bind_group_layouts: &[Some(&bgl)],
            immediate_size: 0,
        });
        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
            label: Some("logo_pipeline"),
            layout: Some(&layout),
            vertex: wgpu::VertexState {
                module: &shader,
                entry_point: Some("vs"),
                buffers: &[Some(wgpu::VertexBufferLayout {
                    array_stride: std::mem::size_of::<Vertex>() as u64,
                    step_mode: wgpu::VertexStepMode::Vertex,
                    attributes: &wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3],
                })],
                compilation_options: Default::default(),
            },
            primitive: wgpu::PrimitiveState {
                topology: wgpu::PrimitiveTopology::TriangleList,
                cull_mode: Some(wgpu::Face::Back),
                front_face: wgpu::FrontFace::Ccw,
                ..Default::default()
            },
            // CONS-CORE Phase C: the logo now draws into the shared offscreen
            // colour+depth32 target with a real GPU Z-buffer (`Less`), so concave
            // self-occluding geometry occludes per-pixel (the parity fold with
            // map3d). The CPU painter (headless snapshot) still does the full
            // painter's-algorithm sort, unchanged.
            depth_stencil: Some(wgpu::DepthStencilState {
                format: DEPTH_FORMAT,
                depth_write_enabled: Some(true),
                depth_compare: Some(wgpu::CompareFunction::Less),
                stencil: wgpu::StencilState::default(),
                bias: wgpu::DepthBiasState::default(),
            }),
            multisample: facett_core::render::gpu::msaa_state(),
            fragment: Some(wgpu::FragmentState {
                module: &shader,
                entry_point: Some("fs"),
                // Render into the offscreen colour target (then blit to egui).
                targets: &[Some(wgpu::ColorTargetState {
                    format: OFFSCREEN_FORMAT,
                    blend: Some(wgpu::BlendState::ALPHA_BLENDING),
                    write_mask: wgpu::ColorWrites::ALL,
                })],
                compilation_options: Default::default(),
            }),
            multiview_mask: None,
            cache: None,
        });
        let offscreen = OffscreenColorDepth::new(device, target_format);
        Self { pipeline, uniform, bind, vbuf: None, ibuf: None, index_count: 0, offscreen }
    }

    fn upload(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, geo: &GpuGeometry) {
        use wgpu::util::DeviceExt;
        // Idempotent: keep the first uploaded mesh. The host sends geometry every
        // frame (so whichever frame's callback prepare runs first does the upload —
        // robust under egui_kittest's run_steps); this guard makes the later frames'
        // geometry a no-op. Matches facett-map3d's upload contract.
        if geo.vertices.is_empty() || self.vbuf.is_some() {
            return;
        }
        let vbuf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
            label: Some("logo_vbuf"),
            contents: bytemuck::cast_slice(&geo.vertices),
            usage: wgpu::BufferUsages::VERTEX,
        });
        let ibuf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
            label: Some("logo_ibuf"),
            contents: bytemuck::cast_slice(&geo.indices),
            usage: wgpu::BufferUsages::INDEX,
        });
        let _ = queue;
        self.vbuf = Some(vbuf);
        self.ibuf = Some(ibuf);
        self.index_count = geo.indices.len() as u32;
    }
}

/// The paint callback for one logo frame.
pub struct LogoPaintCallback {
    /// `Some` on the first frame to upload the mesh; `None` afterwards.
    pub geometry: Option<Box<GpuGeometry>>,
    pub yaw: f32,
    pub tilt: f32,
    /// Accent colour `[r, g, b]` (0..255).
    pub accent: [u8; 3],
    /// Pane size in physical px (the offscreen target size). When `0`, the prepare
    /// step falls back to the egui screen-descriptor size.
    pub px_w: u32,
    pub px_h: u32,
}

impl egui_wgpu::CallbackTrait for LogoPaintCallback {
    fn prepare(
        &self,
        device: &wgpu::Device,
        queue: &wgpu::Queue,
        sd: &egui_wgpu::ScreenDescriptor,
        encoder: &mut wgpu::CommandEncoder,
        resources: &mut egui_wgpu::CallbackResources,
    ) -> Vec<wgpu::CommandBuffer> {
        let Some(renderer) = resources.get_mut::<LogoRenderer>() else {
            return vec![];
        };
        if let Some(geo) = &self.geometry {
            renderer.upload(device, queue, geo);
        }
        // Clip space is [-1,1]; a constant scale keeps the [-1,1] design-box logo
        // nicely framed in the pane.
        let scale = 0.7;
        let accent = [self.accent[0] as f32 / 255.0, self.accent[1] as f32 / 255.0, self.accent[2] as f32 / 255.0];
        queue.write_buffer(
            &renderer.uniform,
            0,
            bytemuck::bytes_of(&Uniforms { yaw: self.yaw, tilt: self.tilt, scale, _pad0: 0.0, accent, _pad1: 0.0 }),
        );

        // Ensure the shared offscreen colour+depth target for this pane.
        let (w, h) = if self.px_w > 0 && self.px_h > 0 {
            (self.px_w, self.px_h)
        } else {
            (sd.size_in_pixels[0], sd.size_in_pixels[1])
        };
        renderer.offscreen.ensure(device, w, h);

        let (Some(vbuf), Some(ibuf), Some(color_view), Some(depth_view)) = (
            &renderer.vbuf,
            &renderer.ibuf,
            renderer.offscreen.color_view(),
            renderer.offscreen.depth_view(),
        ) else {
            return vec![];
        };
        if renderer.index_count == 0 {
            return vec![];
        }

        // ── the offscreen depth-tested pass: real per-pixel Z-buffer occlusion ──
        let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
            label: Some("logo3d_offscreen_pass"),
            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
                view: color_view,
                resolve_target: None,
                depth_slice: None,
                ops: wgpu::Operations {
                    // Transparent clear so the blit composites the logo over egui's
                    // existing pane background (alpha-blended), matching the old
                    // straight-into-egui behaviour where the pane bg showed through.
                    load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
                    store: wgpu::StoreOp::Store,
                },
            })],
            depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
                view: depth_view,
                depth_ops: Some(wgpu::Operations { load: wgpu::LoadOp::Clear(1.0), store: wgpu::StoreOp::Store }),
                stencil_ops: None,
            }),
            timestamp_writes: None,
            occlusion_query_set: None,
            multiview_mask: None,
        });
        pass.set_pipeline(&renderer.pipeline);
        pass.set_bind_group(0, &renderer.bind, &[]);
        pass.set_vertex_buffer(0, vbuf.slice(..));
        pass.set_index_buffer(ibuf.slice(..), wgpu::IndexFormat::Uint32);
        pass.draw_indexed(0..renderer.index_count, 0, 0..1);
        drop(pass);
        vec![]
    }

    fn paint(
        &self,
        info: egui::PaintCallbackInfo,
        render_pass: &mut wgpu::RenderPass<'static>,
        resources: &egui_wgpu::CallbackResources,
    ) {
        let Some(renderer) = resources.get::<LogoRenderer>() else { return };
        if renderer.index_count == 0 {
            return;
        }
        // Blit the depth-resolved offscreen colour into egui's pass, scissored to the
        // widget rect (the shared L0 helper — the same path map3d uses).
        let _scissor = renderer.offscreen.blit(&info, render_pass);
    }
}

/// Install a [`LogoRenderer`] into an egui-wgpu `RenderState`'s callback
/// resources (call once at host startup, like facett-map's `install_renderer`).
pub fn install_renderer(render_state: &egui_wgpu::RenderState) {
    // The shared install-once writer (LAW #5) — it also registers this lane with
    // `facett_core::render::lane`, so an About box can report that a GPU lane exists.
    let _ = facett_core::render::gpu::install_renderer(render_state, LogoRenderer::new);
}