Skip to main content

Crate denise_wgpu

Crate denise_wgpu 

Source
Expand description

A GPU painter for Denise, on wgpu.

The second implementation of Painter, and the reason the trait exists: denise-render turns the same calls into pixels on a CPU, this crate turns them into instanced triangles on whatever wgpu can find. Widgets cannot tell which one they are drawing through, and that is the whole point.

This is for the desktop. A kiosk on a Pi draws with the software rasteriser and always will: it needs no Mesa, no compositor and no window system, and at the sizes a panel runs it was never the bottleneck. A GPU earns its keep where the designer runs — a Retina display, a large window, a canvas that zooms — and that is the workload this crate is shaped for.

§How a frame is drawn

Every call on the painter appends vertices. Rectangles are plain triangles; everything with a curve — rounded corners, circles, arcs, lines — is a bounding quad whose fragment shader evaluates a signed distance and turns it into one pixel of anti-aliasing. A polygon has more edges than a vertex can carry, so its quad carries a range of a buffer that holds them and its fragment shader walks that range for the nearest edge and for which side of the outline it is on. Images and glyph masks are the same quads with a texture bound. The clip is carried per vertex and applied per fragment, so a frame is one pipeline and as few draws as the textures force: a whole widget tree with no images is a single draw call.

GpuPainter::finish then encodes the frame into a texture view — a swapchain’s, or an offscreen one — clearing it first. GpuPainter::finish_onto is the incremental form: it keeps what is already on the target and scissors to the damage, for a caller that owns its target between frames. GpuPainter::finish_to_pixels renders offscreen and reads the result back as 0xAARRGGBB words, which is how the parity tests compare it to the software rasteriser.

use denise::{BufferAge, Pen, Size};
use denise_wgpu::Gpu;

let gpu = Gpu::headless()?;
let mut painter = gpu.painter(Size::new(640, 400));
ui.paint_with(&mut Pen::new(&mut painter), BufferAge::Undefined);
let pixels: Vec<u32> = painter.finish_to_pixels()?;

§Glyphs

Text arrives through blit_glyph as a rectangle of an atlas page with an id and a version. The page is uploaded once per version — that is, whenever the text engine packs a glyph it has not seen — and every glyph after that is six vertices sampling it. A label costs what a rectangle costs.

§Images

Pictures arrive through blit_image with an id and a version, the same way a glyph page does, and are cached the same way: one upload when the pixels change, a quad every time after. A photo in a carousel costs what a rectangle costs.

§What it does not do yet

The raw blit family — a PixelView with no identity — still uploads per call. Nothing in the widget set uses it any more; it is there for a caller with pixels that genuinely are different every frame, which is what an upload per call is the honest price of.

Re-exports§

pub use wgpu;

Structs§

Gpu
A device, a queue, and the one pipeline every frame is drawn with.
GpuPainter
One frame’s worth of drawing, recorded and then encoded by GpuPainter::finish.

Enums§

Error
What can go wrong between asking for a GPU and reading pixels back.