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.
GpuOptions
How a Gpu is opened: what its surfaces must be able to do, decided before the first one exists because some of it is the instance’s.
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).
see_through_by_visual
Whether a see-through window on Windows is presented by D3D12 through a DirectComposition visual — the one way its translucent pixels show what is behind it (backlog F126, RG150). Read from the process’s environment once, so the two things it decides cannot disagree: the runner creates the window with no GDI surface of its own (WS_EX_NOREDIRECTIONBITMAP) only when it is true, and Gpu::new_with under GpuOptions::transparent presents through a visual only when it is true. A window with a GDI surface under a visual composites its translucent pixels over that surface’s black; a window with no surface and a swapchain on its handle draws nothing. When it is false a transparent window is opaque, and Renderer::transparent says so. Only Windows reads it.
see_through_by_visual_with
see_through_by_visual from the values of WGPU_BACKEND and WGPU_DX12_PRESENTATION_SYSTEM (None for unset), each parsed the way wgpu parses it: