Skip to content

Render icons as cached triangle meshes instead of rasterized textures#864

Open
nat3Github wants to merge 2 commits into
david-vanderson:mainfrom
nat3Github:tvg-dvui-render
Open

Render icons as cached triangle meshes instead of rasterized textures#864
nat3Github wants to merge 2 commits into
david-vanderson:mainfrom
nat3Github:tvg-dvui-render

Conversation

@nat3Github
Copy link
Copy Markdown
Collaborator

Note of Caution, most of this PR was vibecoded with Claude

Previously each icon was rasterized by svg2tvg's z2d backend into a CPU pixel buffer, uploaded to the GPU as a Texture, cached, and drawn with renderTexture.

This PR Replaces dvui's icon rendering path, tessellating once into a triangle mesh, cached, and replayed every frame as geometry, recached on size change. no textures, no CPU rasterization.

the triangulization happens in a branch of tvg2svg. Users who want rasterization can still use svg2tvg (which still has the rasterization code using z2d)
https://github.com/nat3Github/zig-lib-svg2tvg/tree/dvui-render, there is a option gated dvui demo in that branch

zig build -Ddemo=true demo

Performance:
CPU rasterization is slow compared to calculating triangles, and rendering triangles on modern gpus is cheap. in my measurements ive seen ~10x speed up (widget, did not measure gpu). when the pixel buffer is cached its faster than the mesh lookup but mesh lookup is still fast in absolute numbers and first paint reduces dramatrically which is the better behavior for immediate mode in my opinion. it should be fast enough to redraw every frame (if someone wants to do crazy animation on tvgs)

What changed

  • renderIcon (src/render.zig): builds an svg2tvg_dvui.MeshBuilder mesh on
    cache miss via appendTvg, anchored at (0,0). Each frame the cached mesh is
    duped into the frame arena, scaled/translated to the target rect, rotated
    (opts.rotation), tinted (opts.colormod), and submitted via renderTriangles.
    The z2d raster path for icons is gone.
  • Window (src/Window.zig): new icon_mesh_cache (keyed by hash(bytes, height,
    IconRenderOptions)) + icon_mesh_frame. Stale entries (untouched last frame)
    evicted in begin(); meshes freed on deinit().
  • dvui.zig: re-exports svg2tvg_dvui and adds IconMeshCacheEntry (both
    useTvg-guarded).
  • build.zig: wires the svg2tvg_dvui module — a fresh instance per dvui
    module, since it imports dvui and can't be shared across module graphs.
  • build.zig.zon: svg2tvg dep bumped to the dvui-render branch, which extends
    RenderOptions with fill_color_override / stroke_color_override /
    stroke_width_override / disable_fill. IconRenderOptions maps 1:1 onto these.

Public icon API should be unchanged — icon, buttonIcon, iconWidth, renderIcon,
IconRenderOptions, IconWidget all keep their signatures.

Replace the icon rendering path. Previously each icon was rasterized by
svg2tvg's z2d backend into a CPU pixel buffer, uploaded as a GPU Texture,
cached, and drawn with renderTexture. Now icons are tessellated once into
a triangle mesh (svg2tvg_dvui.MeshBuilder), cached per Window keyed by
(bytes, height, IconRenderOptions), and replayed each frame by duping the
mesh into the frame arena, scaling/translating/rotating to the target
rect, applying colormod, and submitting via renderTriangles. The result
is resolution-independent and avoids per-icon texture uploads.

IconRenderOptions maps onto svg2tvg's extended RenderOptions:
fill_color/stroke_color/stroke_width become the *_override fields, and a
fully transparent fill_color sets disable_fill.

The public icon API (icon, buttonIcon, iconWidth, renderIcon,
IconRenderOptions, IconWidget) is unchanged.
@david-vanderson
Copy link
Copy Markdown
Owner

Interesting! I took a quick look at the code, haven't done any testing.

Why does svg2tvg need a dvui import now? Makes sense locally for testing, but I don't get why the svg2tvg thing that dvui imports needs a dvui import?

@nat3Github
Copy link
Copy Markdown
Collaborator Author

good point, yes it's only for testing (the demo) behind a lazy gate (-Ddemo=true), not needed for the lib. i'm not sure but i think zon still fetches them currently but does not use them? i don't know if there is a cleaner way to do that without having it out of tree (maybe out of tree is cleaner with local path zon dep)? i'll be back on sunday/monday, will try that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants