Skip to main content

Crate kui_wgpu

Crate kui_wgpu 

Source
Expand description

wgpu renderer for kui: draws a kui_core::DisplayList with one instanced pipeline, a single draw call per frame.

kui splits a UI into a model that lays out and paints into a display list (kui-core), a renderer that puts that list on screen (this crate) and a runner that owns the window and the event loop (kui-native, the one most apps use). Reach for kui-wgpu directly when you are writing your own runner: you already have a window, or an event loop, that kui-native does not fit.

A Renderer owns the swapchain of one window and a GPU copy of the core’s glyph atlas, kept in step with the atlas it is handed each frame. Rounded rectangles, borders, shadows, glyphs and images are all instances of the same quad, so an ordinary frame is one draw call; only a texture-backed image or a custom fragment shader splits it. Several windows share one device through Gpu.

§Example

A runner’s whole life with the renderer: open it on a window, tell the core whether subpixel text will render, then build, draw and present one frame at a time. window is anything wgpu can make a surface from, such as a winit window.

use kui_core::{Core, Size, TextStyle};
use kui_wgpu::{RenderError, Renderer};

fn run(
    window: impl Into<kui_wgpu::wgpu::SurfaceTarget<'static>>,
) -> Result<(), Box<dyn std::error::Error>> {
    let (width, height) = (800u32, 600u32);
    let mut renderer = pollster::block_on(Renderer::new(window, width, height))?;
    let mut core = Core::new();
    core.set_subpixel_text(renderer.subpixel_text());

    loop {
        // When the windowing library reports a new size:
        // renderer.resize(new_width, new_height);

        // Build the frame through the core, in logical pixels.
        let scale = 1.0;
        let viewport = Size::new(width as f32 / scale, height as f32 / scale);
        let mut ui = core.frame(viewport, scale);
        ui.text("Hello from a custom runner", TextStyle::new(24.0));
        ui.finish();

        // Draw it. The atlas is `&mut` so the renderer can clear its dirty flag.
        let (list, atlas) = core.output();
        match renderer.render(list, atlas) {
            Ok(report) => {
                let _blocked_on_vsync_ms = report.vsync_wait_ms;
            }
            Err(RenderError::Reconfigure | RenderError::Validation) => {
                renderer.resize(width, height);
            }
            Err(RenderError::Skip) => {}
            Err(RenderError::DeviceLost) => {
                // Open a new `Renderer` (and a new device) and carry on.
                break;
            }
        }
    }
    Ok(())
}

§Where to look

§Subpixel text

Where the device offers dual-source blending (Metal, DX12, most Vulkan) the pipeline blends per channel, which is what LCD subpixel glyphs need. Elsewhere it falls back to ordinary alpha blending and the core should rasterize grayscale masks instead, which is what Renderer::subpixel_text tells it.

The book: https://kui-book.qxuken.dev. Repository: https://github.com/qxuken/kui.

Re-exports§

pub use wgpu;

Structs§

Gpu
The GPU objects an app’s windows share: one instance, adapter, device and queue.
RenderReport
Timing details from one Renderer::render call.
Renderer
One window’s renderer: its surface, the pipelines and a GPU copy of the core’s glyph atlas.

Enums§

RenderError
A frame that produced no image, and what to do about it.

Constants§

DEFAULT_FRAME_LATENCY
How many frames may be queued ahead of the one on screen by default: two, or one on Windows.

Functions§

report_faults
Prints the code and module of a crash to stderr before the process dies (Windows only).