<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>bycruz</title><description>Posts from the bycruz website</description><link>https://bycruz.dev/</link><language>en-us</language><item><title>🖌️ Painting from scratch: Arisu</title><link>https://bycruz.dev/posts/arisu/</link><guid isPermaLink="true">https://bycruz.dev/posts/arisu/</guid><description>How I wrote a paint program entirely from scratch in LuaJIT (basically C)</description><pubDate>Sat, 06 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;arisu&quot;&gt;Arisu&lt;/h2&gt;
&lt;p&gt;Arisu&lt;sup&gt;&lt;a href=&quot;#user-content-fn-arisu&quot; id=&quot;user-content-fnref-arisu&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; is a project I created for Professor Fahim Khan’s graduate CSC 572 class for Fall 2025 at Cal Poly.&lt;br&gt;
The assignment was to implement a novel somewhat state-of-the-art paper into a program.&lt;/p&gt;
&lt;p&gt;I encountered the Ciallo&lt;sup&gt;&lt;a href=&quot;#user-content-fn-ciallo&quot; id=&quot;user-content-fnref-ciallo&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; &lt;a href=&quot;https://doi.org/10.1145/3641519.3657418&quot;&gt;paper&lt;/a&gt; and &lt;a href=&quot;https://github.com/ShenCiao/Ciallo&quot;&gt;implementation&lt;/a&gt; and liked the idea of a paint program powered entirely by compute shaders.&lt;/p&gt;
&lt;p&gt;I also did some research and discovered that most paint programs rarely if all at all use the GPU, which solidified my decision to make this project.&lt;/p&gt;
&lt;h2 id=&quot;what-is-it&quot;&gt;What is it?&lt;/h2&gt;
&lt;p&gt;Arisu is a paint program. Think MS Paint. Just something to open up and appreciate the simple style of before we moved to boring basic WinUI, Qt, GTK, Electron apps.&lt;/p&gt;
&lt;p&gt;I mention all of those GUI toolkits because I made the decision to implement this with one major rule in mind. &lt;strong&gt;ZERO DEPENDENCIES&lt;/strong&gt;. I go in depth about this in the ‘How it works’ section.&lt;/p&gt;
&lt;h2 id=&quot;show-dont-tell&quot;&gt;Show, don’t tell&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/bycruz/arisu&quot;&gt;&lt;img src=&quot;/posts/arisu/v0.4.0.avif&quot; alt=&quot;v4&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This is a screenshot of Arisu 0.4.0, running on Windows 11. Pretty much every feature you see there that isn’t grayed out is implemented and usable.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Basic brush, pencil, eraser are implemented.&lt;/li&gt;
&lt;li&gt;Color picker&lt;/li&gt;
&lt;li&gt;Selection box&lt;/li&gt;
&lt;li&gt;Basic shape tools (rectangle, ellipse, line)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/bycruz/arisu&quot;&gt;&lt;img src=&quot;/posts/arisu/v0.1.0.avif&quot; alt=&quot;v1&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you’re curious, here’s a screenshot of 0.1.0 on Linux, although it’s not too different.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;how-can-i-get-it&quot;&gt;How can I get it?&lt;/h2&gt;
&lt;p&gt;Since it’s written in LuaJIT, it won’t natively compile to a binary, but I do have plans to distribute it as a binary later on with my own solution which will statically link LuaJIT for you. It’s pretty promising, only ~2MB, of which ~1MB is Rust’s dependencies, ~1MB is LuaJIT itself. Arisu is tiny!&lt;/p&gt;
&lt;p&gt;For now, you can use these instructions. &lt;em&gt;It’s really easy, I promise!&lt;/em&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Set up LuaJIT on your system
&lt;ul&gt;
&lt;li&gt;Windows: &lt;code&gt;winget install -e --id DEVCOM.LuaJIT&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Linux: &lt;code&gt;dnf install luajit&lt;/code&gt; or &lt;code&gt;apt install luajit&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Clone the repository
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;git clone https://github.com/bycruz/arisu&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Run this inside the repo folder
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;luajit ./src/main.lua&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;how-it-works&quot;&gt;How it works&lt;/h2&gt;
&lt;p&gt;Arisu is written in LuaJIT and OpenGL. It uses compute shaders to render brush strokes to a framebuffer texture which is then displayed to the screen.&lt;/p&gt;
&lt;h3 id=&quot;from-scratch-x11&quot;&gt;From scratch: X11&lt;/h3&gt;
&lt;p&gt;As I use Linux on my machine, I first created the version of Arisu with X11 as the windowing system.&lt;/p&gt;
&lt;p&gt;The windowing system is the core part of writing an application from scratch, you need a way to allocate a window to create an OpenGL context to it, and get window events like key presses. That’s what X11 provides on Linux.&lt;/p&gt;
&lt;h4 id=&quot;what-about-wayland&quot;&gt;What about Wayland?&lt;/h4&gt;
&lt;p&gt;Linux is moving on to Wayland as its primary windowing system, but it supports X11 apis completely via XWayland. Making an application for Wayland natively is much more complicated, and would have less support. It can be considered for future work.&lt;/p&gt;
&lt;h3 id=&quot;from-scratch-win32&quot;&gt;From scratch: Win32&lt;/h3&gt;
&lt;p&gt;After 0.2.0 and demoing to the professor, he said he’d be willing to try it on Linux but having Windows or MacOS support would be ideal.&lt;/p&gt;
&lt;p&gt;So I took on the task of porting this to Windows.
With the way I created everything through layers of modular abstractions, it was relatively easy.&lt;/p&gt;
&lt;p&gt;All that needed to be done was creating OpenGL bindings on windows which is uniquely a pain as you cannot access non-core OpenGL functions until you create an OpenGL context, and creating bindings to the Win32 APIs to create a window and event loop, implementing the same api as my X11 implementation.&lt;/p&gt;
&lt;p&gt;It took a few hours of research and work but I was able to get it working for 0.3.0.&lt;/p&gt;
&lt;h3 id=&quot;from-scratch-luajit&quot;&gt;From scratch: LuaJIT&lt;/h3&gt;
&lt;p&gt;Another curveball decision was that I would use Lua / LuaJIT as the main programming language.&lt;/p&gt;
&lt;p&gt;Lua&lt;sup&gt;&lt;a href=&quot;#user-content-fn-lua&quot; id=&quot;user-content-fnref-lua&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;3&lt;/a&gt;&lt;/sup&gt; is a lightweight programming language created in 1993 in Brazil. It’s used because its so simple and small. It was used before Python became widely popular, even the popular PyTorch library was preceded by the Torch library which used LuaJIT.&lt;/p&gt;
&lt;p&gt;LuaJIT is an implementation of a lua interpreter that uses JIT compilation. To make a long story short, it compiles your code to machine code on the fly so it runs faster.&lt;/p&gt;
&lt;p&gt;It also comes with an &lt;code&gt;ffi&lt;/code&gt; library which lets you call C functions and pass C structs and functions to C. For example:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;lua&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;local&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; ffi&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;ffi&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ffi&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;cdef&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;[[&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    void&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; glClearColor&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;float&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; red&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;float&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; green&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;float&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; blue&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;float&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; alpha&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    void&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; glClear&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;unsigned&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; int&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; mask&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;]]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;local&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; gl&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ffi&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;load&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;GL&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is how simple it is to call OpenGL functions from LuaJIT on Linux (it’s more complicated on Windows). It basically does stuff like &lt;code&gt;dlopen&lt;/code&gt; and &lt;code&gt;dlsym&lt;/code&gt; for you with a nice C syntax.&lt;/p&gt;
&lt;p&gt;I chose LuaJIT after originally choosing Rust because I knew Rust’s memory safety features would get in the way of what I wanted to do with creating something very low level and from scratch.&lt;/p&gt;
&lt;h3 id=&quot;from-scratch-images&quot;&gt;From scratch: Images&lt;/h3&gt;
&lt;p&gt;To support icons at all, I needed to create an image loader.
I chose to support two types of images, PPM&lt;sup&gt;&lt;a href=&quot;#user-content-fn-ppm&quot; id=&quot;user-content-fnref-ppm&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;4&lt;/a&gt;&lt;/sup&gt; and QOI&lt;sup&gt;&lt;a href=&quot;#user-content-fn-qoi&quot; id=&quot;user-content-fnref-qoi&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;5&lt;/a&gt;&lt;/sup&gt;. QOI is the primarily used format as it supports alpha unlike PPM and is more widely supported.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/posts/arisu/icons.avif&quot; alt=&quot;icons&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Some icons rendered in Arisu.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It works perfectly, and the QOI decoder is tiny, about ~100 lines of Lua. The icons may seem low quality but that’s just the specific icons I chose to use to fit the MS Paint / retro aesthetic. It’s a common pattern you’ll see throughout Arisu, but don’t be fooled - the UI library is a beast and I could easily create a cutting edge UI if I wanted to.&lt;/p&gt;
&lt;h3 id=&quot;from-scratch-ui-library&quot;&gt;From scratch: UI library&lt;/h3&gt;
&lt;p&gt;I didn’t just want to create a super coupled single paint program. I carefully made layers of abstraction without any coupling, from the cross platform windowing library, then an asynchronous event loop above that which allows patterns that work across X11 and Win32, to a UI library that hides the event loop entirely.&lt;/p&gt;
&lt;p&gt;I went for a design inspired by the Rust UI library, Iced&lt;sup&gt;&lt;a href=&quot;#user-content-fn-iced&quot; id=&quot;user-content-fnref-iced&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;6&lt;/a&gt;&lt;/sup&gt;, which is inspired by Elm. It uses a declarative style of UI programming where you define the UI as a tree of elements, and the library handles rendering and event handling.&lt;/p&gt;
&lt;p&gt;Here’s an example.&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;lua&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;local&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; Element&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;ui.element&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;local&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; Arisu&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;arisu&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;local&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; RenderPlugin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;plugins.render&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;---&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;@class&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; App&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;---&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;@field&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; render&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; plugins.Render&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;local&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; App&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = {}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;App&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;__index&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;App&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; App&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;	return&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; setmetatable&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;		render&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;RenderPlugin&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;	}, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;App&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;---&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;@param&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; window&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; Window&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; App&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;view&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;window&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;	return&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; Element&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;div&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;		:&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;withStyle&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;			width&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = { &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;rel&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1.0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;			height&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = { &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;rel&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1.0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;			direction&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;column&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;			gap&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;		})&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;		:&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;withChildren&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;			-- Add children here&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;		})&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; App&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;update&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;-- This is more for internal usage. It allows for direct access to windowing events&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; App&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;handler&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;	return&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; self&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;render&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;handler&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Arisu&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;run&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;App&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It’s a lower level version as Iced doesn’t allow you to directly take windowing events. So I took a bit of inspiration from making my previous project(s) Qun&lt;sup&gt;&lt;a href=&quot;#user-content-fn-qun&quot; id=&quot;user-content-fnref-qun&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;7&lt;/a&gt;&lt;/sup&gt; and Qun-rs&lt;sup&gt;&lt;a href=&quot;#user-content-fn-qun-rs&quot; id=&quot;user-content-fnref-qun-rs&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;8&lt;/a&gt;&lt;/sup&gt; heavily modular sort of like Bevy.&lt;/p&gt;
&lt;p&gt;There’s a lot that goes on in a UI library, it has to lay the whole ui out and calculate positions from the styling, handle events and push them around, manage textures and render the whole thing to OpenGL.&lt;/p&gt;
&lt;p&gt;This is why I separate everything into “plugins”. You can technically write your own render stack entirely.&lt;/p&gt;
&lt;h3 id=&quot;from-scratch-text-rendering&quot;&gt;From scratch: Text rendering&lt;/h3&gt;
&lt;p&gt;Text rendering is possibly one of the most complicated parts of writing a UI renderer from scratch.&lt;/p&gt;
&lt;p&gt;Usually people leave it up to libraries like FreeType&lt;sup&gt;&lt;a href=&quot;#user-content-fn-freetype&quot; id=&quot;user-content-fnref-freetype&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;9&lt;/a&gt;&lt;/sup&gt;, or go a little lower level with HarfBuzz&lt;sup&gt;&lt;a href=&quot;#user-content-fn-harfbuzz&quot; id=&quot;user-content-fnref-harfbuzz&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;10&lt;/a&gt;&lt;/sup&gt;. But of course my rule for this project was zero dependencies.&lt;/p&gt;
&lt;ul class=&quot;contains-task-list&quot;&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Glyph rendering is probably the most complex method of text rendering but provides the best quality.&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Signed Distance Fields (SDF) is a method of rendering text that uses a distance field texture to render glyphs. It is less complex than glyph rendering but still requires a lot of work to generate the distance field textures.&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; Bitmap fonts are the simplest method of text rendering. It uses a texture atlas of pre-rendered glyphs and renders them as quads.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I went with bitmap fonts / a text atlas as it is quite simple to implement and could even reuse my existing pipeline for rendering quads (and I even have it reuse the element pipeline.. text is just an array of divs..)&lt;/p&gt;
&lt;p&gt;Of course it’s nowhere near perfect. I only have a single sized font which is 16px tall.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/posts/arisu/shapes.avif&quot; alt=&quot;text&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This is a screenshot of the text rendering in Arisu.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The two biggest issues with this is that&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Since it’s a single size, it looks worse at other scales&lt;/li&gt;
&lt;li&gt;Vertical characters like ‘q’ or ‘p’ get cut off&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;bonus-sound&quot;&gt;Bonus: Sound&lt;/h3&gt;
&lt;p&gt;I didn’t get to doing this for Win32, but I implemented playing sounds via ALSA&lt;sup&gt;&lt;a href=&quot;#user-content-fn-alsa&quot; id=&quot;user-content-fnref-alsa&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;11&lt;/a&gt;&lt;/sup&gt; on Linux.&lt;/p&gt;
&lt;p&gt;You might not believe me, but it was pretty simple, arguably simpler than images.&lt;/p&gt;
&lt;p&gt;I just had to make a WAV decoder, which was hardly any “decoding” as the format just stores raw PCM data with a header. Then you pass that to libasound which the Linux kernel provides via ALSA.&lt;/p&gt;
&lt;h2 id=&quot;demo&quot;&gt;Demo&lt;/h2&gt;
&lt;center&gt;
&lt;iframe width=&quot;820&quot; height=&quot;530&quot; src=&quot;https://www.youtube.com/embed/yglCT5HGjt4&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;/center&gt;
&lt;blockquote&gt;
&lt;p&gt;Arisu running on my laptop running Fedora Linux.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It’s pretty smooth. I recommend trying it out yourself!&lt;/p&gt;
&lt;p&gt;Best part is that it is intentionally designed so it doesn’t do any layout calculations or even redraws unless absolutely necessary. So it uses practically no CPU time.&lt;/p&gt;
&lt;h2 id=&quot;future-work&quot;&gt;Future work&lt;/h2&gt;
&lt;p&gt;There’s a lot of features that are grayed out currently. Biggest thing is I want to implement is a file dialog. Normally people leave the work to libraries like GTK or WinUI, but as briefly discussed, this has zero dependencies, so I’ll have to make the UI on my own.&lt;/p&gt;
&lt;p&gt;MacOS support is the last operating system that isn’t supported. I don’t have a Mac, so I can’t promise it’ll happen, but I’m not entirely against it.&lt;/p&gt;
&lt;section data-footnotes=&quot;&quot; class=&quot;footnotes&quot;&gt;&lt;h2 class=&quot;sr-only&quot; id=&quot;footnote-label&quot;&gt;Footnotes&lt;/h2&gt;
&lt;ol&gt;
&lt;li id=&quot;user-content-fn-arisu&quot;&gt;
&lt;p&gt;Arisu is the Japanese pronunciation of “Alice” which I picked as it characterizes the program well and matches the Ciallo inspiration paper’s name. &lt;a href=&quot;#user-content-fnref-arisu&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 1&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-ciallo&quot;&gt;
&lt;p&gt;Ciallo: GPU-Accelerated Rendering of Vector Brush Strokes (&lt;a href=&quot;https://doi.org/10.1145/3641519.3657418&quot;&gt;https://doi.org/10.1145/3641519.3657418&lt;/a&gt;) &lt;a href=&quot;#user-content-fnref-ciallo&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 2&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-lua&quot;&gt;
&lt;p&gt;Lua Programming Language (&lt;a href=&quot;https://www.lua.org/&quot;&gt;https://www.lua.org/&lt;/a&gt;) &lt;a href=&quot;#user-content-fnref-lua&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 3&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-ppm&quot;&gt;
&lt;p&gt;PPM Image Format (&lt;a href=&quot;http://netpbm.sourceforge.net/doc/ppm.html&quot;&gt;http://netpbm.sourceforge.net/doc/ppm.html&lt;/a&gt;) &lt;a href=&quot;#user-content-fnref-ppm&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 4&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-qoi&quot;&gt;
&lt;p&gt;Quite OK Image Format (&lt;a href=&quot;https://qoiformat.org/&quot;&gt;https://qoiformat.org/&lt;/a&gt;) &lt;a href=&quot;#user-content-fnref-qoi&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 5&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-iced&quot;&gt;
&lt;p&gt;Iced UI Library (&lt;a href=&quot;https://iced.rs/&quot;&gt;https://iced.rs/&lt;/a&gt;) &lt;a href=&quot;#user-content-fnref-iced&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 6&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-qun&quot;&gt;
&lt;p&gt;Qun, a heavily modular, ECS based game engine in C++ I also made for professor Fahim Khan last quarter for CSC 471: &lt;a href=&quot;https://github.com/bycruz/qun&quot;&gt;https://github.com/bycruz/qun&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-qun&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 7&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-qun-rs&quot;&gt;
&lt;p&gt;Qun-rs, a second iteration of Qun, except in Rust and even more modular: &lt;a href=&quot;https://github.com/bycruz/qun-rs&quot;&gt;https://github.com/bycruz/qun-rs&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-qun-rs&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 8&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-freetype&quot;&gt;
&lt;p&gt;FreeType Font Engine (&lt;a href=&quot;https://www.freetype.org/&quot;&gt;https://www.freetype.org/&lt;/a&gt;) &lt;a href=&quot;#user-content-fnref-freetype&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 9&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-harfbuzz&quot;&gt;
&lt;p&gt;HarfBuzz Text Shaping Engine (&lt;a href=&quot;https://harfbuzz.github.io/&quot;&gt;https://harfbuzz.github.io/&lt;/a&gt;) &lt;a href=&quot;#user-content-fnref-harfbuzz&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 10&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-alsa&quot;&gt;
&lt;p&gt;ALSA Sound System (&lt;a href=&quot;https://www.alsa-project.org/&quot;&gt;https://www.alsa-project.org/&lt;/a&gt;) &lt;a href=&quot;#user-content-fnref-alsa&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 11&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;</content:encoded><category>Lua</category><category>C</category><category>OpenGL</category><category>Linux</category><category>Windows</category><category>Systems</category><author>david@bycruz.dev</author></item></channel></rss>