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

Add WASM support. #142

Merged
merged 16 commits into from
Oct 8, 2024
40 changes: 40 additions & 0 deletions .github/workflows/wasm.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Simple workflow for deploying static content to GitHub Pages
name: Deploy static content to Pages

on:
push:
branches: ["webasm"]
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'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
79 changes: 44 additions & 35 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,13 @@ set(BUILD_TESTS OFF CACHE BOOL "Build the tests to verify the integrity of the l
add_definitions(-D LODEPNG_NO_COMPILE_ENCODER)
add_definitions(-D LODEPNG_NO_COMPILE_ANCILLARY_CHUNKS)

if(UNIX)
set(CCSHADER ${PROJECT_SOURCE_DIR}/tools/compile_shader.sh)
if(UNIX AND NOT EMSCRIPTEN)
add_compile_options(
-Wextra
-Wall
-Werror
-Wunreachable-code

# Some low priority warnings that are annoying.
-Wno-char-subscripts
-Wno-sign-compare
Expand All @@ -58,31 +57,38 @@ if(UNIX)
endif(DEBUG)
else()
# TODO: Figure out what we need for windows.
set(CCSHADER ${PROJECT_SOURCE_DIR}/tools/compile_shader.bat)
endif()

# Build specific files
# @see https://cmake.org/cmake/help/latest/command/add_custom_command.html
# -----------------------------------------------------------------------------

if (UNIX)
set(CCSHADER ${TOOLS_DIR}/compile_shader.sh)
else()
set(CCSHADER ${TOOLS_DIR}/compile_shader.bat)
endif()

# Add custom command for fragment shader
add_custom_command(
COMMENT "Building fragment shader"
DEPENDS ${PROJECT_SOURCE_DIR}/shaders/default.frag
OUTPUT mlx_frag_shader.c
COMMAND ${CCSHADER} ${PROJECT_SOURCE_DIR}/shaders/default.frag > mlx_frag_shader.c
VERBATIM
PRE_BUILD
USES_TERMINAL
COMMENT "Building fragment shader"
DEPENDS ${PROJECT_SOURCE_DIR}/shaders/default.frag
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/mlx_frag_shader.c
COMMAND ${CCSHADER} ${PROJECT_SOURCE_DIR}/shaders/default.frag ${EMSCRIPTEN} > ${CMAKE_CURRENT_BINARY_DIR}/mlx_frag_shader.c
VERBATIM
PRE_BUILD
USES_TERMINAL
)

# Add custom command for vertex shader
add_custom_command(
COMMENT "Building vertex shader"
DEPENDS ${PROJECT_SOURCE_DIR}/shaders/default.vert
OUTPUT mlx_vert_shader.c
COMMAND ${CCSHADER} ${PROJECT_SOURCE_DIR}/shaders/default.vert > mlx_vert_shader.c
VERBATIM
PRE_BUILD
USES_TERMINAL
COMMENT "Building vertex shader"
DEPENDS ${PROJECT_SOURCE_DIR}/shaders/default.vert
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/mlx_vert_shader.c
COMMAND ${CCSHADER} ${PROJECT_SOURCE_DIR}/shaders/default.vert ${EMSCRIPTEN} > ${CMAKE_CURRENT_BINARY_DIR}/mlx_vert_shader.c
VERBATIM
PRE_BUILD
USES_TERMINAL
)

# Sources
Expand Down Expand Up @@ -125,29 +131,32 @@ target_include_directories(mlx42 PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
# Dependencies
# -----------------------------------------------------------------------------

find_package(glfw3)
find_package(OpenGL REQUIRED)

target_link_libraries(mlx42 OpenGL::GL)
if (NOT glfw3_FOUND AND GLFW_FETCH)
message(STATUS "Install GLFW to suppress this message")
message(STATUS "Please wait, fetching GLFW ...")
include(${CMAKE_DIR}/LinkGLFW.cmake)
LinkGLFW(mlx42)
elseif(NOT glfw3_FOUND AND NOT GLFW_FETCH)
message(FATAL_ERROR "Unable to build: GLFW can't be found nor fetched.")
endif()

if (glfw3_FOUND)
target_link_libraries(mlx42 ${GLFW3_LIBRARY})
endif()
if(APPLE)
target_link_libraries(mlx42 "-framework Cocoa" "-framework IOKit")
if(EMSCRIPTEN)
target_link_libraries(mlx42 "-s USE_GLFW=3" "-s FULL_ES3=1")
else()
target_link_libraries(mlx42 OpenGL::GL)
find_package(glfw3)
if (glfw3_FOUND)
target_link_libraries(mlx42 ${GLFW3_LIBRARY})
endif()
if (NOT glfw3_FOUND AND GLFW_FETCH)
message(STATUS "Install GLFW to suppress this message")
message(STATUS "Please wait, fetching GLFW ...")
include(${CMAKE_DIR}/LinkGLFW.cmake)
LinkGLFW(mlx42)
elseif(NOT glfw3_FOUND AND NOT GLFW_FETCH)
message(FATAL_ERROR "Unable to build: GLFW can't be found nor fetched.")
endif()
if(APPLE)
target_link_libraries(mlx42 "-framework Cocoa" "-framework IOKit")
endif()
endif()

# Testing
# -----------------------------------------------------------------------------
# Only build tests if we are the main project or explicitly told to, make sure
# Only build tests if we are the main project or explicitly told to, make sure
# tests are not built when mlx42 is included as a subproject, use MLX42_BUILD_TESTS to overwrite this
# use cmake -DBUILD_TESTS=ON/-DMLX42_BUILD_TESTS=ON to build tests

Expand Down
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ MLX42 is a performant, easy to use, cross-platform, minimal windowing graphics l

It provides primitive tools to draw textures onto the window as well as modifying them at runtime as they get displayed on the window.

> [!IMPORTANT]
> At times it may seem like no updates have taken place for a long time. This is expected, the project / lib is considered completed and requires minimal updates. Bug fixes are still guaranteed and the project is still being actively maintained.

# Features ✨

MLX42 comes with a plethora of features that make using it actually a joy instead of a chore.
Expand All @@ -39,8 +42,13 @@ It is built on OpenGL and uses batched rendering to speed up the rendering proce
## Open source && Community driven 🌐
This project is being actively maintained by Codam as well as students from the 42 Network. This gives students the direct opportunity to learn more about the library itself as well as fix any potential bugs instead of merely accepting them.

> [!IMPORTANT]
> At times it may seem like no updates have taken place for a long time. This is expected, the project / lib is considered completed and requires minimal updates. Bug fixes are still guaranteed and the project is still being actively maintained.
## Emscripten Compatibility 🚀
MLX42 introduces compatibility with [Emscripten](https://emscripten.org/), allowing MLX42 to run in web browsers through WebAssembly. This modification were made possible thanks to [@PepeLevi](https://github.com/PepeLevi/MLX42_emcc), credits to him for his fork and contributions.

### Highlights
- **Emscripten Support**: Compile MLX42 with Emscripten, enabling graphical applications to run in a web environment.
- **WebAssembly Compatibility**: Ensures that MLX42 can be utilized in modern web browsers, expanding its usability beyond traditional desktop environments.
- **Updated Documentation**: Provided guidance on how to build and run MLX42 projects using Emscripten.

---

Expand Down
117 changes: 65 additions & 52 deletions src/mlx_init.c
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ static bool mlx_create_buffers(mlx_t* mlx)
/**
* Compiles the given shader source code of a given shader type.
* Returns shader object via param.
*
*
* @param code The shader source code.
* @param Type GL_VERTEX_SHADER, GL_FRAGMENT_SHADER, GL_GEOMETRY_SHADER, ...
* @return Non-zero on success, else 0.
Expand All @@ -81,7 +81,7 @@ static uint32_t mlx_compile_shader(const char* code, int32_t type)
int32_t success;
char infolog[512] = {0};

if (!code || (shader = glCreateShader(type)) == 0)
if (!code || (shader = glCreateShader(type)) == 0)
return (0);

GLint len = strlen(code);
Expand All @@ -100,53 +100,56 @@ static uint32_t mlx_compile_shader(const char* code, int32_t type)

static bool mlx_init_render(mlx_t* mlx)
{
uint32_t vshader = 0;
uint32_t fshader = 0;
char infolog[512] = {0};
mlx_ctx_t* mlxctx = mlx->context;

glfwMakeContextCurrent(mlx->window);
glfwSetFramebufferSizeCallback(mlx->window, framebuffer_callback);
glfwSetWindowUserPointer(mlx->window, mlx);
glfwSwapInterval(MLX_SWAP_INTERVAL);

// Load all OpenGL function pointers
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
return (mlx_error(MLX_GLADFAIL));

if (!(vshader = mlx_compile_shader(vert_shader, GL_VERTEX_SHADER)))
return (mlx_error(MLX_VERTFAIL));
if (!(fshader = mlx_compile_shader(frag_shader, GL_FRAGMENT_SHADER)))
return (mlx_error(MLX_FRAGFAIL));
if (!(mlxctx->shaderprogram = glCreateProgram()))
{
glDeleteShader(fshader);
glDeleteShader(vshader);
return (mlx_error(MLX_SHDRFAIL));
}
glAttachShader(mlxctx->shaderprogram, vshader);
glAttachShader(mlxctx->shaderprogram, fshader);
glLinkProgram(mlxctx->shaderprogram);

glDeleteShader(vshader);
glDeleteShader(fshader);
glDetachShader(mlxctx->shaderprogram, vshader);
glDetachShader(mlxctx->shaderprogram, fshader);

int32_t success;
glGetProgramiv(mlxctx->shaderprogram, GL_LINK_STATUS, &success);
if (!success)
{
glGetProgramInfoLog(mlxctx->shaderprogram, sizeof(infolog), NULL, infolog);
fprintf(stderr, "%s", infolog);
return (mlx_error(MLX_SHDRFAIL));
}
glUseProgram(mlxctx->shaderprogram);

for (size_t i = 0; i < 16; i++)
mlxctx->bound_textures[i] = 0;

return (true);
uint32_t vshader = 0;
uint32_t fshader = 0;
char infolog[512] = {0};
mlx_ctx_t* mlxctx = mlx->context;

glfwMakeContextCurrent(mlx->window);
glfwSetFramebufferSizeCallback(mlx->window, framebuffer_callback);
glfwSetWindowUserPointer(mlx->window, mlx);
glfwSwapInterval(MLX_SWAP_INTERVAL);

// Load all OpenGL function pointers
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
return (mlx_error(MLX_GLADFAIL));
if (!(vshader = mlx_compile_shader(vert_shader, GL_VERTEX_SHADER)))
return (mlx_error(MLX_VERTFAIL));
if (!(fshader = mlx_compile_shader(frag_shader, GL_FRAGMENT_SHADER)))
return (mlx_error(MLX_FRAGFAIL));;
if (!(mlxctx->shaderprogram = glCreateProgram()))
{
glDeleteShader(fshader);
glDeleteShader(vshader);
return (mlx_error(MLX_SHDRFAIL));
}
glAttachShader(mlxctx->shaderprogram, vshader);
glAttachShader(mlxctx->shaderprogram, fshader);
glLinkProgram(mlxctx->shaderprogram);

int32_t success;
glGetProgramiv(mlxctx->shaderprogram, GL_LINK_STATUS, &success);
if (!success)
{
glGetProgramInfoLog(mlxctx->shaderprogram, sizeof(infolog), NULL, infolog);
fprintf(stderr, "%s", infolog);
glDeleteProgram(mlxctx->shaderprogram);
glDeleteShader(vshader);
glDeleteShader(fshader);
return (mlx_error(MLX_SHDRFAIL));
}

// Detach shaders after linking but before deleting them
glDetachShader(mlxctx->shaderprogram, vshader);
glDetachShader(mlxctx->shaderprogram, fshader);

// Delete shaders
glDeleteShader(vshader);
glDeleteShader(fshader);
glUseProgram(mlxctx->shaderprogram);
for (size_t i = 0; i < 16; i++)
mlxctx->bound_textures[i] = 0;
return (true);
}

//= Public =//
Expand Down Expand Up @@ -174,25 +177,35 @@ mlx_t* mlx_init(int32_t width, int32_t height, const char* title, bool resize)
return (free(mlx), (void*)mlx_error(MLX_MEMFAIL));

mlx_ctx_t* const mlxctx = mlx->context;
mlx->window = NULL;
mlx->width = width;
mlx->height = height;
mlxctx->initialWidth = width;
mlxctx->initialHeight = height;

#ifdef EMSCRIPTEN
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
glfwWindowHint(GLFW_DECORATED, GLFW_TRUE);
glfwWindowHint(GLFW_VISIBLE, GLFW_TRUE);
glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#else
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_MAXIMIZED, mlx_settings[MLX_MAXIMIZED]);
glfwWindowHint(GLFW_DECORATED, mlx_settings[MLX_DECORATED]);
glfwWindowHint(GLFW_VISIBLE, !mlx_settings[MLX_HEADLESS]);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#endif
#ifdef __APPLE__
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif
glfwWindowHint(GLFW_RESIZABLE, resize);
glfwWindowHint(GLFW_RESIZABLE, resize ? GLFW_TRUE : GLFW_FALSE);
if (!(mlx->window = glfwCreateWindow(width, height, title, mlx_settings[MLX_FULLSCREEN] ? glfwGetPrimaryMonitor() : NULL, NULL)))
return (mlx_terminate(mlx), (void*)mlx_error(MLX_WINFAIL));
return (glfwTerminate(), (void*)mlx_error(MLX_WINFAIL));
if (!mlx_init_render(mlx) || !mlx_create_buffers(mlx))
return (mlx_terminate(mlx), NULL);
glfwMakeContextCurrent(mlx->window);
return (mlx);
}

Expand Down
14 changes: 12 additions & 2 deletions src/mlx_loop.c
Original file line number Diff line number Diff line change
Expand Up @@ -90,17 +90,25 @@ bool mlx_loop_hook(mlx_t* mlx, void (*f)(void*), void* param)
}

// glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
/**
* In Emscripten the lood is defined differently, there the this function
* is passed to the while loop instead
*/
void mlx_loop(mlx_t* mlx)
{
MLX_NONNULL(mlx);

#ifdef EMSCRIPTEN
static double start, oldstart = 0;
#else
double start, oldstart = 0;
while (!glfwWindowShouldClose(mlx->window))
while (!glfwWindowShouldClose(mlx->window))
{
#endif
start = glfwGetTime();
mlx->delta_time = start - oldstart;
oldstart = start;

glClearColor(0.2f, 0.2f, 0.2f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glfwGetWindowSize(mlx->window, &(mlx->width), &(mlx->height));
Expand All @@ -114,5 +122,7 @@ void mlx_loop(mlx_t* mlx)

glfwSwapBuffers(mlx->window);
glfwPollEvents();
#ifndef EMSCRIPTEN
}
#endif
}
Loading
Loading