aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYaroslav de la Peña Smirnov <yps@yaroslavps.com>2024-09-18 00:02:33 +0300
committerYaroslav de la Peña Smirnov <yps@yaroslavps.com>2024-09-21 18:06:33 +0300
commit5b1d5fc0325dc5e20ccb7b1ce445721762256848 (patch)
tree89e8eaa3dcecfb62a776371319bc6a3bbcf9ee06
parent65b93e57a8caac901348d373bf49df3ebb509984 (diff)
downloadzigway-5b1d5fc0325dc5e20ccb7b1ce445721762256848.tar.gz
zigway-5b1d5fc0325dc5e20ccb7b1ce445721762256848.zip
opengl: triangle
OpenGL hello world triangle on Wayland.
-rw-r--r--build.zig3
-rw-r--r--src/egl.zig186
-rw-r--r--src/main.zig298
3 files changed, 377 insertions, 110 deletions
diff --git a/build.zig b/build.zig
index 5e885fc..a41680b 100644
--- a/build.zig
+++ b/build.zig
@@ -28,6 +28,9 @@ pub fn build(b: *std.Build) void {
exe.root_module.addImport("wayland", wayland);
exe.linkLibC();
exe.linkSystemLibrary("wayland-client");
+ exe.linkSystemLibrary("wayland-egl");
+ exe.linkSystemLibrary("egl");
+ exe.linkSystemLibrary("gl");
scanner.addCSource(exe);
diff --git a/src/egl.zig b/src/egl.zig
new file mode 100644
index 0000000..ad5be7c
--- /dev/null
+++ b/src/egl.zig
@@ -0,0 +1,186 @@
+const c = @cImport({
+ @cInclude("EGL/egl.h");
+ @cInclude("GL/gl.h");
+});
+
+pub const Error = error{
+ Unknown,
+ NoDisplay,
+ NotInitialized,
+ BadAccess,
+ BadAlloc,
+ BadAttribute,
+ BadContext,
+ BadConfig,
+ BadCurrentSurface,
+ BadDisplay,
+ BadMatch,
+ BadNativePixmap,
+ BadNativeWindow,
+ BadParameter,
+ BadSurface,
+ ContextLost,
+};
+
+extern fn eglGetError() i32;
+fn getError() Error!void {
+ return switch (eglGetError()) {
+ c.EGL_NOT_INITIALIZED => Error.NotInitialized,
+ c.EGL_BAD_ACCESS => Error.BadDisplay,
+ c.EGL_BAD_ATTRIBUTE => Error.BadAttribute,
+ c.EGL_BAD_CONFIG => Error.BadConfig,
+ c.EGL_BAD_CONTEXT => Error.BadContext,
+ c.EGL_BAD_CURRENT_SURFACE => Error.BadCurrentSurface,
+ c.EGL_BAD_DISPLAY => Error.BadDisplay,
+ c.EGL_BAD_MATCH => Error.BadMatch,
+ c.EGL_BAD_NATIVE_PIXMAP => Error.BadNativePixmap,
+ c.EGL_BAD_NATIVE_WINDOW => Error.BadNativeWindow,
+ c.EGL_BAD_PARAMETER => Error.BadParameter,
+ c.EGL_BAD_SURFACE => Error.BadSurface,
+ c.EGL_CONTEXT_LOST => Error.ContextLost,
+ else => Error.Unknown,
+ };
+}
+
+pub const Api = enum(u32) {
+ opengl = c.EGL_OPENGL_API,
+ opengl_es = c.EGL_OPENGL_ES_API,
+};
+
+extern fn eglBindAPI(api: Api) bool;
+pub fn bindApi(api: Api) Error!void {
+ if (!eglBindAPI(api)) try getError();
+}
+
+pub const AttributeType = enum(i32) {
+ none = c.EGL_NONE,
+ alpha_size = c.EGL_ALPHA_SIZE,
+ red_size = c.EGL_RED_SIZE,
+ green_size = c.EGL_GREEN_SIZE,
+ blue_size = c.EGL_BLUE_SIZE,
+};
+
+pub const Attribute = packed struct {
+ type: AttributeType,
+ value: i32,
+};
+
+pub const Config = opaque {};
+
+pub const Surface = opaque {};
+
+pub const Context = opaque {};
+
+pub const Display = opaque {
+ extern fn eglGetDisplay(native_display: *anyopaque) ?*Display;
+ pub fn get(native_display: *anyopaque) Error!*Display {
+ return eglGetDisplay(
+ native_display,
+ ) orelse Error.NoDisplay;
+ }
+
+ extern fn eglInitialize(display: *Display, major: ?*i32, minor: ?*i32) bool;
+ pub fn initialize(display: *Display, major: ?*i32, minor: ?*i32) Error!void {
+ if (!eglInitialize(display, major, minor)) try getError();
+ }
+
+ extern fn eglChooseConfig(
+ display: *Display,
+ attributes: [*c]Attribute,
+ configs: [*c]*Config,
+ configs_len: i32,
+ config_cnt: *i32,
+ ) bool;
+ /// Returns the number of configs that were put in @configs.
+ pub fn chooseConfig(
+ display: *Display,
+ attributes: []Attribute,
+ configs: []*Config,
+ ) Error!i32 {
+ var config_cnt: i32 = 0;
+ if (!eglChooseConfig(
+ display,
+ attributes.ptr,
+ configs.ptr,
+ @intCast(configs.len),
+ &config_cnt,
+ )) {
+ try getError();
+ }
+ return config_cnt;
+ }
+
+ extern fn eglCreateContext(
+ display: *Display,
+ config: *Config,
+ share_ctx: ?*Context,
+ attributes: [*c]Attribute,
+ ) ?*Context;
+ pub fn createContext(
+ display: *Display,
+ config: *Config,
+ share_ctx: ?*Context,
+ attributes: ?[]Attribute,
+ ) Error!*Context {
+ var attrs: [*c]Attribute = null;
+ if (attributes) |_attrs| attrs = _attrs.ptr;
+ return eglCreateContext(display, config, share_ctx, attrs) orelse {
+ try getError();
+ unreachable;
+ };
+ }
+
+ extern fn eglCreateWindowSurface(
+ display: *Display,
+ config: *Config,
+ native_window: *anyopaque,
+ attributes: [*c]Attribute,
+ ) ?*Surface;
+ pub fn createWindowSurface(
+ display: *Display,
+ config: *Config,
+ native_window: *anyopaque,
+ attributes: ?[]Attribute,
+ ) Error!*Surface {
+ var attrs: [*c]Attribute = null;
+ if (attributes) |_attrs| attrs = _attrs.ptr;
+ return eglCreateWindowSurface(display, config, native_window, attrs) orelse {
+ try getError();
+ unreachable;
+ };
+ }
+
+ extern fn eglMakeCurrent(
+ display: *Display,
+ draw: *Surface,
+ read: *Surface,
+ ctx: *Context,
+ ) bool;
+ pub fn makeCurrent(
+ display: *Display,
+ draw: *Surface,
+ read: *Surface,
+ ctx: *Context,
+ ) Error!void {
+ if (!eglMakeCurrent(display, draw, read, ctx)) try getError();
+ }
+
+ extern fn eglQueryString(display: *Display, name: i32) [*:0]const u8;
+ pub const queryString = eglQueryString;
+
+ extern fn eglSwapBuffers(display: *Display, surface: *Surface) bool;
+ pub fn swapBuffers(display: *Display, surface: *Surface) Error!void {
+ if (!eglSwapBuffers(display, surface)) try getError();
+ }
+
+ extern fn eglSwapInterval(display: *Display, interval: i32) bool;
+ pub fn swapInterval(display: *Display, interval: i32) Error!void {
+ if (!eglSwapInterval(display, interval)) try getError();
+ }
+
+ extern fn eglTerminate(display: *Display) bool;
+ pub fn terminate(display: *Display) void {
+ // Do I really care if destruction fails?
+ _ = eglTerminate(display);
+ }
+};
diff --git a/src/main.zig b/src/main.zig
index 4902176..564fe37 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -2,29 +2,100 @@ const std = @import("std");
const mem = std.mem;
const posix = std.posix;
+const c = @cImport({
+ @cDefine("GL_GLEXT_PROTOTYPES", {});
+ @cInclude("GL/gl.h");
+ @cInclude("GL/glext.h");
+});
+
const wayland = @import("wayland");
const wl = wayland.client.wl;
const xdg = wayland.client.xdg;
+const egl = @import("egl.zig");
+
+const vertex_shader_src: [*c]const u8 =
+ \\#version 330 core
+ \\layout (location = 0) in vec3 pos;
+ \\
+ \\void main()
+ \\{
+ \\ gl_Position = vec4(pos.x, pos.y, pos.z, 1.0);
+ \\}
+;
+
+const frag_shader_src: [*c]const u8 =
+ \\#version 330 core
+ \\out vec4 frag_color;
+ \\
+ \\void main()
+ \\{
+ \\ frag_color = vec4(1.0f, 0.2f, 0.2f, 1.0f);
+ \\}
+;
+
/// Global Wayland registry context; contains pointers to the globals we expect
/// from the compositor.
const RegistryContext = struct {
- shm: ?*wl.Shm = null,
compositor: ?*wl.Compositor = null,
wm_base: ?*xdg.WmBase = null,
};
var registry_ctx = RegistryContext{};
+var egl_display: *egl.Display = undefined;
+var egl_cfg: [1]*egl.Config = undefined;
+
+const GlGetIvFn = *const fn (
+ obj: c_uint,
+ pname: c.GLenum,
+ params: [*c]c_int,
+) callconv(.C) void;
+
+const GlGetLogFn = *const fn (
+ obj: c_uint,
+ max: c_int,
+ len: [*c]c_int,
+ buf: [*c]u8,
+) callconv(.C) void;
+
+fn checkGlError(
+ getIvFn: GlGetIvFn,
+ getLogFn: GlGetLogFn,
+ obj: c_uint,
+ pname: c.GLenum,
+) bool {
+ var success: c_int = 0;
+ var gl_log: [512:0]u8 = undefined;
+ getIvFn(obj, pname, &success);
+ const err = success == 0;
+ if (err) {
+ getLogFn(obj, 512, null, &gl_log);
+ std.debug.print("Shader compilation error: {s}\n", .{gl_log});
+ }
+ return err;
+}
-/// A window's buffer data; we will paint the frames to these buffers.
-const WindowBuffer = struct {
- /// Wayland buffer
- wl_buffer: *wl.Buffer,
- /// Backing data slice from memory mapping for this buffer
- map: []u8,
- /// Are we being read by the compositor right now?
- busy: bool = false,
-};
+fn checkProgramLinkError(prog: c_uint) !void {
+ if (checkGlError(
+ c.glGetProgramiv,
+ c.glGetProgramInfoLog,
+ prog,
+ c.GL_LINK_STATUS,
+ )) {
+ return error.GlProgramLinkError;
+ }
+}
+
+fn checkShaderError(shader: c_uint) !void {
+ if (checkGlError(
+ c.glGetShaderiv,
+ c.glGetShaderInfoLog,
+ shader,
+ c.GL_COMPILE_STATUS,
+ )) {
+ return error.GlShaderCompileError;
+ }
+}
/// A single Window (surface); data and methods we need to tell the compositor
/// to render a surface for us.
@@ -37,10 +108,20 @@ pub const Window = struct {
xdg_surface: *xdg.Surface = undefined,
/// XDG toplevel data
xdg_toplevel: *xdg.Toplevel = undefined,
- /// Buffers A and B; Wayland requires double-buffering
- buffers: [2]WindowBuffer = undefined,
- /// Memory mapped for both buffers
- buffers_map: []align(mem.page_size) u8 = undefined,
+ /// EGL Wayland display
+ egl_win: *wl.EglWindow = undefined,
+ /// EGL context
+ egl_ctx: *egl.Context = undefined,
+ /// EGL surface
+ egl_surface: *egl.Surface = undefined,
+ /// OpenGl triangle shader program
+ shader_prog: c_uint = undefined,
+ /// Triangle vertices
+ vertices: [9]f32,
+ /// OpenGL vertex buffer object
+ vbo: c_uint = undefined,
+ /// OpenGL vertex array object
+ vao: c_uint = undefined,
/// This window's title
title: [*:0]const u8,
/// Width in pixels
@@ -51,6 +132,8 @@ pub const Window = struct {
offset: f32 = 0.0,
/// Last frame's timestamp in ms
last_frame: u32 = 0,
+ /// Frames per second statistic
+ current_fps: f32 = 0.0,
/// Should we keep this window open?
open: bool = true,
@@ -64,11 +147,64 @@ pub const Window = struct {
.allocator = allocator,
.wl_surface = try compositor.createSurface(),
.title = title,
+ .vertices = .{
+ -0.5, -0.5, 0.0,
+ 0.5, -0.5, 0.0,
+ 0.0, 0.5, 0.0,
+ },
};
win.xdg_surface = try wm_base.getXdgSurface(win.wl_surface);
+ errdefer win.xdg_surface.destroy();
win.xdg_toplevel = try win.xdg_surface.getToplevel();
+ errdefer win.xdg_toplevel.destroy();
+
+ win.egl_ctx = try egl_display.createContext(egl_cfg[0], null, null);
+ win.egl_win = try wl.EglWindow.create(win.wl_surface, win.width, win.height);
+ win.egl_surface = try egl_display.createWindowSurface(egl_cfg[0], win.egl_win, null);
+ try egl_display.makeCurrent(win.egl_surface, win.egl_surface, win.egl_ctx);
+
+ const gl_vendor = c.glGetString(c.GL_VENDOR);
+ const gl_version = c.glGetString(c.GL_VERSION);
+ std.debug.print("{?s} OpenGL {?s}\n", .{ gl_vendor, gl_version });
+
+ const vertex_shader = c.glCreateShader(c.GL_VERTEX_SHADER);
+ defer c.glDeleteShader(vertex_shader);
+ c.glShaderSource(vertex_shader, 1, &vertex_shader_src, null);
+ c.glCompileShader(vertex_shader);
+ try checkShaderError(vertex_shader);
+
+ const frag_shader = c.glCreateShader(c.GL_FRAGMENT_SHADER);
+ defer c.glDeleteShader(frag_shader);
+ c.glShaderSource(frag_shader, 1, &frag_shader_src, null);
+ c.glCompileShader(frag_shader);
+ try checkShaderError(frag_shader);
+
+ win.shader_prog = c.glCreateProgram();
+ c.glAttachShader(win.shader_prog, vertex_shader);
+ c.glAttachShader(win.shader_prog, frag_shader);
+ c.glLinkProgram(win.shader_prog);
+ try checkProgramLinkError(win.shader_prog);
+
+ c.glGenVertexArrays(1, &win.vao);
+ c.glGenBuffers(1, &win.vbo);
+
+ c.glBindVertexArray(win.vao);
+
+ c.glBindBuffer(c.GL_ARRAY_BUFFER, win.vbo);
+ c.glBufferData(
+ c.GL_ARRAY_BUFFER,
+ @sizeOf(@TypeOf(win.vertices)),
+ &win.vertices,
+ c.GL_STATIC_DRAW,
+ );
- try win.allocBuffers();
+ c.glVertexAttribPointer(0, 3, c.GL_FLOAT, c.GL_FALSE, 3 * @sizeOf(f32), null);
+ c.glEnableVertexAttribArray(0);
+
+ c.glBindBuffer(c.GL_ARRAY_BUFFER, 0);
+ c.glBindVertexArray(0);
+
+ c.glViewport(@divTrunc(win.width, 2) - 160, @divTrunc(win.height, 2) - 160, 320, 320);
win.xdg_surface.setListener(*Window, xdgSurfaceListener, win);
win.xdg_toplevel.setListener(*Window, xdgToplevelListener, win);
@@ -85,95 +221,37 @@ pub const Window = struct {
/// Destroy this window and all its associated resources
pub fn destroy(win: *Window) void {
- win.destroyBuffers();
+ c.glDeleteVertexArrays(1, &win.vao);
+ c.glDeleteBuffers(1, &win.vbo);
+ c.glDeleteProgram(win.shader_prog);
+ win.egl_win.destroy();
win.xdg_toplevel.destroy();
win.xdg_surface.destroy();
win.wl_surface.destroy();
win.allocator.destroy(win);
}
- /// Allocate new buffers for this window
- fn allocBuffers(win: *Window) !void {
- const stride = win.width * 4;
- const buffer_size = stride * win.height;
- const total_size = buffer_size * 2; // we need two buffers
-
- const fd = try posix.memfd_create("zigway", 0);
- defer posix.close(fd);
- try posix.ftruncate(fd, @intCast(total_size));
- win.buffers_map = try posix.mmap(
- null,
- @intCast(total_size),
- posix.PROT.READ | posix.PROT.WRITE,
- .{ .TYPE = .SHARED },
- fd,
- 0,
- );
-
- const shm = registry_ctx.shm orelse return error.NoShm;
- const pool = try shm.createPool(fd, total_size);
- defer pool.destroy();
- inline for (&win.buffers, 0..win.buffers.len) |*buffer, i| {
- buffer.wl_buffer = try pool.createBuffer(
- buffer_size *% @as(i32, i),
- win.width,
- win.height,
- stride,
- .argb8888,
- );
- const bsize: usize = @intCast(buffer_size);
- buffer.busy = false;
- buffer.map = win.buffers_map[bsize *% i .. bsize *% (i + 1)];
- buffer.wl_buffer.setListener(*WindowBuffer, bufferListener, buffer);
- }
- }
-
- /// Destroy this window's buffers
- fn destroyBuffers(win: *Window) void {
- win.buffers[0].wl_buffer.destroy();
- win.buffers[1].wl_buffer.destroy();
- posix.munmap(win.buffers_map);
- }
+ /// Draw a new frame
+ pub fn drawFrame(win: *Window) !void {
+ try egl_display.swapInterval(0);
- /// Draw a new frame to the first free buffer and return it
- pub fn drawFrame(win: *Window) !*wl.Buffer {
- var buffer: *WindowBuffer = undefined;
- if (!win.buffers[0].busy) {
- buffer = &win.buffers[0];
- } else if (!win.buffers[1].busy) {
- buffer = &win.buffers[1];
- } else {
- return error.NoBuffersAvaiable;
- }
+ c.glClearColor(0, 0.5, 1, 1);
+ c.glClear(c.GL_COLOR_BUFFER_BIT);
- const canvas: *[]u32 = @ptrCast(&buffer.map);
- const box_side = 16;
- var offset: i32 = @intFromFloat(win.offset);
- offset = @mod(offset, box_side);
- var y: i32 = 0;
- while (y < win.height) : (y += 1) {
- var x: i32 = 0;
- while (x < win.width) : (x += 1) {
- const i: usize = @intCast(y * win.width + x);
- const side = @mod(
- x + offset + @divFloor(y + offset, box_side) * box_side,
- (box_side * 2),
- );
- canvas.*[i] = if (side < box_side) 0xFF0E0E0E else 0xFF2F2F2F;
- }
- }
+ c.glUseProgram(win.shader_prog);
+ c.glBindVertexArray(win.vao);
+ c.glDrawArrays(c.GL_TRIANGLES, 0, 3);
- buffer.busy = true;
- return buffer.wl_buffer;
+ try egl_display.swapBuffers(win.egl_surface);
}
- /// Resize this window and reallocate the buffers if needed
+ /// Resize this window
fn resize(win: *Window, width: i32, height: i32) !void {
win.height = if (height != 0) height else win.height;
win.width = if (width != 0) width else win.width;
if (width == 0 and height == 0) return;
- win.destroyBuffers();
- try win.allocBuffers();
+ win.egl_win.resize(win.width, win.height, 0, 0);
+ c.glViewport(@divTrunc(win.width, 2) - 160, @divTrunc(win.height, 2) - 160, 320, 320);
}
/// XDG Surface callbacks
@@ -181,13 +259,9 @@ pub const Window = struct {
switch (event) {
.configure => |configure| {
xdg_surface.ackConfigure(configure.serial);
- brk: {
- const buffer = win.drawFrame() catch |err| {
- std.debug.print("error drawing frame {}\n", .{err});
- break :brk;
- };
- win.wl_surface.attach(buffer, 0, 0);
- }
+ win.drawFrame() catch |err| {
+ std.debug.print("error drawing frame {}\n", .{err});
+ };
win.wl_surface.commit();
},
}
@@ -206,15 +280,6 @@ pub const Window = struct {
}
}
- /// Wayland buffer callbacks
- fn bufferListener(_: *wl.Buffer, event: wl.Buffer.Event, buffer: *WindowBuffer) void {
- switch (event) {
- .release => {
- buffer.busy = false;
- },
- }
- }
-
/// Wayland frame callback
fn frameListener(wl_cb: *wl.Callback, event: wl.Callback.Event, win: *Window) void {
switch (event) {
@@ -230,14 +295,14 @@ pub const Window = struct {
if (win.last_frame != 0) {
const elapsed: f32 = @floatFromInt(done.callback_data -% win.last_frame);
win.offset -= elapsed / 1000.0 * 32.0;
+ win.current_fps = 1000.0 / elapsed;
}
- defer win.last_frame = done.callback_data;
+ win.last_frame = done.callback_data;
- const wl_buffer = win.drawFrame() catch |err| {
+ win.drawFrame() catch |err| {
std.debug.print("error drawing frame {}\n", .{err});
return;
};
- win.wl_surface.attach(wl_buffer, 0, 0);
win.wl_surface.damageBuffer(0, 0, std.math.maxInt(i32), std.math.maxInt(i32));
win.wl_surface.commit();
},
@@ -258,6 +323,21 @@ pub fn main() !void {
const wm_base = registry_ctx.wm_base orelse return error.NoXdgWmBase;
wm_base.setListener(?*void, xdgWmBaseListener, null);
+ try egl.bindApi(.opengl);
+ var attrs = [_]egl.Attribute{
+ .{ .type = .red_size, .value = 8 },
+ .{ .type = .green_size, .value = 8 },
+ .{ .type = .blue_size, .value = 8 },
+ .{ .type = .none, .value = 0 },
+ };
+ egl_display = try egl.Display.get(display);
+ var major: i32 = 0;
+ var minor: i32 = 0;
+ try egl_display.initialize(&major, &minor);
+ defer egl_display.terminate();
+ std.debug.print("EGL version {}.{}\n", .{ major, minor });
+ _ = try egl_display.chooseConfig(&attrs, &egl_cfg);
+
const win = try Window.create(allocator, "Hello, wayland!");
defer win.destroy();
@@ -277,8 +357,6 @@ fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, ctx: *Regi
});
if (mem.orderZ(u8, global.interface, wl.Compositor.getInterface().name) == .eq) {
ctx.compositor = registry.bind(global.name, wl.Compositor, 6) catch return;
- } else if (mem.orderZ(u8, global.interface, wl.Shm.getInterface().name) == .eq) {
- ctx.shm = registry.bind(global.name, wl.Shm, 1) catch return;
} else if (mem.orderZ(u8, global.interface, xdg.WmBase.getInterface().name) == .eq) {
ctx.wm_base = registry.bind(global.name, xdg.WmBase, 2) catch return;
}