renderer/opengl: create the screen texture

This commit is contained in:
Mitchell Hashimoto
2023-11-17 20:39:20 -08:00
parent db244da101
commit c8a51a2158
4 changed files with 111 additions and 40 deletions

View File

@ -20,8 +20,13 @@ pub fn destroy(v: Framebuffer) void {
} }
pub fn bind(v: Framebuffer, target: Target) !Binding { pub fn bind(v: Framebuffer, target: Target) !Binding {
// The default framebuffer is documented as being zero but
// on multiple OpenGL drivers its not zero, so we grab it
// at runtime.
var current: c.GLint = undefined;
glad.context.GetIntegerv.?(c.GL_FRAMEBUFFER_BINDING, &current);
glad.context.BindFramebuffer.?(@intFromEnum(target), v.id); glad.context.BindFramebuffer.?(@intFromEnum(target), v.id);
return .{ .target = target }; return .{ .target = target, .previous = @intCast(current) };
} }
/// Enum for possible binding targets. /// Enum for possible binding targets.
@ -55,9 +60,13 @@ pub const Status = enum(c_uint) {
pub const Binding = struct { pub const Binding = struct {
target: Target, target: Target,
previous: c.GLuint,
pub fn unbind(self: Binding) void { pub fn unbind(self: Binding) void {
glad.context.BindFramebuffer.?(@intFromEnum(self.target), 0); glad.context.BindFramebuffer.?(
@intFromEnum(self.target),
self.previous,
);
} }
pub fn texture2D( pub fn texture2D(
@ -78,6 +87,6 @@ pub const Binding = struct {
} }
pub fn checkStatus(self: Binding) Status { pub fn checkStatus(self: Binding) Status {
return @enumFromInt(glad.context.CheckFramebufferStatus.?(self.target)); return @enumFromInt(glad.context.CheckFramebufferStatus.?(@intFromEnum(self.target)));
} }
}; };

View File

@ -7,11 +7,29 @@ const glad = @import("glad.zig");
id: c.GLuint, id: c.GLuint,
pub inline fn active(target: c.GLenum) !void { pub fn active(target: c.GLenum) !void {
glad.context.ActiveTexture.?(target); glad.context.ActiveTexture.?(target);
try errors.getError(); try errors.getError();
} }
/// Create a single texture.
pub fn create() !Texture {
var id: c.GLuint = undefined;
glad.context.GenTextures.?(1, &id);
return .{ .id = id };
}
/// glBindTexture
pub fn bind(v: Texture, target: Target) !Binding {
glad.context.BindTexture.?(@intFromEnum(target), v.id);
try errors.getError();
return .{ .target = target };
}
pub fn destroy(v: Texture) void {
glad.context.DeleteTextures.?(1, &v.id);
}
/// Enun for possible texture binding targets. /// Enun for possible texture binding targets.
pub const Target = enum(c_uint) { pub const Target = enum(c_uint) {
@"1D" = c.GL_TEXTURE_1D, @"1D" = c.GL_TEXTURE_1D,
@ -48,8 +66,9 @@ pub const Parameter = enum(c_uint) {
/// Internal format enum for texture images. /// Internal format enum for texture images.
pub const InternalFormat = enum(c_int) { pub const InternalFormat = enum(c_int) {
Red = c.GL_RED, red = c.GL_RED,
RGBA = c.GL_RGBA, rgb = c.GL_RGB,
rgba = c.GL_RGBA,
// There are so many more that I haven't filled in. // There are so many more that I haven't filled in.
_, _,
@ -57,8 +76,9 @@ pub const InternalFormat = enum(c_int) {
/// Format for texture images /// Format for texture images
pub const Format = enum(c_uint) { pub const Format = enum(c_uint) {
Red = c.GL_RED, red = c.GL_RED,
BGRA = c.GL_BGRA, rgb = c.GL_RGB,
bgra = c.GL_BGRA,
// There are so many more that I haven't filled in. // There are so many more that I haven't filled in.
_, _,
@ -75,9 +95,8 @@ pub const DataType = enum(c_uint) {
pub const Binding = struct { pub const Binding = struct {
target: Target, target: Target,
pub inline fn unbind(b: *Binding) void { pub fn unbind(b: *const Binding) void {
glad.context.BindTexture.?(@intFromEnum(b.target), 0); glad.context.BindTexture.?(@intFromEnum(b.target), 0);
b.* = undefined;
} }
pub fn generateMipmap(b: Binding) void { pub fn generateMipmap(b: Binding) void {
@ -143,21 +162,3 @@ pub const Binding = struct {
); );
} }
}; };
/// Create a single texture.
pub inline fn create() !Texture {
var id: c.GLuint = undefined;
glad.context.GenTextures.?(1, &id);
return Texture{ .id = id };
}
/// glBindTexture
pub inline fn bind(v: Texture, target: Target) !Binding {
glad.context.BindTexture.?(@intFromEnum(target), v.id);
try errors.getError();
return Binding{ .target = target };
}
pub inline fn destroy(v: Texture) void {
glad.context.DeleteTextures.?(1, &v.id);
}

View File

@ -1346,11 +1346,11 @@ fn flushAtlas(self: *OpenGL) !void {
atlas.resized = false; atlas.resized = false;
try texbind.image2D( try texbind.image2D(
0, 0,
.Red, .red,
@intCast(atlas.size), @intCast(atlas.size),
@intCast(atlas.size), @intCast(atlas.size),
0, 0,
.Red, .red,
.UnsignedByte, .UnsignedByte,
atlas.data.ptr, atlas.data.ptr,
); );
@ -1361,7 +1361,7 @@ fn flushAtlas(self: *OpenGL) !void {
0, 0,
@intCast(atlas.size), @intCast(atlas.size),
@intCast(atlas.size), @intCast(atlas.size),
.Red, .red,
.UnsignedByte, .UnsignedByte,
atlas.data.ptr, atlas.data.ptr,
); );
@ -1380,11 +1380,11 @@ fn flushAtlas(self: *OpenGL) !void {
atlas.resized = false; atlas.resized = false;
try texbind.image2D( try texbind.image2D(
0, 0,
.RGBA, .rgba,
@intCast(atlas.size), @intCast(atlas.size),
@intCast(atlas.size), @intCast(atlas.size),
0, 0,
.BGRA, .bgra,
.UnsignedByte, .UnsignedByte,
atlas.data.ptr, atlas.data.ptr,
); );
@ -1395,7 +1395,7 @@ fn flushAtlas(self: *OpenGL) !void {
0, 0,
@intCast(atlas.size), @intCast(atlas.size),
@intCast(atlas.size), @intCast(atlas.size),
.BGRA, .bgra,
.UnsignedByte, .UnsignedByte,
atlas.data.ptr, atlas.data.ptr,
); );
@ -1441,8 +1441,7 @@ fn drawCustomPrograms(
const custom_bind = try custom_state.bind(); const custom_bind = try custom_state.bind();
defer custom_bind.unbind(); defer custom_bind.unbind();
// Sync the uniform data. // Setup the new frame
// TODO: only do this when the data has changed
try custom_state.newFrame(); try custom_state.newFrame();
// Go through each custom shader and draw it. // Go through each custom shader and draw it.
@ -1469,6 +1468,14 @@ fn drawCellProgram(
// are changes to the atlas. // are changes to the atlas.
try self.flushAtlas(); try self.flushAtlas();
// If we have custom shaders, then we draw to the custom
// shader framebuffer.
const fbobind: ?gl.Framebuffer.Binding = fbobind: {
const state = gl_state.custom orelse break :fbobind null;
break :fbobind try state.fbo.bind(.framebuffer);
};
defer if (fbobind) |v| v.unbind();
// Clear the surface // Clear the surface
gl.clearColor( gl.clearColor(
@as(f32, @floatFromInt(self.draw_background.r)) / 255, @as(f32, @floatFromInt(self.draw_background.r)) / 255,
@ -1615,11 +1622,11 @@ const GLState = struct {
try texbind.parameter(.MagFilter, gl.c.GL_LINEAR); try texbind.parameter(.MagFilter, gl.c.GL_LINEAR);
try texbind.image2D( try texbind.image2D(
0, 0,
.Red, .red,
@intCast(font_group.atlas_greyscale.size), @intCast(font_group.atlas_greyscale.size),
@intCast(font_group.atlas_greyscale.size), @intCast(font_group.atlas_greyscale.size),
0, 0,
.Red, .red,
.UnsignedByte, .UnsignedByte,
font_group.atlas_greyscale.data.ptr, font_group.atlas_greyscale.data.ptr,
); );
@ -1636,11 +1643,11 @@ const GLState = struct {
try texbind.parameter(.MagFilter, gl.c.GL_LINEAR); try texbind.parameter(.MagFilter, gl.c.GL_LINEAR);
try texbind.image2D( try texbind.image2D(
0, 0,
.RGBA, .rgba,
@intCast(font_group.atlas_color.size), @intCast(font_group.atlas_color.size),
@intCast(font_group.atlas_color.size), @intCast(font_group.atlas_color.size),
0, 0,
.BGRA, .bgra,
.UnsignedByte, .UnsignedByte,
font_group.atlas_color.data.ptr, font_group.atlas_color.data.ptr,
); );

View File

@ -3,6 +3,8 @@ const Allocator = std.mem.Allocator;
const gl = @import("opengl"); const gl = @import("opengl");
const ScreenSize = @import("../size.zig").ScreenSize; const ScreenSize = @import("../size.zig").ScreenSize;
const log = std.log.scoped(.opengl_custom);
/// The "INDEX" is the index into the global GL state and the /// The "INDEX" is the index into the global GL state and the
/// "BINDING" is the binding location in the shader. /// "BINDING" is the binding location in the shader.
const UNIFORM_INDEX: gl.c.GLuint = 0; const UNIFORM_INDEX: gl.c.GLuint = 0;
@ -27,9 +29,11 @@ pub const State = struct {
uniforms: Uniforms, uniforms: Uniforms,
/// The OpenGL buffers /// The OpenGL buffers
fbo: gl.Framebuffer,
ubo: gl.Buffer, ubo: gl.Buffer,
vao: gl.VertexArray, vao: gl.VertexArray,
ebo: gl.Buffer, ebo: gl.Buffer,
fb_texture: gl.Texture,
/// The set of programs for the custom shaders. /// The set of programs for the custom shaders.
programs: []const Program, programs: []const Program,
@ -50,6 +54,44 @@ pub const State = struct {
try programs.append(try Program.init(src)); try programs.append(try Program.init(src));
} }
// Create the texture for the framebuffer
const fb_tex = try gl.Texture.create();
errdefer fb_tex.destroy();
{
const texbind = try fb_tex.bind(.@"2D");
try texbind.parameter(.WrapS, gl.c.GL_CLAMP_TO_EDGE);
try texbind.parameter(.WrapT, gl.c.GL_CLAMP_TO_EDGE);
try texbind.parameter(.MinFilter, gl.c.GL_LINEAR);
try texbind.parameter(.MagFilter, gl.c.GL_LINEAR);
try texbind.image2D(
0,
.rgb,
1,
1,
0,
.rgb,
.UnsignedByte,
null,
);
}
// Create our framebuffer for rendering off screen.
// The shader prior to custom shaders should use this
// framebuffer.
const fbo = try gl.Framebuffer.create();
errdefer fbo.destroy();
const fbbind = try fbo.bind(.framebuffer);
defer fbbind.unbind();
try fbbind.texture2D(.color0, .@"2D", fb_tex, 0);
const fbstatus = fbbind.checkStatus();
if (fbstatus != .complete) {
log.warn(
"framebuffer is not complete state={}",
.{fbstatus},
);
return error.InvalidFramebuffer;
}
// Create our uniform buffer that is shared across all // Create our uniform buffer that is shared across all
// custom shaders // custom shaders
const ubo = try gl.Buffer.create(); const ubo = try gl.Buffer.create();
@ -79,9 +121,11 @@ pub const State = struct {
return .{ return .{
.programs = try programs.toOwnedSlice(), .programs = try programs.toOwnedSlice(),
.uniforms = .{}, .uniforms = .{},
.fbo = fbo,
.ubo = ubo, .ubo = ubo,
.vao = vao, .vao = vao,
.ebo = ebo, .ebo = ebo,
.fb_texture = fb_tex,
.last_frame_time = try std.time.Instant.now(), .last_frame_time = try std.time.Instant.now(),
}; };
} }
@ -92,6 +136,8 @@ pub const State = struct {
self.ubo.destroy(); self.ubo.destroy();
self.ebo.destroy(); self.ebo.destroy();
self.vao.destroy(); self.vao.destroy();
self.fb_texture.destroy();
self.fbo.destroy();
} }
pub fn setScreenSize(self: *State, size: ScreenSize) !void { pub fn setScreenSize(self: *State, size: ScreenSize) !void {
@ -137,6 +183,11 @@ pub const State = struct {
// the global state. // the global state.
try self.ubo.bindBase(.uniform, UNIFORM_INDEX); try self.ubo.bindBase(.uniform, UNIFORM_INDEX);
// Bind our texture that is shared amongst all
try gl.Texture.active(gl.c.GL_TEXTURE0);
var texbind = try self.fb_texture.bind(.@"2D");
errdefer texbind.unbind();
const vao = try self.vao.bind(); const vao = try self.vao.bind();
errdefer vao.unbind(); errdefer vao.unbind();
@ -146,16 +197,19 @@ pub const State = struct {
return .{ return .{
.vao = vao, .vao = vao,
.ebo = ebo, .ebo = ebo,
.fb_texture = texbind,
}; };
} }
pub const Binding = struct { pub const Binding = struct {
vao: gl.VertexArray.Binding, vao: gl.VertexArray.Binding,
ebo: gl.Buffer.Binding, ebo: gl.Buffer.Binding,
fb_texture: gl.Texture.Binding,
pub fn unbind(self: Binding) void { pub fn unbind(self: Binding) void {
self.ebo.unbind(); self.ebo.unbind();
self.vao.unbind(); self.vao.unbind();
self.fb_texture.unbind();
} }
}; };
}; };