This repository has no description
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449//! Graphics API wrapper for OpenGL.pub const OpenGL = @This();
const std = @import("std");const assert = std.debug.assert;const Allocator = std.mem.Allocator;const builtin = @import("builtin");const gl = @import("opengl");const shadertoy = @import("shadertoy.zig");const apprt = @import("../apprt.zig");const font = @import("../font/main.zig");const configpkg = @import("../config.zig");const rendererpkg = @import("../renderer.zig");const Renderer = rendererpkg.GenericRenderer(OpenGL);
pub const GraphicsAPI = OpenGL;pub const Target = @import("opengl/Target.zig");pub const Frame = @import("opengl/Frame.zig");pub const RenderPass = @import("opengl/RenderPass.zig");pub const Pipeline = @import("opengl/Pipeline.zig");const bufferpkg = @import("opengl/buffer.zig");pub const Buffer = bufferpkg.Buffer;pub const Texture = @import("opengl/Texture.zig");pub const shaders = @import("opengl/shaders.zig");
pub const custom_shader_target: shadertoy.Target = .glsl;// The fragCoord for OpenGL shaders is +Y = up.pub const custom_shader_y_is_down = false;
/// Because OpenGL's frame completion is always/// sync, we have no need for multi-buffering.pub const swap_chain_count = 1;
const log = std.log.scoped(.opengl);
/// We require at least OpenGL 4.3pub const MIN_VERSION_MAJOR = 4;pub const MIN_VERSION_MINOR = 3;
alloc: std.mem.Allocator,
/// Alpha blending modeblending: configpkg.Config.AlphaBlending,
/// The most recently presented target, in case we need to present it again.last_target: ?Target = null,
/// NOTE: This is an error{}!OpenGL instead of just OpenGL for parity with/// Metal, since it needs to be fallible so does this, even though it/// can't actually fail.pub fn init(alloc: Allocator, opts: rendererpkg.Options) error{}!OpenGL { return .{ .alloc = alloc, .blending = opts.config.blending, };}
pub fn deinit(self: *OpenGL) void { self.* = undefined;}
/// 32-bit windows cross-compilation breaks with `.c` for some reason, so...const gl_debug_proc_callconv = @typeInfo( @typeInfo( @typeInfo( gl.c.GLDEBUGPROC, ).optional.child, ).pointer.child, ).@"fn".calling_convention;
fn glDebugMessageCallback( src: gl.c.GLenum, typ: gl.c.GLenum, id: gl.c.GLuint, severity: gl.c.GLenum, len: gl.c.GLsizei, msg: [*c]const gl.c.GLchar, user_param: ?*const anyopaque,) callconv(gl_debug_proc_callconv) void { _ = user_param;
const src_str: []const u8 = switch (src) { gl.c.GL_DEBUG_SOURCE_API => "OpenGL API", gl.c.GL_DEBUG_SOURCE_WINDOW_SYSTEM => "Window System", gl.c.GL_DEBUG_SOURCE_SHADER_COMPILER => "Shader Compiler", gl.c.GL_DEBUG_SOURCE_THIRD_PARTY => "Third Party", gl.c.GL_DEBUG_SOURCE_APPLICATION => "User", gl.c.GL_DEBUG_SOURCE_OTHER => "Other", else => "Unknown", };
const typ_str: []const u8 = switch (typ) { gl.c.GL_DEBUG_TYPE_ERROR => "Error", gl.c.GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR => "Deprecated Behavior", gl.c.GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR => "Undefined Behavior", gl.c.GL_DEBUG_TYPE_PORTABILITY => "Portability Issue", gl.c.GL_DEBUG_TYPE_PERFORMANCE => "Performance Issue", gl.c.GL_DEBUG_TYPE_MARKER => "Marker", gl.c.GL_DEBUG_TYPE_PUSH_GROUP => "Group Push", gl.c.GL_DEBUG_TYPE_POP_GROUP => "Group Pop", gl.c.GL_DEBUG_TYPE_OTHER => "Other", else => "Unknown", };
const msg_str = msg[0..@intCast(len)];
(switch (severity) { gl.c.GL_DEBUG_SEVERITY_HIGH => log.err( "[{d}] ({s}: {s}) {s}", .{ id, src_str, typ_str, msg_str }, ), gl.c.GL_DEBUG_SEVERITY_MEDIUM => log.warn( "[{d}] ({s}: {s}) {s}", .{ id, src_str, typ_str, msg_str }, ), gl.c.GL_DEBUG_SEVERITY_LOW => log.info( "[{d}] ({s}: {s}) {s}", .{ id, src_str, typ_str, msg_str }, ), gl.c.GL_DEBUG_SEVERITY_NOTIFICATION => log.debug( "[{d}] ({s}: {s}) {s}", .{ id, src_str, typ_str, msg_str }, ), else => log.warn( "UNKNOWN SEVERITY [{d}] ({s}: {s}) {s}", .{ id, src_str, typ_str, msg_str }, ), });}
/// Prepares the provided GL context, loading it with glad.fn prepareContext(getProcAddress: anytype) !void { const version = try gl.glad.load(getProcAddress); const major = gl.glad.versionMajor(@intCast(version)); const minor = gl.glad.versionMinor(@intCast(version)); errdefer gl.glad.unload(); log.info("loaded OpenGL {}.{}", .{ major, minor });
// Enable debug output for the context. try gl.enable(gl.c.GL_DEBUG_OUTPUT);
// Register our debug message callback with the OpenGL context. gl.glad.context.DebugMessageCallback.?(glDebugMessageCallback, null);
// Enable SRGB framebuffer for linear blending support. try gl.enable(gl.c.GL_FRAMEBUFFER_SRGB);
if (major < MIN_VERSION_MAJOR or (major == MIN_VERSION_MAJOR and minor < MIN_VERSION_MINOR)) { log.warn( "OpenGL version is too old. Ghostty requires OpenGL {d}.{d}", .{ MIN_VERSION_MAJOR, MIN_VERSION_MINOR }, ); return error.OpenGLOutdated; }}
/// This is called early right after surface creation.pub fn surfaceInit(surface: *apprt.Surface) !void { _ = surface;
switch (apprt.runtime) { else => @compileError("unsupported app runtime for OpenGL"),
// GTK uses global OpenGL context so we load from null. apprt.gtk => try prepareContext(null),
apprt.embedded => { // TODO(mitchellh): this does nothing today to allow libghostty // to compile for OpenGL targets but libghostty is strictly // broken for rendering on this platforms. }, }
// These are very noisy so this is commented, but easy to uncomment // whenever we need to check the OpenGL extension list // if (builtin.mode == .Debug) { // var ext_iter = try gl.ext.iterator(); // while (try ext_iter.next()) |ext| { // log.debug("OpenGL extension available name={s}", .{ext}); // } // }}
/// This is called just prior to spinning up the renderer/// thread for final main thread setup requirements.pub fn finalizeSurfaceInit(self: *const OpenGL, surface: *apprt.Surface) !void { _ = self; _ = surface;}
/// Callback called by renderer.Thread when it begins.pub fn threadEnter(self: *const OpenGL, surface: *apprt.Surface) !void { _ = self; _ = surface;
switch (apprt.runtime) { else => @compileError("unsupported app runtime for OpenGL"),
apprt.gtk => { // GTK doesn't support threaded OpenGL operations as far as I can // tell, so we use the renderer thread to setup all the state // but then do the actual draws and texture syncs and all that // on the main thread. As such, we don't do anything here. },
apprt.embedded => { // TODO(mitchellh): this does nothing today to allow libghostty // to compile for OpenGL targets but libghostty is strictly // broken for rendering on this platforms. }, }}
/// Callback called by renderer.Thread when it exits.pub fn threadExit(self: *const OpenGL) void { _ = self;
switch (apprt.runtime) { else => @compileError("unsupported app runtime for OpenGL"),
apprt.gtk => { // We don't need to do any unloading for GTK because we may // be sharing the global bindings with other windows. },
apprt.embedded => { // TODO: see threadEnter }, }}
pub fn displayRealized(self: *const OpenGL) void { _ = self;
switch (apprt.runtime) { apprt.gtk => prepareContext(null) catch |err| { log.warn( "Error preparing GL context in displayRealized, err={}", .{err}, ); },
else => @compileError("only GTK should be calling displayRealized"), }}
/// Actions taken before doing anything in `drawFrame`.////// Right now there's nothing we need to do for OpenGL.pub fn drawFrameStart(self: *OpenGL) void { _ = self;}
/// Actions taken after `drawFrame` is done.////// Right now there's nothing we need to do for OpenGL.pub fn drawFrameEnd(self: *OpenGL) void { _ = self;}
pub fn initShaders( self: *const OpenGL, alloc: Allocator, custom_shaders: []const [:0]const u8,) !shaders.Shaders { _ = alloc; return try shaders.Shaders.init( self.alloc, custom_shaders, );}
/// Get the current size of the runtime surface.pub fn surfaceSize(self: *const OpenGL) !struct { width: u32, height: u32 } { _ = self; var viewport: [4]gl.c.GLint = undefined; gl.glad.context.GetIntegerv.?(gl.c.GL_VIEWPORT, &viewport); return .{ .width = @intCast(viewport[2]), .height = @intCast(viewport[3]), };}
/// Initialize a new render target which can be presented by this API.pub fn initTarget(self: *const OpenGL, width: usize, height: usize) !Target { return Target.init(.{ .internal_format = if (self.blending.isLinear()) .srgba else .rgba, .width = width, .height = height, });}
/// Present the provided target.pub fn present(self: *OpenGL, target: Target) !void { // In order to present a target we blit it to the default framebuffer.
// We disable GL_FRAMEBUFFER_SRGB while doing this blit, otherwise the // values may be linearized as they're copied, but even though the draw // framebuffer has a linear internal format, the values in it should be // sRGB, not linear! try gl.disable(gl.c.GL_FRAMEBUFFER_SRGB); defer gl.enable(gl.c.GL_FRAMEBUFFER_SRGB) catch |err| { log.err("Error re-enabling GL_FRAMEBUFFER_SRGB, err={}", .{err}); };
// Bind the target for reading. const fbobind = try target.framebuffer.bind(.read); defer fbobind.unbind();
// Blit gl.glad.context.BlitFramebuffer.?( 0, 0, @intCast(target.width), @intCast(target.height), 0, 0, @intCast(target.width), @intCast(target.height), gl.c.GL_COLOR_BUFFER_BIT, gl.c.GL_NEAREST, );
// Keep track of this target in case we need to repeat it. self.last_target = target;}
/// Present the last presented target again.pub fn presentLastTarget(self: *OpenGL) !void { if (self.last_target) |target| try self.present(target);}
/// Returns the options to use when constructing buffers.pub inline fn bufferOptions(self: OpenGL) bufferpkg.Options { _ = self; return .{ .target = .array, .usage = .dynamic_draw, };}
pub const instanceBufferOptions = bufferOptions;pub const uniformBufferOptions = bufferOptions;pub const fgBufferOptions = bufferOptions;pub const bgBufferOptions = bufferOptions;pub const imageBufferOptions = bufferOptions;pub const bgImageBufferOptions = bufferOptions;
/// Returns the options to use when constructing textures.pub inline fn textureOptions(self: OpenGL) Texture.Options { _ = self; return .{ .format = .rgba, .internal_format = .srgba, .target = .@"2D", .min_filter = .linear, .mag_filter = .linear, .wrap_s = .clamp_to_edge, .wrap_t = .clamp_to_edge, };}
/// Pixel format for image texture options.pub const ImageTextureFormat = enum { /// 1 byte per pixel grayscale. gray, /// 4 bytes per pixel RGBA. rgba, /// 4 bytes per pixel BGRA. bgra,
fn toPixelFormat(self: ImageTextureFormat) gl.Texture.Format { return switch (self) { .gray => .red, .rgba => .rgba, .bgra => .bgra, }; }};
/// Returns the options to use when constructing textures for images.pub inline fn imageTextureOptions( self: OpenGL, format: ImageTextureFormat, srgb: bool,) Texture.Options { _ = self; return .{ .format = format.toPixelFormat(), .internal_format = if (srgb) .srgba else .rgba, .target = .@"2D", // TODO: Generate mipmaps for image textures and use // linear_mipmap_linear filtering so that they // look good even when scaled way down. .min_filter = .linear, .mag_filter = .linear, // TODO: Separate out background image options, use // repeating coordinate modes so we don't have // to do the modulus in the shader. .wrap_s = .clamp_to_edge, .wrap_t = .clamp_to_edge, };}
/// Initializes a Texture suitable for the provided font atlas.pub fn initAtlasTexture( self: *const OpenGL, atlas: *const font.Atlas,) Texture.Error!Texture { _ = self; const format: gl.Texture.Format, const internal_format: gl.Texture.InternalFormat = switch (atlas.format) { .grayscale => .{ .red, .red }, .bgra => .{ .bgra, .srgba }, else => @panic("unsupported atlas format for OpenGL texture"), };
return try Texture.init( .{ .format = format, .internal_format = internal_format, .target = .Rectangle, .min_filter = .nearest, .mag_filter = .nearest, .wrap_s = .clamp_to_edge, .wrap_t = .clamp_to_edge, }, atlas.size, atlas.size, null, );}
/// Begin a frame.pub inline fn beginFrame( self: *const OpenGL, /// Once the frame has been completed, the `frameCompleted` method /// on the renderer is called with the health status of the frame. renderer: *Renderer, /// The target is presented via the provided renderer's API when completed. target: *Target,) !Frame { _ = self; return try Frame.begin(.{}, renderer, target);}