From 5b1d5fc0325dc5e20ccb7b1ce445721762256848 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yaroslav=20de=20la=20Pe=C3=B1a=20Smirnov?= Date: Wed, 18 Sep 2024 00:02:33 +0300 Subject: opengl: triangle OpenGL hello world triangle on Wayland. --- src/main.zig | 298 +++++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 188 insertions(+), 110 deletions(-) (limited to 'src/main.zig') 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; } -- cgit v1.2.3