src/gui/gl.zig
Ref: Size: 9.1 KiB History
//! OpenGL 3.3 instanced R8-atlas renderer, loaded entirely by function pointer.
const std = @import("std");
const quads = @import("quads.zig");
const atlas = @import("atlas.zig");
const c = @cImport({
@cInclude("GL/glcorearb.h");
});
pub const GetProc = *const fn ([*:0]const u8) callconv(.c) ?*anyopaque;
pub const Fns = struct {
glCreateShader: c.PFNGLCREATESHADERPROC,
glShaderSource: c.PFNGLSHADERSOURCEPROC,
glCompileShader: c.PFNGLCOMPILESHADERPROC,
glGetShaderiv: c.PFNGLGETSHADERIVPROC,
glGetShaderInfoLog: c.PFNGLGETSHADERINFOLOGPROC,
glDeleteShader: c.PFNGLDELETESHADERPROC,
glCreateProgram: c.PFNGLCREATEPROGRAMPROC,
glAttachShader: c.PFNGLATTACHSHADERPROC,
glLinkProgram: c.PFNGLLINKPROGRAMPROC,
glGetProgramiv: c.PFNGLGETPROGRAMIVPROC,
glGetProgramInfoLog: c.PFNGLGETPROGRAMINFOLOGPROC,
glDeleteProgram: c.PFNGLDELETEPROGRAMPROC,
glUseProgram: c.PFNGLUSEPROGRAMPROC,
glGetUniformLocation: c.PFNGLGETUNIFORMLOCATIONPROC,
glUniform2f: c.PFNGLUNIFORM2FPROC,
glUniform1i: c.PFNGLUNIFORM1IPROC,
glGenVertexArrays: c.PFNGLGENVERTEXARRAYSPROC,
glBindVertexArray: c.PFNGLBINDVERTEXARRAYPROC,
glDeleteVertexArrays: c.PFNGLDELETEVERTEXARRAYSPROC,
glGenBuffers: c.PFNGLGENBUFFERSPROC,
glBindBuffer: c.PFNGLBINDBUFFERPROC,
glBufferData: c.PFNGLBUFFERDATAPROC,
glDeleteBuffers: c.PFNGLDELETEBUFFERSPROC,
glEnableVertexAttribArray: c.PFNGLENABLEVERTEXATTRIBARRAYPROC,
glVertexAttribPointer: c.PFNGLVERTEXATTRIBPOINTERPROC,
glVertexAttribIPointer: c.PFNGLVERTEXATTRIBIPOINTERPROC,
glVertexAttribDivisor: c.PFNGLVERTEXATTRIBDIVISORPROC,
glGenTextures: c.PFNGLGENTEXTURESPROC,
glBindTexture: c.PFNGLBINDTEXTUREPROC,
glDeleteTextures: c.PFNGLDELETETEXTURESPROC,
glTexImage2D: c.PFNGLTEXIMAGE2DPROC,
glTexParameteri: c.PFNGLTEXPARAMETERIPROC,
glPixelStorei: c.PFNGLPIXELSTOREIPROC,
glActiveTexture: c.PFNGLACTIVETEXTUREPROC,
glViewport: c.PFNGLVIEWPORTPROC,
glClearColor: c.PFNGLCLEARCOLORPROC,
glClear: c.PFNGLCLEARPROC,
glEnable: c.PFNGLENABLEPROC,
glBlendFunc: c.PFNGLBLENDFUNCPROC,
glDrawArraysInstanced: c.PFNGLDRAWARRAYSINSTANCEDPROC,
glReadPixels: c.PFNGLREADPIXELSPROC,
pub fn load(get: GetProc) error{MissingGlFunction}!Fns {
var f: Fns = undefined;
inline for (std.meta.fields(Fns)) |field| {
@field(f, field.name) = @ptrCast(get(field.name ++ "") orelse {
std.debug.print("muxg: missing GL function {s}\n", .{field.name});
return error.MissingGlFunction;
});
}
return f;
}
};
const vs: [*:0]const u8 =
\\#version 330 core
\\layout(location=0) in vec4 rect;
\\layout(location=1) in vec4 uv;
\\layout(location=2) in uint rgba;
\\layout(location=3) in uint kind;
\\uniform vec2 viewport; out vec2 v_uv; flat out vec4 v_color; flat out uint v_kind;
\\void main(){vec2 q=vec2(gl_VertexID&1,(gl_VertexID>>1)&1);vec2 p=rect.xy+q*rect.zw;gl_Position=vec4(p.x/viewport.x*2.-1.,1.-p.y/viewport.y*2.,0,1);v_uv=mix(uv.xy,uv.zw,q);v_color=vec4(float((rgba>>24u)&255u),float((rgba>>16u)&255u),float((rgba>>8u)&255u),float(rgba&255u))/255.;v_kind=kind;}
;
const fs: [*:0]const u8 =
\\#version 330 core
\\in vec2 v_uv; flat in vec4 v_color; flat in uint v_kind; uniform sampler2D atlas; out vec4 frag;
\\void main(){float a=v_kind==1u?texture(atlas,v_uv).r:1.;frag=vec4(v_color.rgb,v_color.a*a);}
;
pub const Renderer = struct {
f: Fns,
program: c.GLuint,
vao: c.GLuint,
vbo: c.GLuint,
tex: c.GLuint,
u_viewport: c.GLint,
pub const Error = error{ MissingGlFunction, ShaderCompile, ProgramLink };
fn shader(f: *const Fns, kind: c.GLenum, src: [*:0]const u8) Error!c.GLuint {
const sh = f.glCreateShader.?(kind);
f.glShaderSource.?(sh, 1, @ptrCast(&src), null);
f.glCompileShader.?(sh);
var ok: c.GLint = 0;
f.glGetShaderiv.?(sh, c.GL_COMPILE_STATUS, &ok);
if (ok == 0) {
var log: [2048]u8 = undefined;
var n: c.GLsizei = 0;
f.glGetShaderInfoLog.?(sh, log.len, &n, &log);
std.debug.print("muxg: shader compile: {s}\n", .{log[0..@intCast(n)]});
f.glDeleteShader.?(sh);
return error.ShaderCompile;
}
return sh;
}
pub fn init(get: GetProc) Error!Renderer {
const f = try Fns.load(get);
const vert = try shader(&f, c.GL_VERTEX_SHADER, vs);
defer f.glDeleteShader.?(vert);
const frag = try shader(&f, c.GL_FRAGMENT_SHADER, fs);
defer f.glDeleteShader.?(frag);
const p = f.glCreateProgram.?();
f.glAttachShader.?(p, vert);
f.glAttachShader.?(p, frag);
f.glLinkProgram.?(p);
var ok: c.GLint = 0;
f.glGetProgramiv.?(p, c.GL_LINK_STATUS, &ok);
if (ok == 0) {
var log: [2048]u8 = undefined;
var n: c.GLsizei = 0;
f.glGetProgramInfoLog.?(p, log.len, &n, &log);
std.debug.print("muxg: program link: {s}\n", .{log[0..@intCast(n)]});
f.glDeleteProgram.?(p);
return error.ProgramLink;
}
var vao: c.GLuint = 0;
var vbo: c.GLuint = 0;
var tex: c.GLuint = 0;
f.glGenVertexArrays.?(1, &vao);
f.glBindVertexArray.?(vao);
f.glGenBuffers.?(1, &vbo);
f.glBindBuffer.?(c.GL_ARRAY_BUFFER, vbo);
const stride: c.GLsizei = @sizeOf(quads.Instance);
f.glEnableVertexAttribArray.?(0);
f.glVertexAttribPointer.?(0, 4, c.GL_FLOAT, c.GL_FALSE, stride, @ptrFromInt(@offsetOf(quads.Instance, "x")));
f.glVertexAttribDivisor.?(0, 1);
f.glEnableVertexAttribArray.?(1);
f.glVertexAttribPointer.?(1, 4, c.GL_FLOAT, c.GL_FALSE, stride, @ptrFromInt(@offsetOf(quads.Instance, "u0")));
f.glVertexAttribDivisor.?(1, 1);
f.glEnableVertexAttribArray.?(2);
f.glVertexAttribIPointer.?(2, 1, c.GL_UNSIGNED_INT, stride, @ptrFromInt(@offsetOf(quads.Instance, "rgba")));
f.glVertexAttribDivisor.?(2, 1);
f.glEnableVertexAttribArray.?(3);
f.glVertexAttribIPointer.?(3, 1, c.GL_UNSIGNED_INT, stride, @ptrFromInt(@offsetOf(quads.Instance, "kind")));
f.glVertexAttribDivisor.?(3, 1);
f.glGenTextures.?(1, &tex);
f.glBindTexture.?(c.GL_TEXTURE_2D, tex);
f.glTexParameteri.?(c.GL_TEXTURE_2D, c.GL_TEXTURE_MIN_FILTER, c.GL_NEAREST);
f.glTexParameteri.?(c.GL_TEXTURE_2D, c.GL_TEXTURE_MAG_FILTER, c.GL_NEAREST);
f.glTexParameteri.?(c.GL_TEXTURE_2D, c.GL_TEXTURE_WRAP_S, c.GL_CLAMP_TO_EDGE);
f.glTexParameteri.?(c.GL_TEXTURE_2D, c.GL_TEXTURE_WRAP_T, c.GL_CLAMP_TO_EDGE);
f.glPixelStorei.?(c.GL_UNPACK_ALIGNMENT, 1);
f.glUseProgram.?(p);
f.glUniform1i.?(f.glGetUniformLocation.?(p, "atlas"), 0);
f.glEnable.?(c.GL_BLEND);
f.glBlendFunc.?(c.GL_SRC_ALPHA, c.GL_ONE_MINUS_SRC_ALPHA);
return .{ .f = f, .program = p, .vao = vao, .vbo = vbo, .tex = tex, .u_viewport = f.glGetUniformLocation.?(p, "viewport") };
}
pub fn deinit(s: *Renderer) void {
s.f.glDeleteTextures.?(1, &s.tex);
s.f.glDeleteBuffers.?(1, &s.vbo);
s.f.glDeleteVertexArrays.?(1, &s.vao);
s.f.glDeleteProgram.?(s.program);
}
pub fn uploadAtlas(s: *Renderer, a: *atlas.Atlas) void {
if (!a.dirty) return;
s.f.glActiveTexture.?(c.GL_TEXTURE0);
s.f.glBindTexture.?(c.GL_TEXTURE_2D, s.tex);
s.f.glTexImage2D.?(c.GL_TEXTURE_2D, 0, c.GL_R8, a.width, a.height, 0, c.GL_RED, c.GL_UNSIGNED_BYTE, a.pixels.ptr);
a.dirty = false;
}
pub fn uploadInstances(s: *Renderer, items: []const quads.Instance) void {
s.f.glBindBuffer.?(c.GL_ARRAY_BUFFER, s.vbo);
s.f.glBufferData.?(c.GL_ARRAY_BUFFER, @intCast(items.len * @sizeOf(quads.Instance)), items.ptr, c.GL_STREAM_DRAW);
}
pub fn draw(s: *Renderer, count: usize, w: i32, h: i32, color: u32) void {
const cv = struct {
fn f(v: u32, shift: u5) f32 {
return @as(f32, @floatFromInt((v >> shift) & 255)) / 255;
}
}.f;
s.f.glViewport.?(0, 0, w, h);
s.f.glClearColor.?(cv(color, 24), cv(color, 16), cv(color, 8), cv(color, 0));
s.f.glClear.?(c.GL_COLOR_BUFFER_BIT);
s.f.glUseProgram.?(s.program);
s.f.glUniform2f.?(s.u_viewport, @floatFromInt(w), @floatFromInt(h));
s.f.glBindVertexArray.?(s.vao);
s.f.glDrawArraysInstanced.?(c.GL_TRIANGLE_STRIP, 0, 4, @intCast(count));
}
pub fn readPixels(s: *Renderer, alloc: std.mem.Allocator, w: u32, h: u32) ![]u8 {
const row = @as(usize, w) * 3;
const out = try alloc.alloc(u8, row * h);
errdefer alloc.free(out);
s.f.glPixelStorei.?(c.GL_PACK_ALIGNMENT, 1);
s.f.glReadPixels.?(0, 0, @intCast(w), @intCast(h), c.GL_RGB, c.GL_UNSIGNED_BYTE, out.ptr);
const tmp = try alloc.alloc(u8, row);
defer alloc.free(tmp);
for (0..h / 2) |y| {
const a = out[y * row ..][0..row];
const b = out[(@as(usize, h) - 1 - y) * row ..][0..row];
@memcpy(tmp, a);
@memcpy(a, b);
@memcpy(b, tmp);
}
return out;
}
};