Knots - A library for building GUIs with Zig

Hi! In this post I briefly mentioned an immediate-mode GUI library I was working on, that is being used to build a watch-party MPV client. I wanted to make a more detailed post sharing more about the GUI library knots which i find myself spending more and more time on. knots is an immediate mode GUI rendering library/framework (I don’t like the idea of a framework but I think it makes it more clear that it handles more than just putting stuff on the screen).

You might already be familiar with projects like dvui (awesome project, you should check it out), and while there is definitely a large overlap in terms of features I would say the main differentiator between knots and dvui are the goals. dvui is a lot more “general-purpose”, from their README: “GUI toolkit for whole applications or debugging windows in existing apps/games.”. The current scope of knots is a lot slimmer/focused, I am mainly interested in providing a way for people to build really performant cross-platform standalone desktop applications, where “UI code” is essentially out of the way entirely, so you can spend more time on solving “real” problems. I.e, if you are looking for a library that lets you easily create a debug UI for your game engine, or really render anything on top of an existing application/surface, knots is not for you, and will likely never be. It goes without saying that this library is in very early stages, (I just recently decided to stop force pushing on to the initial commit, as I was making massive overhauls to parts of the library every other day). Particularly, you will find many basic widgets/components to not be implemented yet, as I am still putting most of my focus on trimming down the main core of the library.

Below are just some questions and answers I think most of you might have:

  • What platforms are supported?
    • Windows, macOS, Linux, and web via emscripten + webgpu
  • Any examples?
    • Like I mentioned before, this mpv client shaven is being built with knots. You can also clone the knots repo and run some of the examples, or test the playground app on the web Playground!
  • What are you currently working on?
    • I am trying to get rid of all my c/c++ dependencies, to trim down compile times and binary sizes. I used to depend on GLFW for windowing but have now dropped it in favor of platform specific implementations which have been implemented for windows, macOS (partially), and emscripten/web. Linux builds unfortunately still depend on GLFW. As of this PR, I deleted harfbuzz as a dependency as I was not using any of its features anyways, and it was pulling all kinds of garbage like libc++, this open PR will drop freetype as a dependency in favor of https://codeberg.org/andrewrk/TrueType (built by Andrew Kelley!) to remove the final C dependency. After that, I will likely spend some time improving how text is handled in the layout module, to support text wrapping and other niceties.
  • Cross platform GUI rendering? How?
    • This is particularly important to get right and I took some risks here which might have repercussions in the future. I bet essentially on two APIs, WebGPU and Vulkan. Why these two? Well I always wanted to have the door open for supporting web, and to do that I would need to either support WebGL or WebGPU, afaik WebGL is planned to be superseded by WebGPU, and while WebGPU will potentially not work on some browsers, I am fine with that knowing they will eventually support it. I also get the added benefit of WebGPU acting as a “catch-all” GPU API which can be used when targeting native builds as well. This means you can build knots with only the WebGPU backend and be able to target Windows, macOS, Linux and Web! So why also support Vulkan? I wanted to leave the door open for opting in to even better performance for native builds, and Vulkan seemed to be the best choice here for performant cross-platform graphics. I honestly can’t see myself maintaining more than 2 GPU backends, so it is likely this will not change any time soon. Also, the WebGPU backend for native builds depends on wgpu-native being statically linked to the binary, which bloats binary sizes quite a bit (however, ensure it will run on a lot more machines).

Okay, I rambled quite a bit now and need to get back to work, please let me know if you have any more questions/concerns. This is not really an ad, as I still think the library is far from being useable for production apps. I am just more interested in what people think, am I wasting my time, or is it something you would be interested in?

24 Likes

Just wanted to post some updates here for people interested:

  • Playground web build is now hosted on codeberg pages, here: Playground
    • Web wasm build is down to 1.93 MB (900KB compressed), with about 400KB of the uncompressed data being just the font ttf. I want to get this even lower, potentially by dropping emcc as a build dependency, but will require some work to support wasm-freestanding as a target currently.
  • Some new components added
    • Color picker
    • Checkbox
    • Multi-line text input
    • Virtual list
    • General improvements to all existing components
  • The theme system got some upgrades, the theme can now be defined as a .zon file, an also changed at runtime. Themes are checked at compile theme for errors (invalid hex, rgba e.t.c). You can view the existing ones that come with knots here https://codeberg.org/shahwali/knots/src/branch/main/src/ui/themes
  • The vulkan backend vertex shaders are now written in Zig, and compiled using Zig, I wrote about it here: Writing Compute Shaders In/With Zig - #13 by shahwali
  • Text word wrapping added.
  • Grid layout support added.
  • The renderer settings/dev tools got an upgrade, now with some more stats, and even a sparklines graph!

Still so much to do…, below is a screenshot of as much I could possibly pack in one frame on the playground app. Would like to write some docs when I have time, mostly to free up some space in my head but also if anyone else is willing to try knots out, at your own risk of course.

20 Likes

Very cool! I managed to get a basic app working (adjusted from the playground example) really quickly. There was a few updates I needed to make to get Knots working on latest Zig main (patch submitted btw) but it wasn’t much. Apologies for the gif compression:

edit: the gif breaks if I try to embed bc it’s too big
https://i.imgur.com/zlvw18f.gif

4 Likes

Another update as it has been nearly 2 months since the last one now. Quite a lot has changed so below are some of the highlights, in no particular order.

glslc is no longer a required system dependency to use the Vulkan backend :tada:.

You might be wondering how, and the TDLR is that the Zig compiler is now powerful enough that I can write my fragment and vertex shaders in Zig, and compiling them at build time using the Zig build system, and embedding the SPIRV as an anonymous import. Huge props to the Zig compiler team and any contributors that are working on the SPIRV backend. Easily one of the coolest usages of Zig compiler and build system in this project right now! Vulkan required version has also been bumped to 1.3 from 1.2 to support dynamic rendering, which allowed me to simplify the backend.

emscripten/emcc is no longer a required system dependency to compile a knots app for the web :tada:

In other words wasm<32|64>-freestanding is now a supported target, and wasmxx-emscripten support has been dropped. Unfortunately this was not solved quite as elegantly, and essentially required an unwanted amount of JS glue, and build time logic I had to delegate downstream to the user. The upsides are much smaller wasm binaries, simplified compilation machinery (no need to call emcc and research its flags e.t.c), and reduced compile times. Downsides are that I have taken on yet another compilation target I will have to test and support going forward.

Snapshot tests

.qoi files generated from a simple test runner are checked in to the repository such that I can easily detect regressions in the renderer. This is all currently being done manually however as I am too lazy to set up a CI pipeline for this.

GPU backend is no longer configurable at runtime

This was a cool feature, I however do not think it really has any real practical use-case besides easily debugging both backends in one instance. To support this I had to have wrap my backends behind a vtable, which is just an unnecessary performance hit, as well as just making the code harder to debug. GPU backends are now behind a comptime interface, so the GPU backend must be known at compile time.

Features

This is quite a long list and I won’t bother going in to every one so I’ll just list them as bullet points below. You can try all of these in the web playground.

  • Support for opening multiple native windows.
  • Floating in-app window panels as a component.
  • Tooltip component.
  • Context menu component.
  • Dialog component.
  • Menu button component.
  • Radio input component.
  • Basic input key
  • General bug fixes, performance improvements and housekeeping.

Going forward

I think I am going to spend some time building out the playground app, I want to find a way to showcase the true potential of knots, any suggestions would be appreciated. I also want to spend some time on improving documentation across the board, to make it easy for anyone to pick up the library and start building their next GUI application.

Side note

I am maintaining what I believe is the only wgpu-native bindings for Zig which are actually up to date, the reason I mention that is because the wgpu website and docs seem to point to a repository that has not had a commit in almost a year now wgpu_native_zig. With that being said, if you want to get in to GPU programming with Zig, and you do not want to dive head first in to the monstrosity that is Vulkan, I would recommend trying these bindings wgpu-zig and following Learn WebGPU for C++ documentation (it is in C++, but it is trivially translatable to Zig). You can probably also follow the rust guide Introduction | Learn Wgpu although I’m not sure how easy it is to translate to Zig. The bindings support Zig 0.16 and newer. Please open an issue or a PR if you find some part of the API I have not covered yet.

Now to enjoy the sun while it is still here :sun:

5 Likes