ghostty/pkg/glfw/opengl.zig
Mitchell Hashimoto 221f905a1c pkg/glfw
Closes #6702

This removes our mach-glfw dependency and replaces it with an in-tree
pkg/glfw that includes both the source for compiling glfw as well as the
Zig bindings. This matches the pattern from our other packages.

This is based on the upstream mach-glfw work and therefore includes the
original license and copyright information.

The reasoning is stated in the issue but to summarize for the commit:

  - mach-glfw is no longer maintained, so we have to take ownership
  - mach-glfw depended on some large blobs of header files to enable
    cross-compilation but this isn't something we actually care about,
    so we can (and do) drop the blobs
  - mach-glfw blobs were hosted on mach hosts. given mach-glfw is
    unmaintained, we can't rely on this hosting
  - mach-glfw relied on a "glfw" package which was owned by another
    person to be Zig 0.14 compatible, but we no longer need to rely on
    this
  - mach-glfw builds were outdated based on latest Zig practices
2025-03-13 20:52:33 -07:00

257 lines
11 KiB
Zig

const std = @import("std");
const builtin = @import("builtin");
const c = @import("c.zig").c;
const Window = @import("Window.zig");
const internal_debug = @import("internal_debug.zig");
/// Makes the context of the specified window current for the calling thread.
///
/// This function makes the OpenGL or OpenGL ES context of the specified window current on the
/// calling thread. A context must only be made current on a single thread at a time and each
/// thread can have only a single current context at a time.
///
/// When moving a context between threads, you must make it non-current on the old thread before
/// making it current on the new one.
///
/// By default, making a context non-current implicitly forces a pipeline flush. On machines that
/// support `GL_KHR_context_flush_control`, you can control whether a context performs this flush
/// by setting the glfw.context_release_behavior hint.
///
/// The specified window must have an OpenGL or OpenGL ES context. Specifying a window without a
/// context will generate glfw.ErrorCode.NoWindowContext.
///
/// @param[in] window The window whose context to make current, or null to
/// detach the current context.
///
/// Possible errors include glfw.ErrorCode.NoWindowContext and glfw.ErrorCode.PlatformError.
///
/// @thread_safety This function may be called from any thread.
///
/// see also: context_current, glfwGetCurrentContext
pub inline fn makeContextCurrent(window: ?Window) void {
internal_debug.assertInitialized();
if (window) |w| c.glfwMakeContextCurrent(w.handle) else c.glfwMakeContextCurrent(null);
}
/// Returns the window whose context is current on the calling thread.
///
/// This function returns the window whose OpenGL or OpenGL ES context is current on the calling
/// thread.
///
/// Returns he window whose context is current, or null if no window's context is current.
///
/// @thread_safety This function may be called from any thread.
///
/// see also: context_current, glfwMakeContextCurrent
pub inline fn getCurrentContext() ?Window {
internal_debug.assertInitialized();
if (c.glfwGetCurrentContext()) |handle| return Window.from(handle);
return null;
}
/// Sets the swap interval for the current context.
///
/// This function sets the swap interval for the current OpenGL or OpenGL ES context, i.e. the
/// number of screen updates to wait from the time glfw.SwapBuffers was called before swapping the
/// buffers and returning. This is sometimes called _vertical synchronization_, _vertical retrace
/// synchronization_ or just _vsync_.
///
/// A context that supports either of the `WGL_EXT_swap_control_tear` and `GLX_EXT_swap_control_tear`
/// extensions also accepts _negative_ swap intervals, which allows the driver to swap immediately
/// even if a frame arrives a little bit late. You can check for these extensions with glfw.extensionSupported.
///
/// A context must be current on the calling thread. Calling this function without a current context
/// will cause glfw.ErrorCode.NoCurrentContext.
///
/// This function does not apply to Vulkan. If you are rendering with Vulkan, see the present mode
/// of your swapchain instead.
///
/// @param[in] interval The minimum number of screen updates to wait for until the buffers are
/// swapped by glfw.swapBuffers.
///
/// Possible errors include glfw.ErrorCode.NoCurrentContext and glfw.ErrorCode.PlatformError.
///
/// This function is not called during context creation, leaving the swap interval set to whatever
/// is the default for that API. This is done because some swap interval extensions used by
/// GLFW do not allow the swap interval to be reset to zero once it has been set to a non-zero
/// value.
///
/// Some GPU drivers do not honor the requested swap interval, either because of a user setting
/// that overrides the application's request or due to bugs in the driver.
///
/// @thread_safety This function may be called from any thread.
///
/// see also: buffer_swap, glfwSwapBuffers
pub inline fn swapInterval(interval: i32) void {
internal_debug.assertInitialized();
c.glfwSwapInterval(@as(c_int, @intCast(interval)));
}
/// Returns whether the specified extension is available.
///
/// This function returns whether the specified API extension (see context_glext) is supported by
/// the current OpenGL or OpenGL ES context. It searches both for client API extension and context
/// creation API extensions.
///
/// A context must be current on the calling thread. Calling this function without a current
/// context will cause glfw.ErrorCode.NoCurrentContext.
///
/// As this functions retrieves and searches one or more extension strings each call, it is
/// recommended that you cache its results if it is going to be used frequently. The extension
/// strings will not change during the lifetime of a context, so there is no danger in doing this.
///
/// This function does not apply to Vulkan. If you are using Vulkan, see glfw.getRequiredInstanceExtensions,
/// `vkEnumerateInstanceExtensionProperties` and `vkEnumerateDeviceExtensionProperties` instead.
///
/// @param[in] extension The ASCII encoded name of the extension.
/// @return `true` if the extension is available, or `false` otherwise.
///
/// Possible errors include glfw.ErrorCode.NoCurrentContext, glfw.ErrorCode.InvalidValue
/// and glfw.ErrorCode.PlatformError.
///
/// @thread_safety This function may be called from any thread.
///
/// see also: context_glext, glfw.getProcAddress
pub inline fn extensionSupported(extension: [:0]const u8) bool {
internal_debug.assertInitialized();
std.debug.assert(extension.len != 0);
std.debug.assert(extension[0] != 0);
return c.glfwExtensionSupported(extension.ptr) == c.GLFW_TRUE;
}
/// Client API function pointer type.
///
/// Generic function pointer used for returning client API function pointers.
///
/// see also: context_glext, glfwGetProcAddress
pub const GLProc = *const fn () callconv(if (builtin.os.tag == .windows and builtin.cpu.arch == .x86) .Stdcall else .C) void;
/// Returns the address of the specified function for the current context.
///
/// This function returns the address of the specified OpenGL or OpenGL ES core or extension
/// function (see context_glext), if it is supported by the current context.
///
/// A context must be current on the calling thread. Calling this function without a current
/// context will cause glfw.ErrorCode.NoCurrentContext.
///
/// This function does not apply to Vulkan. If you are rendering with Vulkan, see glfw.getInstanceProcAddress,
/// `vkGetInstanceProcAddr` and `vkGetDeviceProcAddr` instead.
///
/// @param[in] procname The ASCII encoded name of the function.
/// @return The address of the function, or null if an error occurred.
///
/// To maintain ABI compatability with the C glfwGetProcAddress, as it is commonly passed into
/// libraries expecting that exact ABI, this function does not return an error. Instead, if
/// glfw.ErrorCode.NotInitialized, glfw.ErrorCode.NoCurrentContext, or glfw.ErrorCode.PlatformError
/// would occur this function will panic. You should ensure a valid OpenGL context exists and the
/// GLFW is initialized before calling this function.
///
/// The address of a given function is not guaranteed to be the same between contexts.
///
/// This function may return a non-null address despite the associated version or extension
/// not being available. Always check the context version or extension string first.
///
/// @pointer_lifetime The returned function pointer is valid until the context is destroyed or the
/// library is terminated.
///
/// @thread_safety This function may be called from any thread.
///
/// see also: context_glext, glfwExtensionSupported
pub fn getProcAddress(proc_name: [*:0]const u8) callconv(.C) ?GLProc {
internal_debug.assertInitialized();
if (c.glfwGetProcAddress(proc_name)) |proc_address| return @ptrCast(proc_address);
return null;
}
test "makeContextCurrent" {
const glfw = @import("main.zig");
defer glfw.clearError(); // clear any error we generate
if (!glfw.init(.{})) {
std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()});
std.process.exit(1);
}
defer glfw.terminate();
const window = Window.create(640, 480, "Hello, Zig!", null, null, .{}) orelse {
std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()});
return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows
};
defer window.destroy();
glfw.makeContextCurrent(window);
}
test "getCurrentContext" {
const glfw = @import("main.zig");
defer glfw.clearError(); // clear any error we generate
if (!glfw.init(.{})) {
std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()});
std.process.exit(1);
}
defer glfw.terminate();
const current_context = glfw.getCurrentContext();
std.debug.assert(current_context == null);
}
test "swapInterval" {
const glfw = @import("main.zig");
defer glfw.clearError(); // clear any error we generate
if (!glfw.init(.{})) {
std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()});
std.process.exit(1);
}
defer glfw.terminate();
const window = Window.create(640, 480, "Hello, Zig!", null, null, .{}) orelse {
std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()});
return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows
};
defer window.destroy();
glfw.makeContextCurrent(window);
glfw.swapInterval(1);
}
test "getProcAddress" {
const glfw = @import("main.zig");
defer glfw.clearError(); // clear any error we generate
if (!glfw.init(.{})) {
std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()});
std.process.exit(1);
}
defer glfw.terminate();
const window = Window.create(640, 480, "Hello, Zig!", null, null, .{}) orelse {
std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()});
return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows
};
defer window.destroy();
glfw.makeContextCurrent(window);
_ = glfw.getProcAddress("foobar");
}
test "extensionSupported" {
const glfw = @import("main.zig");
defer glfw.clearError(); // clear any error we generate
if (!glfw.init(.{})) {
std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()});
std.process.exit(1);
}
defer glfw.terminate();
const window = Window.create(640, 480, "Hello, Zig!", null, null, .{}) orelse {
std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()});
return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows
};
defer window.destroy();
glfw.makeContextCurrent(window);
_ = glfw.extensionSupported("foobar");
}