pkg/opengl: polish API

This commit is contained in:
Leah Amelia Chen
2026-08-30 13:49:05 +08:00
parent b87af8646f
commit adf161473d
8 changed files with 145 additions and 65 deletions

View File

@@ -24,8 +24,8 @@ pub fn bind(v: Sampler, index: c_uint) !void {
pub fn parameter(
self: Sampler,
name: Texture.Parameter,
value: anytype,
comptime name: Texture.Parameter,
value: name.Type(),
) errors.Error!void {
switch (@TypeOf(value)) {
c.GLint => glad.context.SamplerParameteri.?(

View File

@@ -33,36 +33,45 @@ pub fn destroy(v: Texture) void {
/// Enum for possible texture binding targets.
pub const Target = enum(c_uint) {
@"1D" = c.GL_TEXTURE_1D,
@"2D" = c.GL_TEXTURE_2D,
@"3D" = c.GL_TEXTURE_3D,
@"1DArray" = c.GL_TEXTURE_1D_ARRAY,
@"2DArray" = c.GL_TEXTURE_2D_ARRAY,
Rectangle = c.GL_TEXTURE_RECTANGLE,
CubeMap = c.GL_TEXTURE_CUBE_MAP,
Buffer = c.GL_TEXTURE_BUFFER,
@"2DMultisample" = c.GL_TEXTURE_2D_MULTISAMPLE,
@"2DMultisampleArray" = c.GL_TEXTURE_2D_MULTISAMPLE_ARRAY,
@"1d" = c.GL_TEXTURE_1D,
@"2d" = c.GL_TEXTURE_2D,
@"3d" = c.GL_TEXTURE_3D,
@"1d_array" = c.GL_TEXTURE_1D_ARRAY,
@"2d_array" = c.GL_TEXTURE_2D_ARRAY,
rectangle = c.GL_TEXTURE_RECTANGLE,
cube_map = c.GL_TEXTURE_CUBE_MAP,
buffer = c.GL_TEXTURE_BUFFER,
@"2d_multisample" = c.GL_TEXTURE_2D_MULTISAMPLE,
@"2d_multisample_array" = c.GL_TEXTURE_2D_MULTISAMPLE_ARRAY,
};
/// Enum for possible texture parameters.
pub const Parameter = enum(c_uint) {
BaseLevel = c.GL_TEXTURE_BASE_LEVEL,
CompareFunc = c.GL_TEXTURE_COMPARE_FUNC,
CompareMode = c.GL_TEXTURE_COMPARE_MODE,
LodBias = c.GL_TEXTURE_LOD_BIAS,
MinFilter = c.GL_TEXTURE_MIN_FILTER,
MagFilter = c.GL_TEXTURE_MAG_FILTER,
MinLod = c.GL_TEXTURE_MIN_LOD,
MaxLod = c.GL_TEXTURE_MAX_LOD,
MaxLevel = c.GL_TEXTURE_MAX_LEVEL,
SwizzleR = c.GL_TEXTURE_SWIZZLE_R,
SwizzleG = c.GL_TEXTURE_SWIZZLE_G,
SwizzleB = c.GL_TEXTURE_SWIZZLE_B,
SwizzleA = c.GL_TEXTURE_SWIZZLE_A,
WrapS = c.GL_TEXTURE_WRAP_S,
WrapT = c.GL_TEXTURE_WRAP_T,
WrapR = c.GL_TEXTURE_WRAP_R,
base_level = c.GL_TEXTURE_BASE_LEVEL,
compare_func = c.GL_TEXTURE_COMPARE_FUNC,
compare_mode = c.GL_TEXTURE_COMPARE_MODE,
lod_bias = c.GL_TEXTURE_LOD_BIAS,
min_filter = c.GL_TEXTURE_MIN_FILTER,
mag_filter = c.GL_TEXTURE_MAG_FILTER,
min_lod = c.GL_TEXTURE_MIN_LOD,
max_lod = c.GL_TEXTURE_MAX_LOD,
max_level = c.GL_TEXTURE_MAX_LEVEL,
swizzle_r = c.GL_TEXTURE_SWIZZLE_R,
swizzle_g = c.GL_TEXTURE_SWIZZLE_G,
swizzle_b = c.GL_TEXTURE_SWIZZLE_B,
swizzle_a = c.GL_TEXTURE_SWIZZLE_A,
wrap_s = c.GL_TEXTURE_WRAP_S,
wrap_t = c.GL_TEXTURE_WRAP_T,
wrap_r = c.GL_TEXTURE_WRAP_R,
pub fn Type(comptime self: Parameter) type {
return switch (self) {
.min_filter => MinFilter,
.mag_filter => MagFilter,
.wrap_s, .wrap_t, .wrap_r => Wrap,
else => c.GLint,
};
}
};
/// Internal format enum for texture images.
@@ -118,7 +127,7 @@ pub const Wrap = enum(c_int) {
/// Data type for texture images.
pub const DataType = enum(c_uint) {
UnsignedByte = c.GL_UNSIGNED_BYTE,
unsigned_byte = c.GL_UNSIGNED_BYTE,
// There are so many more that I haven't filled in.
_,
@@ -135,14 +144,21 @@ pub const Binding = struct {
glad.context.GenerateMipmap.?(@intFromEnum(b.target));
}
pub fn parameter(b: Binding, name: Parameter, value: anytype) errors.Error!void {
switch (@TypeOf(value)) {
pub fn parameter(b: Binding, comptime name: Parameter, value: name.Type()) errors.Error!void {
switch (name.Type()) {
c.GLint => glad.context.TexParameteri.?(
@intFromEnum(b.target),
@intFromEnum(name),
value,
),
else => unreachable,
else => switch (@typeInfo(name.Type())) {
.@"enum" => glad.context.TexParameteri.?(
@intFromEnum(b.target),
@intFromEnum(name),
@intFromEnum(value),
),
else => @compileError("unknown parameter type"),
},
}
try errors.getError();
}

View File

@@ -1,7 +1,9 @@
const std = @import("std");
const c = @import("c");
const errors = @import("errors.zig");
const glad = @import("glad.zig");
const Primitive = @import("primitives.zig").Primitive;
const Texture = @import("Texture.zig");
pub fn clearColor(r: f32, g: f32, b: f32, a: f32) void {
glad.context.ClearColor.?(r, g, b, a);
@@ -75,11 +77,60 @@ pub fn blendFunc(sfactor: c.GLenum, dfactor: c.GLenum) !void {
pub fn viewport(x: c.GLint, y: c.GLint, width: c.GLsizei, height: c.GLsizei) !void {
glad.context.Viewport.?(x, y, width, height);
try errors.getError();
}
pub fn readPixels(
x: c.GLint,
y: c.GLint,
width: c.GLsizei,
height: c.GLsizei,
format: Texture.Format,
typ: Texture.DataType,
data: ?*anyopaque,
) !void {
glad.context.ReadPixels.?(
x,
y,
width,
height,
@intFromEnum(format),
@intFromEnum(typ),
data,
);
try errors.getError();
}
pub fn blitFramebuffer(
src_x0: c.GLint,
src_y0: c.GLint,
src_x1: c.GLint,
src_y1: c.GLint,
dst_x0: c.GLint,
dst_y0: c.GLint,
dst_x1: c.GLint,
dst_y1: c.GLint,
mask: BlitMask,
filter: Texture.MagFilter,
) !void {
glad.context.BlitFramebuffer.?(
src_x0,
src_y0,
src_x1,
src_y1,
dst_x0,
dst_y0,
dst_x1,
dst_y1,
@bitCast(mask),
@intCast(@intFromEnum(filter)),
);
try errors.getError();
}
pub fn pixelStore(mode: c.GLenum, value: anytype) !void {
switch (@typeInfo(@TypeOf(value))) {
.ComptimeInt, .Int => glad.context.PixelStorei.?(mode, value),
.comptime_int, .int => glad.context.PixelStorei.?(mode, value),
else => unreachable,
}
try errors.getError();
@@ -92,3 +143,13 @@ pub fn finish() void {
pub fn flush() void {
glad.context.Flush.?();
}
pub const BlitMask = packed struct(c.GLbitfield) {
_pad1: u8 = 0,
depth_buffer_bit: bool = false,
_pad2: u1 = 0,
stencil_buffer_bit: bool = false,
_pad3: u3 = 0,
color_buffer_bit: bool = false,
_pad4: std.meta.Int(.unsigned, @bitSizeOf(c.GLbitfield) - 15) = 0,
};

View File

@@ -42,5 +42,6 @@ pub const frontFace = draw.frontFace;
pub const readPixels = draw.readPixels;
pub const pixelStore = draw.pixelStore;
pub const viewport = draw.viewport;
pub const blitFramebuffer = draw.blitFramebuffer;
pub const flush = draw.flush;
pub const finish = draw.finish;

View File

@@ -244,7 +244,9 @@ pub fn surfaceSize(self: *const OpenGL) !struct { width: u32, height: u32 } {
/// but now we need to do this manually.
pub fn setViewport(self: *const OpenGL, width: u32, height: u32) void {
_ = self;
gl.glad.context.Viewport.?(0, 0, @intCast(width), @intCast(height));
gl.viewport(0, 0, @intCast(width), @intCast(height)) catch |err| {
log.warn("failed to set OpenGL viewport err={}", .{err});
};
}
/// Actions taken before doing anything in `drawFrame`.
@@ -348,7 +350,7 @@ pub inline fn textureOptions(self: OpenGL) Texture.Options {
return .{
.format = .rgba,
.internal_format = .srgba,
.target = .@"2D",
.target = .@"2d",
.min_filter = .linear,
.mag_filter = .linear,
.wrap_s = .clamp_to_edge,
@@ -395,7 +397,7 @@ pub inline fn imageTextureOptions(
return .{
.format = format.toPixelFormat(),
.internal_format = if (srgb) .srgba else .rgba,
.target = .@"2D",
.target = .@"2d",
// TODO: Generate mipmaps for image textures and use
// linear_mipmap_linear filtering so that they
// look good even when scaled way down.
@@ -426,7 +428,7 @@ pub fn initAtlasTexture(
.{
.format = format,
.internal_format = internal_format,
.target = .Rectangle,
.target = .rectangle,
.min_filter = .nearest,
.mag_filter = .nearest,
.wrap_s = .clamp_to_edge,

View File

@@ -30,10 +30,10 @@ pub fn init(
) Error!Self {
const sampler = gl.Sampler.create() catch return error.OpenGLFailed;
errdefer sampler.destroy();
sampler.parameter(.WrapS, @intFromEnum(opts.wrap_s)) catch return error.OpenGLFailed;
sampler.parameter(.WrapT, @intFromEnum(opts.wrap_t)) catch return error.OpenGLFailed;
sampler.parameter(.MinFilter, @intFromEnum(opts.min_filter)) catch return error.OpenGLFailed;
sampler.parameter(.MagFilter, @intFromEnum(opts.mag_filter)) catch return error.OpenGLFailed;
sampler.parameter(.wrap_s, opts.wrap_s) catch return error.OpenGLFailed;
sampler.parameter(.wrap_t, opts.wrap_t) catch return error.OpenGLFailed;
sampler.parameter(.min_filter, opts.min_filter) catch return error.OpenGLFailed;
sampler.parameter(.mag_filter, opts.mag_filter) catch return error.OpenGLFailed;
return .{
.sampler = sampler,

View File

@@ -56,23 +56,23 @@ pub fn init(opts: Options) !Self {
const texture = try gl.Texture.create();
errdefer texture.destroy();
{
const bound_tex = try texture.bind(.@"2D");
const bound_tex = try texture.bind(.@"2d");
defer bound_tex.unbind();
try bound_tex.parameter(.MinFilter, @intFromEnum(gl.Texture.MinFilter.nearest));
try bound_tex.parameter(.MagFilter, @intFromEnum(gl.Texture.MagFilter.nearest));
try bound_tex.parameter(.WrapS, @intFromEnum(gl.Texture.Wrap.clamp_to_edge));
try bound_tex.parameter(.WrapT, @intFromEnum(gl.Texture.Wrap.clamp_to_edge));
try bound_tex.parameter(.min_filter, .nearest);
try bound_tex.parameter(.mag_filter, .nearest);
try bound_tex.parameter(.wrap_s, .clamp_to_edge);
try bound_tex.parameter(.wrap_t, .clamp_to_edge);
try bound_tex.image2D(
0,
.srgba,
@intCast(opts.width),
@intCast(opts.height),
.rgba,
.UnsignedByte,
.unsigned_byte,
null,
);
try bound_tex.parameter(.BaseLevel, @as(gl.c.GLint, 0));
try bound_tex.parameter(.MaxLevel, @as(gl.c.GLint, 0));
try bound_tex.parameter(.base_level, 0);
try bound_tex.parameter(.max_level, 0);
}
const fbo = try gl.Framebuffer.create();
@@ -80,7 +80,7 @@ pub fn init(opts: Options) !Self {
{
const bound_fbo = try fbo.bind(.framebuffer);
defer bound_fbo.unbind();
try bound_fbo.texture2D(.color0, .@"2D", texture, 0);
try bound_fbo.texture2D(.color0, .@"2d", texture, 0);
switch (bound_fbo.checkStatus()) {
.complete => {},
else => |status| {
@@ -94,23 +94,23 @@ pub fn init(opts: Options) !Self {
const export_texture = try gl.Texture.create();
errdefer export_texture.destroy();
{
const bound_tex = try export_texture.bind(.@"2D");
const bound_tex = try export_texture.bind(.@"2d");
defer bound_tex.unbind();
try bound_tex.parameter(.MinFilter, @intFromEnum(gl.Texture.MinFilter.nearest));
try bound_tex.parameter(.MagFilter, @intFromEnum(gl.Texture.MagFilter.nearest));
try bound_tex.parameter(.WrapS, @intFromEnum(gl.Texture.Wrap.clamp_to_edge));
try bound_tex.parameter(.WrapT, @intFromEnum(gl.Texture.Wrap.clamp_to_edge));
try bound_tex.parameter(.min_filter, .nearest);
try bound_tex.parameter(.mag_filter, .nearest);
try bound_tex.parameter(.wrap_s, .clamp_to_edge);
try bound_tex.parameter(.wrap_t, .clamp_to_edge);
try bound_tex.image2D(
0,
.rgba,
@intCast(opts.width),
@intCast(opts.height),
.rgba,
.UnsignedByte,
.unsigned_byte,
null,
);
try bound_tex.parameter(.BaseLevel, @as(gl.c.GLint, 0));
try bound_tex.parameter(.MaxLevel, @as(gl.c.GLint, 0));
try bound_tex.parameter(.base_level, 0);
try bound_tex.parameter(.max_level, 0);
}
const export_fbo = try gl.Framebuffer.create();
@@ -118,7 +118,7 @@ pub fn init(opts: Options) !Self {
{
const bound_fbo = try export_fbo.bind(.framebuffer);
defer bound_fbo.unbind();
try bound_fbo.texture2D(.color0, .@"2D", export_texture, 0);
try bound_fbo.texture2D(.color0, .@"2d", export_texture, 0);
switch (bound_fbo.checkStatus()) {
.complete => {},
else => |status| {

View File

@@ -50,17 +50,17 @@ pub fn init(
{
const texbind = tex.bind(opts.target) catch return error.OpenGLFailed;
defer texbind.unbind();
texbind.parameter(.WrapS, @intFromEnum(opts.wrap_s)) catch return error.OpenGLFailed;
texbind.parameter(.WrapT, @intFromEnum(opts.wrap_t)) catch return error.OpenGLFailed;
texbind.parameter(.MinFilter, @intFromEnum(opts.min_filter)) catch return error.OpenGLFailed;
texbind.parameter(.MagFilter, @intFromEnum(opts.mag_filter)) catch return error.OpenGLFailed;
texbind.parameter(.wrap_s, opts.wrap_s) catch return error.OpenGLFailed;
texbind.parameter(.wrap_t, opts.wrap_t) catch return error.OpenGLFailed;
texbind.parameter(.min_filter, opts.min_filter) catch return error.OpenGLFailed;
texbind.parameter(.mag_filter, opts.mag_filter) catch return error.OpenGLFailed;
texbind.image2D(
0,
opts.internal_format,
@intCast(width),
@intCast(height),
opts.format,
.UnsignedByte,
.unsigned_byte,
if (data) |d| @ptrCast(d.ptr) else null,
) catch return error.OpenGLFailed;
}
@@ -98,7 +98,7 @@ pub fn replaceRegion(
@intCast(width),
@intCast(height),
self.format,
.UnsignedByte,
.unsigned_byte,
data.ptr,
) catch return error.OpenGLFailed;
}