a73x

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;
    }
};