lupyuen.org/articles/lvgl3.html

1055 lines
No EOL
63 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="generator" content="rustdoc">
<title>(Possibly) LVGL in WebAssembly with Zig Compiler</title>
<!-- Begin scripts/articles/*-header.html: Article Header for Custom Markdown files processed by rustdoc, like chip8.md -->
<meta property="og:title"
content="(Possibly) LVGL in WebAssembly with Zig Compiler"
data-rh="true">
<meta property="og:description"
content="Can we preview an LVGL App in the Web Browser... With WebAssembly and Zig Compiler? Let's find out!"
data-rh="true">
<meta name="description"
content="Can we preview an LVGL App in the Web Browser... With WebAssembly and Zig Compiler? Let's find out!">
<meta property="og:image"
content="https://lupyuen.github.io/images/lvgl3-title.png">
<meta property="og:type"
content="article" data-rh="true">
<link rel="canonical" href="https://lupyuen.org/articles/lvgl3.html" />
<!-- End scripts/articles/*-header.html -->
<!-- Begin scripts/rustdoc-header.html: Header for Custom Markdown files processed by rustdoc, like chip8.md -->
<link rel="alternate" type="application/rss+xml" title="RSS Feed for lupyuen" href="/rss.xml" />
<link rel="stylesheet" type="text/css" href="../normalize.css">
<link rel="stylesheet" type="text/css" href="../rustdoc.css" id="mainThemeStyle">
<link rel="stylesheet" type="text/css" href="../dark.css">
<link rel="stylesheet" type="text/css" href="../light.css" id="themeStyle">
<link rel="stylesheet" type="text/css" href="../prism.css">
<script src="../storage.js"></script><noscript>
<link rel="stylesheet" href="../noscript.css"></noscript>
<link rel="shortcut icon" href="../favicon.ico">
<style type="text/css">
#crate-search {
background-image: url("../down-arrow.svg");
}
</style>
<!-- End scripts/rustdoc-header.html -->
</head>
<body class="rustdoc">
<!--[if lte IE 8]>
<div class="warning">
This old browser is unsupported and will most likely display funky
things.
</div>
<![endif]-->
<!-- Begin scripts/rustdoc-before.html: Pre-HTML for Custom Markdown files processed by rustdoc, like chip8.md -->
<!-- Begin Theme Picker -->
<div class="theme-picker" style="left: 0"><button id="theme-picker" aria-label="Pick another theme!"><img src="../brush.svg"
width="18" alt="Pick another theme!"></button>
<div id="theme-choices"></div>
</div>
<!-- Theme Picker -->
<!-- End scripts/rustdoc-before.html -->
<h1 class="title">(Possibly) LVGL in WebAssembly with Zig Compiler</h1>
<nav id="rustdoc"><ul>
<li><a href="#webassembly-with-zig" title="WebAssembly with Zig">1 WebAssembly with Zig</a><ul></ul></li>
<li><a href="#zig-calls-javascript" title="Zig Calls JavaScript">2 Zig Calls JavaScript</a><ul></ul></li>
<li><a href="#lvgl-app-in-zig" title="LVGL App in Zig">3 LVGL App in Zig</a><ul></ul></li>
<li><a href="#lvgl-app-in-webassembly" title="LVGL App in WebAssembly">4 LVGL App in WebAssembly</a><ul></ul></li>
<li><a href="#compile-lvgl-to-webassembly-with-zig-compiler" title="Compile LVGL to WebAssembly with Zig Compiler">5 Compile LVGL to WebAssembly with Zig Compiler</a><ul></ul></li>
<li><a href="#compile-entire-lvgl-library-to-webassembly" title="Compile Entire LVGL Library to WebAssembly">6 Compile Entire LVGL Library to WebAssembly</a><ul></ul></li>
<li><a href="#lvgl-porting-layer-for-webassembly" title="LVGL Porting Layer for WebAssembly">7 LVGL Porting Layer for WebAssembly</a><ul></ul></li>
<li><a href="#webassembly-logger-for-lvgl" title="WebAssembly Logger for LVGL">8 WebAssembly Logger for LVGL</a><ul></ul></li>
<li><a href="#initialise-lvgl-display" title="Initialise LVGL Display">9 Initialise LVGL Display</a><ul></ul></li>
<li><a href="#handle-lvgl-tasks" title="Handle LVGL Tasks">10 Handle LVGL Tasks</a><ul></ul></li>
<li><a href="#render-lvgl-display-in-zig" title="Render LVGL Display in Zig">11 Render LVGL Display in Zig</a><ul></ul></li>
<li><a href="#render-lvgl-display-in-javascript" title="Render LVGL Display in JavaScript">12 Render LVGL Display in JavaScript</a><ul></ul></li>
<li><a href="#whats-next" title="Whats Next">13 Whats Next</a><ul></ul></li>
<li><a href="#appendix-c-standard-library-is-missing" title="Appendix: C Standard Library is Missing">14 Appendix: C Standard Library is Missing</a><ul></ul></li>
<li><a href="#appendix-lvgl-memory-allocation" title="Appendix: LVGL Memory Allocation">15 Appendix: LVGL Memory Allocation</a><ul></ul></li>
<li><a href="#appendix-lvgl-fonts" title="Appendix: LVGL Fonts">16 Appendix: LVGL Fonts</a><ul></ul></li>
<li><a href="#appendix-lvgl-screen-not-found" title="Appendix: LVGL Screen Not Found">17 Appendix: LVGL Screen Not Found</a><ul></ul></li></ul></nav><p>📝 <em>31 May 2023</em></p>
<p><img src="https://lupyuen.github.io/images/lvgl3-title.png" alt="Zig LVGL App rendered in Web Browser with WebAssembly" /></p>
<p><a href="https://lupyuen.github.io/pinephone-lvgl-zig/lvglwasm.html"><em>Zig LVGL App rendered in Web Browser with WebAssembly</em></a></p>
<p><a href="https://docs.lvgl.io/master/index.html"><strong>LVGL</strong></a> is a popular <strong>Graphics Library</strong> for Microcontrollers. (In C)</p>
<p><a href="https://ziglang.org/"><strong>Zig Compiler</strong></a> works great for compiling <strong>C Libraries into WebAssembly</strong>. (Based on Clang Compiler)</p>
<p>Can we preview an <strong>LVGL App in the Web Browser</strong>… With WebAssembly and Zig Compiler? Lets find out!</p>
<p><em>Why are we doing this?</em></p>
<p>Right now were creating a <a href="https://lupyuen.github.io/articles/usb2#pinephone--nuttx--feature-phone"><strong>Feature Phone UI</strong></a> (in Zig) for <a href="https://lupyuen.github.io/articles/what"><strong>Apache NuttX RTOS</strong></a> (Real-Time Operating System) on <a href="https://wiki.pine64.org/index.php/PinePhone"><strong>Pine64 PinePhone</strong></a>.</p>
<p>Would be awesome if we could prototype the Feature Phone UI in our Web Browser… To make the <strong>UI Coding a little easier</strong>!</p>
<p><em>Doesnt LVGL support WebAssembly already?</em></p>
<p>Today LVGL runs in a Web Browser by compiling with <a href="https://github.com/lvgl/lv_web_emscripten"><strong>Emscripten and SDL</strong></a>.</p>
<p>Maybe we can do better with newer tools like <strong>Zig Compiler</strong>? In this article well…</p>
<ul>
<li>
<p>Run a <strong>Zig LVGL App</strong> on PinePhone (with NuttX RTOS)</p>
</li>
<li>
<p>Explain how <strong>Zig works with WebAssembly</strong> (and C Libraries)</p>
</li>
<li>
<p>Compile <strong>LVGL Library from C to WebAssembly</strong> (with Zig Compiler)</p>
</li>
<li>
<p>Test it with our <strong>LVGL App</strong> (in Zig)</p>
</li>
<li>
<p>Render <strong>Simple LVGL UIs</strong> (in Web Browser)</p>
</li>
<li>
<p>Later we might render <strong>LVGL UI Controls</strong> (with Touch Input)</p>
</li>
</ul>
<p>Maybe someday well code and test our LVGL Apps in a Web Browser, thanks to Zig Compiler and WebAssembly!</p>
<p><img src="https://lupyuen.github.io/images/lvgl3-wasm.png" alt="Mandelbrot Set rendered with Zig and WebAssembly" /></p>
<p><a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/demo"><em>Mandelbrot Set rendered with Zig and WebAssembly</em></a></p>
<h1 id="webassembly-with-zig"><a class="doc-anchor" href="#webassembly-with-zig">§</a>1 WebAssembly with Zig</h1>
<p><em>Why Zig? How does it work with WebAssembly?</em></p>
<p><a href="https://ziglang.org/"><strong>Zig Programming Language</strong></a> is a Low-Level Systems Language (like C and Rust) that works surprisingly well with WebAssembly.</p>
<p>(And Embedded Devices like PinePhone)</p>
<p>The pic above shows a <strong>WebAssembly App</strong> that we created with Zig, JavaScript and HTML…</p>
<ol>
<li>
<p>Our <a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/demo/mandelbrot.zig"><strong>Zig Program</strong></a> exports a function that computes the <a href="https://en.wikipedia.org/wiki/Mandelbrot_set"><strong>Mandelbrot Set</strong></a> pixels: <a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/demo/mandelbrot.zig">mandelbrot.zig</a></p>
<div class="example-wrap"><pre class="language-zig"><code>/// Compute the Pixel Color at (px,py) for Mandelbrot Set
export fn get_pixel_color(px: i32, py: i32) u8 {
var iterations: u8 = 0;
var x0 = @intToFloat(f32, px);
var y0 = @intToFloat(f32, py);
...
while ((xsquare + ysquare &lt; 4.0) and (iterations &lt; MAX_ITER)) : (iterations += 1) {
tmp = xsquare - ysquare + x0;
y = 2 * x * y + y0;
x = tmp;
xsquare = x * x;
ysquare = y * y;
}
return iterations;
}</code></pre></div></li>
<li>
<p>Our <a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/demo/game.js"><strong>JavaScript</strong></a> calls the Zig Function above to compute the Mandelbrot Set: <a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/demo/game.js">game.js</a></p>
<div class="example-wrap"><pre class="language-javascript"><code>// Load our WebAssembly Module `mandelbrot.wasm`
// https://developer.mozilla.org/en-US/docs/WebAssembly/JavaScript_interface/instantiateStreaming
let Game = await WebAssembly.instantiateStreaming(
fetch(&quot;mandelbrot.wasm&quot;),
importObject
);
...
// For every Pixel in our HTML Canvas...
for (let x = 0; x &lt; canvas.width; x++) {
for (let y = 0; y &lt; canvas.height; y++) {
// Get the Pixel Color from Zig
const color = Game.instance.exports
.get_pixel_color(x, y);
// Render the Pixel in our HTML Canvas
if (color &lt; 10) { context.fillStyle = &quot;red&quot;; }
else if (color &lt; 128) { context.fillStyle = &quot;grey&quot;; }
else { context.fillStyle = &quot;white&quot;; }
context.fillRect(x, y, x + 1, y + 1);
}
}</code></pre></div>
<p>And it renders the pixels in a HTML Canvas.</p>
</li>
<li>
<p>Our <a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/demo/demo.html"><strong>HTML Page</strong></a> defines the HTML Canvas and loads the above JavaScript: <a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/demo/demo.html">demo.html</a></p>
<div class="example-wrap"><pre class="language-html"><code>&lt;html&gt;
&lt;body&gt;
&lt;!-- HTML Canvas for rendering Mandelbrot Set --&gt;
&lt;canvas id=&quot;game_canvas&quot; width=&quot;640&quot; height=&quot;480&quot;&gt;&lt;/canvas&gt;
&lt;/body&gt;
&lt;!-- Load our JavaScript --&gt;
&lt;script src=&quot;game.js&quot;&gt;&lt;/script&gt;
&lt;/html&gt;</code></pre></div></li>
</ol>
<p>Thats all we need to create a WebAssembly App with Zig!</p>
<p><a href="https://github.com/sleibrock/zigtoys/blob/main/toys/mandelbrot/mandelbrot.zig">(Thanks to <strong>sleibrock/zigtoys</strong>)</a></p>
<p><em>Whats mandelbrot.wasm?</em></p>
<p><a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/demo/mandelbrot.wasm"><strong>mandelbrot.wasm</strong></a> is the <strong>WebAssembly Module</strong> for our Zig Program, compiled by the <strong>Zig Compiler</strong></p>
<div class="example-wrap"><pre class="language-bash"><code>## Download and compile the Zig Program for our Mandelbrot Demo
git clone --recursive https://github.com/lupyuen/pinephone-lvgl-zig
cd pinephone-lvgl-zig/demo
zig build-lib \
mandelbrot.zig \
-target wasm32-freestanding \
-dynamic \
-rdynamic</code></pre></div>
<p><strong>wasm32-freestanding</strong> tells the Zig Compiler to compile our <a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/demo/mandelbrot.zig"><strong>Zig Program</strong></a> into a <strong>WebAssembly Module</strong>.</p>
<p><a href="https://ziglang.org/documentation/master/#Freestanding">(More about this)</a></p>
<p><em>How do we run this?</em></p>
<p>Start a <strong>Local Web Server</strong>. <a href="https://chrome.google.com/webstore/detail/web-server-for-chrome/ofhbbkphhbklhfoeikjpcbhemlocgigb">(Like Web Server for Chrome)</a></p>
<p>Browse to <strong>demo/demo.html</strong>. And well see the Mandelbrot Set in our Web Browser! (Pic above)</p>
<p><a href="https://lupyuen.github.io/pinephone-lvgl-zig/demo/demo.html">(Try the <strong>Mandelbrot Demo</strong>)</a></p>
<h1 id="zig-calls-javascript"><a class="doc-anchor" href="#zig-calls-javascript">§</a>2 Zig Calls JavaScript</h1>
<p><em>Can Zig call out to JavaScript?</em></p>
<p>Yep Zig and JavaScript will happily <strong>interoperate both ways</strong>!</p>
<p>In our Zig Program, this is how we <strong>import a JavaScript Function</strong> and call it: <a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/demo/mandelbrot.zig">mandelbrot.zig</a></p>
<div class="example-wrap"><pre class="language-zig"><code>// Import Print Function from JavaScript into Zig
extern fn print(i32) void;
// Print a number to JavaScript Console. Warning: This is slow!
if (iterations == 1) { print(iterations); }</code></pre></div>
<p>In our JavaScript, we export the <strong>print</strong> function as we load the WebAssembly Module: <a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/demo/game.js">game.js</a></p>
<div class="example-wrap"><pre class="language-javascript"><code>// Export JavaScript Functions to Zig
let importObject = {
// JavaScript Environment exported to Zig
env: {
// JavaScript Print Function exported to Zig
print: function(x) { console.log(x); }
}
};
// Load our WebAssembly Module
// and export our Print Function to Zig
let Game = await WebAssembly.instantiateStreaming(
fetch(&quot;mandelbrot.wasm&quot;), // Load our WebAssembly Module
importObject // Export our Print Function to Zig
);</code></pre></div>
<p>This works OK for printing numbers to the JavaScript Console.</p>
<p><a href="https://ziglang.org/documentation/master/#WebAssembly">(As explained here)</a></p>
<p><em>Will this work for passing Strings and Buffers?</em></p>
<p>It gets complicated… We need to snoop the <strong>WebAssembly Memory</strong>.</p>
<p>Well come back to this when we talk about WebAssembly Logging.</p>
<p><img src="https://lupyuen.github.io/images/lvgl2-zig.jpg" alt="Zig LVGL App on PinePhone with Apache NuttX RTOS" /></p>
<p><a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/lvgltest.zig"><em>Zig LVGL App on PinePhone with Apache NuttX RTOS</em></a></p>
<h1 id="lvgl-app-in-zig"><a class="doc-anchor" href="#lvgl-app-in-zig">§</a>3 LVGL App in Zig</h1>
<p><em>Will Zig work with LVGL?</em></p>
<p>Yep we tested an <strong>LVGL App in Zig</strong> with PinePhone and Apache NuttX RTOS (pic above): <a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/lvgltest.zig#L28-L90">lvgltest.zig</a></p>
<div class="example-wrap"><pre class="language-zig"><code>/// LVGL App in Zig that renders a Text Label
fn createWidgetsWrapped() !void {
// Get the Active Screen
var screen = try lvgl.getActiveScreen();
// Create a Label Widget
var label = try screen.createLabel();
// Wrap long lines in the label text
label.setLongMode(c.LV_LABEL_LONG_WRAP);
// Interpret color codes in the label text
label.setRecolor(true);
// Center align the label text
label.setAlign(c.LV_TEXT_ALIGN_CENTER);
// Set the label text and colors
label.setText(
&quot;#ff0000 HELLO# &quot; ++ // Red Text
&quot;#00aa00 LVGL ON# &quot; ++ // Green Text
&quot;#0000ff PINEPHONE!# &quot; // Blue Text
);
// Set the label width
label.setWidth(200);
// Align the label to the center of the screen, shift 30 pixels up
label.alignObject(c.LV_ALIGN_CENTER, 0, -30);
}</code></pre></div>
<p><a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/feature-phone.zig#L83-L116">(Moving to feature-phone.zig)</a></p>
<p><a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/lvgl.zig">(<strong>lvgl</strong> is our LVGL Wrapper for Zig)</a></p>
<p><a href="https://github.com/lupyuen/pinephone-lvgl-zig#lvgl-zig-app">(More about this)</a></p>
<p>To <strong>compile our Zig LVGL App</strong> for PinePhone and NuttX RTOS…</p>
<div class="example-wrap"><pre class="language-bash"><code>## Compile the Zig App `lvgltest.zig`
## for PinePhone (Armv8-A with Cortex-A53)
zig build-obj \
-target aarch64-freestanding-none \
-mcpu cortex_a53 \
-isystem &quot;../nuttx/include&quot; \
-I &quot;../apps/include&quot; \
-I &quot;../apps/graphics/lvgl&quot; \
... \
lvgltest.zig
## Copy the Compiled Zig App to NuttX RTOS
## and overwrite `lv_demo_widgets.*.o`
cp lvgltest.o \
../apps/graphics/lvgl/lvgl/demos/widgets/lv_demo_widgets.*.o
## Omitted: Link the Compiled Zig App with NuttX RTOS</code></pre></div>
<p><a href="https://github.com/lupyuen/pinephone-lvgl-zig#build-lvgl-zig-app">(See the Complete Command)</a></p>
<p><a href="https://github.com/lupyuen/pinephone-lvgl-zig/releases/tag/nuttx-build-files">(NuttX Build Files)</a></p>
<p>Zig Compiler produces an Object File <strong>lvgltest.o</strong> that looks exactly like an ordinary C Object File…</p>
<p>Which links perfectly fine into <strong>Apache NuttX RTOS</strong>.</p>
<p>And our LVGL Zig App runs OK on PinePhone! (Pic above)</p>
<p><a href="https://github.com/lupyuen/pinephone-lvgl-zig#build-lvgl-zig-app">(More about this)</a></p>
<h1 id="lvgl-app-in-webassembly"><a class="doc-anchor" href="#lvgl-app-in-webassembly">§</a>4 LVGL App in WebAssembly</h1>
<p><em>But will our Zig LVGL App run in a Web Browser with WebAssembly?</em></p>
<p>Lets find out! We shall…</p>
<ol>
<li>
<p>Compile our <strong>Zig LVGL App</strong> to WebAssembly</p>
</li>
<li>
<p>Compile <strong>LVGL Library</strong> from C to WebAssembly</p>
<p>(With Zig Compiler)</p>
</li>
<li>
<p>Render the <strong>LVGL Display</strong> in JavaScript</p>
</li>
</ol>
<p><em>Will our Zig LVGL App compile to WebAssembly?</em></p>
<p>Lets take the earlier steps to compile our Zig LVGL App. To <strong>compile for WebAssembly</strong>, we change…</p>
<ul>
<li>
<p><strong>zig build-obj</strong>” to “<strong>zig build-lib</strong></p>
</li>
<li>
<p>Target becomes “<strong>wasm32-freestanding</strong></p>
</li>
<li>
<p>Add “<strong>-dynamic</strong>” and “<strong>-rdynamic</strong></p>
</li>
<li>
<p>Remove “<strong>-mcpu</strong></p>
</li>
</ul>
<p>Like this…</p>
<div class="example-wrap"><pre class="language-bash"><code>## Compile the Zig App `lvglwasm.zig`
## for WebAssembly
zig build-lib \
-target wasm32-freestanding \
-dynamic \
-rdynamic \
-isystem &quot;../nuttx/include&quot; \
-I &quot;../apps/include&quot; \
-I &quot;../apps/graphics/lvgl&quot; \
...\
lvglwasm.zig</code></pre></div>
<p><a href="https://github.com/lupyuen/pinephone-lvgl-zig#compile-zig-lvgl-app-to-webassembly">(See the Complete Command)</a></p>
<p><a href="https://github.com/lupyuen/pinephone-lvgl-zig/releases/tag/nuttx-build-files">(NuttX Build Files)</a></p>
<p>And we cloned <a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/lvgltest.zig"><strong>lvgltest.zig</strong></a> to <a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/lvglwasm.zig"><strong>lvglwasm.zig</strong></a>, because well tweak it for WebAssembly.</p>
<p>We removed our <a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/lvgltest.zig#L128-L149"><strong>Custom Panic Handler</strong></a>, the default one works fine for WebAssembly.</p>
<p><a href="https://github.com/lupyuen/pinephone-lvgl-zig#compile-zig-lvgl-app-to-webassembly">(More about this)</a></p>
<p><em>What happens when we run this?</em></p>
<p>The command above produces the Compiled WebAssembly <a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/lvglwasm.wasm"><strong>lvglwasm.wasm</strong></a>.</p>
<p>We start a Local Web Server. <a href="https://chrome.google.com/webstore/detail/web-server-for-chrome/ofhbbkphhbklhfoeikjpcbhemlocgigb">(Like Web Server for Chrome)</a></p>
<p>And browse to our HTML <a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/lvglwasm.html"><strong>lvglwasm.html</strong></a></p>
<ul>
<li>
<p>Which calls our JavaScript <a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/lvglwasm.js#L96-L114"><strong>lvglwasm.js</strong></a></p>
<p>(To load the Compiled WebAssembly)</p>
</li>
<li>
<p>Which calls our Zig Function <a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/lvglwasm.zig#L35-L85"><strong>lv_demo_widgets</strong></a></p>
<p>(To render the LVGL Widgets)</p>
</li>
<li>
<p>Thats exported to WebAssembly by our Zig App <a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/lvglwasm.zig#L35-L85"><strong>lvglwasm.zig</strong></a></p>
<p><a href="https://lupyuen.github.io/pinephone-lvgl-zig/lvglwasm.html">(Try the <strong>LVGL Demo</strong>)</a></p>
</li>
</ul>
<p>But the WebAssembly wont load in our Web Browser!</p>
<div class="example-wrap"><pre class="language-text"><code>Uncaught (in promise) LinkError:
WebAssembly.instantiate():
Import #1 module=&quot;env&quot; function=&quot;lv_label_create&quot; error:
function import requires a callable</code></pre></div>
<p>Thats because we havent linked <strong>lv_label_create</strong> from the LVGL Library.</p>
<p>Lets compile the LVGL Library to WebAssembly…</p>
<h1 id="compile-lvgl-to-webassembly-with-zig-compiler"><a class="doc-anchor" href="#compile-lvgl-to-webassembly-with-zig-compiler">§</a>5 Compile LVGL to WebAssembly with Zig Compiler</h1>
<p><em>Will Zig Compiler compile C Libraries? Like LVGL?</em></p>
<p>Yep! This is how we call Zig Compiler to compile <strong>lv_label_create</strong> and <strong>lv_label.c</strong> from the LVGL Library…</p>
<div class="example-wrap"><pre class="language-bash"><code>## Compile LVGL from C to WebAssembly
zig cc \
-target wasm32-freestanding \
-dynamic \
-rdynamic \
-lc \
-DFAR= \
-DLV_MEM_CUSTOM=1 \
-DLV_FONT_MONTSERRAT_20=1 \
-DLV_FONT_DEFAULT_MONTSERRAT_20=1 \
-DLV_USE_LOG=1 \
-DLV_LOG_LEVEL=LV_LOG_LEVEL_TRACE \
&quot;-DLV_ASSERT_HANDLER={void lv_assert_handler(void); lv_assert_handler();}&quot; \
... \
lvgl/src/widgets/lv_label.c \
-o ../../../pinephone-lvgl-zig/lv_label.o</code></pre></div>
<p><a href="https://github.com/lupyuen/pinephone-lvgl-zig#compile-lvgl-to-webassembly-with-zig-compiler">(See the Complete Command)</a></p>
<p><a href="https://github.com/lupyuen/pinephone-lvgl-zig/releases/tag/nuttx-build-files">(NuttX Build Files)</a></p>
<p>This compiles <strong>lv_label.c</strong> from C to WebAssembly and generates <strong>lv_label.o</strong>.</p>
<p>We changed these options…</p>
<ul>
<li>
<p><strong>zig build-lib</strong>” becomes “<strong>zig cc</strong></p>
<p>(Because were compiling C, not Zig)</p>
</li>
<li>
<p>Add “<strong>-lc</strong></p>
<p>(Because were calling C Standard Library)</p>
</li>
<li>
<p>Add “<strong>-DFAR=</strong></p>
<p>(Because we wont need Far Pointers)</p>
</li>
<li>
<p>Add “<strong>-DLV_MEM_CUSTOM=1</strong></p>
<p><a href="https://lupyuen.github.io/articles/lvgl3#appendix-lvgl-memory-allocation">(Because were calling <strong>malloc</strong> instead of LVGLs TLSF Allocator)</a></p>
</li>
<li>
<p>Set the <strong>Default Font</strong> to Montserrat 20…</p>
<div class="example-wrap"><pre class="language-text"><code>-DLV_FONT_MONTSERRAT_20=1 \
-DLV_FONT_DEFAULT_MONTSERRAT_20=1 \</code></pre></div>
<p><a href="https://lupyuen.github.io/articles/lvgl3#appendix-lvgl-fonts">(Remember to compile <strong>LVGL Fonts</strong>!)</a></p>
</li>
<li>
<p>Enable <strong>Detailed Logging</strong></p>
<div class="example-wrap"><pre class="language-text"><code>-DLV_USE_LOG=1 \
-DLV_LOG_LEVEL=LV_LOG_LEVEL_TRACE \</code></pre></div>
<p><a href="https://lupyuen.github.io/articles/lvgl3#webassembly-logger-for-lvgl">(Well come back to this)</a></p>
</li>
<li>
<p>Handle <strong>Assertion Failure</strong></p>
<div class="example-wrap"><pre class="language-text"><code>&quot;-DLV_ASSERT_HANDLER={void lv_assert_handler(void); lv_assert_handler();} \&quot;</code></pre></div>
<p><a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/lvglwasm.zig#L190-L195">(Like this)</a></p>
<p><a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/wasm.zig#L143-L148">(Moving to wasm.zig)</a></p>
</li>
<li>
<p>Emit the <strong>WebAssembly Object File</strong></p>
<div class="example-wrap"><pre class="language-text"><code>-o ../../../pinephone-lvgl-zig/lv_label.o</code></pre></div></li>
</ul>
<p>This works because Zig Compiler calls <a href="https://andrewkelley.me/post/zig-cc-powerful-drop-in-replacement-gcc-clang.html"><strong>Clang Compiler</strong></a> to compile LVGL Library from C to WebAssembly.</p>
<p><em>So we link lv_label.o with our Zig LVGL App?</em></p>
<p>Yep we ask Zig Compiler to link the Compiled WebAssembly <strong>lv_label.o</strong> with our Zig LVGL App <a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/lvglwasm.zig"><strong>lvglwasm.zig</strong></a></p>
<div class="example-wrap"><pre class="language-bash"><code>## Compile the Zig App `lvglwasm.zig` for WebAssembly
## and link with `lv_label.o` from LVGL Library
zig build-lib \
-target wasm32-freestanding \
-dynamic \
-rdynamic \
-lc \
-DFAR= \
-DLV_MEM_CUSTOM=1 \
-DLV_FONT_MONTSERRAT_20=1 \
-DLV_FONT_DEFAULT_MONTSERRAT_20=1 \
-DLV_USE_LOG=1 \
-DLV_LOG_LEVEL=LV_LOG_LEVEL_TRACE \
&quot;-DLV_ASSERT_HANDLER={void lv_assert_handler(void); lv_assert_handler();}&quot; \
... \
lvglwasm.zig \
lv_label.o</code></pre></div>
<p><a href="https://github.com/lupyuen/pinephone-lvgl-zig#compile-lvgl-to-webassembly-with-zig-compiler">(See the Complete Command)</a></p>
<p><a href="https://github.com/lupyuen/pinephone-lvgl-zig/releases/tag/nuttx-build-files">(NuttX Build Files)</a></p>
<p>When we browse to our HTML <a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/lvglwasm.html"><strong>lvglwasm.html</strong></a>, we see this in the JavaScript Console…</p>
<div class="example-wrap"><pre class="language-text"><code>Uncaught (in promise) LinkError:
WebAssembly.instantiate():
Import #0 module=&quot;env&quot; function=&quot;lv_obj_clear_flag&quot; error:
function import requires a callable</code></pre></div>
<p><strong>lv_label_create</strong> is no longer missing, because Zig Compiler has linked <strong>lv_label.o</strong> into our Zig LVGL App.</p>
<p>(Yep Zig Compiler works great for linking WebAssembly Object Files with our Zig App!)</p>
<p>Now we need to compile <strong>lv_obj_clear_flag</strong> (and the other LVGL Files) from C to WebAssembly…</p>
<h1 id="compile-entire-lvgl-library-to-webassembly"><a class="doc-anchor" href="#compile-entire-lvgl-library-to-webassembly">§</a>6 Compile Entire LVGL Library to WebAssembly</h1>
<p><em>Compile the entire LVGL Library to WebAssembly? Sounds so tedious!</em></p>
<p>Yeah through sheer tenacity we tracked down <strong>lv_obj_clear_flag</strong> and all the <strong>Missing LVGL Functions</strong> called by our Zig LVGL App…</p>
<div class="example-wrap"><pre class="language-text"><code>widgets/lv_label.c
core/lv_obj.c
misc/lv_mem.c
core/lv_event.c
core/lv_obj_style.c
core/lv_obj_pos.c
misc/lv_txt.c
draw/lv_draw_label.c
core/lv_obj_draw.c
misc/lv_area.c
core/lv_obj_scroll.c
font/lv_font.c
core/lv_obj_class.c
(Many many more)</code></pre></div>
<p><a href="https://github.com/lvgl/lvgl/tree/v8.3.3">(Based on LVGL 8.3.3)</a></p>
<p>So we wrote a script to <strong>compile the above LVGL Source Files</strong> from C to WebAssembly: <a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/build.sh#L7-L86">build.sh</a></p>
<div class="example-wrap"><pre class="language-bash"><code>## Compile our LVGL Display Driver from C to WebAssembly with Zig Compiler
compile_lvgl ../../../../../pinephone-lvgl-zig/display.c display.o
## Compile LVGL Library from C to WebAssembly with Zig Compiler
compile_lvgl font/lv_font_montserrat_14.c lv_font_montserrat_14.o
compile_lvgl font/lv_font_montserrat_20.c lv_font_montserrat_20.o
compile_lvgl widgets/lv_label.c lv_label.o
compile_lvgl core/lv_obj.c lv_obj.o
compile_lvgl misc/lv_mem.c lv_mem.o
## Many many more</code></pre></div>
<p><a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/build.sh#L229-L292">(<strong>compile_lvgl</strong> is defined here)</a></p>
<p><a href="https://github.com/lupyuen/pinephone-lvgl-zig/releases/tag/nuttx-build-files">(NuttX Build Files)</a></p>
<p>(More about <strong>display.c</strong> later)</p>
<p>And <strong>link the Compiled LVGL WebAssemblies</strong> with our Zig LVGL App: <a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/build.sh#L88-L195">build.sh</a></p>
<div class="example-wrap"><pre class="language-bash"><code>## Compile the Zig App `lvglwasm.zig` for WebAssembly
## and link with LVGL Library compiled for WebAssembly
zig build-lib \
-target wasm32-freestanding \
... \
lvglwasm.zig \
display.o \
lv_font_montserrat_14.o \
lv_font_montserrat_20.o \
lv_label.o \
lv_mem.o \
...</code></pre></div>
<p>Were done with LVGL Library in WebAssembly! (Almost)</p>
<p><em>Now what happens when we run it?</em></p>
<p>JavaScript Console says that <strong>strlen</strong> is missing…</p>
<div class="example-wrap"><pre class="language-text"><code>Uncaught (in promise) LinkError:
WebAssembly.instantiate():
Import #0 module=&quot;env&quot; function=&quot;strlen&quot; error:
function import requires a callable</code></pre></div>
<p>Which comes from the <strong>C Standard Library</strong>. Heres the workaround…</p>
<ul>
<li><a href="https://lupyuen.github.io/articles/lvgl3#appendix-c-standard-library-is-missing"><strong>“C Standard Library is Missing”</strong></a></li>
</ul>
<p><em>Is it really OK to compile only the necessary LVGL Source Files?</em></p>
<p><em>Instead of compiling ALL the LVGL Source Files?</em></p>
<p>Be careful! We might miss out some <strong>Undefined Variables</strong>… Zig Compiler blissfully assumes theyre at <strong>WebAssembly Address 0</strong>. And remember to compile the <strong>LVGL Fonts</strong>!</p>
<ul>
<li>
<p><a href="https://lupyuen.github.io/articles/lvgl3#appendix-lvgl-screen-not-found"><strong>“LVGL Screen Not Found”</strong></a></p>
</li>
<li>
<p><a href="https://lupyuen.github.io/articles/lvgl3#appendix-lvgl-fonts"><strong>“LVGL Fonts”</strong></a></p>
</li>
</ul>
<p>Thus we really ought to compile ALL the LVGL Source Files.</p>
<p>(Maybe we should disassemble the Compiled WebAssembly and look for other Undefined Variables at WebAssembly Address 0)</p>
<h1 id="lvgl-porting-layer-for-webassembly"><a class="doc-anchor" href="#lvgl-porting-layer-for-webassembly">§</a>7 LVGL Porting Layer for WebAssembly</h1>
<p><em>Anything else we need for LVGL in WebAssembly?</em></p>
<p>LVGL expects a <strong>millis</strong> function that returns the number of <strong>Elapsed Milliseconds</strong></p>
<div class="example-wrap"><pre class="language-text"><code>Uncaught (in promise) LinkError:
WebAssembly.instantiate():
Import #0 module=&quot;env&quot; function=&quot;millis&quot; error:
function import requires a callable</code></pre></div>
<p><a href="https://github.com/lvgl/lvgl/blob/v8.3.3/src/lv_conf_internal.h#L252-L254">(Because of this)</a></p>
<p>We implement <strong>millis</strong> in Zig: <a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/lvglwasm.zig#L179-L200">lvglwasm.zig</a></p>
<div class="example-wrap"><pre class="language-zig"><code>/// TODO: Return the number of elapsed milliseconds
export fn millis() u32 {
elapsed_ms += 1;
return elapsed_ms;
}
/// Number of elapsed milliseconds
var elapsed_ms: u32 = 0;
/// On Assertion Failure, ask Zig to print a Stack Trace and halt
export fn lv_assert_handler() void {
@panic(&quot;*** lv_assert_handler: ASSERTION FAILED&quot;);
}
/// Custom Logger for LVGL that writes to JavaScript Console
export fn custom_logger(buf: [*c]const u8) void {
wasmlog.Console.log(&quot;{s}&quot;, .{buf});
}</code></pre></div>
<p><a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/wasm.zig#L131-L153">(Moving to wasm.zig)</a></p>
<p><a href="https://lupyuen.github.io/articles/lvgl4#appendix-handle-lvgl-timer">(We should reimplement <strong>millis</strong> with JavaScript)</a></p>
<p>In the code above, we defined <strong>lv_assert_handler</strong> and <strong>custom_logger</strong> to handle <strong>Assertions and Logging</strong> in LVGL.</p>
<p>Lets talk about LVGL Logging…</p>
<p><img src="https://lupyuen.github.io/images/lvgl3-wasm2.png" alt="WebAssembly Logger for LVGL" /></p>
<h1 id="webassembly-logger-for-lvgl"><a class="doc-anchor" href="#webassembly-logger-for-lvgl">§</a>8 WebAssembly Logger for LVGL</h1>
<p><em>printf wont work in WebAssembly…</em></p>
<p><em>How will we trace the LVGL Execution?</em></p>
<p>We set the <strong>Custom Logger</strong> for LVGL, so that we can print Log Messages to the JavaScript Console: <a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/lvglwasm.zig#L35-L51">lvglwasm.zig</a></p>
<div class="example-wrap"><pre class="language-zig"><code>/// Main Function for our Zig LVGL App
pub export fn lv_demo_widgets() void {
// Set the Custom Logger for LVGL
c.lv_log_register_print_cb(custom_logger);
// Init LVGL
c.lv_init();</code></pre></div>
<p><a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/wasm.zig#L16-L59">(Moving to wasm.zig)</a></p>
<p><a href="https://lupyuen.github.io/articles/lvgl#import-c-functions">(“<strong><code>c.</code></strong>” refers to functions <strong>imported from C to Zig</strong>)</a></p>
<p><strong>custom_logger</strong> is defined in our Zig Program: <a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/lvglwasm.zig#L195-L200">lvglwasm.zig</a></p>
<div class="example-wrap"><pre class="language-zig"><code>/// Custom Logger for LVGL that writes to JavaScript Console
export fn custom_logger(buf: [*c]const u8) void {
wasmlog.Console.log(&quot;{s}&quot;, .{buf});
}</code></pre></div>
<p><a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/wasm.zig#L148-L153">(Moving to wasm.zig)</a></p>
<p><a href="https://ziglang.org/documentation/master/#C-Pointers">(“<strong><code>[*c]</code></strong>” means <strong>C Pointer</strong>)</a></p>
<p><strong>wasmlog</strong> is our <strong>Zig Logger for WebAssembly</strong>: <a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/wasmlog.zig">wasmlog.zig</a></p>
<p>Which calls JavaScript Functions <strong>jsConsoleLogWrite</strong> and <strong>jsConsoleLogFlush</strong> to write logs to the JavaScript Console: <a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/lvglwasm.js#L54C1-L69">lvglwasm.js</a></p>
<div class="example-wrap"><pre class="language-javascript"><code>// Export JavaScript Functions to Zig
const importObject = {
// JavaScript Functions exported to Zig
env: {
// Write to JavaScript Console from Zig
// https://github.com/daneelsan/zig-wasm-logger/blob/master/script.js
jsConsoleLogWrite: function(ptr, len) {
console_log_buffer += wasm.getString(ptr, len);
},
// Flush JavaScript Console from Zig
// https://github.com/daneelsan/zig-wasm-logger/blob/master/script.js
jsConsoleLogFlush: function() {
console.log(console_log_buffer);
console_log_buffer = &quot;&quot;;
},</code></pre></div>
<p><a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/feature-phone.js#L28-L67">(Moving to feature-phone.js)</a></p>
<p>(Thanks to <a href="https://github.com/daneelsan/zig-wasm-logger"><strong>daneelsan/zig-wasm-logger</strong></a>)</p>
<p><em>Whats wasm.getString?</em></p>
<p><strong>wasm.getString</strong> is our JavaScript Function that <strong>reads the WebAssembly Memory</strong> into a JavaScript Array: <a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/lvglwasm.js#L10-L27">lvglwasm.js</a></p>
<div class="example-wrap"><pre class="language-javascript"><code>// WebAssembly Helper Functions in JavaScript
const wasm = {
// WebAssembly Instance
instance: undefined,
// Init the WebAssembly Instance
init: function (obj) {
this.instance = obj.instance;
},
// Fetch the Zig String from a WebAssembly Pointer
getString: function (ptr, len) {
const memory = this.instance.exports.memory;
const text_decoder = new TextDecoder();
return text_decoder.decode(
new Uint8Array(memory.buffer, ptr, len)
);
},
};</code></pre></div>
<p><a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/feature-phone.js#L9-L28">(Moving to feature-phone.js)</a></p>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder">(<strong>TextDecoder</strong> converts bytes to text)</a></p>
<p>(Remember earlier we spoke about <strong>snooping WebAssembly Memory</strong> with a WebAssembly Pointer? This is how we do it)</p>
<p>Now we can see the <strong>LVGL Log Messages</strong> in the JavaScript Console yay! (Pic above)</p>
<div class="example-wrap"><pre class="language-text"><code>[Warn] lv_disp_get_scr_act:
no display registered to get its active screen
(in lv_disp.c line #54)</code></pre></div>
<p>Lets initialise the LVGL Display…</p>
<h1 id="initialise-lvgl-display"><a class="doc-anchor" href="#initialise-lvgl-display">§</a>9 Initialise LVGL Display</h1>
<p><em>What happens when LVGL runs?</em></p>
<p>According to the <a href="https://docs.lvgl.io/8.3/porting/project.html#initialization"><strong>LVGL Docs</strong></a>, this is how we <strong>initialise and operate LVGL</strong></p>
<ol>
<li>
<p>Call <strong>lv_init</strong></p>
</li>
<li>
<p>Register the <strong>LVGL Display</strong> (and Input Devices)</p>
</li>
<li>
<p>Call <strong>lv_tick_inc(x)</strong> every <strong>x</strong> milliseconds (in an Interrupt) to report the <strong>Elapsed Time</strong> to LVGL</p>
<p><a href="https://lupyuen.github.io/articles/lvgl3#lvgl-porting-layer-for-webassembly">(Not required, because LVGL calls <strong>millis</strong> to fetch the Elapsed Time)</a></p>
</li>
<li>
<p>Call <strong>lv_timer_handler</strong> every few milliseconds to handle <strong>LVGL Tasks</strong></p>
</li>
</ol>
<p>To <strong>register the LVGL Display</strong>, we follow these steps…</p>
<ul>
<li>
<p><a href="https://docs.lvgl.io/8.3/porting/display.html#draw-buffer"><strong>Create the LVGL Draw Buffer</strong></a></p>
</li>
<li>
<p><a href="https://docs.lvgl.io/8.3/porting/display.html#examples"><strong>Register the LVGL Display Driver</strong></a></p>
</li>
</ul>
<p><em>Easy peasy for Zig right?</em></p>
<p>Sadly we cant do it in Zig…</p>
<div class="example-wrap"><pre class="language-zig"><code>// Nope, can&#39;t allocate LVGL Display Driver in Zig!
// `lv_disp_drv_t` is an Opaque Type
var disp_drv = c.lv_disp_drv_t{};
c.lv_disp_drv_init(&amp;disp_drv);</code></pre></div>
<p>Because LVGL Display Driver <strong>lv_disp_drv_t</strong> is an <strong>Opaque Type</strong>.</p>
<p>(Same for the LVGL Draw Buffer <strong>lv_disp_draw_buf_t</strong>)</p>
<p><em>Whats an Opaque Type in Zig?</em></p>
<p>When we <strong>import a C Struct</strong> into Zig and it contains <strong>Bit Fields</strong></p>
<p>Zig Compiler wont let us <strong>access the fields</strong> of the C Struct. And we cant allocate the C Struct either.</p>
<p><strong>lv_disp_drv_t</strong> contains Bit Fields, hence its an <strong>Opaque Type</strong> and inaccessible in Zig. <a href="https://lupyuen.github.io/articles/lvgl#appendix-zig-opaque-types">(See this)</a></p>
<p><em>Bummer. How to fix Opaque Types in Zig?</em></p>
<p>Our workaround is to write <strong>C Functions to allocate</strong> and initialise the Opaque Types…</p>
<ul>
<li><a href="https://lupyuen.github.io/articles/lvgl#fix-opaque-types"><strong>“Fix Opaque Types”</strong></a></li>
</ul>
<p>Which gives us this <strong>LVGL Display Interface</strong> for Zig: <a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/display.c">display.c</a></p>
<p>Finally with the workaround, heres how we <strong>initialise the LVGL Display</strong> in Zig: <a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/lvglwasm.zig#L38-L84">lvglwasm.zig</a></p>
<div class="example-wrap"><pre class="language-zig"><code>/// Main Function for our Zig LVGL App
pub export fn lv_demo_widgets() void {
// Create the Memory Allocator for malloc
memory_allocator = std.heap.FixedBufferAllocator
.init(&amp;memory_buffer);
// Set the Custom Logger for LVGL
c.lv_log_register_print_cb(custom_logger);
// Init LVGL
c.lv_init();
// Fetch pointers to Display Driver and Display Buffer,
// exported by our C Functions
const disp_drv = c.get_disp_drv();
const disp_buf = c.get_disp_buf();
// Init Display Buffer and Display Driver as pointers,
// by calling our C Functions
c.init_disp_buf(disp_buf);
c.init_disp_drv(
disp_drv, // Display Driver
disp_buf, // Display Buffer
flushDisplay, // Callback Function to Flush Display
720, // Horizontal Resolution
1280 // Vertical Resolution
);
// Register the Display Driver as a pointer
const disp = c.lv_disp_drv_register(disp_drv);
// Create the widgets for display
createWidgetsWrapped() catch |e| {
// In case of error, quit
std.log.err(&quot;createWidgetsWrapped failed: {}&quot;, .{e});
return;
};
// Up Next: Handle LVGL Tasks</code></pre></div>
<p><a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/wasm.zig#L16-L59">(Moving to wasm.zig)</a></p>
<p><a href="https://lupyuen.github.io/articles/lvgl3#appendix-lvgl-memory-allocation">(<strong>memory_allocator</strong> is explained here)</a></p>
<p><a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/86700c3453d91bc7d2fe0a46192fa41b7a24b6df/display.c#L94-L95">(Remember to set <strong>Direct Mode</strong> in the Display Driver!)</a></p>
<p>Now we handle LVGL Tasks…</p>
<h1 id="handle-lvgl-tasks"><a class="doc-anchor" href="#handle-lvgl-tasks">§</a>10 Handle LVGL Tasks</h1>
<p>Earlier we talked about <strong>handling LVGL Tasks</strong></p>
<ol>
<li>
<p>Call <strong>lv_tick_inc(x)</strong> every <strong>x</strong> milliseconds (in an Interrupt) to report the <strong>Elapsed Time</strong> to LVGL</p>
<p><a href="https://lupyuen.github.io/articles/lvgl3#lvgl-porting-layer-for-webassembly">(Not required, because LVGL calls <strong>millis</strong> to fetch the Elapsed Time)</a></p>
</li>
<li>
<p>Call <strong>lv_timer_handler</strong> every few milliseconds to handle <strong>LVGL Tasks</strong></p>
</li>
</ol>
<p><a href="https://docs.lvgl.io/8.3/porting/project.html#initialization">(From the <strong>LVGL Docs</strong>)</a></p>
<p>This is how we call <strong>lv_timer_handler</strong> in Zig: <a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/lvglwasm.zig#L69-L85">lvglwasm.zig</a></p>
<div class="example-wrap"><pre class="language-zig"><code>/// Main Function for our Zig LVGL App
pub export fn lv_demo_widgets() void {
// Omitted: Init LVGL Display
// Create the widgets for display
createWidgetsWrapped() catch |e| {
// In case of error, quit
std.log.err(&quot;createWidgetsWrapped failed: {}&quot;, .{e});
return;
};
// Handle LVGL Tasks
// TODO: Call this from Web Browser JavaScript,
// so that our Web Browser won&#39;t block
var i: usize = 0;
while (i &lt; 5) : (i += 1) {
_ = c.lv_timer_handler();
}</code></pre></div>
<p><a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/wasm.zig#L76-L90">(Moving to wasm.zig)</a></p>
<p>Were ready to render the LVGL Display in our HTML Page!</p>
<p><em>Something doesnt look right…</em></p>
<p>Yeah actually we should trigger <strong>lv_timer_handler</strong> from our JavaScript like this…</p>
<ul>
<li><a href="https://lupyuen.github.io/articles/lvgl4#appendix-handle-lvgl-timer"><strong>“Handle LVGL Timer”</strong></a></li>
</ul>
<p><img src="https://lupyuen.github.io/images/lvgl3-render.jpg" alt="Render LVGL Display in WebAssembly" /></p>
<h1 id="render-lvgl-display-in-zig"><a class="doc-anchor" href="#render-lvgl-display-in-zig">§</a>11 Render LVGL Display in Zig</h1>
<p>Finally we <strong>render our LVGL Display</strong> in the Web Browser… Spanning C, Zig and JavaScript! (Pic above)</p>
<p>Earlier we saw this <strong>LVGL Initialisation</strong> in our Zig App: <a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/lvglwasm.zig#L49-L63">lvglwasm.zig</a></p>
<div class="example-wrap"><pre class="language-zig"><code>// Init LVGL
c.lv_init();
// Fetch pointers to Display Driver and Display Buffer,
// exported by our C Functions
const disp_drv = c.get_disp_drv();
const disp_buf = c.get_disp_buf();
// Init Display Buffer and Display Driver as pointers,
// by calling our C Functions
c.init_disp_buf(disp_buf);
c.init_disp_drv(
disp_drv, // Display Driver
disp_buf, // Display Buffer
flushDisplay, // Callback Function to Flush Display
720, // Horizontal Resolution
1280 // Vertical Resolution
);</code></pre></div>
<p><a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/wasm.zig#L16-L59">(Moving to wasm.zig)</a></p>
<p><em>Whats inside init_disp_buf?</em></p>
<p><strong>init_disp_buf</strong> tells LVGL to render the display pixels to our <strong>LVGL Canvas Buffer</strong>: <a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/display.c#L95-L109">display.c</a></p>
<div class="example-wrap"><pre class="language-c"><code>// Init the LVGL Display Buffer in C, because Zig
// can&#39;t access the fields of the Opaque Type
void init_disp_buf(lv_disp_draw_buf_t *disp_buf) {
lv_disp_draw_buf_init(
disp_buf, // LVGL Display Buffer
canvas_buffer, // Render the pixels to our LVGL Canvas Buffer
NULL, // No Secondary Buffer
BUFFER_SIZE // Buffer the entire display (720 x 1280 pixels)
);
}</code></pre></div>
<p><a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/display.c#L8C1-L27">(<strong>canvas_buffer</strong> is defined here)</a></p>
<p>Then our Zig App initialises the <strong>LVGL Display Driver</strong>: <a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/lvglwasm.zig#L49-L63">lvglwasm.zig</a></p>
<div class="example-wrap"><pre class="language-zig"><code>// Init Display Driver as pointer,
// by calling our C Function
c.init_disp_drv(
disp_drv, // Display Driver
disp_buf, // Display Buffer
flushDisplay, // Callback Function to Flush Display
720, // Horizontal Resolution
1280 // Vertical Resolution
);</code></pre></div>
<p><a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/wasm.zig#L16-L59">(Moving to wasm.zig)</a></p>
<p><a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/display.c#L60-L93">(<strong>init_disp_drv</strong> is defined here)</a></p>
<p>This tells LVGL to call <strong>flushDisplay</strong> (in Zig) when the LVGL Display Canvas is ready to be rendered: <a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/lvglwasm.zig#L86-L98">lvglwasm.zig</a></p>
<div class="example-wrap"><pre class="language-zig"><code>/// LVGL calls this Callback Function to flush our display
export fn flushDisplay(
disp_drv: ?*c.lv_disp_drv_t, // LVGL Display Driver
area: [*c]const c.lv_area_t, // LVGL Display Area
color_p: [*c]c.lv_color_t // LVGL Display Buffer
) void {
// Call the Web Browser JavaScript
// to render the LVGL Canvas Buffer
render();
// Notify LVGL that the display has been flushed.
// Remember to call `lv_disp_flush_ready`
// or Web Browser will hang on reload!
c.lv_disp_flush_ready(disp_drv);
}</code></pre></div>
<p><a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/wasm.zig#L59-L70">(Moving to wasm.zig)</a></p>
<p><strong>flushDisplay</strong> (in Zig) calls <strong>render</strong> (in JavaScript) to render the LVGL Display Canvas.</p>
<p>We bubble up from Zig to JavaScript…</p>
<p><img src="https://lupyuen.github.io/images/lvgl3-title.png" alt="Zig LVGL App rendered in Web Browser with WebAssembly" /></p>
<p><a href="https://lupyuen.github.io/pinephone-lvgl-zig/lvglwasm.html"><em>Zig LVGL App rendered in Web Browser with WebAssembly</em></a></p>
<h1 id="render-lvgl-display-in-javascript"><a class="doc-anchor" href="#render-lvgl-display-in-javascript">§</a>12 Render LVGL Display in JavaScript</h1>
<p><em>Phew OK. What happens in our JavaScript?</em></p>
<p>Earlier we saw that <a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/lvglwasm.zig#L86-L98"><strong>flushDisplay</strong></a> (in Zig) calls <strong>render</strong> (in JavaScript) to render the LVGL Display Canvas.</p>
<p><strong>render</strong> (in JavaScript) draws the LVGL Canvas Buffer to our HTML Canvas: <a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/lvglwasm.js#L29-L53">lvglwasm.js</a></p>
<div class="example-wrap"><pre class="language-javascript"><code>// Render the LVGL Canvas from Zig to HTML
// https://github.com/daneelsan/minimal-zig-wasm-canvas/blob/master/script.js
render: function() { // TODO: Add width and height
// Get the WebAssembly Pointer to the LVGL Canvas Buffer
const bufferOffset = wasm.instance.exports.getCanvasBuffer();
// Load the WebAssembly Pointer into a JavaScript Image Data
const memory = wasm.instance.exports.memory;
const ptr = bufferOffset;
const len = (canvas.width * canvas.height) * 4;
const imageDataArray = new Uint8Array(memory.buffer, ptr, len)
imageData.data.set(imageDataArray);
// Render the Image Data to the HTML Canvas
context.clearRect(0, 0, canvas.width, canvas.height);
context.putImageData(imageData, 0, 0);
}</code></pre></div>
<p><a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/feature-phone.js#L28-L67">(Moving to feature-phone.js)</a></p>
<p><a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/lvglwasm.js#L69-L75">(<strong>imageData</strong> and <strong>context</strong> are defined here)</a></p>
<p><em>How does it fetch the LVGL Canvas Buffer?</em></p>
<p>The JavaScript above calls <a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/lvglwasm.zig#L100-L104"><strong>getCanvasBuffer</strong></a> (in Zig) and <strong>get_canvas_buffer</strong> (in C) to fetch the LVGL Canvas Buffer: <a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/display.c#L8-L27">display.c</a></p>
<div class="example-wrap"><pre class="language-c"><code>// Canvas Buffer for rendering LVGL Display
// TODO: Swap the RGB Bytes in LVGL, the colours are inverted for HTML Canvas
#define HOR_RES 720 // Horizontal Resolution
#define VER_RES 1280 // Vertical Resolution
#define BUFFER_ROWS VER_RES // Number of rows to buffer
#define BUFFER_SIZE (HOR_RES * BUFFER_ROWS)
static lv_color_t canvas_buffer[BUFFER_SIZE];
// Return a pointer to the LVGL Canvas Buffer
lv_color_t *get_canvas_buffer(void) {
return canvas_buffer;
}</code></pre></div>
<p>And the LVGL Display renders OK in our HTML Canvas yay! (Pic above)</p>
<p><a href="https://lupyuen.github.io/pinephone-lvgl-zig/lvglwasm.html">(Try the <strong>LVGL Demo</strong>)</a></p>
<p><a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/8c9f45401eb15ff68961bd53e237baa798cc8fb5/README.md#todo">(See the <strong>JavaScript Log</strong>)</a></p>
<p>(Thanks to <a href="https://github.com/daneelsan/minimal-zig-wasm-canvas"><strong>daneelsan/minimal-zig-wasm-canvas</strong></a>)</p>
<h1 id="whats-next"><a class="doc-anchor" href="#whats-next">§</a>13 Whats Next</h1>
<p>Up Next: <a href="https://lupyuen.github.io/articles/usb2#pinephone--nuttx--feature-phone"><strong>Feature Phone UI</strong></a> for PinePhone! To make our Feature Phone clickable, well pass <strong>Mouse Events</strong> from JavaScript to LVGL…</p>
<ul>
<li><a href="https://lupyuen.github.io/articles/lvgl4"><strong>“NuttX RTOS for PinePhone: Feature Phone UI in LVGL, Zig and WebAssembly”</strong></a></li>
</ul>
<p>Well experiment with <strong>Live Reloading</strong>: Whenever we save our Zig LVGL App, it <strong>auto-recompiles</strong> and <strong>auto-reloads</strong> the WebAssembly HTML.</p>
<p>Which makes UI Prototyping a lot quicker in LVGL. Stay Tuned for updates!</p>
<p>Meanwhile please check out the other articles on NuttX for PinePhone…</p>
<ul>
<li><a href="https://github.com/lupyuen/pinephone-nuttx"><strong>“Apache NuttX RTOS for PinePhone”</strong></a></li>
</ul>
<p>Many Thanks to my <a href="https://lupyuen.github.io/articles/sponsor"><strong>GitHub Sponsors</strong></a> for supporting my work! This article wouldnt have been possible without your support.</p>
<ul>
<li>
<p><a href="https://lupyuen.github.io/articles/sponsor"><strong>Sponsor me a coffee</strong></a></p>
</li>
<li>
<p><a href="https://forum.lvgl.io/t/possibly-lvgl-in-webassembly-with-zig-compiler/11886"><strong>Discuss this article on LVGL Forum</strong></a></p>
</li>
<li>
<p><a href="https://www.reddit.com/r/Zig/comments/13vgbfp/possibly_lvgl_in_webassembly_with_zig_compiler/"><strong>Discuss this article on Reddit</strong></a></p>
</li>
<li>
<p><a href="https://news.ycombinator.com/item?id=36121090"><strong>Discuss this article on Hacker News</strong></a></p>
</li>
<li>
<p><a href="https://github.com/lupyuen/nuttx-ox64"><strong>My Current Project: “Apache NuttX RTOS for Ox64 BL808”</strong></a></p>
</li>
<li>
<p><a href="https://github.com/lupyuen/nuttx-star64"><strong>My Other Project: “NuttX for Star64 JH7110”</strong></a></p>
</li>
<li>
<p><a href="https://github.com/lupyuen/pinephone-nuttx"><strong>Older Project: “NuttX for PinePhone”</strong></a></p>
</li>
<li>
<p><a href="https://lupyuen.github.io/articles/sourdough"><strong>My Sourdough Recipe</strong></a></p>
</li>
<li>
<p><a href="https://lupyuen.github.io"><strong>Check out my articles</strong></a></p>
</li>
<li>
<p><a href="https://lupyuen.github.io/rss.xml"><strong>RSS Feed</strong></a></p>
</li>
</ul>
<p><em>Got a question, comment or suggestion? Create an Issue or submit a Pull Request here…</em></p>
<p><a href="https://github.com/lupyuen/lupyuen.github.io/blob/master/src/lvgl3.md"><strong>lupyuen.github.io/src/lvgl3.md</strong></a></p>
<h1 id="appendix-c-standard-library-is-missing"><a class="doc-anchor" href="#appendix-c-standard-library-is-missing">§</a>14 Appendix: C Standard Library is Missing</h1>
<p><em>strlen is missing from our Zig WebAssembly…</em></p>
<p><em>But strlen should come from the C Standard Library! (musl)</em></p>
<p>Not sure why <strong>strlen</strong> is missing, but we fixed it (temporarily) by copying from the <a href="https://github.com/ziglang/zig/blob/master/lib/c.zig"><strong>Zig Library Source Code</strong></a>: <a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/lvglwasm.zig#L280-L336">lvglwasm.zig</a></p>
<div class="example-wrap"><pre class="language-zig"><code>// C Standard Library from zig-macos-x86_64-0.10.0-dev.2351+b64a1d5ab/lib/zig/c.zig
export fn strlen(s: [*:0]const u8) callconv(.C) usize {
return std.mem.len(s);
}
// Also memset, memcpy, strcpy...</code></pre></div>
<p>(Maybe because we didnt export <strong>strlen</strong> in our Zig Main Program <strong>lvglwasm.zig</strong>?)</p>
<p><em>What if we change the target to wasm32-freestanding-musl?</em></p>
<p>Nope doesnt help, same problem.</p>
<p><em>What if we use “zig build-exe” instead of “zig build-lib”?</em></p>
<p>Sorry “<strong>zig build-exe</strong>” is meant for building <strong>WASI Executables</strong>. <a href="https://www.fermyon.com/wasm-languages/c-lang">(See this)</a></p>
<p><strong>zig build-exe</strong>” is not supposed to work for WebAssembly in the Web Browser. <a href="https://github.com/ziglang/zig/issues/1570#issuecomment-426370371">(See this)</a></p>
<h1 id="appendix-lvgl-memory-allocation"><a class="doc-anchor" href="#appendix-lvgl-memory-allocation">§</a>15 Appendix: LVGL Memory Allocation</h1>
<p><em>What happens if we omit “-DLV_MEM_CUSTOM=1”?</em></p>
<p>By default, LVGL uses the <a href="http://www.gii.upv.es/tlsf/"><strong>Two-Level Segregate Fit (TLSF) Allocator</strong></a> for Heap Memory.</p>
<p>But TLSF Allocator fails inside <a href="https://github.com/lvgl/lvgl/blob/v8.3.3/src/misc/lv_tlsf.c#L453-L460"><strong>block_next</strong></a></p>
<div class="example-wrap"><pre class="language-text"><code>main: start
loop: start
lv_demo_widgets: start
before lv_init
[Info] lv_init: begin (in lv_obj.c line #102)
[Trace] lv_mem_alloc: allocating 76 bytes (in lv_mem.c line #127)
[Trace] lv_mem_alloc: allocated at 0x1a700 (in lv_mem.c line #160)
[Trace] lv_mem_alloc: allocating 28 bytes (in lv_mem.c line #127)
[Trace] lv_mem_alloc: allocated at 0x1a750 (in lv_mem.c line #160)
[Warn] lv_init: Log level is set to &#39;Trace&#39; which makes LVGL much slower (in lv_obj.c line #176)
[Trace] lv_mem_realloc: reallocating 0x14 with 8 size (in lv_mem.c line #196)
[Error] block_next: Asserted at expression: !block_is_last(block) (in lv_tlsf.c line #459)
004a5b4a:0x29ab2 Uncaught (in promise) RuntimeError: unreachable
at std.builtin.default_panic (004a5b4a:0x29ab2)
at lv_assert_handler (004a5b4a:0x2ac6c)
at block_next (004a5b4a:0xd5b3)
at lv_tlsf_realloc (004a5b4a:0xe226)
at lv_mem_realloc (004a5b4a:0x20f1)
at lv_layout_register (004a5b4a:0x75d8)
at lv_flex_init (004a5b4a:0x16afe)
at lv_extra_init (004a5b4a:0x16ae5)
at lv_init (004a5b4a:0x3f28)
at lv_demo_widgets (004a5b4a:0x29bb9)</code></pre></div>
<p>Thus we set “<strong>-DLV_MEM_CUSTOM=1</strong>” to call <strong>malloc</strong> instead of LVGLs TLSF Allocator.</p>
<p>(<a href="https://github.com/lvgl/lvgl/blob/v8.3.3/src/misc/lv_tlsf.c#L453-L460"><strong>block_next</strong></a> calls <a href="https://github.com/lvgl/lvgl/blob/v8.3.3/src/misc/lv_tlsf.c#L440-L444"><strong>offset_to_block</strong></a>, which calls <a href="https://github.com/lvgl/lvgl/blob/v8.3.3/src/misc/lv_tlsf.c#L274"><strong>tlsf_cast</strong></a>. Maybe the Pointer Cast doesnt work for <a href="https://github.com/lvgl/lvgl/blob/v8.3.3/src/misc/lv_tlsf.c#L25-L215"><strong>Clang WebAssembly</strong></a>?)</p>
<p><em>But Zig doesnt support malloc for WebAssembly!</em></p>
<p>We call Zigs <a href="https://ziglang.org/documentation/master/#Memory"><strong>FixedBufferAllocator</strong></a> to implement <strong>malloc</strong>: <a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/lvglwasm.zig#L38-L44">lvglwasm.zig</a></p>
<div class="example-wrap"><pre class="language-zig"><code>/// Main Function for our Zig LVGL App
pub export fn lv_demo_widgets() void {
// Create the Memory Allocator for malloc
memory_allocator = std.heap.FixedBufferAllocator
.init(&amp;memory_buffer);</code></pre></div>
<p><a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/wasm.zig#L16-L27">(Moving to wasm.zig)</a></p>
<p>Heres our (incomplete) implementation of <strong>malloc</strong>: <a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/lvglwasm.zig#L201-L244">lvglwasm.zig</a></p>
<div class="example-wrap"><pre class="language-zig"><code>/// Zig replacement for malloc
export fn malloc(size: usize) ?*anyopaque {
// TODO: Save the slice length
const mem = memory_allocator.allocator().alloc(u8, size) catch {
@panic(&quot;*** malloc error: out of memory&quot;);
};
return mem.ptr;
}
/// Zig replacement for realloc
export fn realloc(old_mem: [*c]u8, size: usize) ?*anyopaque {
// TODO: Call realloc instead
const mem = memory_allocator.allocator().alloc(u8, size) catch {
@panic(&quot;*** realloc error: out of memory&quot;);
};
_ = memcpy(mem.ptr, old_mem, size);
if (old_mem != null) {
// TODO: How to free without the slice length?
// memory_allocator.allocator().free(old_mem[0..???]);
}
return mem.ptr;
}
/// Zig replacement for free
export fn free(mem: [*c]u8) void {
if (mem == null) {
@panic(&quot;*** free error: pointer is null&quot;);
}
// TODO: How to free without the slice length?
// memory_allocator.allocator().free(mem[0..???]);
}
/// Memory Allocator for malloc
var memory_allocator: std.heap.FixedBufferAllocator = undefined;
/// Memory Buffer for malloc
var memory_buffer = std.mem.zeroes([1024 * 1024]u8);</code></pre></div>
<p><a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/wasm.zig#L178-L222">(Moving to wasm.zig)</a></p>
<p><a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/aade32dd70286866676b2d9728970c6b3cca9489/README.md#todo">(Remember to copy the old memory in <strong>realloc</strong>!)</a></p>
<p><a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/aa080fb2ce55f9959cce2b6fff7e5fd5c9907cd6/README.md#lvgl-memory-allocation">(If we ever remove “<strong>-DLV_MEM_CUSTOM=1</strong>”, remember to set “<strong>-DLV_MEM_SIZE=1000000</strong>”)</a></p>
<h1 id="appendix-lvgl-fonts"><a class="doc-anchor" href="#appendix-lvgl-fonts">§</a>16 Appendix: LVGL Fonts</h1>
<p>Remember to <strong>compile the LVGL Fonts</strong>! Or our LVGL Text Label wont be rendered…</p>
<div class="example-wrap"><pre class="language-bash"><code>## Compile LVGL Fonts from C to WebAssembly with Zig Compiler
compile_lvgl font/lv_font_montserrat_14.c lv_font_montserrat_14
compile_lvgl font/lv_font_montserrat_20.c lv_font_montserrat_20
## Compile the Zig LVGL App for WebAssembly
## and link with LVGL Fonts
zig build-lib \
-DLV_FONT_MONTSERRAT_14=1 \
-DLV_FONT_MONTSERRAT_20=1 \
-DLV_FONT_DEFAULT_MONTSERRAT_20=1 \
-DLV_USE_FONT_PLACEHOLDER=1 \
...
lv_font_montserrat_14.o \
lv_font_montserrat_20.o \</code></pre></div>
<p><a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/main/build.sh#L21-L191">(Source)</a></p>
<h1 id="appendix-lvgl-screen-not-found"><a class="doc-anchor" href="#appendix-lvgl-screen-not-found">§</a>17 Appendix: LVGL Screen Not Found</h1>
<p><em>Why does LVGL say “No Screen Found” in <a href="https://github.com/lvgl/lvgl/blob/v8.3.3/src/core/lv_obj_tree.c#L270-L289">lv_obj_get_disp</a>?</em></p>
<div class="example-wrap"><pre class="language-text"><code>[Info] lv_init: begin (in lv_obj.c line #102)
[Trace] lv_init: finished (in lv_obj.c line #183)
before lv_disp_drv_register
[Warn] lv_obj_get_disp: No screen found (in lv_obj_tree.c line #290)
[Info] lv_obj_create: begin (in lv_obj.c line #206)
[Trace] lv_obj_class_create_obj: Creating object with 0x12014 class on 0 parent (in lv_obj_class.c line #45)
[Warn] lv_obj_get_disp: No screen found (in lv_obj_tree.c line #290)</code></pre></div>
<p><a href="https://github.com/lupyuen/pinephone-lvgl-zig/blob/9610bb5209a072fc5950cf0559b1274d53dd8b8b/README.md#lvgl-screen-not-found">(See the Complete Log)</a></p>
<p>Thats because the Display Linked List _<strong>lv_disp_ll</strong> is allocated by <strong>LV_ITERATE_ROOTS</strong> in <a href="https://github.com/lvgl/lvgl/blob/v8.3.3/src/misc/lv_gc.c#L42">_<strong>lv_gc_clear_roots</strong></a></p>
<p>And we forgot to compile <a href="https://github.com/lvgl/lvgl/blob/v8.3.3/src/misc/lv_gc.c#L42">_<strong>lv_gc_clear_roots</strong></a> in <a href="https://github.com/lvgl/lvgl/blob/v8.3.3/src/misc/lv_gc.c#L42"><strong>lv_gc.c</strong></a>. Duh!</p>
<p>(Zig Compiler assumes that Undefined Variables like _<strong>lv_disp_ll</strong> are at <strong>WebAssembly Address 0</strong>)</p>
<p>After compiling <a href="https://github.com/lvgl/lvgl/blob/v8.3.3/src/misc/lv_gc.c#L42">_<strong>lv_gc_clear_roots</strong></a> and <a href="https://github.com/lvgl/lvgl/blob/v8.3.3/src/misc/lv_gc.c#L42"><strong>lv_gc.c</strong></a>, the “No Screen Found” error no longer appears.</p>
<p>(Maybe we should disassemble the Compiled WebAssembly and look for other Undefined Variables at WebAssembly Address 0)</p>
<p>TODO: For easier debugging, how to disassemble Compiled WebAssembly with cross-reference to Source Code? Similar to “<strong>objdump source</strong>”? Maybe with <a href="https://github.com/WebAssembly/wabt"><strong>wabt</strong></a> or <a href="https://github.com/WebAssembly/binaryen"><strong>binaryen</strong></a>?</p>
<!-- Begin scripts/rustdoc-after.html: Post-HTML for Custom Markdown files processed by rustdoc, like chip8.md -->
<!-- Begin Theme Picker and Prism Theme -->
<script src="../theme.js"></script>
<script src="../prism.js"></script>
<!-- Theme Picker and Prism Theme -->
<!-- End scripts/rustdoc-after.html -->
</body>
</html>