glfw: rework error handling system to prevent footguns

Signed-off-by: Stephen Gutekanst <stephen@hexops.com>
This commit is contained in:
Stephen Gutekanst 2022-12-27 04:20:16 -07:00 committed by Stephen Gutekanst
parent cf8922cc33
commit abb1077052
12 changed files with 1136 additions and 1531 deletions

View file

@ -49,13 +49,10 @@ pub const VKGetInstanceProcAddr = *const fn (vk_instance: c.VkInstance, name: [*
///
/// @return `true` if Vulkan is minimally available, or `false` otherwise.
///
/// Possible errors include glfw.Error.NotInitialized.
///
/// @thread_safety This function may be called from any thread.
pub inline fn vulkanSupported() bool {
internal_debug.assertInitialized();
const supported = c.glfwVulkanSupported();
getError() catch unreachable; // Only error 'GLFW_NOT_INITIALIZED' is impossible
return supported == c.GLFW_TRUE;
}
@ -73,7 +70,8 @@ pub inline fn vulkanSupported() bool {
/// If Vulkan is available but no set of extensions allowing window surface creation was found,
/// this function returns null. You may still use Vulkan for off-screen rendering and compute work.
///
/// Possible errors include glfw.Error.NotInitialized and glfw.Error.APIUnavailable.
/// Possible errors include glfw.Error.APIUnavailable.
/// Returns null in the event of an error.
///
/// Additional extensions may be required by future versions of GLFW. You should check if any
/// extensions you wish to enable are already in the returned array, as it is an error to specify
@ -85,17 +83,11 @@ pub inline fn vulkanSupported() bool {
/// @thread_safety This function may be called from any thread.
///
/// see also: vulkan_ext, glfwCreateWindowSurface
pub inline fn getRequiredInstanceExtensions() error{APIUnavailable}![][*:0]const u8 {
pub inline fn getRequiredInstanceExtensions() ?[][*:0]const u8 {
internal_debug.assertInitialized();
var count: u32 = 0;
if (c.glfwGetRequiredInstanceExtensions(&count)) |extensions| return @ptrCast([*][*:0]const u8, extensions)[0..count];
getError() catch |err| return switch (err) {
Error.NotInitialized => unreachable,
Error.APIUnavailable => |e| e,
else => unreachable,
};
// `glfwGetRequiredInstanceExtensions` returns `null` only for errors
unreachable;
return null;
}
/// Vulkan API function pointer type.
@ -139,7 +131,6 @@ pub const VKProc = *const fn () callconv(.C) void;
pub fn getInstanceProcAddress(vk_instance: ?*anyopaque, proc_name: [*:0]const u8) callconv(.C) ?VKProc {
internal_debug.assertInitialized();
if (c.glfwGetInstanceProcAddress(if (vk_instance) |v| @ptrCast(c.VkInstance, v) else null, proc_name)) |proc_address| return proc_address;
getError() catch |err| @panic(@errorName(err));
return null;
}
@ -159,7 +150,8 @@ pub fn getInstanceProcAddress(vk_instance: ?*anyopaque, proc_name: [*:0]const u8
/// @param[in] queuefamily The index of the queue family to query.
/// @return `true` if the queue family supports presentation, or `false` otherwise.
///
/// Possible errors include glfw.Error.NotInitialized, glfw.Error.APIUnavailable and glfw.Error.PlatformError.
/// Possible errors include glfw.Error.APIUnavailable and glfw.Error.PlatformError.
/// Returns false in the event of an error.
///
/// macos: This function currently always returns `true`, as the `VK_MVK_macos_surface` and
/// 'VK_EXT_metal_surface' extension does not provide a `vkGetPhysicalDevice*PresentationSupport` type function.
@ -172,19 +164,13 @@ pub inline fn getPhysicalDevicePresentationSupport(
vk_instance: *anyopaque,
vk_physical_device: *anyopaque,
queue_family: u32,
) error{ APIUnavailable, PlatformError }!bool {
) bool {
internal_debug.assertInitialized();
const v = c.glfwGetPhysicalDevicePresentationSupport(
return c.glfwGetPhysicalDevicePresentationSupport(
@ptrCast(c.VkInstance, vk_instance),
@ptrCast(c.VkPhysicalDevice, vk_physical_device),
queue_family,
);
getError() catch |err| return switch (err) {
Error.NotInitialized => unreachable,
Error.APIUnavailable, Error.PlatformError => |e| e,
else => unreachable,
};
return v == c.GLFW_TRUE;
) == c.GLFW_TRUE;
}
/// Creates a Vulkan surface for the specified window.
@ -217,7 +203,8 @@ pub inline fn getPhysicalDevicePresentationSupport(
/// @return `VkResult` type, `VK_SUCCESS` if successful, or a Vulkan error code if an
/// error occurred.
///
/// Possible errors include glfw.Error.NotInitialized, glfw.Error.APIUnavailable, glfw.Error.PlatformError and glfw.Error.InvalidValue
/// Possible errors include glfw.Error.APIUnavailable, glfw.Error.PlatformError and glfw.Error.InvalidValue
/// Returns a bool indicating success.
///
/// If an error occurs before the creation call is made, GLFW returns the Vulkan error code most
/// appropriate for the error. Appropriate use of glfw.vulkanSupported and glfw.getRequiredInstanceExtensions
@ -241,7 +228,7 @@ pub inline fn getPhysicalDevicePresentationSupport(
/// Vulkan objects, see the Vulkan specification.
///
/// see also: vulkan_surface, glfw.getRequiredInstanceExtensions
pub inline fn createWindowSurface(vk_instance: anytype, window: Window, vk_allocation_callbacks: anytype, vk_surface_khr: anytype) error{ APIUnavailable, PlatformError }!i32 {
pub inline fn createWindowSurface(vk_instance: anytype, window: Window, vk_allocation_callbacks: anytype, vk_surface_khr: anytype) i32 {
internal_debug.assertInitialized();
// zig-vulkan uses enums to represent opaque pointers:
// pub const Instance = enum(usize) { null_handle = 0, _ };
@ -250,26 +237,21 @@ pub inline fn createWindowSurface(vk_instance: anytype, window: Window, vk_alloc
else => @ptrCast(c.VkInstance, vk_instance),
};
const v = c.glfwCreateWindowSurface(
return c.glfwCreateWindowSurface(
instance,
window.handle,
if (vk_allocation_callbacks == null) null else @ptrCast(*const c.VkAllocationCallbacks, @alignCast(@alignOf(c.VkAllocationCallbacks), vk_allocation_callbacks)),
@ptrCast(*c.VkSurfaceKHR, @alignCast(@alignOf(c.VkSurfaceKHR), vk_surface_khr)),
);
if (v == c.VK_SUCCESS) return v;
getError() catch |err| return switch (err) {
Error.NotInitialized => unreachable,
Error.InvalidValue => @panic("Attempted to use window with client api to create vulkan surface."),
Error.APIUnavailable, Error.PlatformError => |e| e,
else => unreachable,
};
// `glfwCreateWindowSurface` returns `!VK_SUCCESS` only for errors
unreachable;
) == c.VK_SUCCESS;
}
test "vulkanSupported" {
const glfw = @import("main.zig");
try glfw.init(.{});
defer glfw.getError() catch {}; // 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();
_ = glfw.vulkanSupported();
@ -277,15 +259,23 @@ test "vulkanSupported" {
test "getRequiredInstanceExtensions" {
const glfw = @import("main.zig");
try glfw.init(.{});
defer glfw.getError() catch {}; // 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();
_ = glfw.getRequiredInstanceExtensions() catch |err| std.debug.print("failed to get vulkan instance extensions, error={}\n", .{err});
_ = glfw.getRequiredInstanceExtensions();
}
test "getInstanceProcAddress" {
const glfw = @import("main.zig");
try glfw.init(.{});
defer glfw.getError() catch {}; // 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();
// syntax check only, we don't have a real vulkan instance and so this function would panic.