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
Renderer: one window’s swapchain, pipelines and atlas texture.Renderer::render: a display list in, a presented frame (or aRenderError) out.Renderer::resize: reconfigure after the window changed size.Renderer::subpixel_text: what to pass toCore::set_subpixel_text.Gpu: the device, queue and adapter that windows share;Renderer::new_inopens a second window on it.RenderError: what each failed frame asks the runner to do next.DEFAULT_FRAME_LATENCYandRenderer::set_frame_latency: how many frames may queue ahead of the one on screen.wgpuis re-exported, so a runner builds against the same version this crate was.
§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
Gpuis opened: what its surfaces must be able to do, decided before the first one exists because some of it is the instance’s. - Render
Report - Timing details from one
Renderer::rendercall. - Renderer
- One window’s renderer: its surface, the pipelines and a GPU copy of the core’s glyph atlas.
Enums§
- Render
Error - 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, andGpu::new_withunderGpuOptions::transparentpresents 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, andRenderer::transparentsays so. Only Windows reads it. - see_
through_ by_ visual_ with see_through_by_visualfrom the values ofWGPU_BACKENDandWGPU_DX12_PRESENTATION_SYSTEM(Nonefor unset), each parsed the way wgpu parses it: