diff --git a/scripts/wlbg/build.zig b/scripts/wlbg/build.zig index 56638e4..b8e7b08 100644 --- a/scripts/wlbg/build.zig +++ b/scripts/wlbg/build.zig @@ -37,7 +37,7 @@ pub fn build(b: *std.Build) void { exe.root_module.linkSystemLibrary("wayland-client", .{}); exe.root_module.linkSystemLibrary("wayland-egl", .{}); exe.root_module.linkSystemLibrary("EGL", .{}); - exe.root_module.linkSystemLibrary("GLESv2", .{}); + exe.root_module.linkSystemLibrary("GL", .{}); b.installArtifact(exe); const run_cmd = b.addRunArtifact(exe); diff --git a/scripts/wlbg/src/Gfx.zig b/scripts/wlbg/src/Gfx.zig index aeb9f90..b5b7a06 100644 --- a/scripts/wlbg/src/Gfx.zig +++ b/scripts/wlbg/src/Gfx.zig @@ -1,5 +1,6 @@ const std = @import("std"); const c = @import("ffi.zig").c; +const wayland = @import("wayland"); const glutil = @import("glutil.zig"); const options = @import("options.zig"); @@ -9,12 +10,17 @@ const OutputInfo = @import("OutputInfo.zig"); const OutputWindow = @import("OutputWindow.zig"); const PointerState = @import("PointerState.zig"); +const wl = wayland.client.wl; + +dpy: *wl.Display, egl_dpy: c.EGLDisplay, bg_shader_program: c_uint, main_shader_program: c_uint, bg_bufs: std.MultiArrayList(BgBuf), time: i64, cursor_positions: [][2]c_int, +vao: c_uint, +vbo: c_uint, const Gfx = @This(); @@ -24,7 +30,7 @@ const BgBuf = struct { zbuffer: c_uint, }; -pub fn init(egl_dpy: c.EGLDisplay, output_info: []const OutputInfo) !Gfx { +pub fn init(dpy: *wl.Display, egl_dpy: c.EGLDisplay, output_info: []const OutputInfo) !Gfx { const bg_program = shader: { const vert_shader = try glutil.createShader(c.GL_VERTEX_SHADER, @embedFile("bg_vert.glsl")); defer c.glDeleteShader(vert_shader); @@ -125,15 +131,24 @@ pub fn init(egl_dpy: c.EGLDisplay, output_info: []const OutputInfo) !Gfx { c.glBindFramebuffer(c.GL_FRAMEBUFFER, 0); c.glBindTexture(c.GL_TEXTURE_2D, 0); - c.glBindRenderbuffer(c.GL_FRAMEBUFFER, 0); + c.glBindRenderbuffer(c.GL_RENDERBUFFER, 0); + + var vao: c_uint = 0; + c.glGenVertexArrays(1, &vao); + + var vbo: c_uint = 0; + c.glGenBuffers(1, &vbo); return .{ + .dpy = dpy, .egl_dpy = egl_dpy, .bg_shader_program = bg_program, .main_shader_program = main_program, .bg_bufs = bg_bufs, .time = 0, .cursor_positions = cursor_positions, + .vao = vao, + .vbo = vbo, }; } @@ -197,6 +212,8 @@ pub fn draw( ) !void { self.time += dt; dth.should_redraw[output_idx] = false; + c.glBindVertexArray(self.vao); + c.glBindBuffer(c.GL_ARRAY_BUFFER, self.vbo); c.glBindFramebuffer(c.GL_FRAMEBUFFER, 0); // use default framebuffer c.glUseProgram(self.main_shader_program); @@ -210,7 +227,8 @@ pub fn draw( -1.0, 1.0, 0.0, 0.0, 1.0, }; - c.glVertexAttribPointer(0, 3, c.GL_FLOAT, c.GL_FALSE, @sizeOf(f32) * 5, &vertices); + c.glBufferData(c.GL_ARRAY_BUFFER, @sizeOf(f32) * vertices.len, &vertices, c.GL_STATIC_DRAW); + c.glVertexAttribPointer(0, 3, c.GL_FLOAT, c.GL_FALSE, @sizeOf(f32) * 5, null); c.glEnableVertexAttribArray(0); c.glVertexAttribPointer( 1, @@ -218,7 +236,7 @@ pub fn draw( c.GL_FLOAT, c.GL_FALSE, @sizeOf(f32) * 5, - @ptrFromInt(@intFromPtr(&vertices) + @sizeOf(f32) * 3), + @ptrFromInt(@sizeOf(f32) * 3), ); c.glEnableVertexAttribArray(1); @@ -243,8 +261,25 @@ pub fn draw( c.glDrawArrays(c.GL_TRIANGLES, 0, vertices.len / 3); - if (c.eglSwapInterval(self.egl_dpy, 0) != c.EGL_TRUE or - c.eglSwapBuffers(self.egl_dpy, outputs[output_idx].egl_surface) != c.EGL_TRUE) return error.EGLError; + // This is necessary because some EGL implementations (Mesa) will try reading the wayland + // socket from eglSwapBuffers. + // This causes a deadlock, is abysmal API design, and not preventable. It's gotten so bad + // that another suggested workaround on the archive linked below is to put this on another + // thread in order to force EGL to use its own wayland event queue. + // + // By cancelling the read operation first (which is always prep'd usually and handled by the + // event loop), we can ensure that EGL gets to do its nonsense here before making libwayland + // handle another read correctly by calling prepareRead again. + // + // Somehow the only trace of this BS on the entire internet and only reason I managed to figure + // this out: https://lists.freedesktop.org/archives/wayland-devel/2013-March/007739.html + self.dpy.cancelRead(); + if (c.eglSwapBuffers( + self.egl_dpy, + outputs[output_idx].egl_surface, + ) != c.EGL_TRUE) return error.EGLError; + if (self.dpy.dispatchPending() != .SUCCESS) return error.RoundtipFail; + std.debug.assert(self.dpy.prepareRead()); } pub fn drawBackground( @@ -271,6 +306,8 @@ pub fn drawBackground( -1.0, 1.0, 0.0, }; + c.glBindVertexArray(self.vao); + c.glBindBuffer(c.GL_ARRAY_BUFFER, self.vbo); c.glBindFramebuffer(c.GL_FRAMEBUFFER, self.bg_bufs.get(output_idx).framebuffer); c.glClearColor(1.0, 0.0, 0.0, 1.0); @@ -278,7 +315,8 @@ pub fn drawBackground( c.glUseProgram(self.bg_shader_program); - c.glVertexAttribPointer(0, 3, c.GL_FLOAT, c.GL_FALSE, 0, &vertices); + c.glBufferData(c.GL_ARRAY_BUFFER, @sizeOf(f32) * vertices.len, &vertices, c.GL_STATIC_DRAW); + c.glVertexAttribPointer(0, 3, c.GL_FLOAT, c.GL_FALSE, 0, null); c.glEnableVertexAttribArray(0); c.glUniform2f(c.glGetUniformLocation(self.bg_shader_program, "offset"), off.x, off.y); diff --git a/scripts/wlbg/src/bg_frag.glsl b/scripts/wlbg/src/bg_frag.glsl index f3a3bbb..17510a7 100644 --- a/scripts/wlbg/src/bg_frag.glsl +++ b/scripts/wlbg/src/bg_frag.glsl @@ -1,6 +1,4 @@ -#version 300 es - -precision highp float; +#version 430 uniform float time; @@ -20,7 +18,7 @@ void main() { vec2 R = vec2(1); ivec4 b = ivec4(o -= o); // Initialize b=0 float t = .1*time, B, h, z; - vec4 g; + vec4 g = vec4(0); u = (5. + cos(t) * 1.5) * // * Camera push in/out diff --git a/scripts/wlbg/src/bg_vert.glsl b/scripts/wlbg/src/bg_vert.glsl index df32e1c..d44d74f 100644 --- a/scripts/wlbg/src/bg_vert.glsl +++ b/scripts/wlbg/src/bg_vert.glsl @@ -1,6 +1,4 @@ -#version 300 es - -precision mediump float; +#version 430 uniform vec2 offset; diff --git a/scripts/wlbg/src/ffi.zig b/scripts/wlbg/src/ffi.zig index 8a15957..8dd1365 100644 --- a/scripts/wlbg/src/ffi.zig +++ b/scripts/wlbg/src/ffi.zig @@ -1,5 +1,8 @@ pub const c = @cImport({ @cInclude("wayland-egl.h"); // required for egl include to work @cInclude("EGL/egl.h"); - @cInclude("GLES2/gl2.h"); + + @cDefine("GL_GLEXT_PROTOTYPES", "1"); + @cInclude("GL/gl.h"); + @cInclude("GL/glext.h"); }); diff --git a/scripts/wlbg/src/main.zig b/scripts/wlbg/src/main.zig index 1602139..b68544a 100644 --- a/scripts/wlbg/src/main.zig +++ b/scripts/wlbg/src/main.zig @@ -42,6 +42,8 @@ pub fn main() !void { if (dpy.roundtrip() != .SUCCESS) return error.RoundtipFail; + if (c.eglBindAPI(c.EGL_OPENGL_API) == 0) return error.EGLError; + const egl_dpy = c.eglGetDisplay(@ptrCast(dpy)) orelse return error.EGLError; if (c.eglInitialize(egl_dpy, null, null) != c.EGL_TRUE) return error.EGLError; defer _ = c.eglTerminate(egl_dpy); @@ -53,7 +55,7 @@ pub fn main() !void { egl_dpy, &[_]i32{ c.EGL_SURFACE_TYPE, c.EGL_WINDOW_BIT, - c.EGL_RENDERABLE_TYPE, c.EGL_OPENGL_ES2_BIT, + c.EGL_RENDERABLE_TYPE, c.EGL_OPENGL_BIT, c.EGL_RED_SIZE, 8, c.EGL_GREEN_SIZE, 8, c.EGL_BLUE_SIZE, 8, @@ -72,8 +74,10 @@ pub fn main() !void { config, c.EGL_NO_CONTEXT, &[_]i32{ - c.EGL_CONTEXT_MAJOR_VERSION, 2, - c.EGL_CONTEXT_OPENGL_DEBUG, 1, + c.EGL_CONTEXT_MAJOR_VERSION, 4, + c.EGL_CONTEXT_MINOR_VERSION, 3, + c.EGL_CONTEXT_OPENGL_DEBUG, c.EGL_TRUE, + c.EGL_CONTEXT_OPENGL_PROFILE_MASK, c.EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT, c.EGL_NONE, }, ) orelse return error.EGLError; @@ -188,7 +192,11 @@ pub fn main() !void { egl_ctx, ) != c.EGL_TRUE) return error.EGLError; - var gfx = try Gfx.init(egl_dpy, output_info); + c.glDebugMessageCallback(&glDebugCb, null); + c.glEnable(c.GL_DEBUG_OUTPUT); + std.log.info("initialized OpenGL {s}", .{c.glGetString(c.GL_VERSION)}); + + var gfx = try Gfx.init(dpy, egl_dpy, output_info); defer gfx.deinit(); var rdata = RenderData{ @@ -232,6 +240,9 @@ pub fn main() !void { }; loop.add(&wl_poll_completion); + if (dpy.dispatchPending() != .SUCCESS) return error.RoundtipFail; + std.debug.assert(dpy.prepareRead()); + std.log.info("running event loop", .{}); try loop.run(.until_done); } @@ -349,12 +360,18 @@ fn wlPollCb( }; const dpy: *wl.Display = @ptrCast(@alignCast(userdata)); - if (dpy.dispatchPending() != .SUCCESS or dpy.flush() != .SUCCESS) { + if (dpy.readEvents() != .SUCCESS or + dpy.dispatchPending() != .SUCCESS or + dpy.flush() != .SUCCESS) + { std.log.err("error processing wayland events", .{}); loop.stop(); return .disarm; } + // This is only false if the queue is not empty, but we just emptied the queue. + std.debug.assert(dpy.prepareRead()); + return .rearm; } @@ -426,3 +443,27 @@ fn resetXevTimerCompletion(completion: *xev.Completion, now: i64, in: i64) void .tv_nsec = @mod(next_time, std.time.ms_per_s) * std.time.ns_per_ms, }; } + +fn glDebugCb( + source: c.GLenum, + @"type": c.GLenum, + id: c.GLuint, + severity: c.GLenum, + len: c.GLsizei, + msgp: ?[*:0]const u8, + udata: ?*const anyopaque, +) callconv(.C) void { + _ = source; + _ = @"type"; + _ = id; + _ = udata; + const log = std.log.scoped(.gl); + // Mesa likes to include trailing newlines sometimes + const msg = std.mem.trim(u8, msgp.?[0..@intCast(len)], &std.ascii.whitespace); + switch (severity) { + c.GL_DEBUG_SEVERITY_HIGH => log.err("{s}", .{msg}), + c.GL_DEBUG_SEVERITY_MEDIUM, c.GL_DEBUG_SEVERITY_LOW => log.warn("{s}", .{msg}), + c.GL_DEBUG_SEVERITY_NOTIFICATION => log.info("{s}", .{msg}), + else => unreachable, + } +} diff --git a/scripts/wlbg/src/main_frag.glsl b/scripts/wlbg/src/main_frag.glsl index aedd328..ccc7bd6 100644 --- a/scripts/wlbg/src/main_frag.glsl +++ b/scripts/wlbg/src/main_frag.glsl @@ -1,6 +1,4 @@ -#version 300 es - -precision highp float; +#version 430 uniform sampler2D bg; diff --git a/scripts/wlbg/src/main_vert.glsl b/scripts/wlbg/src/main_vert.glsl index 3404cab..2521370 100644 --- a/scripts/wlbg/src/main_vert.glsl +++ b/scripts/wlbg/src/main_vert.glsl @@ -1,6 +1,4 @@ -#version 300 es - -precision mediump float; +#version 430 in vec4 vPos; in vec2 uv;