Añadiendo mlx
This commit is contained in:
151
lib/mlx/web/README.md
Normal file
151
lib/mlx/web/README.md
Normal file
@@ -0,0 +1,151 @@
|
||||
# Web
|
||||
|
||||
MLX42 supports compilation towards [WASM](https://webassembly.org/). What this means is you can run any application written in C directly in the browser!
|
||||
This overcomes a lot of issues with for instance showing projects towards others or have an environment where building natively just won't work.
|
||||
|
||||
In this README you will learn how to compile your project towards Webassembly and later deploy it on github!
|
||||
|
||||
## Pre-requisites
|
||||
|
||||
- [Emscripten](https://emscripten.org/), you can install this via brew or read the instructions they provide for [Windows or Linux](https://emscripten.org/docs/getting_started/downloads.html)
|
||||
|
||||
## Building
|
||||
|
||||
Once you made sure you have emscripten installed (check if `emcc` and `emcmake` work).
|
||||
|
||||
Run:
|
||||
```bash
|
||||
emcmake cmake -B build && cmake --build build --parallel
|
||||
```
|
||||
|
||||
## Modifications
|
||||
|
||||
You're only required to do a few modifications to your `main.c`.
|
||||
For this we will use the demo main provided in the root [readme](../README.md).
|
||||
|
||||
Add the following headers at the top:
|
||||
```c
|
||||
#include <emscripten/html5.h>
|
||||
#include <emscripten/emscripten.h>
|
||||
```
|
||||
|
||||
Modify your main:
|
||||
```c
|
||||
// Invoked instead of mlx_loop directly.
|
||||
void emscripten_main_loop() {
|
||||
mlx_loop(mlx);
|
||||
}
|
||||
|
||||
int32_t main(int argc, char **argv)
|
||||
{
|
||||
// Gotta error check this stuff
|
||||
if (!(mlx = mlx_init(WIDTH, HEIGHT, "MLX42", true)))
|
||||
{
|
||||
puts(mlx_strerror(mlx_errno));
|
||||
return(EXIT_FAILURE);
|
||||
}
|
||||
if (!(image = mlx_new_image(mlx, 128, 128)))
|
||||
{
|
||||
mlx_close_window(mlx);
|
||||
puts(mlx_strerror(mlx_errno));
|
||||
return(EXIT_FAILURE);
|
||||
}
|
||||
if (mlx_image_to_window(mlx, image, 0, 0) == -1)
|
||||
{
|
||||
mlx_close_window(mlx);
|
||||
puts(mlx_strerror(mlx_errno));
|
||||
return(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
mlx_loop_hook(mlx, ft_randomize, mlx);
|
||||
mlx_loop_hook(mlx, ft_hook, mlx);
|
||||
|
||||
// This function will set up the main loop
|
||||
emscripten_set_main_loop(emscripten_main_loop, 0, true);
|
||||
mlx_terminate(mlx);
|
||||
return (EXIT_SUCCESS);
|
||||
}
|
||||
```
|
||||
|
||||
Thats actually it! It may or may not be necessary to modify your own source code depending on what you do but that's most often not the case.
|
||||
It is that easy to just re-deploy your own app into webassembly.
|
||||
|
||||
## Building
|
||||
|
||||
```bash
|
||||
# Compile C into JS/WASM
|
||||
emcc -O3 -I include -I mlx -pthread main.c \
|
||||
-o ./web/demo.js \
|
||||
./build/libmlx42.a \
|
||||
-s USE_GLFW=3 -s USE_WEBGL2=1 -s FULL_ES3=1 -s WASM=1 \
|
||||
-s NO_EXIT_RUNTIME=1 -s EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]' \
|
||||
-s ALLOW_MEMORY_GROWTH
|
||||
|
||||
# Navigate into the web folder (if you're running this directly from this repo)
|
||||
cd web
|
||||
|
||||
# Launch local webserver, this is required to make the service worker function.
|
||||
python3 -m http.server 8000
|
||||
```
|
||||
|
||||
Once the server is up and running all you need to do now is go to [localhost](http://localhost:8000/index.html).
|
||||
There you can locally develop your application without having to do any git commits or actions shenanigans.
|
||||
|
||||
# Deploying to Github Pages
|
||||
|
||||
For a free, quick and easy hosting solution you can realistically deploy this anywhere.
|
||||
However for now we will only focus on putting this up via github pages.
|
||||
|
||||
What you need in your repository is a `.github/workflows/static.yml` file.
|
||||
It can be named anything `static`, `ci`, whatever. Later on if you learn more about CI Pipelines you can use this to do a lot of useful things.
|
||||
|
||||
## Enabling github pages
|
||||
Follow this step: https://docs.github.com/en/pages/getting-started-with-github-pages/configuring-a-publishing-source-for-your-github-pages-site#publishing-with-a-custom-github-actions-workflow
|
||||
|
||||
Once selected, you need to commit an actions file.
|
||||
For now you can copy paste MLX42's `wasm.yml` file which functionally does the exact same.
|
||||
```yml
|
||||
# Simple workflow for deploying static content to GitHub Pages
|
||||
name: Deploy static content to Pages
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["master"] # Change to main or whatever fancy name
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
|
||||
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
|
||||
concurrency:
|
||||
group: "pages"
|
||||
cancel-in-progress: false
|
||||
|
||||
# Single deploy job since we're just deploying
|
||||
jobs:
|
||||
deploy:
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
runs-on: ubuntu-latest
|
||||
#TODO: add a build step to get the wasm file instead of commiting it.
|
||||
#Doesn't really matter atm since the git history is polluted anyway
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v5
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: './web' # <= Set this variable to the directory relative to the root of the repo.
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
```
|
||||
|
||||
Once you commit this file github will do it's magic and create a deployment.
|
||||
You should then get a link to where you can access you program. Now you can access your app anywhere!
|
||||
146
lib/mlx/web/coi-serviceworker.js
Normal file
146
lib/mlx/web/coi-serviceworker.js
Normal file
@@ -0,0 +1,146 @@
|
||||
/*! coi-serviceworker v0.1.7 - Guido Zuidhof and contributors, licensed under MIT */
|
||||
let coepCredentialless = false;
|
||||
if (typeof window === 'undefined') {
|
||||
self.addEventListener("install", () => self.skipWaiting());
|
||||
self.addEventListener("activate", (event) => event.waitUntil(self.clients.claim()));
|
||||
|
||||
self.addEventListener("message", (ev) => {
|
||||
if (!ev.data) {
|
||||
return;
|
||||
} else if (ev.data.type === "deregister") {
|
||||
self.registration
|
||||
.unregister()
|
||||
.then(() => {
|
||||
return self.clients.matchAll();
|
||||
})
|
||||
.then(clients => {
|
||||
clients.forEach((client) => client.navigate(client.url));
|
||||
});
|
||||
} else if (ev.data.type === "coepCredentialless") {
|
||||
coepCredentialless = ev.data.value;
|
||||
}
|
||||
});
|
||||
|
||||
self.addEventListener("fetch", function (event) {
|
||||
const r = event.request;
|
||||
if (r.cache === "only-if-cached" && r.mode !== "same-origin") {
|
||||
return;
|
||||
}
|
||||
|
||||
const request = (coepCredentialless && r.mode === "no-cors")
|
||||
? new Request(r, {
|
||||
credentials: "omit",
|
||||
})
|
||||
: r;
|
||||
event.respondWith(
|
||||
fetch(request)
|
||||
.then((response) => {
|
||||
if (response.status === 0) {
|
||||
return response;
|
||||
}
|
||||
|
||||
const newHeaders = new Headers(response.headers);
|
||||
newHeaders.set("Cross-Origin-Embedder-Policy",
|
||||
coepCredentialless ? "credentialless" : "require-corp"
|
||||
);
|
||||
if (!coepCredentialless) {
|
||||
newHeaders.set("Cross-Origin-Resource-Policy", "cross-origin");
|
||||
}
|
||||
newHeaders.set("Cross-Origin-Opener-Policy", "same-origin");
|
||||
|
||||
return new Response(response.body, {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
headers: newHeaders,
|
||||
});
|
||||
})
|
||||
.catch((e) => console.error(e))
|
||||
);
|
||||
});
|
||||
|
||||
} else {
|
||||
(() => {
|
||||
const reloadedBySelf = window.sessionStorage.getItem("coiReloadedBySelf");
|
||||
window.sessionStorage.removeItem("coiReloadedBySelf");
|
||||
const coepDegrading = (reloadedBySelf == "coepdegrade");
|
||||
|
||||
// You can customize the behavior of this script through a global `coi` variable.
|
||||
const coi = {
|
||||
shouldRegister: () => !reloadedBySelf,
|
||||
shouldDeregister: () => false,
|
||||
coepCredentialless: () => true,
|
||||
coepDegrade: () => true,
|
||||
doReload: () => window.location.reload(),
|
||||
quiet: false,
|
||||
...window.coi
|
||||
};
|
||||
|
||||
const n = navigator;
|
||||
const controlling = n.serviceWorker && n.serviceWorker.controller;
|
||||
|
||||
// Record the failure if the page is served by serviceWorker.
|
||||
if (controlling && !window.crossOriginIsolated) {
|
||||
window.sessionStorage.setItem("coiCoepHasFailed", "true");
|
||||
}
|
||||
const coepHasFailed = window.sessionStorage.getItem("coiCoepHasFailed");
|
||||
|
||||
if (controlling) {
|
||||
// Reload only on the first failure.
|
||||
const reloadToDegrade = coi.coepDegrade() && !(
|
||||
coepDegrading || window.crossOriginIsolated
|
||||
);
|
||||
n.serviceWorker.controller.postMessage({
|
||||
type: "coepCredentialless",
|
||||
value: (reloadToDegrade || coepHasFailed && coi.coepDegrade())
|
||||
? false
|
||||
: coi.coepCredentialless(),
|
||||
});
|
||||
if (reloadToDegrade) {
|
||||
!coi.quiet && console.log("Reloading page to degrade COEP.");
|
||||
window.sessionStorage.setItem("coiReloadedBySelf", "coepdegrade");
|
||||
coi.doReload("coepdegrade");
|
||||
}
|
||||
|
||||
if (coi.shouldDeregister()) {
|
||||
n.serviceWorker.controller.postMessage({ type: "deregister" });
|
||||
}
|
||||
}
|
||||
|
||||
// If we're already coi: do nothing. Perhaps it's due to this script doing its job, or COOP/COEP are
|
||||
// already set from the origin server. Also if the browser has no notion of crossOriginIsolated, just give up here.
|
||||
if (window.crossOriginIsolated !== false || !coi.shouldRegister()) return;
|
||||
|
||||
if (!window.isSecureContext) {
|
||||
!coi.quiet && console.log("COOP/COEP Service Worker not registered, a secure context is required.");
|
||||
return;
|
||||
}
|
||||
|
||||
// In some environments (e.g. Firefox private mode) this won't be available
|
||||
if (!n.serviceWorker) {
|
||||
!coi.quiet && console.error("COOP/COEP Service Worker not registered, perhaps due to private mode.");
|
||||
return;
|
||||
}
|
||||
|
||||
n.serviceWorker.register(window.document.currentScript.src).then(
|
||||
(registration) => {
|
||||
!coi.quiet && console.log("COOP/COEP Service Worker registered", registration.scope);
|
||||
|
||||
registration.addEventListener("updatefound", () => {
|
||||
!coi.quiet && console.log("Reloading page to make use of updated COOP/COEP Service Worker.");
|
||||
window.sessionStorage.setItem("coiReloadedBySelf", "updatefound");
|
||||
coi.doReload();
|
||||
});
|
||||
|
||||
// If the registration is active, but it's not controlling the page
|
||||
if (registration.active && !n.serviceWorker.controller) {
|
||||
!coi.quiet && console.log("Reloading page to make use of COOP/COEP Service Worker.");
|
||||
window.sessionStorage.setItem("coiReloadedBySelf", "notcontrolling");
|
||||
coi.doReload();
|
||||
}
|
||||
},
|
||||
(err) => {
|
||||
!coi.quiet && console.error("COOP/COEP Service Worker failed to register:", err);
|
||||
}
|
||||
);
|
||||
})();
|
||||
}
|
||||
1
lib/mlx/web/demo.js
Normal file
1
lib/mlx/web/demo.js
Normal file
File diff suppressed because one or more lines are too long
BIN
lib/mlx/web/demo.wasm
Executable file
BIN
lib/mlx/web/demo.wasm
Executable file
Binary file not shown.
147
lib/mlx/web/index.html
Normal file
147
lib/mlx/web/index.html
Normal file
@@ -0,0 +1,147 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>WebAssembly Test</title>
|
||||
<style>
|
||||
body {
|
||||
margin: auto;
|
||||
padding-top: 1rem;
|
||||
width: 100%;
|
||||
min-height: 100dvh;
|
||||
background-color: #121212;
|
||||
color: #ffffff;
|
||||
font-family: 'Arial', sans-serif;
|
||||
max-width: 65rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
header {
|
||||
text-align: center;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
font-size: 2.5em;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
a {
|
||||
color: #1e90ff;
|
||||
text-decoration: none;
|
||||
font-size: 1.1em;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #00bfff;
|
||||
}
|
||||
|
||||
.canvas-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
canvas {
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.7);
|
||||
border: 2px solid #1e90ff;
|
||||
}
|
||||
|
||||
.loading {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-size: 1.5em;
|
||||
color: #1e90ff;
|
||||
font-weight: bold;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.description {
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.5);
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
.description p {
|
||||
line-height: 1.6;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.instructions {
|
||||
text-align: center;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.instructions p {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
kbd {
|
||||
background-color: #333;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #666;
|
||||
box-shadow: 0 1px 0 #666, 0 1px 0 1px rgba(255, 255, 255, 0.1);
|
||||
color: #fff;
|
||||
display: inline-block;
|
||||
font-size: 1em;
|
||||
line-height: 1.4;
|
||||
padding: 2px 6px;
|
||||
margin: 0 2px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<h1>WebAssembly Test</h1>
|
||||
<a href="https://github.com/codam-coding-college/MLX42" target="_blank">Visit MLX42 GitHub Repo</a>
|
||||
</header>
|
||||
<section class="description">
|
||||
<p>
|
||||
This little demo demonstrates how you compile MLX42 using emscripten to leverage the power of WebAssembly
|
||||
and run any graphical project directly in the web!
|
||||
</p>
|
||||
</section>
|
||||
<div class="canvas-container">
|
||||
<span class="loading">Loading...</span>
|
||||
<canvas width="1024" height="1024" id="canvas"></canvas>
|
||||
</div>
|
||||
<div class="instructions">
|
||||
<p>Use <kbd>↑</kbd> <kbd>↓</kbd> <kbd>←</kbd> <kbd>→</kbd> to move the element in the canvas.</p>
|
||||
</div>
|
||||
<script src="coi-serviceworker.js"></script>
|
||||
<script>
|
||||
var Module = {
|
||||
// This is called when the Emscripten module is ready
|
||||
onRuntimeInitialized: () => {
|
||||
console.log("Emscripten module initialized");
|
||||
var loadingText = document.querySelector('.loading');
|
||||
loadingText.style.display = 'none';
|
||||
},
|
||||
canvas: (() => {
|
||||
var canvas = document.getElementById('canvas');
|
||||
canvas.addEventListener("webglcontextlost", function (e) {
|
||||
alert('WebGL context lost. Reload the page.');
|
||||
e.preventDefault();
|
||||
}, false);
|
||||
return canvas;
|
||||
})()
|
||||
};
|
||||
</script>
|
||||
<script src="demo.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user