aboutsummaryrefslogtreecommitdiff
path: root/src/main.zig
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 /src/main.zig
parent65b93e57a8caac901348d373bf49df3ebb509984 (diff)
downloadzigway-5b1d5fc0325dc5e20ccb7b1ce445721762256848.tar.gz
zigway-5b1d5fc0325dc5e20ccb7b1ce445721762256848.zip
opengl: triangle
OpenGL hello world triangle on Wayland.
Diffstat (limited to 'src/main.zig')
-rw-r--r--src/main.zig298
1 files changed, 188 insertions, 110 deletions
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;
}