Skip to content

Port minimal_glfw_gl to Emscripten #758

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

Merged
merged 3 commits into from
Jul 5, 2025
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: 4 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,12 @@ jobs:
- name: Check format
continue-on-error: true
run: zig fmt --check .
- name: Build all
- name: Build all native
run: zig build -Dexperiments
- name: Cross compile
- name: Cross compile Linux->Windows
if: runner.os == 'Linux'
run: zig build -Dtarget=x86_64-windows-gnu
- name: Build for Web
run: zig build -Dtarget=wasm32-emscripten


2 changes: 1 addition & 1 deletion build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,9 @@ pub const samples = struct {
/// Sample apps that can be built as web applications using zemscripten.
pub const web = struct {
pub const sdl2_demo = samples.crossplatform.sdl2_demo;
pub const minimal_glfw_gl = samples.crossplatform.minimal_glfw_gl;

// TODO: WebGL samples
// pub const minimal_glfw_gl = samples.crossplatform.minimal_glfw_gl;
// pub const minimal_sdl_gl = samples.crossplatform.minimal_sdl_gl;
// pub const minimal_zgui_glfw_gl = samples.crossplatform.minimal_zgui_glfw_gl;

Expand Down
4 changes: 2 additions & 2 deletions build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@
},

.zemscripten = .{
.url = "https://github.com/zig-gamedev/zemscripten/archive/cac24b72f87432ce607c02abcfad7822b702aad9.tar.gz",
.hash = "zemscripten-0.2.0-dev-sRlDqBtLAADUk3IZprWI0srnchHAXRSQu1n4wZiRvXmr",
.url = "https://github.com/zig-gamedev/zemscripten/archive/dce5c0b043ff8cc8ae1bd2eb597fd86bfa94591c.tar.gz",
.hash = "zemscripten-0.2.0-dev-sRlDqKdPAACNU94D6IU-7WYWoJhtCS7Yc28hjqOHVA5T",
},
.emsdk = .{
.url = "https://github.com/emscripten-core/emsdk/archive/refs/tags/4.0.3.tar.gz",
Expand Down
2 changes: 1 addition & 1 deletion libs/zemscripten
Submodule zemscripten updated 1 files
+41 −10 build.zig
54 changes: 53 additions & 1 deletion samples/minimal_glfw_gl/build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ pub fn build(b: *std.Build, options: anytype) *std.Build.Step.Compile {
const src_path = b.pathJoin(&.{ cwd_path, "src" });
const exe = b.addExecutable(.{
.name = demo_name,
.root_source_file = b.path(b.pathJoin(&.{ src_path, demo_name ++ ".zig" })),
.root_source_file = b.path(b.pathJoin(&.{ src_path, "main.zig" })),
.target = options.target,
.optimize = options.optimize,
});
Expand All @@ -34,3 +34,55 @@ pub fn build(b: *std.Build, options: anytype) *std.Build.Step.Compile {

return exe;
}

pub fn buildWeb(b: *std.Build, options: anytype) *std.Build.Step {
const cwd_path = b.pathJoin(&.{ "samples", demo_name });
const src_path = b.pathJoin(&.{ cwd_path, "src" });

const zemscripten = @import("zemscripten");

const zglfw = b.dependency("zglfw", .{
.target = options.target,
});
const zopengl = b.dependency("zopengl", .{
.target = options.target,
});

const wasm = b.addStaticLibrary(.{
.name = demo_name,
.root_source_file = b.path(b.pathJoin(&.{ src_path, "main-web.zig" })),
.target = options.target,
.optimize = options.optimize,
});

wasm.root_module.addImport("zglfw", zglfw.module("root"));

wasm.root_module.addImport("zopengl", zopengl.module("root"));

wasm.root_module.addImport("zemscripten", b.dependency("zemscripten", .{}).module("root"));

const emcc_flags = zemscripten.emccDefaultFlags(b.allocator, .{
.optimize = options.optimize,
.fsanitize = true,
});

var emcc_settings = zemscripten.emccDefaultSettings(b.allocator, .{
.optimize = options.optimize,
});
emcc_settings.put("ALLOW_MEMORY_GROWTH", "1") catch unreachable;
emcc_settings.put("USE_GLFW", "3") catch unreachable;
emcc_settings.put("MIN_WEBGL_VERSION", "2") catch unreachable;
emcc_settings.put("MAX_WEBGL_VERSION", "2") catch unreachable;
emcc_settings.put("FULL_ES2", "1") catch unreachable;

return zemscripten.emccStep(
b,
wasm,
.{
.optimize = options.optimize,
.flags = emcc_flags,
.settings = emcc_settings,
.install_dir = .{ .custom = "web" },
},
);
}
33 changes: 33 additions & 0 deletions samples/minimal_glfw_gl/src/main-web.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const std = @import("std");

const zemscripten = @import("zemscripten");
pub const panic = zemscripten.panic;

pub const std_options = std.Options{
.logFn = zemscripten.log,
};

const minimal_glfw_gl = @import("minimal_glfw_gl.zig");

var initialised = false;

export fn main() c_int {
zemscripten.setMainLoop(mainLoopCallback, null, false);
return 0;
}

export fn mainLoopCallback() void {
if (initialised == false) {
minimal_glfw_gl.init(.{
.api = .opengl_es_api,
.version_major = 2,
.version_minor = 0,
}) catch |err| {
std.log.err("minimal_glfw_gl.init failed with error: {s}", .{@errorName(err)});
return;
};
initialised = true;
}

minimal_glfw_gl.updateAndRender();
}
22 changes: 22 additions & 0 deletions samples/minimal_glfw_gl/src/main.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const std = @import("std");

const minimal_glfw_gl = @import("minimal_glfw_gl.zig");

pub fn main() !void {
{ // Change current working directory to where the executable is located.
var buffer: [1024]u8 = undefined;
const path = std.fs.selfExeDirPath(buffer[0..]) catch ".";
try std.posix.chdir(path);
}

try minimal_glfw_gl.init(.{
.api = .opengl_api,
.version_major = 4,
.version_minor = 0,
});
defer minimal_glfw_gl.deinit();

while (minimal_glfw_gl.shouldQuit() == false) {
minimal_glfw_gl.updateAndRender();
}
}
62 changes: 45 additions & 17 deletions samples/minimal_glfw_gl/src/minimal_glfw_gl.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,63 @@ const std = @import("std");
const glfw = @import("zglfw");
const zopengl = @import("zopengl");

pub fn main() !void {
var window: *glfw.Window = undefined;

pub fn init(gl_settings: struct {
api: glfw.ClientApi,
version_major: u16,
version_minor: u16,
}) !void {
try glfw.init();
defer glfw.terminate();

const gl_major = 4;
const gl_minor = 0;
glfw.windowHint(.context_version_major, gl_major);
glfw.windowHint(.context_version_minor, gl_minor);
glfw.windowHint(.client_api, gl_settings.api);
glfw.windowHint(.context_version_major, gl_settings.version_major);
glfw.windowHint(.context_version_minor, gl_settings.version_minor);
glfw.windowHint(.opengl_profile, .opengl_core_profile);
glfw.windowHint(.opengl_forward_compat, true);
glfw.windowHint(.client_api, .opengl_api);
glfw.windowHint(.doublebuffer, true);

const window = try glfw.Window.create(600, 600, "zig-gamedev: minimal_glfw_gl", null);
defer window.destroy();
window = try glfw.Window.create(600, 600, "zig-gamedev: minimal_glfw_gl", null);

glfw.makeContextCurrent(window);

try zopengl.loadCoreProfile(glfw.getProcAddress, gl_major, gl_minor);

const gl = zopengl.bindings;
switch (gl_settings.api) {
.no_api => unreachable,
.opengl_api => {
try zopengl.loadCoreProfile(
glfw.getProcAddress,
gl_settings.version_major,
gl_settings.version_minor,
);
},
.opengl_es_api => {
try zopengl.loadEsProfile(
glfw.getProcAddress,
gl_settings.version_major,
gl_settings.version_minor,
);
},
}

glfw.swapInterval(1);
}

while (!window.shouldClose()) {
glfw.pollEvents();
pub fn deinit() void {
window.destroy();
glfw.terminate();
}

gl.clearBufferfv(gl.COLOR, 0, &[_]f32{ 0.2, 0.6, 0.4, 1.0 });
pub fn shouldQuit() bool {
return window.shouldClose();
}

window.swapBuffers();
}
pub fn updateAndRender() void {
glfw.pollEvents();

const gl = zopengl.bindings;

gl.clearColor(0.12, 0.24, 0.36, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);

window.swapBuffers();
}
Loading