Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Web support #94

Merged
merged 16 commits into from
Dec 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,8 @@ t2-output
.cxx
build
kk
vc
vc
cmake-build-debug
cmake-build-debug-emscripten
cmake-build-release
cmake-build-release-emscripten
56 changes: 53 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ set(SrcGL
src/gl/MiniFB_GL.c
)

#--
set(SrcWeb
src/web/WebMiniFB.c
)

# Avoid RelWithDebInfo and MinSizeRel
#--------------------------------------
set(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING "" FORCE)
Expand Down Expand Up @@ -110,9 +115,11 @@ if(APPLE AND NOT IOS)
option(USE_METAL_API "Build the project using metal API code" ON)
option(USE_INVERTED_Y_ON_MACOS "Use default mouse position: (0, 0) at (left, down)" OFF)
elseif(UNIX)
option(USE_WAYLAND_API "Build the project using wayland API code" OFF)
if(NOT USE_WAYLAND_API)
option(USE_OPENGL_API "Build the project using OpenGL API code" ON)
if (NOT EMSCRIPTEN)
option(USE_WAYLAND_API "Build the project using wayland API code" OFF)
if(NOT USE_WAYLAND_API)
option(USE_OPENGL_API "Build the project using OpenGL API code" ON)
endif()
endif()
elseif(WIN32)
option(USE_OPENGL_API "Build the project using OpenGL API code" ON)
Expand Down Expand Up @@ -183,6 +190,9 @@ endif()
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
add_definitions(-D_DEBUG)
add_definitions(-DDEBUG)
if(EMSCRIPTEN)
add_link_options(-g)
endif()
endif()

# Set compiler/platform specific flags and dependencies
Expand Down Expand Up @@ -222,6 +232,8 @@ elseif(UNIX)

if(USE_WAYLAND_API)
list(APPEND SrcLib ${SrcWayland})
elseif(EMSCRIPTEN)
list(APPEND SrcLib ${SrcWeb})
else()
if(USE_OPENGL_API)
list(APPEND SrcLib ${SrcGL})
Expand Down Expand Up @@ -266,6 +278,22 @@ elseif(UNIX)
"-lwayland-client"
"-lwayland-cursor"
)
elseif(EMSCRIPTEN)
add_link_options(
"-sSTRICT=1"
"-sENVIRONMENT=web"
"-sLLD_REPORT_UNDEFINED"
"-sMODULARIZE=1"
"-sALLOW_MEMORY_GROWTH=1"
"-sALLOW_TABLE_GROWTH"
"-sMALLOC=emmalloc"
"-sEXPORT_ALL=1"
"-sEXPORTED_FUNCTIONS=[\"_malloc\",\"_free\",\"_main\"]"
"-sEXPORTED_RUNTIME_METHODS=ccall,cwrap"
"-sASYNCIFY"
"--no-entry"
"-sSINGLE_FILE"
)
else()
target_link_libraries(minifb
"-lX11"
Expand Down Expand Up @@ -329,6 +357,28 @@ if(MINIFB_BUILD_EXAMPLES)
tests/fullscreen.c
)

add_executable(timer
tests/timer.c
)

if(EMSCRIPTEN)
add_custom_target(web_assets
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_CURRENT_SOURCE_DIR}/tests/web
${CMAKE_CURRENT_BINARY_DIR}
)
add_dependencies(noise web_assets)
target_link_options(noise PRIVATE "-sEXPORT_NAME=noise")
add_dependencies(input_events web_assets)
target_link_options(input_events PRIVATE "-sEXPORT_NAME=input_events")
add_dependencies(hidpi web_assets)
target_link_options(hidpi PRIVATE "-sEXPORT_NAME=hidpi")
add_dependencies(multiple_windows web_assets)
target_link_options(multiple_windows PRIVATE "-sEXPORT_NAME=multiple_windows")
add_dependencies(timer web_assets)
target_link_options(timer PRIVATE "-sEXPORT_NAME=timer")
endif()

else()

add_executable(noise
Expand Down
113 changes: 112 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,9 @@ See https://github.com/emoon/minifb/blob/master/tests/noise.c for a complete exa
- Wayland (Linux) [there are some issues]
- iOS (beta)
- Android (beta)
- Web (WASM) (beta)

MiniFB has been tested on Windows, Mac OS X, Linux, iOS and Android but may of course have trouble depending on your setup. Currently the code will not do any converting of data if not a proper 32-bit display can be created.
MiniFB has been tested on Windows, Mac OS X, Linux, iOS, Android and web but may of course have trouble depending on your setup. Currently the code will not do any converting of data if not a proper 32-bit display can be created.

# Features:

Expand Down Expand Up @@ -512,6 +513,116 @@ cd build
cmake .. -DUSE_WAYLAND_API=ON
```

## Web (WASM)
Download and install [Emscripten](https://emscripten.org/). When configuring your CMake build, specify the Emscripten toolchain file. Then proceed to build as usual.

### Building and running the examples

```bash
cmake -DCMAKE_TOOLCHAIN_FILE=/path/to/emsdk/<version>/emscripten/cmake/Modules/Platform/Emscripten.cmake -S . -B build
cmake --build build
```

> *Note*: On Windows, you will need a build tool other than Visual Studio. [Ninja](https://ninja-build.org/) is the best and easiest option. Simply download it, put the `ninja.exe` executable somewhere, and make it available on the command line via your `PATH` environment variable. Then invoke the first command above with the addition of `-G Ninja` at the end.

Then open the file `build/index.html` in your browser to view the example index.

The examples are build using the Emscripten flag `-sSINGLE_FILE`, which will coalesce the `.js` and `.wasm` files into a single `.js` file. If you build your own apps without the `-sSINGLE_FILE` flag, you can not simply open the `.html` file in the browser from disk. Instead, you need an HTTP server to serve the build output. The simplest solution for that is Python's `http.server` module:

```
python3 -m http.server build/
```

You can then open the index at [http://localhost:8000](http://localhost:8000) in your browser.

### Integrating a MiniFB app in a website
To build an executable target for the web, you need to add a linker option specifying its module name, e.g.:

```
target_link_options(my_app PRIVATE "-sEXPORT_NAME=my_app")
```

The Emscripten toolchain will then build a `my_app.wasm` and `my_app.js` file containing your app's WASM code and JavaScript glue code to load the WASM file and run it. To load and run your app, you need to:

1. Create a `<canvas>` element with an `id` attribute matching the `title` you specify when calling `mfb_open_window()` or `mfb_open_window_ex()`.
2. Call the `<my_module_name>()` in JavaScript.

Example app:

```c
int main() {
struct mfb_window *window = mfb_open_ex("my_app", 320, 240);
if (!window)
return 0;
uint32_t *buffer = (uint32_t *) malloc(g_width * g_height * 4);
mfb_update_state state;
do {
state = mfb_update_ex(window, buffer, 320, 200);
if (state != STATE_OK) {
break;
}
} while(mfb_wait_sync(window));
return 0;
}
```

Assuming the build will generate `my_app.wasm` and `my_app.js`, the simplest `.html` file to load and run the app would look like this:

```html
<html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset='utf-8'>
<meta http-equiv='X-UA-Compatible' content='IE=edge'>
<meta name='viewport' content='width=device-width, initial-scale=1'>
<!-- Load the app's .js file -->
<script src="./my_app.js"></script>
</head>
<body>
<div>
<canvas id="Noise Test" style="background: #000;"></canvas>
</div>
<script>
// Call the app's main() function
noise();
</script>
</body>
</html>
```

### Limitations & caveats
The web backend currently does not support the following MiniFB features:

* The flags to `mfb_open_ex()` are ignored
* `mfb_set_viewport()` (no-op)
* `mfb_set_viewport_best_fit()` (no-op)
* `mfb_get_monitor_dpi()` (reports a fixed value)
* `mfb_get_monitor_scale()` (reports a fixed value)
* `mfb_set_target_fps()` (no-op)
* `mfb_get_target_fps()` (no-op)

Everything else is supported.

When calling `mfb_open()` or `mfb_open_ex()`, the specified title must match the `id` attribute of a `<canvas>` element in the DOM. The functions will modify the `width` and `height` attribute of the `<canvas>` element. If not already set, then the functions will also modify the CSS style `width` and `height` attributes of the canvas.

Setting the CSS width and height of the canvas allows you to up-scale the framebuffer arbitrarily:

```
// Request a 320x240 window
mfb_open("my_app", 320, 240);

// Up-scale 2x via CSS
<canvas id="my_app" style="width: 640px; height: 480px">
````

If not already set, the backend will also set a handfull of CSS styles on the canvas that are good defaults for pixel graphics.

* `image-rendering: pixelated`
* `user-select: none`
* `border: none`
* `outline-style: none`;

# How to add it to your project

First add this **repository as a submodule** in your dependencies folder. Something like `dependencies/`:
Expand Down
11 changes: 7 additions & 4 deletions include/MiniFB.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@ extern "C" {

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#ifndef __ANDROID__
#define MFB_RGB(r, g, b) (((uint32_t) r) << 16) | (((uint32_t) g) << 8) | ((uint32_t) b)
#define MFB_RGB(r, g, b) (((uint32_t) r) << 16) | (((uint32_t) g) << 8) | ((uint32_t) b)
#define MFB_ARGB(a, r, g, b) (((uint32_t) a) << 24) | (((uint32_t) r) << 16) | (((uint32_t) g) << 8) | ((uint32_t) b)
#else
#ifdef HOST_WORDS_BIGENDIAN
#define MFB_RGB(r, g, b) (((uint32_t) r) << 16) | (((uint32_t) g) << 8) | ((uint32_t) b)
#define MFB_RGB(r, g, b) (((uint32_t) r) << 16) | (((uint32_t) g) << 8) | ((uint32_t) b)
#define MFB_ARGB(a, r, g, b) (((uint32_t) a) << 24) | (((uint32_t) r) << 16) | (((uint32_t) g) << 8) | ((uint32_t) b)
#else
#define MFB_RGB(r, g, b) (((uint32_t) b) << 16) | (((uint32_t) g) << 8) | ((uint32_t) r)
#define MFB_ARGB(r, g, b) (((uint32_t) a) << 24) | (((uint32_t) b) << 16) | (((uint32_t) g) << 8) | ((uint32_t) r)
#define MFB_RGB(r, g, b) (((uint32_t) b) << 16) | (((uint32_t) g) << 8) | ((uint32_t) r)
#endif
#endif

Expand Down Expand Up @@ -78,7 +81,7 @@ const uint8_t * mfb_get_key_buffer(struct mfb_window *window); // O

// FPS
void mfb_set_target_fps(uint32_t fps);
unsigned mfb_get_target_fps();
unsigned mfb_get_target_fps(void);
bool mfb_wait_sync(struct mfb_window *window);

// Timer
Expand Down
Loading