Marey

13. The GPU, a window and the browser

Every backend so far turns a scene into a document or a file. Three more turn it into pixels on a screen: Marey.Backend.Gpu draws on the GPU, offscreen or in a window, and Marey.Backend.Web draws on a canvas in a browser page, where the figure is compiled too. Nothing above the scene differs between them.

On the GPU

GpuRenderer draws with the Skia backend's code on an OpenGL 3.3 context, so text, gradients and clips come out as they do in a PNG, except that a marker batch is one instanced draw, each mark's shape computed in a shader. A million points take milliseconds. GpuBackend.probe () names the driver a renderer would get, or says why there is none; on Linux without a GPU, xvfb-run and Mesa's software driver provide one.

#r "nuget: Marey.Backend.Gpu"

let cloudCount = 1_000_000
let cloudNumbers, _ = Rng.units (2 * cloudCount) (Rng.seeded 11UL)
let cloudX = Array.init cloudCount (fun i -> cloudNumbers.[2 * i] * 1.0<s>)
let cloudY = Array.init cloudCount (fun i -> (cloudNumbers.[2 * i + 1] - 0.5) * 1.0<m>)
let cloud = Chart.scatterIn U.second U.metre cloudX cloudY

match GpuBackend.probe () with
| Ok driver ->
    use gpu = new GpuRenderer()
    let extent = Chart.size 800.0 500.0
    let png = cloud |> Chart.toSceneWith gpu.Measurer extent |> gpu.RenderPng extent
    printfn "a million points on %s: %d bytes of PNG" driver png.Length
| Error why -> printfn "no OpenGL 3.3 context here: %s" why

One renderer owns one thread, one context and one text measurer, and any thread may call it. Keep one for many renders. GpuSim.stam and GpuSim.lbm step ยง8's fluids on it, over the same states, and agree with the CPU's steps to a few parts in a million.

In a window

Chart.show fig opens a figure in a window and returns when it is closed; Live.show does the same for a live figure, with its controls in a strip above it. Drag to zoom, shift-drag to pan, double-click to reset, hover to read, Tab and the arrows for the controls, Escape to close. The window's pointer and keys become the messages a notebook page sends for the same gestures, stepped by Live.step, so a session in a window reaches the states a notebook session would, and a recording made in one replays in the other.

Live.show explorer

GpuWindow.Open returns at once and drives the same window from code. A window runs on a thread of its own, which Linux and Windows allow and macOS does not.

In a browser

Marey.Backend.Web is for Fable, which compiles F# to JavaScript. A Fable app references Marey and Marey.Backend.Web from NuGet, builds a figure with the code of this guide, and draws it on a canvas:

let ctx = WebCanvas.context (WebCanvas.byId "plot")
let measured = WebText.measurerNow ()
let scene = Chart.toSceneWith measured.Measurer extent figure
CanvasBackend.draw measured.Measurer ctx extent scene

The compiler is the same F#, so the browser lays a figure out as a kernel does: the test suite's 40 SVG baselines are reproduced byte for byte under Node. The canvas measures text in the font it draws with, and WebText.measurerNow () says whether the page's web fonts have arrived. A batch of 2000 marks or more is drawn by WebGL2, composited in place. Marey.Sim compiles under Fable too, so a simulation runs in the page from the same source; a chaotic one diverges from .NET's after some seconds, because JavaScript's sin and exp differ in the last bit. examples/web/ is a page with two sliders and a cloud of up to a million points.