summaryrefslogtreecommitdiff
path: root/pkg/opengl/Program.zig
blob: ee77582c1a0a8e029b2cf0213cba4fa7b0736569 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
const Program = @This();

const std = @import("std");
const assert = std.debug.assert;
const log = std.log.scoped(.opengl);

const c = @import("c.zig").c;
const Shader = @import("Shader.zig");
const errors = @import("errors.zig");
const glad = @import("glad.zig");

id: c.GLuint,

pub const Binding = struct {
    pub fn unbind(_: Binding) void {
        glad.context.UseProgram.?(0);
    }
};

pub fn create() !Program {
    const id = glad.context.CreateProgram.?();
    if (id == 0) try errors.mustError();
    log.debug("program created id={}", .{id});
    return .{ .id = id };
}

/// Create a program from a vertex and fragment shader source. This will
/// compile and link the vertex and fragment shader.
pub fn createVF(vsrc: [:0]const u8, fsrc: [:0]const u8) !Program {
    const vs = try Shader.create(c.GL_VERTEX_SHADER);
    try vs.setSourceAndCompile(vsrc);
    defer vs.destroy();

    const fs = try Shader.create(c.GL_FRAGMENT_SHADER);
    try fs.setSourceAndCompile(fsrc);
    defer fs.destroy();

    const p = try create();
    try p.attachShader(vs);
    try p.attachShader(fs);
    try p.link();

    return p;
}

pub fn destroy(p: Program) void {
    assert(p.id != 0);
    glad.context.DeleteProgram.?(p.id);
    log.debug("program destroyed id={}", .{p.id});
}

pub fn attachShader(p: Program, s: Shader) !void {
    glad.context.AttachShader.?(p.id, s.id);
    try errors.getError();
}

pub fn link(p: Program) !void {
    glad.context.LinkProgram.?(p.id);

    // Check if linking succeeded
    var success: c_int = undefined;
    glad.context.GetProgramiv.?(p.id, c.GL_LINK_STATUS, &success);
    if (success == c.GL_TRUE) {
        log.debug("program linked id={}", .{p.id});
        return;
    }

    log.err("program link failure id={} message={s}", .{
        p.id,
        std.mem.sliceTo(&p.getInfoLog(), 0),
    });
    return error.CompileFailed;
}

pub fn use(p: Program) !Binding {
    glad.context.UseProgram.?(p.id);
    try errors.getError();
    return .{};
}

pub fn uniformBlockBinding(
    self: Program,
    index: c.GLuint,
    binding: c.GLuint,
) !void {
    glad.context.UniformBlockBinding.?(self.id, index, binding);
    try errors.getError();
}

/// Requires the program is currently in use.
pub fn setUniform(
    p: Program,
    n: [:0]const u8,
    value: anytype,
) !void {
    const loc = glad.context.GetUniformLocation.?(
        p.id,
        @ptrCast(n.ptr),
    );
    if (loc < 0) {
        return error.UniformNameInvalid;
    }
    try errors.getError();

    // Perform the correct call depending on the type of the value.
    switch (@TypeOf(value)) {
        bool => glad.context.Uniform1i.?(loc, if (value) 1 else 0),
        comptime_int => glad.context.Uniform1i.?(loc, value),
        f32 => glad.context.Uniform1f.?(loc, value),
        @Vector(2, f32) => glad.context.Uniform2f.?(loc, value[0], value[1]),
        @Vector(3, f32) => glad.context.Uniform3f.?(loc, value[0], value[1], value[2]),
        @Vector(4, f32) => glad.context.Uniform4f.?(loc, value[0], value[1], value[2], value[3]),
        [4]@Vector(4, f32) => glad.context.UniformMatrix4fv.?(
            loc,
            1,
            c.GL_FALSE,
            @ptrCast(&value),
        ),
        else => {
            log.warn("unsupported uniform type {}", .{@TypeOf(value)});
            unreachable;
        },
    }
    try errors.getError();
}

/// getInfoLog returns the info log for this program. This attempts to
/// keep the log fully stack allocated and is therefore limited to a max
/// amount of elements.
//
// NOTE(mitchellh): we can add a dynamic version that uses an allocator
// if we ever need it.
pub fn getInfoLog(s: Program) [512]u8 {
    var msg: [512]u8 = undefined;
    glad.context.GetProgramInfoLog.?(s.id, msg.len, null, &msg);
    return msg;
}