mirror of
https://github.com/lupyuen/lupyuen.github.io.git
synced 2025-01-13 02:08:32 +08:00
1055 lines
No EOL
63 KiB
HTML
1055 lines
No EOL
63 KiB
HTML
<!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="What’s Next">13 What’s 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? Let’s find out!</p>
|
||
<p><em>Why are we doing this?</em></p>
|
||
<p>Right now we’re 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>Doesn’t 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 we’ll…</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 we’ll 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 < 4.0) and (iterations < 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("mandelbrot.wasm"),
|
||
importObject
|
||
);
|
||
...
|
||
// For every Pixel in our HTML Canvas...
|
||
for (let x = 0; x < canvas.width; x++) {
|
||
for (let y = 0; y < 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 < 10) { context.fillStyle = "red"; }
|
||
else if (color < 128) { context.fillStyle = "grey"; }
|
||
else { context.fillStyle = "white"; }
|
||
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><html>
|
||
<body>
|
||
<!-- HTML Canvas for rendering Mandelbrot Set -->
|
||
<canvas id="game_canvas" width="640" height="480"></canvas>
|
||
</body>
|
||
<!-- Load our JavaScript -->
|
||
<script src="game.js"></script>
|
||
</html></code></pre></div></li>
|
||
</ol>
|
||
<p>That’s 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>What’s 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 we’ll 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("mandelbrot.wasm"), // 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>We’ll 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(
|
||
"#ff0000 HELLO# " ++ // Red Text
|
||
"#00aa00 LVGL ON# " ++ // Green Text
|
||
"#0000ff PINEPHONE!# " // 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 "../nuttx/include" \
|
||
-I "../apps/include" \
|
||
-I "../apps/graphics/lvgl" \
|
||
... \
|
||
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>Let’s 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>Let’s 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 "../nuttx/include" \
|
||
-I "../apps/include" \
|
||
-I "../apps/graphics/lvgl" \
|
||
...\
|
||
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 we’ll 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>That’s 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 won’t load in our Web Browser!</p>
|
||
<div class="example-wrap"><pre class="language-text"><code>Uncaught (in promise) LinkError:
|
||
WebAssembly.instantiate():
|
||
Import #1 module="env" function="lv_label_create" error:
|
||
function import requires a callable</code></pre></div>
|
||
<p>That’s because we haven’t linked <strong>lv_label_create</strong> from the LVGL Library.</p>
|
||
<p>Let’s 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 \
|
||
"-DLV_ASSERT_HANDLER={void lv_assert_handler(void); lv_assert_handler();}" \
|
||
... \
|
||
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 we’re compiling C, not Zig)</p>
|
||
</li>
|
||
<li>
|
||
<p>Add “<strong>-lc</strong>”</p>
|
||
<p>(Because we’re calling C Standard Library)</p>
|
||
</li>
|
||
<li>
|
||
<p>Add “<strong>-DFAR=</strong>”</p>
|
||
<p>(Because we won’t 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 we’re calling <strong>malloc</strong> instead of LVGL’s 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">(We’ll come back to this)</a></p>
|
||
</li>
|
||
<li>
|
||
<p>Handle <strong>Assertion Failure</strong>…</p>
|
||
<div class="example-wrap"><pre class="language-text"><code>"-DLV_ASSERT_HANDLER={void lv_assert_handler(void); lv_assert_handler();} \"</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 \
|
||
"-DLV_ASSERT_HANDLER={void lv_assert_handler(void); lv_assert_handler();}" \
|
||
... \
|
||
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="env" function="lv_obj_clear_flag" 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>We’re 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="env" function="strlen" error:
|
||
function import requires a callable</code></pre></div>
|
||
<p>Which comes from the <strong>C Standard Library</strong>. Here’s 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 they’re 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="env" function="millis" 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("*** lv_assert_handler: ASSERTION FAILED");
|
||
}
|
||
|
||
/// Custom Logger for LVGL that writes to JavaScript Console
|
||
export fn custom_logger(buf: [*c]const u8) void {
|
||
wasmlog.Console.log("{s}", .{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>Let’s 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 won’t 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("{s}", .{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 = "";
|
||
},</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>What’s 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>Let’s 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 can’t do it in Zig…</p>
|
||
<div class="example-wrap"><pre class="language-zig"><code>// Nope, can'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(&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>What’s 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 won’t let us <strong>access the fields</strong> of the C Struct. And we can’t allocate the C Struct either.</p>
|
||
<p><strong>lv_disp_drv_t</strong> contains Bit Fields, hence it’s 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, here’s 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(&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("createWidgetsWrapped failed: {}", .{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("createWidgetsWrapped failed: {}", .{e});
|
||
return;
|
||
};
|
||
|
||
// Handle LVGL Tasks
|
||
// TODO: Call this from Web Browser JavaScript,
|
||
// so that our Web Browser won't block
|
||
var i: usize = 0;
|
||
while (i < 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>We’re ready to render the LVGL Display in our HTML Page!</p>
|
||
<p><em>Something doesn’t 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>What’s 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'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 What’s 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, we’ll 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>We’ll 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 wouldn’t 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 didn’t 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 doesn’t 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 'Trace' 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 LVGL’s 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 doesn’t 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 doesn’t support malloc for WebAssembly!</em></p>
|
||
<p>We call Zig’s <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(&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>Here’s 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("*** malloc error: out of memory");
|
||
};
|
||
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("*** realloc error: out of memory");
|
||
};
|
||
_ = 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("*** free error: pointer is null");
|
||
}
|
||
// 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 won’t 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>That’s 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> |