2023-10-27 00:54:07 +02:00
|
|
|
const std = @import("std");
|
|
|
|
const c = @import("ffi.zig").c;
|
|
|
|
|
|
|
|
const glutil = @import("glutil.zig");
|
|
|
|
|
|
|
|
const OutputInfo = @import("OutputInfo.zig");
|
2023-10-28 11:51:12 +02:00
|
|
|
const OutputWindow = @import("OutputWindow.zig");
|
|
|
|
const PointerState = @import("PointerState.zig");
|
2023-10-27 00:54:07 +02:00
|
|
|
|
|
|
|
egl_dpy: c.EGLDisplay,
|
|
|
|
bg_shader_program: c_uint,
|
2023-10-28 11:51:12 +02:00
|
|
|
main_shader_program: c_uint,
|
|
|
|
bg_bufs: std.MultiArrayList(BgBuf),
|
|
|
|
time: i64,
|
|
|
|
cursor_positions: [][2]c_int,
|
2023-10-28 12:28:17 +02:00
|
|
|
should_redraw: []bool,
|
2023-10-27 00:54:07 +02:00
|
|
|
|
|
|
|
const Gfx = @This();
|
|
|
|
|
2023-10-28 11:51:12 +02:00
|
|
|
const BgBuf = struct {
|
|
|
|
texture: c_uint,
|
|
|
|
framebuffer: c_uint,
|
|
|
|
zbuffer: c_uint,
|
|
|
|
};
|
2023-10-27 00:54:07 +02:00
|
|
|
|
2023-10-28 11:51:12 +02:00
|
|
|
pub fn init(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);
|
|
|
|
const frag_shader = try glutil.createShader(c.GL_FRAGMENT_SHADER, @embedFile("bg_frag.glsl"));
|
|
|
|
defer c.glDeleteShader(frag_shader);
|
2023-10-27 00:54:07 +02:00
|
|
|
|
2023-10-28 11:51:12 +02:00
|
|
|
const program = c.glCreateProgram();
|
|
|
|
errdefer c.glDeleteProgram(program);
|
|
|
|
c.glAttachShader(program, vert_shader);
|
|
|
|
c.glAttachShader(program, frag_shader);
|
|
|
|
c.glLinkProgram(program);
|
|
|
|
|
|
|
|
var success: c_int = 0;
|
|
|
|
c.glGetProgramiv(program, c.GL_LINK_STATUS, &success);
|
|
|
|
if (success != 1)
|
|
|
|
return error.ShaderLinkFail;
|
|
|
|
|
|
|
|
break :shader program;
|
|
|
|
};
|
|
|
|
|
|
|
|
const main_program = shader: {
|
|
|
|
const vert_shader = try glutil.createShader(c.GL_VERTEX_SHADER, @embedFile("main_vert.glsl"));
|
|
|
|
defer c.glDeleteShader(vert_shader);
|
|
|
|
const frag_shader = try glutil.createShader(c.GL_FRAGMENT_SHADER, @embedFile("main_frag.glsl"));
|
|
|
|
defer c.glDeleteShader(frag_shader);
|
|
|
|
|
|
|
|
const program = c.glCreateProgram();
|
|
|
|
errdefer c.glDeleteProgram(program);
|
|
|
|
c.glAttachShader(program, vert_shader);
|
|
|
|
c.glAttachShader(program, frag_shader);
|
|
|
|
c.glLinkProgram(program);
|
|
|
|
|
|
|
|
var success: c_int = 0;
|
|
|
|
c.glGetProgramiv(program, c.GL_LINK_STATUS, &success);
|
|
|
|
if (success != 1)
|
|
|
|
return error.ShaderLinkFail;
|
|
|
|
|
|
|
|
break :shader program;
|
|
|
|
};
|
|
|
|
|
|
|
|
const cursor_positions = try std.heap.c_allocator.alloc([2]c_int, output_info.len);
|
|
|
|
errdefer std.heap.c_allocator.free(cursor_positions);
|
|
|
|
@memset(cursor_positions, .{ 0, 0 });
|
|
|
|
|
2023-10-28 12:28:17 +02:00
|
|
|
const should_redraw = try std.heap.c_allocator.alloc(bool, output_info.len);
|
|
|
|
errdefer std.heap.c_allocator.free(should_redraw);
|
|
|
|
@memset(should_redraw, true);
|
|
|
|
|
2023-10-28 11:51:12 +02:00
|
|
|
var bg_bufs = std.MultiArrayList(BgBuf){};
|
|
|
|
errdefer bg_bufs.deinit(std.heap.c_allocator);
|
|
|
|
|
|
|
|
try bg_bufs.resize(std.heap.c_allocator, output_info.len);
|
|
|
|
|
|
|
|
const bg_slice = bg_bufs.slice();
|
|
|
|
|
|
|
|
// @intCast safety: user is somewhat unlikely to have 2^32 - 1 monitors.
|
|
|
|
c.glGenTextures(@intCast(output_info.len), bg_slice.items(.texture).ptr);
|
|
|
|
errdefer c.glDeleteTextures(@intCast(bg_bufs.len), bg_slice.items(.texture).ptr);
|
|
|
|
c.glGenFramebuffers(@intCast(output_info.len), bg_slice.items(.framebuffer).ptr);
|
|
|
|
errdefer c.glDeleteFramebuffers(@intCast(bg_bufs.len), bg_slice.items(.framebuffer).ptr);
|
|
|
|
c.glGenRenderbuffers(@intCast(output_info.len), bg_slice.items(.zbuffer).ptr);
|
|
|
|
errdefer c.glDeleteRenderbuffers(@intCast(output_info.len), bg_slice.items(.zbuffer).ptr);
|
|
|
|
|
|
|
|
for (
|
|
|
|
output_info,
|
|
|
|
bg_slice.items(.texture),
|
|
|
|
bg_slice.items(.framebuffer),
|
|
|
|
bg_slice.items(.zbuffer),
|
|
|
|
) |inf, tex, fb, zb| {
|
|
|
|
c.glBindFramebuffer(c.GL_FRAMEBUFFER, fb);
|
|
|
|
c.glBindTexture(c.GL_TEXTURE_2D, tex);
|
|
|
|
c.glBindRenderbuffer(c.GL_RENDERBUFFER, zb);
|
|
|
|
|
|
|
|
c.glTexImage2D(
|
|
|
|
c.GL_TEXTURE_2D,
|
|
|
|
0,
|
|
|
|
c.GL_RGBA,
|
|
|
|
inf.width,
|
|
|
|
inf.height,
|
|
|
|
0,
|
|
|
|
c.GL_RGBA,
|
|
|
|
c.GL_UNSIGNED_BYTE,
|
|
|
|
null,
|
|
|
|
);
|
|
|
|
c.glTexParameteri(c.GL_TEXTURE_2D, c.GL_TEXTURE_MAG_FILTER, c.GL_NEAREST);
|
|
|
|
c.glTexParameteri(c.GL_TEXTURE_2D, c.GL_TEXTURE_MIN_FILTER, c.GL_NEAREST);
|
|
|
|
|
|
|
|
c.glFramebufferTexture2D(
|
|
|
|
c.GL_FRAMEBUFFER,
|
|
|
|
c.GL_COLOR_ATTACHMENT0,
|
|
|
|
c.GL_TEXTURE_2D,
|
|
|
|
tex,
|
|
|
|
0,
|
|
|
|
);
|
|
|
|
|
|
|
|
c.glRenderbufferStorage(c.GL_RENDERBUFFER, c.GL_DEPTH_COMPONENT16, inf.width, inf.height);
|
|
|
|
c.glFramebufferRenderbuffer(c.GL_FRAMEBUFFER, c.GL_DEPTH_ATTACHMENT, c.GL_RENDERBUFFER, zb);
|
|
|
|
|
|
|
|
if (c.glCheckFramebufferStatus(c.GL_FRAMEBUFFER) != c.GL_FRAMEBUFFER_COMPLETE)
|
|
|
|
return error.FramebufferIncomplete;
|
|
|
|
}
|
|
|
|
|
|
|
|
c.glBindFramebuffer(c.GL_FRAMEBUFFER, 0);
|
|
|
|
c.glBindTexture(c.GL_TEXTURE_2D, 0);
|
|
|
|
c.glBindRenderbuffer(c.GL_FRAMEBUFFER, 0);
|
2023-10-27 00:54:07 +02:00
|
|
|
|
|
|
|
return .{
|
|
|
|
.egl_dpy = egl_dpy,
|
2023-10-28 11:51:12 +02:00
|
|
|
.bg_shader_program = bg_program,
|
|
|
|
.main_shader_program = main_program,
|
|
|
|
.bg_bufs = bg_bufs,
|
|
|
|
.time = 0,
|
|
|
|
.cursor_positions = cursor_positions,
|
2023-10-28 12:28:17 +02:00
|
|
|
.should_redraw = should_redraw,
|
2023-10-27 00:54:07 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn deinit(self: *Gfx) void {
|
2023-10-28 11:51:12 +02:00
|
|
|
const bg_slice = self.bg_bufs.slice();
|
|
|
|
c.glDeleteTextures(@intCast(bg_slice.len), bg_slice.items(.texture).ptr);
|
|
|
|
c.glDeleteFramebuffers(@intCast(bg_slice.len), bg_slice.items(.framebuffer).ptr);
|
|
|
|
c.glDeleteRenderbuffers(@intCast(bg_slice.len), bg_slice.items(.zbuffer).ptr);
|
|
|
|
self.bg_bufs.deinit(std.heap.c_allocator);
|
|
|
|
|
2023-10-27 00:54:07 +02:00
|
|
|
c.glDeleteProgram(self.bg_shader_program);
|
2023-10-28 12:28:17 +02:00
|
|
|
c.glDeleteProgram(self.main_shader_program);
|
|
|
|
|
|
|
|
std.heap.c_allocator.free(self.cursor_positions);
|
|
|
|
std.heap.c_allocator.free(self.should_redraw);
|
|
|
|
|
2023-10-27 00:54:07 +02:00
|
|
|
self.* = undefined;
|
|
|
|
}
|
|
|
|
|
2023-10-28 11:51:12 +02:00
|
|
|
pub fn draw(
|
2023-10-27 00:54:07 +02:00
|
|
|
self: *Gfx,
|
2023-10-27 15:30:28 +02:00
|
|
|
dt: i64,
|
2023-10-28 11:51:12 +02:00
|
|
|
pointer_state: *PointerState,
|
|
|
|
output_idx: usize,
|
|
|
|
outputs: []const OutputWindow,
|
|
|
|
infos: []const OutputInfo,
|
2023-10-27 00:54:07 +02:00
|
|
|
) !void {
|
2023-10-28 11:51:12 +02:00
|
|
|
self.time += dt;
|
|
|
|
c.glBindFramebuffer(c.GL_FRAMEBUFFER, 0); // use default framebuffer
|
|
|
|
c.glUseProgram(self.main_shader_program);
|
2023-10-27 00:54:07 +02:00
|
|
|
|
2023-10-28 12:28:17 +02:00
|
|
|
for (self.cursor_positions, self.should_redraw, infos, outputs) |*pos, *redraw, inf, outp| {
|
2023-10-28 11:51:12 +02:00
|
|
|
const target = if (pointer_state.surface == outp.surface)
|
|
|
|
.{ pointer_state.x, pointer_state.y }
|
|
|
|
else
|
|
|
|
.{ @divTrunc(inf.width, 2), @divTrunc(inf.height, 2) };
|
|
|
|
|
2023-10-28 12:28:17 +02:00
|
|
|
const new_x: c_int = @intFromFloat(std.math.lerp(
|
2023-10-28 11:51:12 +02:00
|
|
|
@as(f32, @floatFromInt(pos[0])),
|
|
|
|
@as(f32, @floatFromInt(target[0])),
|
|
|
|
std.math.clamp(@as(f32, @floatFromInt(dt)) / 1000.0, 0.0, 1.0),
|
|
|
|
));
|
2023-10-28 12:28:17 +02:00
|
|
|
const new_y: c_int = @intFromFloat(std.math.lerp(
|
2023-10-28 11:51:12 +02:00
|
|
|
@as(f32, @floatFromInt(pos[1])),
|
|
|
|
@as(f32, @floatFromInt(target[1])),
|
|
|
|
std.math.clamp(@as(f32, @floatFromInt(dt)) / 1000.0, 0.0, 1.0),
|
|
|
|
));
|
2023-10-28 12:28:17 +02:00
|
|
|
|
|
|
|
if (new_x != pos[0] or new_y != pos[1])
|
|
|
|
redraw.* = true;
|
|
|
|
|
|
|
|
pos[0] = new_x;
|
|
|
|
pos[1] = new_y;
|
2023-10-28 11:51:12 +02:00
|
|
|
}
|
|
|
|
|
2023-10-28 12:28:17 +02:00
|
|
|
if (!self.should_redraw[output_idx])
|
|
|
|
return;
|
|
|
|
self.should_redraw[output_idx] = false;
|
|
|
|
|
2023-10-28 11:51:12 +02:00
|
|
|
const vertices = [_]f32{
|
|
|
|
-1.0, -1.0, 0.0, 0.0, 0.0,
|
|
|
|
1.0, -1.0, 0.0, 1.0, 0.0,
|
|
|
|
1.0, 1.0, 0.0, 1.0, 1.0,
|
|
|
|
|
|
|
|
-1.0, -1.0, 0.0, 0.0, 0.0,
|
|
|
|
1.0, 1.0, 0.0, 1.0, 1.0,
|
|
|
|
-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.glEnableVertexAttribArray(0);
|
|
|
|
c.glVertexAttribPointer(
|
|
|
|
1,
|
|
|
|
2,
|
|
|
|
c.GL_FLOAT,
|
|
|
|
c.GL_FALSE,
|
|
|
|
@sizeOf(f32) * 5,
|
|
|
|
@ptrFromInt(@intFromPtr(&vertices) + @sizeOf(f32) * 3),
|
|
|
|
);
|
|
|
|
c.glEnableVertexAttribArray(1);
|
|
|
|
|
|
|
|
c.glClearColor(1.0, 0.0, 0.0, 1.0);
|
|
|
|
c.glClear(c.GL_COLOR_BUFFER_BIT);
|
|
|
|
|
|
|
|
c.glBindTexture(c.GL_TEXTURE_2D, self.bg_bufs.get(output_idx).texture);
|
|
|
|
|
|
|
|
c.glUniform2f(
|
|
|
|
c.glGetUniformLocation(self.main_shader_program, "cursorPos"),
|
|
|
|
@as(f32, @floatFromInt(self.cursor_positions[output_idx][0])) / @as(f32, @floatFromInt(infos[output_idx].width)),
|
|
|
|
1.0 - @as(f32, @floatFromInt(self.cursor_positions[output_idx][1])) / @as(f32, @floatFromInt(infos[output_idx].height)),
|
|
|
|
);
|
|
|
|
c.glUniform1f(
|
|
|
|
c.glGetUniformLocation(self.main_shader_program, "hasCursor"),
|
|
|
|
if (outputs[output_idx].surface == pointer_state.surface) 1.0 else 0.0,
|
|
|
|
);
|
|
|
|
//c.glUniform1f(
|
|
|
|
// c.glGetUniformLocation(self.main_shader_program, "time"),
|
|
|
|
// @as(f32, @floatFromInt(self.time)) / 1000.0,
|
|
|
|
//);
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn drawBackground(
|
|
|
|
self: *Gfx,
|
|
|
|
info: OutputInfo,
|
|
|
|
output_idx: usize,
|
|
|
|
base_off: [2]i32,
|
|
|
|
rand: f32,
|
|
|
|
) !void {
|
2023-10-27 00:54:07 +02:00
|
|
|
// There's just about a 0% chance this works properly when monitors have different resolutions,
|
|
|
|
// but I can't even begin thinking about that.
|
2023-10-28 11:51:12 +02:00
|
|
|
const xoff = @as(f32, @floatFromInt(info.x - base_off[0])) / @as(f32, @floatFromInt(info.width));
|
|
|
|
const yoff = @as(f32, @floatFromInt(info.y - base_off[1])) / @as(f32, @floatFromInt(info.height));
|
2023-10-27 00:54:07 +02:00
|
|
|
|
|
|
|
const vertices = [_]f32{
|
|
|
|
-1.0, -1.0, 0.0, xoff, yoff,
|
|
|
|
1.0, -1.0, 0.0, xoff, yoff,
|
|
|
|
1.0, 1.0, 0.0, xoff, yoff,
|
|
|
|
|
|
|
|
-1.0, -1.0, 0.0, xoff, yoff,
|
|
|
|
1.0, 1.0, 0.0, xoff, yoff,
|
|
|
|
-1.0, 1.0, 0.0, xoff, yoff,
|
|
|
|
};
|
|
|
|
|
2023-10-28 11:51:12 +02:00
|
|
|
c.glBindFramebuffer(c.GL_FRAMEBUFFER, self.bg_bufs.get(output_idx).framebuffer);
|
|
|
|
|
2023-10-27 00:54:07 +02:00
|
|
|
c.glClearColor(1.0, 0.0, 0.0, 1.0);
|
|
|
|
c.glClear(c.GL_COLOR_BUFFER_BIT);
|
|
|
|
|
|
|
|
c.glUseProgram(self.bg_shader_program);
|
|
|
|
|
|
|
|
c.glVertexAttribPointer(0, 3, c.GL_FLOAT, c.GL_FALSE, @sizeOf(f32) * 5, &vertices);
|
|
|
|
c.glEnableVertexAttribArray(0);
|
|
|
|
|
|
|
|
c.glVertexAttribPointer(1, 2, c.GL_FLOAT, c.GL_FALSE, @sizeOf(f32) * 5, @ptrFromInt(@intFromPtr(&vertices) + @sizeOf(f32) * 3));
|
|
|
|
c.glEnableVertexAttribArray(1);
|
|
|
|
|
2023-10-27 15:30:28 +02:00
|
|
|
c.glUniform1f(c.glGetUniformLocation(self.bg_shader_program, "time"), rand * 2000.0 - 1000.0);
|
2023-10-27 00:54:07 +02:00
|
|
|
|
|
|
|
c.glDrawArrays(c.GL_TRIANGLES, 0, vertices.len / 3);
|
|
|
|
}
|