diff --git a/README.md b/README.md index 1d08ecd..aa006d2 100644 --- a/README.md +++ b/README.md @@ -129,8 +129,8 @@ Assume thou that functions lacking in this array are, for now, not intended to b | vkCreateSwapchainKHR | ✅ Implemented | | vkCreateWaylandSurfaceKHR | ✅ Implemented | | vkCreateWin32SurfaceKHR | ⚙️ WIP | -| vkCreateXcbSurfaceKHR | ⚙️ WIP | -| vkCreateXlibSurfaceKHR | ⚙️ WIP | +| vkCreateXcbSurfaceKHR | ✅ Implemented | +| vkCreateXlibSurfaceKHR | ✅ Implemented | | vkDestroyBuffer | ✅ Implemented | | vkDestroyBufferView | ✅ Implemented | | vkDestroyCommandPool | ✅ Implemented | @@ -194,8 +194,8 @@ Assume thou that functions lacking in this array are, for now, not intended to b | vkGetPhysicalDeviceSurfaceSupportKHR | ✅ Implemented | | vkGetPhysicalDeviceWaylandPresentationSupportKHR | ✅ Implemented | | vkGetPhysicalDeviceWin32PresentationSupportKHR | ⚙️ WIP | -| vkGetPhysicalDeviceXcbPresentationSupportKHR | ⚙️ WIP | -| vkGetPhysicalDeviceXlibPresentationSupportKHR | ⚙️ WIP | +| vkGetPhysicalDeviceXcbPresentationSupportKHR | ✅ Implemented | +| vkGetPhysicalDeviceXlibPresentationSupportKHR | ✅ Implemented | | vkGetPipelineCacheData | ✅ Implemented | | vkGetQueryPoolResults | ✅ Implemented | | vkGetRenderAreaGranularity | ✅ Implemented | diff --git a/src/intel/FlintInstance.zig b/src/intel/FlintInstance.zig index f161f16..9407bbd 100644 --- a/src/intel/FlintInstance.zig +++ b/src/intel/FlintInstance.zig @@ -31,6 +31,8 @@ pub const extensions = [_]vk.ExtensionProperties{ castExtension(vk.extensions.khr_get_physical_device_properties_2), castExtension(vk.extensions.khr_surface), castExtension(vk.extensions.khr_wayland_surface), + castExtension(vk.extensions.khr_xlib_surface), + castExtension(vk.extensions.khr_xcb_surface), }; pub fn create(allocator: std.mem.Allocator, infos: *const vk.InstanceCreateInfo) VkError!*Interface { diff --git a/src/phi/PhiInstance.zig b/src/phi/PhiInstance.zig index ea3c6eb..12fcab3 100644 --- a/src/phi/PhiInstance.zig +++ b/src/phi/PhiInstance.zig @@ -32,6 +32,8 @@ pub const extensions = [_]vk.ExtensionProperties{ castExtension(vk.extensions.khr_get_physical_device_properties_2), castExtension(vk.extensions.khr_surface), castExtension(vk.extensions.khr_wayland_surface), + castExtension(vk.extensions.khr_xlib_surface), + castExtension(vk.extensions.khr_xcb_surface), }; pub fn create(allocator: std.mem.Allocator, infos: *const vk.InstanceCreateInfo) VkError!*Interface { diff --git a/src/software/SoftInstance.zig b/src/software/SoftInstance.zig index 427f171..7802f04 100644 --- a/src/software/SoftInstance.zig +++ b/src/software/SoftInstance.zig @@ -29,8 +29,8 @@ pub const extensions = [_]vk.ExtensionProperties{ castExtension(vk.extensions.khr_get_physical_device_properties_2), castExtension(vk.extensions.khr_surface), castExtension(vk.extensions.khr_wayland_surface), - //castExtension(vk.extensions.khr_xlib_surface), - //castExtension(vk.extensions.khr_xcb_surface), + castExtension(vk.extensions.khr_xlib_surface), + castExtension(vk.extensions.khr_xcb_surface), }; pub fn create(allocator: std.mem.Allocator, infos: *const vk.InstanceCreateInfo) VkError!*Interface { diff --git a/src/vulkan/lib_vulkan.zig b/src/vulkan/lib_vulkan.zig index 13386ed..7047842 100644 --- a/src/vulkan/lib_vulkan.zig +++ b/src/vulkan/lib_vulkan.zig @@ -52,7 +52,14 @@ const has_wayland = switch (builtin.os.tag) { else => false, }; +const has_x11 = switch (builtin.os.tag) { + .linux, .freebsd, .netbsd, .openbsd, .dragonfly => true, + else => false, +}; + pub const WaylandSurfaceKHR = if (has_wayland) @import("wsi/WaylandSurfaceKHR.zig") else null; +pub const XlibSurfaceKHR = if (has_x11) @import("wsi/XlibSurfaceKHR.zig") else null; +pub const XcbSurfaceKHR = if (has_x11) @import("wsi/XcbSurfaceKHR.zig") else null; inline fn entryPointBeginLogTrace(comptime scope: @EnumLiteral()) void { std.log.scoped(scope).debug("Calling {s}...", .{@tagName(scope)}); @@ -100,6 +107,8 @@ const global_pfn_map = std.StaticStringMap(vk.PfnVoidFunction).initComptime(.{ const instance_pfn_map = std.StaticStringMap(vk.PfnVoidFunction).initComptime(.{ functionMapEntryPoint("vkCreateWaylandSurfaceKHR"), + functionMapEntryPoint("vkCreateXlibSurfaceKHR"), + functionMapEntryPoint("vkCreateXcbSurfaceKHR"), functionMapEntryPoint("vkDestroyInstance"), functionMapEntryPoint("vkDestroySurfaceKHR"), functionMapEntryPoint("vkEnumeratePhysicalDeviceGroups"), @@ -131,6 +140,8 @@ const physical_device_pfn_map = std.StaticStringMap(vk.PfnVoidFunction).initComp functionMapEntryPoint("vkGetPhysicalDeviceSurfacePresentModesKHR"), functionMapEntryPoint("vkGetPhysicalDeviceSurfaceSupportKHR"), functionMapEntryPoint("vkGetPhysicalDeviceWaylandPresentationSupportKHR"), + functionMapEntryPoint("vkGetPhysicalDeviceXlibPresentationSupportKHR"), + functionMapEntryPoint("vkGetPhysicalDeviceXcbPresentationSupportKHR"), }); const device_pfn_map = block: { @@ -2406,6 +2417,42 @@ pub export fn apeCreateWaylandSurfaceKHR(p_instance: vk.Instance, info: *const v } } +pub export fn apeCreateXlibSurfaceKHR(p_instance: vk.Instance, info: *const vk.XlibSurfaceCreateInfoKHR, callbacks: ?*const vk.AllocationCallbacks, p_surface: *vk.SurfaceKHR) callconv(vk.vulkan_call_conv) vk.Result { + if (comptime has_x11) { + entryPointBeginLogTrace(.vkCreateXlibSurfaceKHR); + defer entryPointEndLogTrace(); + + if (info.s_type != .xlib_surface_create_info_khr) + return .error_validation_failed; + + const allocator = VulkanAllocator.init(callbacks, .object).allocator(); + const instance = Dispatchable(Instance).fromHandleObject(p_instance) catch |err| return toVkResult(err); + const surface = XlibSurfaceKHR.create(instance, allocator, info) catch |err| return toVkResult(err); + p_surface.* = wrapNonDispatchable(SurfaceKHR, allocator, surface, vk.SurfaceKHR) catch |err| return toVkResult(err); + return .success; + } else { + return .error_unknown; + } +} + +pub export fn apeCreateXcbSurfaceKHR(p_instance: vk.Instance, info: *const vk.XcbSurfaceCreateInfoKHR, callbacks: ?*const vk.AllocationCallbacks, p_surface: *vk.SurfaceKHR) callconv(vk.vulkan_call_conv) vk.Result { + if (comptime has_x11) { + entryPointBeginLogTrace(.vkCreateXcbSurfaceKHR); + defer entryPointEndLogTrace(); + + if (info.s_type != .xcb_surface_create_info_khr) + return .error_validation_failed; + + const allocator = VulkanAllocator.init(callbacks, .object).allocator(); + const instance = Dispatchable(Instance).fromHandleObject(p_instance) catch |err| return toVkResult(err); + const surface = XcbSurfaceKHR.create(instance, allocator, info) catch |err| return toVkResult(err); + p_surface.* = wrapNonDispatchable(SurfaceKHR, allocator, surface, vk.SurfaceKHR) catch |err| return toVkResult(err); + return .success; + } else { + return .error_unknown; + } +} + pub export fn apeDestroySurfaceKHR(p_instance: vk.Instance, p_surface: vk.SurfaceKHR, callbacks: ?*const vk.AllocationCallbacks) callconv(vk.vulkan_call_conv) void { entryPointBeginLogTrace(.vkDestroySurfaceKHR); defer entryPointEndLogTrace(); @@ -2481,6 +2528,46 @@ pub export fn apeGetPhysicalDeviceWaylandPresentationSupportKHR(p_physical_devic } } +pub export fn apeGetPhysicalDeviceXlibPresentationSupportKHR(p_physical_device: vk.PhysicalDevice, queue_family_index: u32, display: *vk.Display, visual_id: vk.VisualID) callconv(vk.vulkan_call_conv) vk.Bool32 { + if (comptime has_x11) { + entryPointBeginLogTrace(.vkGetPhysicalDeviceXlibPresentationSupportKHR); + defer entryPointEndLogTrace(); + + const physical_device = Dispatchable(PhysicalDevice).fromHandleObject(p_physical_device) catch |err| { + errorLogger(err); + return .false; + }; + const queue_index = std.math.cast(usize, queue_family_index) orelse return .false; + if (queue_index >= physical_device.queue_family_props.items.len or + physical_device.queue_family_props.items[queue_index].queue_count == 0) + return .false; + + return if (XlibSurfaceKHR.supportsPresentation(display, visual_id)) .true else .false; + } else { + return .false; + } +} + +pub export fn apeGetPhysicalDeviceXcbPresentationSupportKHR(p_physical_device: vk.PhysicalDevice, queue_family_index: u32, connection: *vk.xcb_connection_t, visual_id: vk.xcb_visualid_t) callconv(vk.vulkan_call_conv) vk.Bool32 { + if (comptime has_x11) { + entryPointBeginLogTrace(.vkGetPhysicalDeviceXcbPresentationSupportKHR); + defer entryPointEndLogTrace(); + + const physical_device = Dispatchable(PhysicalDevice).fromHandleObject(p_physical_device) catch |err| { + errorLogger(err); + return .false; + }; + const queue_index = std.math.cast(usize, queue_family_index) orelse return .false; + if (queue_index >= physical_device.queue_family_props.items.len or + physical_device.queue_family_props.items[queue_index].queue_count == 0) + return .false; + + return if (XcbSurfaceKHR.supportsPresentation(connection, visual_id)) .true else .false; + } else { + return .false; + } +} + pub export fn apeGetSwapchainImagesKHR(p_device: vk.Device, p_swapchain: vk.SwapchainKHR, count: *u32, p_images: ?[*]vk.Image) callconv(vk.vulkan_call_conv) vk.Result { entryPointBeginLogTrace(.vkGetSwapchainImagesKHR); defer entryPointEndLogTrace(); diff --git a/src/vulkan/wsi/SurfaceKHR.zig b/src/vulkan/wsi/SurfaceKHR.zig index 1c86218..e5e0d56 100644 --- a/src/vulkan/wsi/SurfaceKHR.zig +++ b/src/vulkan/wsi/SurfaceKHR.zig @@ -31,6 +31,7 @@ pub const VTable = struct { getCapabilities: *const fn (*const Self, *vk.SurfaceCapabilitiesKHR) VkError!void, attachImage: *const fn (*Self, std.mem.Allocator, *PresentImage) VkError!void, detachImage: *const fn (*Self, std.mem.Allocator, *PresentImage) VkError!void, + waitForImage: *const fn (*Self, u64) VkError!void, presentImage: *const fn (*Self, std.mem.Allocator, *PresentImage) VkError!void, }; @@ -81,6 +82,10 @@ pub inline fn detachImage(self: *Self, allocator: std.mem.Allocator, image: *Pre try self.vtable.detachImage(self, allocator, image); } +pub inline fn waitForImage(self: *Self, timeout: u64) VkError!void { + try self.vtable.waitForImage(self, timeout); +} + pub inline fn presentImage(self: *Self, allocator: std.mem.Allocator, image: *PresentImage) VkError!void { try self.vtable.presentImage(self, allocator, image); } diff --git a/src/vulkan/wsi/SwapchainKHR.zig b/src/vulkan/wsi/SwapchainKHR.zig index 0de5271..07687f6 100644 --- a/src/vulkan/wsi/SwapchainKHR.zig +++ b/src/vulkan/wsi/SwapchainKHR.zig @@ -82,23 +82,36 @@ pub fn create(device: *Device, allocator: std.mem.Allocator, surface: *SurfaceKH } pub fn getNextImage(self: *const Self, timeout: u64, semaphore: ?*BinarySemaphore, fence: ?*Fence, index: *u32) VkError!void { - // TODO: handle timeout correctly + // TODO: handle finite timeouts correctly + if (try self.acquireAvailableImage(semaphore, fence, index)) + return; - for (self.images, 0..) |*image, i| { - if (image.state == .available) { - image.state = .drawing; - index.* = @intCast(i); - if (semaphore) |s| - try s.signal(); - if (fence) |f| - try f.signal(); - return; - } - } + if (self.surface) |surface| + try surface.waitForImage(timeout); + + if (try self.acquireAvailableImage(semaphore, fence, index)) + return; return if (timeout > 0) VkError.Timeout else VkError.NotReady; } +fn acquireAvailableImage(self: *const Self, semaphore: ?*BinarySemaphore, fence: ?*Fence, index: *u32) VkError!bool { + for (self.images, 0..) |*image, i| { + if (image.state != .available) + continue; + + image.state = .drawing; + index.* = @intCast(i); + if (semaphore) |s| + try s.signal(); + if (fence) |f| + try f.signal(); + return true; + } + + return false; +} + pub fn present(self: *Self, index: usize) VkError!void { const allocator = self.owner.host_allocator.allocator(); @@ -114,19 +127,19 @@ pub fn detachSurface(self: *Self) VkError!void { if (self.surface) |surface| { surface.swapchain = null; - for (self.images) |*image| { - if (image.state == .available) - try surface.detachImage(allocator, image); - } + for (self.images) |*image| + try surface.detachImage(allocator, image); } self.surface = null; } pub fn destroy(self: *Self, allocator: std.mem.Allocator) void { + if (self.surface) |surface| + surface.swapchain = null; + for (self.images) |*image| { - if (self.surface) |surface| { + if (self.surface) |surface| surface.detachImage(allocator, image) catch @panic("Caught an error while handling an error"); - } image.deinit(allocator); } allocator.free(self.images); diff --git a/src/vulkan/wsi/WaylandSurfaceKHR.zig b/src/vulkan/wsi/WaylandSurfaceKHR.zig index ce0ed76..ac60232 100644 --- a/src/vulkan/wsi/WaylandSurfaceKHR.zig +++ b/src/vulkan/wsi/WaylandSurfaceKHR.zig @@ -2,6 +2,7 @@ const std = @import("std"); const vk = @import("vulkan"); const lib = @import("../lib.zig"); +const gbm = @import("clients/gbm.zig"); const wayland = @import("clients/wayland.zig"); const PresentImage = @import("PresentImage.zig"); @@ -13,19 +14,31 @@ pub const Interface = @import("SurfaceKHR.zig"); const WaylandImage = struct { buffer: *wayland.wl_buffer, - data: []align(std.heap.page_size_min) u8, + dma_buf: gbm.Buffer, + staging: []u8, + image: *PresentImage, width: u32, height: u32, - stride: u32, + row_size: usize, }; -fn wlRegistryHandleGlobal(data: ?*anyopaque, registry: ?*wayland.wl_registry, name: u32, interface: [*c]const u8, _: u32) callconv(.c) void { - const pshm: *?*wayland.wl_shm = @ptrCast(@alignCast(data orelse return)); - if (std.mem.eql(u8, std.mem.span(interface), "wl_shm")) { - if (wayland.wl_registry_bind(registry orelse return, name, wayland.wl_shm_interface, 1)) |shm| { - pshm.* = @ptrCast(@alignCast(shm)); - } - } +fn wlRegistryHandleGlobal(data: ?*anyopaque, registry: ?*wayland.wl_registry, name: u32, interface: [*c]const u8, version: u32) callconv(.c) void { + const self: *Self = @ptrCast(@alignCast(data orelse return)); + if (!std.mem.eql(u8, std.mem.span(interface), "zwp_linux_dmabuf_v1") or version < 2 or self.dmabuf != null) + return; + + const bind_version = @min(version, @as(u32, wayland.zwp_linux_dmabuf_v1_interface.version)); + const proxy = wayland.wlRegistryBind(registry orelse return, name, &wayland.zwp_linux_dmabuf_v1_interface, bind_version) orelse { + self.protocol_failed = true; + return; + }; + + const dmabuf: *wayland.zwp_linux_dmabuf_v1 = @ptrCast(@alignCast(proxy)); + wayland.wlProxyAssignQueue(dmabuf, self.event_queue); + self.dmabuf = dmabuf; + self.dmabuf_version = bind_version; + if (wayland.zwpLinuxDmabufV1AddListener(dmabuf, &linux_dmabuf_listener, self) != 0) + self.protocol_failed = true; } fn wlRegistryHandleGlobalRemove(_: ?*anyopaque, _: ?*wayland.wl_registry, _: u32) callconv(.c) void {} @@ -35,10 +48,75 @@ const wl_registry_listener: wayland.wl_registry_listener = .{ .global_remove = wlRegistryHandleGlobalRemove, }; +fn linuxDmabufHandleFormat(data: ?*anyopaque, _: ?*wayland.zwp_linux_dmabuf_v1, format: u32) callconv(.c) void { + const self: *Self = @ptrCast(@alignCast(data orelse return)); + if (format != gbm.drm_format_argb8888) + return; + + self.supports_argb8888 = true; + if (self.dmabuf_version < 3) + self.supports_implicit_modifier = true; +} + +fn linuxDmabufHandleModifier(data: ?*anyopaque, _: ?*wayland.zwp_linux_dmabuf_v1, format: u32, modifier_hi: u32, modifier_lo: u32) callconv(.c) void { + const self: *Self = @ptrCast(@alignCast(data orelse return)); + if (format != gbm.drm_format_argb8888) + return; + + const modifier = (@as(u64, modifier_hi) << 32) | modifier_lo; + switch (modifier) { + gbm.drm_format_modifier_linear => self.supports_linear_modifier = true, + gbm.drm_format_modifier_invalid => self.supports_implicit_modifier = true, + else => {}, + } +} + +const linux_dmabuf_listener: wayland.zwp_linux_dmabuf_v1_listener = .{ + .format = linuxDmabufHandleFormat, + .modifier = linuxDmabufHandleModifier, +}; + +const BufferCreationResult = struct { + buffer: ?*wayland.wl_buffer = null, + failed: bool = false, +}; + +fn linuxBufferParamsHandleCreated(data: ?*anyopaque, _: ?*wayland.zwp_linux_buffer_params_v1, buffer: ?*wayland.wl_buffer) callconv(.c) void { + const result: *BufferCreationResult = @ptrCast(@alignCast(data orelse return)); + result.buffer = buffer; + result.failed = buffer == null; +} + +fn linuxBufferParamsHandleFailed(data: ?*anyopaque, _: ?*wayland.zwp_linux_buffer_params_v1) callconv(.c) void { + const result: *BufferCreationResult = @ptrCast(@alignCast(data orelse return)); + result.failed = true; +} + +const linux_buffer_params_listener: wayland.zwp_linux_buffer_params_v1_listener = .{ + .created = linuxBufferParamsHandleCreated, + .failed = linuxBufferParamsHandleFailed, +}; + +fn wlBufferHandleRelease(data: ?*anyopaque, _: ?*wayland.wl_buffer) callconv(.c) void { + const wl_image: *WaylandImage = @ptrCast(@alignCast(data orelse return)); + wl_image.image.state = .available; +} + +const wl_buffer_listener: wayland.wl_buffer_listener = .{ + .release = wlBufferHandleRelease, +}; + interface: Interface, display: *wayland.wl_display, surface: *wayland.wl_surface, -shm: ?*wayland.wl_shm, +event_queue: *wayland.wl_event_queue, +dmabuf: ?*wayland.zwp_linux_dmabuf_v1, +dmabuf_version: u32, +supports_argb8888: bool, +supports_linear_modifier: bool, +supports_implicit_modifier: bool, +protocol_failed: bool, +gbm_device: ?gbm.Device, image_map: std.AutoHashMapUnmanaged(*PresentImage, *WaylandImage), pub fn create(instance: *Instance, allocator: std.mem.Allocator, info: *const vk.WaylandSurfaceCreateInfoKHR) VkError!*Interface { @@ -46,44 +124,90 @@ pub fn create(instance: *Instance, allocator: std.mem.Allocator, info: *const vk errdefer allocator.destroy(self); try wayland.load(); + errdefer wayland.unload(); + try gbm.load(); + errdefer gbm.unload(); var interface = try Interface.init(instance, allocator); - interface.vtable = &.{ .destroy = destroy, .getCapabilities = getCapabilities, .attachImage = attachImage, .detachImage = detachImage, + .waitForImage = waitForImage, .presentImage = presentImage, }; + const event_queue = wayland.wl_display_create_queue(info.display) orelse return VkError.OutOfHostMemory; + errdefer wayland.wl_event_queue_destroy(event_queue); + self.* = .{ .interface = interface, .display = info.display, .surface = info.surface, - .shm = null, + .event_queue = event_queue, + .dmabuf = null, + .dmabuf_version = 0, + .supports_argb8888 = false, + .supports_linear_modifier = false, + .supports_implicit_modifier = false, + .protocol_failed = false, + .gbm_device = null, .image_map = .empty, }; + errdefer if (self.dmabuf) |dmabuf| wayland.zwpLinuxDmabufV1Destroy(dmabuf); - const registry = wayland.wl_display_get_registry(@ptrCast(self.display)) orelse return VkError.Unknown; - _ = wayland.wl_registry_add_listener(registry, &wl_registry_listener, @ptrCast(&self.shm)); - _ = wayland.wl_display_dispatch(@ptrCast(self.display)); - if (self.shm == null) return VkError.Unknown; + const registry = wayland.wlDisplayGetRegistry(self.display) orelse return VkError.InitializationFailed; + defer wayland.wlRegistryDestroy(registry); + wayland.wlProxyAssignQueue(registry, self.event_queue); + + if (wayland.wlRegistryAddListener(registry, &wl_registry_listener, self) != 0) + return VkError.InitializationFailed; + + // The first roundtrip receives registry globals. The second receives the + // format and modifier events emitted in response to binding linux-dmabuf. + if (wayland.wl_display_roundtrip_queue(self.display, self.event_queue) < 0 or self.dmabuf == null or self.protocol_failed) + return VkError.InitializationFailed; + if (wayland.wl_display_roundtrip_queue(self.display, self.event_queue) < 0 or self.protocol_failed) + return VkError.InitializationFailed; + + if (!self.supports_argb8888 or (!self.supports_linear_modifier and !self.supports_implicit_modifier)) { + std.log.scoped(.WaylandSurface).err("The compositor does not advertise a usable DMA-BUF ARGB8888 format", .{}); + return VkError.FormatNotSupported; + } return &self.interface; } pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void { const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); + + if (interface.swapchain) |swapchain| + swapchain.surface = null; + + var image_iterator = self.image_map.valueIterator(); + while (image_iterator.next()) |wl_image| + destroyWaylandImage(allocator, wl_image.*); self.image_map.deinit(allocator); + + if (self.gbm_device) |*device| + device.deinit(); + if (self.dmabuf) |dmabuf| + wayland.zwpLinuxDmabufV1Destroy(dmabuf); + wayland.wl_event_queue_destroy(self.event_queue); + allocator.destroy(self); + gbm.unload(); wayland.unload(); } pub fn getCapabilities(interface: *const Interface, capabilities: *vk.SurfaceCapabilitiesKHR) VkError!void { - // No-op _ = interface; - _ = capabilities; + capabilities.min_image_count = 2; + capabilities.max_image_extent = .{ + .width = std.math.maxInt(i32), + .height = std.math.maxInt(i32), + }; } pub fn attachImage(interface: *Interface, allocator: std.mem.Allocator, image: *PresentImage) VkError!void { @@ -92,78 +216,183 @@ pub fn attachImage(interface: *Interface, allocator: std.mem.Allocator, image: * if (self.image_map.contains(image)) return; - const width: u32 = image.image.extent.width; - const height: u32 = image.image.extent.height; + const width = image.image.extent.width; + const height = image.image.extent.height; + if (width > std.math.maxInt(i32) or height > std.math.maxInt(i32)) + return VkError.OutOfDeviceMemory; - const stride: u32 = @intCast(image.image.getRowPitchMemSizeForMipLevel(.{ .color_bit = true }, 0)); - const size: usize = @as(usize, stride) * @as(usize, height); + const row_size = std.math.mul(usize, width, 4) catch return VkError.OutOfHostMemory; + const staging_size = std.math.mul(usize, row_size, height) catch return VkError.OutOfHostMemory; const wl_image = allocator.create(WaylandImage) catch return VkError.OutOfHostMemory; errdefer allocator.destroy(wl_image); - const fd = try createShmFile(size); + const staging = allocator.alloc(u8, staging_size) catch return VkError.OutOfHostMemory; + errdefer allocator.free(staging); + + if (self.gbm_device == null) + self.gbm_device = try gbm.Device.open(); + const device = &(self.gbm_device orelse unreachable); + + var dma_buf = try device.createBuffer(width, height); + errdefer dma_buf.deinit(); + + const protocol_modifier: u64 = if (dma_buf.modifier == gbm.drm_format_modifier_linear and self.supports_linear_modifier) + gbm.drm_format_modifier_linear + else if (self.supports_implicit_modifier) + gbm.drm_format_modifier_invalid + else + return VkError.FormatNotSupported; + + const fd = try dma_buf.exportFd(); defer _ = std.c.close(fd); - const data = std.posix.mmap(null, size, .{ .READ = true, .WRITE = true }, .{ .TYPE = .SHARED }, fd, 0) catch return VkError.OutOfHostMemory; - errdefer std.posix.munmap(data); + const dmabuf = self.dmabuf orelse return VkError.InitializationFailed; + const params = wayland.zwpLinuxDmabufV1CreateParams(dmabuf) orelse return VkError.OutOfHostMemory; + defer wayland.zwpLinuxBufferParamsV1Destroy(params); - const shm = self.shm orelse return VkError.Unknown; - const pool = wayland.wl_shm_create_pool(shm, fd, @intCast(size)) orelse return VkError.Unknown; - defer wayland.wl_shm_pool_destroy(pool); + var creation_result: BufferCreationResult = .{}; + if (wayland.zwpLinuxBufferParamsV1AddListener(params, &linux_buffer_params_listener, &creation_result) != 0) + return VkError.InitializationFailed; - const buffer = wayland.wl_shm_pool_create_buffer(pool, 0, @intCast(width), @intCast(height), @intCast(stride), wayland.wl_shm_format_argb8888) orelse return VkError.Unknown; - errdefer wayland.wl_buffer_destroy(buffer); + wayland.zwpLinuxBufferParamsV1Add(params, fd, 0, 0, dma_buf.stride, protocol_modifier); + wayland.zwpLinuxBufferParamsV1Create(params, @intCast(width), @intCast(height), gbm.drm_format_argb8888, 0); + if (wayland.wl_display_roundtrip_queue(self.display, self.event_queue) < 0) + return VkError.SurfaceLostKhr; + if (creation_result.failed) + return VkError.FormatNotSupported; + + const buffer = creation_result.buffer orelse return VkError.Unknown; + errdefer wayland.wlBufferDestroy(buffer); + wayland.wlProxyAssignQueue(buffer, self.event_queue); wl_image.* = .{ .buffer = buffer, - .data = data, + .dma_buf = dma_buf, + .staging = staging, + .image = image, .width = width, .height = height, - .stride = stride, + .row_size = row_size, }; + if (wayland.wlBufferAddListener(buffer, &wl_buffer_listener, wl_image) != 0) + return VkError.InitializationFailed; + self.image_map.put(allocator, image, wl_image) catch return VkError.OutOfHostMemory; } pub fn detachImage(interface: *Interface, allocator: std.mem.Allocator, image: *PresentImage) VkError!void { const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); const entry = self.image_map.fetchRemove(image) orelse return; - const wl_image = entry.value; - - wayland.wl_buffer_destroy(wl_image.buffer); - std.posix.munmap(wl_image.data); - allocator.destroy(wl_image); + destroyWaylandImage(allocator, entry.value); } -pub fn presentImage(interface: *Interface, allocator: std.mem.Allocator, image: *PresentImage) VkError!void { +pub fn waitForImage(interface: *Interface, timeout: u64) VkError!void { const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); - _ = allocator; + + if (wayland.wl_display_dispatch_queue_pending(self.display, self.event_queue) < 0) + return VkError.SurfaceLostKhr; + if (hasAvailableImage(self) or timeout == 0) + return; + + if (timeout == std.math.maxInt(u64)) { + while (!hasAvailableImage(self)) { + if (wayland.wl_display_dispatch_queue(self.display, self.event_queue) < 0) + return VkError.SurfaceLostKhr; + } + return; + } + + const io = interface.owner.io(); + const deadline = std.Io.Clock.Timestamp.fromNow(io, .{ + .raw = .fromNanoseconds(@intCast(@min(timeout, std.math.maxInt(i64)))), + .clock = .awake, + }); + + while (!hasAvailableImage(self)) { + while (wayland.wl_display_prepare_read_queue(self.display, self.event_queue) != 0) { + if (wayland.wl_display_dispatch_queue_pending(self.display, self.event_queue) < 0) + return VkError.SurfaceLostKhr; + if (hasAvailableImage(self)) + return; + } + + const remaining_ns = deadline.durationFromNow(io).raw.nanoseconds; + if (remaining_ns <= 0) { + wayland.wl_display_cancel_read(self.display); + return; + } + + _ = wayland.wl_display_flush(self.display); + var poll_fds = [_]std.posix.pollfd{.{ + .fd = wayland.wl_display_get_fd(self.display), + .events = std.posix.POLL.IN, + .revents = 0, + }}; + const rounded_ms = @divTrunc(@as(u64, @intCast(remaining_ns)) + std.time.ns_per_ms - 1, std.time.ns_per_ms); + const timeout_ms: i32 = @intCast(@min(rounded_ms, std.math.maxInt(i32))); + const ready_count = std.posix.poll(&poll_fds, timeout_ms) catch { + wayland.wl_display_cancel_read(self.display); + return VkError.SurfaceLostKhr; + }; + if (ready_count == 0) { + wayland.wl_display_cancel_read(self.display); + return; + } + + if (wayland.wl_display_read_events(self.display) < 0) + return VkError.SurfaceLostKhr; + if (wayland.wl_display_dispatch_queue_pending(self.display, self.event_queue) < 0) + return VkError.SurfaceLostKhr; + } +} + +pub fn presentImage(interface: *Interface, _: std.mem.Allocator, image: *PresentImage) VkError!void { + const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); + errdefer image.state = .available; const wl_image = self.image_map.get(image) orelse return VkError.Unknown; - try image.image.copyToMemory(wl_image.data, .{ + try image.image.copyToMemory(wl_image.staging, .{ .aspect_mask = .{ .color_bit = true }, .mip_level = 0, .base_array_layer = 0, .layer_count = 1, }); - wayland.wl_surface_attach(@ptrCast(self.surface), wl_image.buffer, 0, 0); - wayland.wl_surface_damage(@ptrCast(self.surface), 0, 0, @intCast(wl_image.width), @intCast(wl_image.height)); - wayland.wl_surface_commit(@ptrCast(self.surface)); + { + const mapping = try wl_image.dma_buf.mapWrite(); + defer wl_image.dma_buf.unmap(mapping); - _ = wayland.wl_display_flush(@ptrCast(self.display)); + for (0..wl_image.height) |row| { + const source_offset = row * wl_image.row_size; + const destination_offset = row * mapping.stride; + @memcpy( + mapping.data[destination_offset..][0..wl_image.row_size], + wl_image.staging[source_offset..][0..wl_image.row_size], + ); + } + } - image.state = .available; + wayland.wlSurfaceAttach(self.surface, wl_image.buffer, 0, 0); + wayland.wlSurfaceDamage(self.surface, 0, 0, @intCast(wl_image.width), @intCast(wl_image.height)); + wayland.wlSurfaceCommit(self.surface); + _ = wayland.wl_display_flush(self.display); } -fn createShmFile(size: usize) VkError!std.posix.fd_t { - const name = "ape_vk_wayland_surface"; - - const fd = std.posix.memfd_create(name, std.posix.FD_CLOEXEC) catch return VkError.Unknown; - errdefer std.c.close(fd); - - _ = std.c.ftruncate(fd, @intCast(size)); - - return fd; +fn hasAvailableImage(self: *const Self) bool { + var image_iterator = self.image_map.keyIterator(); + while (image_iterator.next()) |image| { + if (image.*.state == .available) + return true; + } + return false; +} + +fn destroyWaylandImage(allocator: std.mem.Allocator, wl_image: *WaylandImage) void { + wayland.wlBufferDestroy(wl_image.buffer); + wl_image.dma_buf.deinit(); + allocator.free(wl_image.staging); + allocator.destroy(wl_image); } diff --git a/src/vulkan/wsi/XcbSurfaceKHR.zig b/src/vulkan/wsi/XcbSurfaceKHR.zig new file mode 100644 index 0000000..21c924f --- /dev/null +++ b/src/vulkan/wsi/XcbSurfaceKHR.zig @@ -0,0 +1,194 @@ +const std = @import("std"); +const vk = @import("vulkan"); + +const lib = @import("../lib.zig"); +const xcb = @import("clients/xcb.zig"); +const PresentImage = @import("PresentImage.zig"); +const scale = @import("scale.zig"); + +const VkError = lib.VkError; +const Instance = lib.Instance; + +const Self = @This(); +pub const Interface = @import("SurfaceKHR.zig"); + +const XcbImage = struct { + data: []u8, + scaled_data: ?[]u8, + width: u32, + height: u32, + row_size: usize, + surface_width: u32, + surface_height: u32, + surface_row_size: usize, +}; + +interface: Interface, +connection: *xcb.Connection, +window: xcb.Window, +gc: xcb.Gcontext, +depth: u8, +maximum_image_payload: usize, +image_map: std.AutoHashMapUnmanaged(*PresentImage, *XcbImage), + +pub fn supportsPresentation(connection: *vk.xcb_connection_t, visual_id: vk.xcb_visualid_t) bool { + xcb.load() catch return false; + defer xcb.unload(); + return xcb.supportsVisual(connection, visual_id) catch false; +} + +pub fn create(instance: *Instance, allocator: std.mem.Allocator, info: *const vk.XcbSurfaceCreateInfoKHR) VkError!*Interface { + const self = allocator.create(Self) catch return VkError.OutOfHostMemory; + errdefer allocator.destroy(self); + + try xcb.load(); + errdefer xcb.unload(); + try xcb.checkConnection(info.connection); + + const geometry = try xcb.getGeometry(info.connection, info.window); + if (geometry.depth != 24 and geometry.depth != 32) + return VkError.FormatNotSupported; + + const gc = try xcb.createGc(info.connection, info.window); + errdefer xcb.freeGc(info.connection, gc); + + var interface = try Interface.init(instance, allocator); + interface.vtable = &.{ + .destroy = destroy, + .getCapabilities = getCapabilities, + .attachImage = attachImage, + .detachImage = detachImage, + .waitForImage = waitForImage, + .presentImage = presentImage, + }; + + self.* = .{ + .interface = interface, + .connection = info.connection, + .window = info.window, + .gc = gc, + .depth = geometry.depth, + .maximum_image_payload = try xcb.maximumImagePayload(info.connection), + .image_map = .empty, + }; + return &self.interface; +} + +pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void { + const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); + if (interface.swapchain) |swapchain| + swapchain.surface = null; + + var image_iterator = self.image_map.valueIterator(); + while (image_iterator.next()) |image| + destroyXcbImage(allocator, image.*); + self.image_map.deinit(allocator); + + xcb.freeGc(self.connection, self.gc); + allocator.destroy(self); + xcb.unload(); +} + +pub fn getCapabilities(interface: *const Interface, capabilities: *vk.SurfaceCapabilitiesKHR) VkError!void { + const self: *const Self = @alignCast(@fieldParentPtr("interface", interface)); + const geometry = try xcb.getGeometry(self.connection, self.window); + capabilities.current_extent = .{ .width = geometry.width, .height = geometry.height }; + capabilities.max_image_extent = .{ .width = std.math.maxInt(i16), .height = std.math.maxInt(i16) }; +} + +pub fn attachImage(interface: *Interface, allocator: std.mem.Allocator, image: *PresentImage) VkError!void { + const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); + if (self.image_map.contains(image)) + return; + + const width = image.image.extent.width; + const height = image.image.extent.height; + const geometry = try xcb.getGeometry(self.connection, self.window); + if (width > std.math.maxInt(i16) or height > std.math.maxInt(i16) or + geometry.width > std.math.maxInt(i16) or geometry.height > std.math.maxInt(i16)) + return VkError.OutOfDeviceMemory; + + const row_size = std.math.mul(usize, width, 4) catch return VkError.OutOfHostMemory; + const size = std.math.mul(usize, row_size, height) catch return VkError.OutOfHostMemory; + const surface_row_size = std.math.mul(usize, geometry.width, 4) catch return VkError.OutOfHostMemory; + const surface_size = std.math.mul(usize, surface_row_size, geometry.height) catch return VkError.OutOfHostMemory; + + const xcb_image = allocator.create(XcbImage) catch return VkError.OutOfHostMemory; + errdefer allocator.destroy(xcb_image); + + const data = allocator.alloc(u8, size) catch return VkError.OutOfHostMemory; + errdefer allocator.free(data); + + const scaled_data = if (width != geometry.width or height != geometry.height) + allocator.alloc(u8, surface_size) catch return VkError.OutOfHostMemory + else + null; + errdefer if (scaled_data) |staging| allocator.free(staging); + + xcb_image.* = .{ + .data = data, + .scaled_data = scaled_data, + .width = width, + .height = height, + .row_size = row_size, + .surface_width = geometry.width, + .surface_height = geometry.height, + .surface_row_size = surface_row_size, + }; + self.image_map.put(allocator, image, xcb_image) catch return VkError.OutOfHostMemory; +} + +pub fn detachImage(interface: *Interface, allocator: std.mem.Allocator, image: *PresentImage) VkError!void { + const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); + const entry = self.image_map.fetchRemove(image) orelse return; + destroyXcbImage(allocator, entry.value); +} + +pub fn waitForImage(_: *Interface, _: u64) VkError!void {} + +pub fn presentImage(interface: *Interface, _: std.mem.Allocator, image: *PresentImage) VkError!void { + const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); + errdefer image.state = .available; + + const xcb_image = self.image_map.get(image) orelse return VkError.Unknown; + const geometry = try xcb.getGeometry(self.connection, self.window); + if (geometry.width != xcb_image.surface_width or geometry.height != xcb_image.surface_height) + return VkError.OutOfDateKhr; + + try image.image.copyToMemory(xcb_image.data, .{ + .aspect_mask = .{ .color_bit = true }, + .mip_level = 0, + .base_array_layer = 0, + .layer_count = 1, + }); + + if (xcb_image.scaled_data) |scaled_data| + scale.scaleBgra8Nearest( + xcb_image.data, + xcb_image.width, + xcb_image.height, + scaled_data, + xcb_image.surface_width, + xcb_image.surface_height, + ) catch return VkError.ValidationFailed; + + try xcb.putImage( + self.connection, + self.window, + self.gc, + self.depth, + xcb_image.surface_width, + xcb_image.surface_height, + xcb_image.surface_row_size, + xcb_image.scaled_data orelse xcb_image.data, + self.maximum_image_payload, + ); + image.state = .available; +} + +fn destroyXcbImage(allocator: std.mem.Allocator, image: *XcbImage) void { + if (image.scaled_data) |scaled_data| + allocator.free(scaled_data); + allocator.free(image.data); + allocator.destroy(image); +} diff --git a/src/vulkan/wsi/XlibSurfaceKHR.zig b/src/vulkan/wsi/XlibSurfaceKHR.zig new file mode 100644 index 0000000..510112a --- /dev/null +++ b/src/vulkan/wsi/XlibSurfaceKHR.zig @@ -0,0 +1,194 @@ +const std = @import("std"); +const vk = @import("vulkan"); + +const lib = @import("../lib.zig"); +const xlib = @import("clients/xlib.zig"); +const PresentImage = @import("PresentImage.zig"); +const scale = @import("scale.zig"); + +const VkError = lib.VkError; +const Instance = lib.Instance; + +const Self = @This(); +pub const Interface = @import("SurfaceKHR.zig"); + +const XlibImage = struct { + image: *xlib.XImage, + data: []u8, + scaled_data: ?[]u8, + width: u32, + height: u32, + surface_width: u32, + surface_height: u32, +}; + +interface: Interface, +display: *xlib.Display, +window: xlib.Window, +gc: *xlib.GC, +visual: *xlib.Visual, +depth: u32, +image_map: std.AutoHashMapUnmanaged(*PresentImage, *XlibImage), + +pub fn supportsPresentation(display: *vk.Display, visual_id: vk.VisualID) bool { + xlib.load() catch return false; + defer xlib.unload(); + return xlib.supportsVisual(display, visual_id); +} + +pub fn create(instance: *Instance, allocator: std.mem.Allocator, info: *const vk.XlibSurfaceCreateInfoKHR) VkError!*Interface { + const self = allocator.create(Self) catch return VkError.OutOfHostMemory; + errdefer allocator.destroy(self); + + try xlib.load(); + errdefer xlib.unload(); + + const geometry = try xlib.getGeometry(info.dpy, info.window); + if (geometry.depth != 24 and geometry.depth != 32) + return VkError.FormatNotSupported; + + const gc = try xlib.createGc(info.dpy, info.window); + errdefer xlib.freeGc(info.dpy, gc); + + var interface = try Interface.init(instance, allocator); + interface.vtable = &.{ + .destroy = destroy, + .getCapabilities = getCapabilities, + .attachImage = attachImage, + .detachImage = detachImage, + .waitForImage = waitForImage, + .presentImage = presentImage, + }; + + self.* = .{ + .interface = interface, + .display = info.dpy, + .window = info.window, + .gc = gc, + .visual = geometry.visual, + .depth = geometry.depth, + .image_map = .empty, + }; + return &self.interface; +} + +pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void { + const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); + if (interface.swapchain) |swapchain| + swapchain.surface = null; + + var image_iterator = self.image_map.valueIterator(); + while (image_iterator.next()) |image| + destroyXlibImage(allocator, image.*); + self.image_map.deinit(allocator); + + xlib.freeGc(self.display, self.gc); + allocator.destroy(self); + xlib.unload(); +} + +pub fn getCapabilities(interface: *const Interface, capabilities: *vk.SurfaceCapabilitiesKHR) VkError!void { + const self: *const Self = @alignCast(@fieldParentPtr("interface", interface)); + const geometry = try xlib.getGeometry(self.display, self.window); + capabilities.current_extent = .{ .width = geometry.width, .height = geometry.height }; + capabilities.max_image_extent = .{ .width = std.math.maxInt(u16), .height = std.math.maxInt(u16) }; +} + +pub fn attachImage(interface: *Interface, allocator: std.mem.Allocator, image: *PresentImage) VkError!void { + const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); + if (self.image_map.contains(image)) + return; + + const width = image.image.extent.width; + const height = image.image.extent.height; + const geometry = try xlib.getGeometry(self.display, self.window); + if (width > std.math.maxInt(u16) or height > std.math.maxInt(u16) or + geometry.width > std.math.maxInt(u16) or geometry.height > std.math.maxInt(u16)) + return VkError.OutOfDeviceMemory; + + const row_size = std.math.mul(usize, width, 4) catch return VkError.OutOfHostMemory; + const size = std.math.mul(usize, row_size, height) catch return VkError.OutOfHostMemory; + const surface_row_size = std.math.mul(usize, geometry.width, 4) catch return VkError.OutOfHostMemory; + const surface_size = std.math.mul(usize, surface_row_size, geometry.height) catch return VkError.OutOfHostMemory; + + const xlib_image = allocator.create(XlibImage) catch return VkError.OutOfHostMemory; + errdefer allocator.destroy(xlib_image); + + const data = allocator.alloc(u8, size) catch return VkError.OutOfHostMemory; + errdefer allocator.free(data); + + const scaled_data = if (width != geometry.width or height != geometry.height) + allocator.alloc(u8, surface_size) catch return VkError.OutOfHostMemory + else + null; + errdefer if (scaled_data) |staging| allocator.free(staging); + + const native_data = scaled_data orelse data; + const native_image = try xlib.createImage( + self.display, + self.visual, + self.depth, + geometry.width, + geometry.height, + surface_row_size, + native_data, + ); + errdefer xlib.destroyImage(native_image); + + xlib_image.* = .{ + .image = native_image, + .data = data, + .scaled_data = scaled_data, + .width = width, + .height = height, + .surface_width = geometry.width, + .surface_height = geometry.height, + }; + self.image_map.put(allocator, image, xlib_image) catch return VkError.OutOfHostMemory; +} + +pub fn detachImage(interface: *Interface, allocator: std.mem.Allocator, image: *PresentImage) VkError!void { + const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); + const entry = self.image_map.fetchRemove(image) orelse return; + destroyXlibImage(allocator, entry.value); +} + +pub fn waitForImage(_: *Interface, _: u64) VkError!void {} + +pub fn presentImage(interface: *Interface, _: std.mem.Allocator, image: *PresentImage) VkError!void { + const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); + errdefer image.state = .available; + + const xlib_image = self.image_map.get(image) orelse return VkError.Unknown; + const geometry = try xlib.getGeometry(self.display, self.window); + if (geometry.width != xlib_image.surface_width or geometry.height != xlib_image.surface_height) + return VkError.OutOfDateKhr; + + try image.image.copyToMemory(xlib_image.data, .{ + .aspect_mask = .{ .color_bit = true }, + .mip_level = 0, + .base_array_layer = 0, + .layer_count = 1, + }); + + if (xlib_image.scaled_data) |scaled_data| + scale.scaleBgra8Nearest( + xlib_image.data, + xlib_image.width, + xlib_image.height, + scaled_data, + xlib_image.surface_width, + xlib_image.surface_height, + ) catch return VkError.ValidationFailed; + + xlib.putImage(self.display, self.window, self.gc, xlib_image.image, xlib_image.surface_width, xlib_image.surface_height); + image.state = .available; +} + +fn destroyXlibImage(allocator: std.mem.Allocator, image: *XlibImage) void { + xlib.destroyImage(image.image); + if (image.scaled_data) |scaled_data| + allocator.free(scaled_data); + allocator.free(image.data); + allocator.destroy(image); +} diff --git a/src/vulkan/wsi/clients/gbm.zig b/src/vulkan/wsi/clients/gbm.zig new file mode 100644 index 0000000..ea77dd0 --- /dev/null +++ b/src/vulkan/wsi/clients/gbm.zig @@ -0,0 +1,209 @@ +//! Minimal dynamically loaded GBM client used to allocate DMA-BUFs. + +const std = @import("std"); +const lib = @import("../../lib.zig"); + +const VkError = lib.VkError; + +pub const drm_format_argb8888 = fourccCode('A', 'R', '2', '4'); +pub const drm_format_modifier_linear: u64 = 0; +pub const drm_format_modifier_invalid: u64 = 0x00ff_ffff_ffff_ffff; + +const bo_use_rendering: u32 = 1 << 2; +const bo_use_linear: u32 = 1 << 4; +const bo_transfer_write: u32 = 1 << 1; + +pub const gbm_device = opaque {}; +pub const gbm_bo = opaque {}; + +// SAFETY: load assigns every function pointer before any public GBM wrapper is used. +var gbm_create_device: *const fn (fd: c_int) callconv(.c) ?*gbm_device = undefined; +// SAFETY: load assigns every function pointer before any public GBM wrapper is used. +var gbm_device_destroy: *const fn (device: *gbm_device) callconv(.c) void = undefined; +// SAFETY: load assigns every function pointer before any public GBM wrapper is used. +var gbm_device_is_format_supported: *const fn (device: *gbm_device, format: u32, usage: u32) callconv(.c) c_int = undefined; +// SAFETY: load assigns every function pointer before any public GBM wrapper is used. +var gbm_bo_create: *const fn (device: *gbm_device, width: u32, height: u32, format: u32, usage: u32) callconv(.c) ?*gbm_bo = undefined; +// SAFETY: load assigns every function pointer before any public GBM wrapper is used. +var gbm_bo_destroy: *const fn (bo: *gbm_bo) callconv(.c) void = undefined; +// SAFETY: load assigns every function pointer before any public GBM wrapper is used. +var gbm_bo_get_fd: *const fn (bo: *gbm_bo) callconv(.c) c_int = undefined; +// SAFETY: load assigns every function pointer before any public GBM wrapper is used. +var gbm_bo_get_modifier: *const fn (bo: *gbm_bo) callconv(.c) u64 = undefined; +// SAFETY: load assigns every function pointer before any public GBM wrapper is used. +var gbm_bo_get_plane_count: *const fn (bo: *gbm_bo) callconv(.c) c_int = undefined; +// SAFETY: load assigns every function pointer before any public GBM wrapper is used. +var gbm_bo_get_stride: *const fn (bo: *gbm_bo) callconv(.c) u32 = undefined; +// SAFETY: load assigns every function pointer before any public GBM wrapper is used. +var gbm_bo_map: *const fn (bo: *gbm_bo, x: u32, y: u32, width: u32, height: u32, flags: u32, stride: *u32, map_data: *?*anyopaque) callconv(.c) ?*anyopaque = undefined; +// SAFETY: load assigns every function pointer before any public GBM wrapper is used. +var gbm_bo_unmap: *const fn (bo: *gbm_bo, map_data: ?*anyopaque) callconv(.c) void = undefined; + +// SAFETY: load initializes the module before it can be closed or queried. +var module: std.DynLib = undefined; +var ref_count = std.atomic.Value(usize).init(0); +var load_mutex: lib.SpinMutex = .{}; + +pub fn load() VkError!void { + load_mutex.lock(); + defer load_mutex.unlock(); + + if (ref_count.load(.monotonic) != 0) { + _ = ref_count.fetchAdd(1, .monotonic); + return; + } + + module = std.DynLib.open("libgbm.so.1") catch { + std.log.scoped(.GBM).err("Could not open 'libgbm.so.1'", .{}); + return VkError.InitializationFailed; + }; + errdefer module.close(); + + gbm_create_device = module.lookup(@TypeOf(gbm_create_device), "gbm_create_device") orelse return VkError.InitializationFailed; + gbm_device_destroy = module.lookup(@TypeOf(gbm_device_destroy), "gbm_device_destroy") orelse return VkError.InitializationFailed; + gbm_device_is_format_supported = module.lookup(@TypeOf(gbm_device_is_format_supported), "gbm_device_is_format_supported") orelse return VkError.InitializationFailed; + gbm_bo_create = module.lookup(@TypeOf(gbm_bo_create), "gbm_bo_create") orelse return VkError.InitializationFailed; + gbm_bo_destroy = module.lookup(@TypeOf(gbm_bo_destroy), "gbm_bo_destroy") orelse return VkError.InitializationFailed; + gbm_bo_get_fd = module.lookup(@TypeOf(gbm_bo_get_fd), "gbm_bo_get_fd") orelse return VkError.InitializationFailed; + gbm_bo_get_modifier = module.lookup(@TypeOf(gbm_bo_get_modifier), "gbm_bo_get_modifier") orelse return VkError.InitializationFailed; + gbm_bo_get_plane_count = module.lookup(@TypeOf(gbm_bo_get_plane_count), "gbm_bo_get_plane_count") orelse return VkError.InitializationFailed; + gbm_bo_get_stride = module.lookup(@TypeOf(gbm_bo_get_stride), "gbm_bo_get_stride") orelse return VkError.InitializationFailed; + gbm_bo_map = module.lookup(@TypeOf(gbm_bo_map), "gbm_bo_map") orelse return VkError.InitializationFailed; + gbm_bo_unmap = module.lookup(@TypeOf(gbm_bo_unmap), "gbm_bo_unmap") orelse return VkError.InitializationFailed; + + _ = ref_count.fetchAdd(1, .monotonic); + std.log.scoped(.GBM).debug("Loaded GBM", .{}); +} + +pub fn unload() void { + load_mutex.lock(); + defer load_mutex.unlock(); + + if (ref_count.fetchSub(1, .release) == 1) { + module.close(); + std.log.scoped(.GBM).debug("Unloaded GBM", .{}); + } +} + +pub const Device = struct { + handle: *gbm_device, + fd: std.posix.fd_t, + + pub fn open() VkError!Device { + var path_buffer: [64]u8 = undefined; + + for (128..192) |node| { + const path = std.fmt.bufPrint(&path_buffer, "/dev/dri/renderD{d}", .{node}) catch return VkError.Unknown; + if (tryOpen(path)) |device| + return device; + } + + for (0..16) |node| { + const path = std.fmt.bufPrint(&path_buffer, "/dev/dri/card{d}", .{node}) catch return VkError.Unknown; + if (tryOpen(path)) |device| + return device; + } + + std.log.scoped(.GBM).err("Could not find a DRM device capable of allocating linear ARGB8888 buffers", .{}); + return VkError.InitializationFailed; + } + + fn tryOpen(path: []const u8) ?Device { + const fd = std.posix.openat(std.posix.AT.FDCWD, path, .{ .ACCMODE = .RDWR, .CLOEXEC = true }, 0) catch return null; + + const handle = gbm_create_device(fd) orelse { + _ = std.c.close(fd); + return null; + }; + + if (gbm_device_is_format_supported(handle, drm_format_argb8888, bo_use_linear | bo_use_rendering) == 0) { + gbm_device_destroy(handle); + _ = std.c.close(fd); + return null; + } + + return .{ .handle = handle, .fd = fd }; + } + + pub fn deinit(self: *Device) void { + gbm_device_destroy(self.handle); + _ = std.c.close(self.fd); + self.* = undefined; + } + + pub fn createBuffer(self: *Device, width: u32, height: u32) VkError!Buffer { + const handle = gbm_bo_create(self.handle, width, height, drm_format_argb8888, bo_use_linear | bo_use_rendering) orelse return VkError.OutOfDeviceMemory; + errdefer gbm_bo_destroy(handle); + + if (gbm_bo_get_plane_count(handle) != 1) + return VkError.FormatNotSupported; + + const stride = gbm_bo_get_stride(handle); + const minimum_stride = std.math.mul(u32, width, 4) catch return VkError.OutOfDeviceMemory; + if (stride < minimum_stride) + return VkError.Unknown; + + return .{ + .handle = handle, + .width = width, + .height = height, + .stride = stride, + .modifier = gbm_bo_get_modifier(handle), + }; + } +}; + +pub const Buffer = struct { + handle: *gbm_bo, + width: u32, + height: u32, + stride: u32, + modifier: u64, + + pub fn deinit(self: *Buffer) void { + gbm_bo_destroy(self.handle); + self.* = undefined; + } + + pub fn exportFd(self: *const Buffer) VkError!std.posix.fd_t { + const fd = gbm_bo_get_fd(self.handle); + if (fd < 0) + return VkError.Unknown; + return fd; + } + + pub fn mapWrite(self: *Buffer) VkError!Mapping { + var stride: u32 = 0; + var map_data: ?*anyopaque = null; + const pointer = gbm_bo_map(self.handle, 0, 0, self.width, self.height, bo_transfer_write, &stride, &map_data) orelse return VkError.MemoryMapFailed; + errdefer gbm_bo_unmap(self.handle, map_data); + + const size = std.math.mul(usize, stride, self.height) catch return VkError.OutOfHostMemory; + const minimum_stride = std.math.mul(u32, self.width, 4) catch return VkError.OutOfHostMemory; + if (stride < minimum_stride or map_data == null) + return VkError.MemoryMapFailed; + + return .{ + .data = @as([*]u8, @ptrCast(pointer))[0..size], + .stride = stride, + .map_data = map_data, + }; + } + + pub fn unmap(self: *Buffer, mapping: Mapping) void { + gbm_bo_unmap(self.handle, mapping.map_data); + } +}; + +pub const Mapping = struct { + data: []u8, + stride: u32, + map_data: ?*anyopaque, +}; + +fn fourccCode(a: u8, b: u8, c: u8, d: u8) u32 { + return @as(u32, a) | + (@as(u32, b) << 8) | + (@as(u32, c) << 16) | + (@as(u32, d) << 24); +} diff --git a/src/vulkan/wsi/clients/wayland.zig b/src/vulkan/wsi/clients/wayland.zig index c300eb8..ec7bdf7 100644 --- a/src/vulkan/wsi/clients/wayland.zig +++ b/src/vulkan/wsi/clients/wayland.zig @@ -1,4 +1,4 @@ -//! Minimal wayland client protocol declarations and loader +//! Minimal Wayland client and linux-dmabuf protocol declarations and loader. const std = @import("std"); const vk = @import("vulkan"); @@ -7,34 +7,52 @@ const lib = @import("../../lib.zig"); const VkError = lib.VkError; pub const wl_registry_listener = extern struct { - global: ?*const fn (data: ?*anyopaque, wl_registry: ?*wl_registry, name: u32, interface: [*c]const u8, version: u32) callconv(.c) void = null, - global_remove: ?*const fn (data: ?*anyopaque, wl_registry: ?*wl_registry, name: u32) callconv(.c) void = null, + global: ?*const fn (data: ?*anyopaque, registry: ?*wl_registry, name: u32, interface: [*c]const u8, version: u32) callconv(.c) void = null, + global_remove: ?*const fn (data: ?*anyopaque, registry: ?*wl_registry, name: u32) callconv(.c) void = null, }; -pub const wl_buffer_destroy_opcode: c_int = 0; -pub const wl_display_get_registry_opcode: c_int = 1; -pub const wl_registry_bind_opcode: c_int = 0; -pub const wl_shm_create_pool_opcode: c_int = 0; -pub const wl_shm_format_argb8888: c_int = 0; -pub const wl_shm_pool_create_buffer_opcode: c_int = 0; -pub const wl_shm_pool_destroy_opcode: c_int = 1; -pub const wl_surface_attach_opcode: c_int = 1; -pub const wl_surface_commit_opcode: c_int = 6; -pub const wl_surface_damage_opcode: c_int = 2; +pub const wl_buffer_listener = extern struct { + release: ?*const fn (data: ?*anyopaque, buffer: ?*wl_buffer) callconv(.c) void = null, +}; + +pub const zwp_linux_dmabuf_v1_listener = extern struct { + format: ?*const fn (data: ?*anyopaque, dmabuf: ?*zwp_linux_dmabuf_v1, format: u32) callconv(.c) void = null, + modifier: ?*const fn (data: ?*anyopaque, dmabuf: ?*zwp_linux_dmabuf_v1, format: u32, modifier_hi: u32, modifier_lo: u32) callconv(.c) void = null, +}; + +pub const zwp_linux_buffer_params_v1_listener = extern struct { + created: ?*const fn (data: ?*anyopaque, params: ?*zwp_linux_buffer_params_v1, buffer: ?*wl_buffer) callconv(.c) void = null, + failed: ?*const fn (data: ?*anyopaque, params: ?*zwp_linux_buffer_params_v1) callconv(.c) void = null, +}; + +pub const wl_buffer_destroy_opcode: u32 = 0; +pub const wl_display_get_registry_opcode: u32 = 1; +pub const wl_registry_bind_opcode: u32 = 0; +pub const wl_surface_attach_opcode: u32 = 1; +pub const wl_surface_commit_opcode: u32 = 6; +pub const wl_surface_damage_opcode: u32 = 2; + +pub const zwp_linux_dmabuf_v1_destroy_opcode: u32 = 0; +pub const zwp_linux_dmabuf_v1_create_params_opcode: u32 = 1; +pub const zwp_linux_buffer_params_v1_destroy_opcode: u32 = 0; +pub const zwp_linux_buffer_params_v1_add_opcode: u32 = 1; +pub const zwp_linux_buffer_params_v1_create_opcode: u32 = 2; + +const wl_marshal_flag_destroy: u32 = 1; pub const wl_buffer = opaque {}; -pub const wl_callback = opaque {}; +pub const wl_event_queue = opaque {}; pub const wl_registry = opaque {}; -pub const wl_shm = opaque {}; -pub const wl_shm_pool = opaque {}; pub const wl_proxy = opaque {}; +pub const zwp_linux_dmabuf_v1 = opaque {}; +pub const zwp_linux_buffer_params_v1 = opaque {}; pub const wl_display = vk.wl_display; pub const wl_surface = vk.wl_surface; pub const wl_message = extern struct { name: ?[*:0]const u8 = null, signature: [*c]const u8 = null, - types: [*c]*const wl_interface = null, + types: [*c]const ?*const wl_interface = null, }; pub const wl_interface = extern struct { @@ -46,8 +64,73 @@ pub const wl_interface = extern struct { events: ?[*]const wl_message = null, }; +const no_types = [_]?*const wl_interface{null}; +const dmabuf_create_params_types = [_]?*const wl_interface{&zwp_linux_buffer_params_v1_interface}; +const params_create_immed_types = [_]?*const wl_interface{ &wl_buffer_interface_storage, null, null, null, null }; +const params_created_types = [_]?*const wl_interface{&wl_buffer_interface_storage}; + +const dmabuf_requests = [_]wl_message{ + .{ .name = "destroy", .signature = "", .types = no_types[0..].ptr }, + .{ .name = "create_params", .signature = "n", .types = dmabuf_create_params_types[0..].ptr }, +}; + +const dmabuf_events = [_]wl_message{ + .{ .name = "format", .signature = "u", .types = no_types[0..].ptr }, + .{ .name = "modifier", .signature = "3uuu", .types = no_types[0..].ptr }, +}; + +pub const zwp_linux_dmabuf_v1_interface: wl_interface = .{ + .name = "zwp_linux_dmabuf_v1", + .version = 3, + .method_count = dmabuf_requests.len, + .methods = &dmabuf_requests, + .event_count = dmabuf_events.len, + .events = &dmabuf_events, +}; + +const params_requests = [_]wl_message{ + .{ .name = "destroy", .signature = "", .types = no_types[0..].ptr }, + .{ .name = "add", .signature = "huuuuu", .types = no_types[0..].ptr }, + .{ .name = "create", .signature = "iiuu", .types = no_types[0..].ptr }, + .{ .name = "create_immed", .signature = "2niiuu", .types = params_create_immed_types[0..].ptr }, +}; + +const params_events = [_]wl_message{ + .{ .name = "created", .signature = "n", .types = params_created_types[0..].ptr }, + .{ .name = "failed", .signature = "", .types = no_types[0..].ptr }, +}; + +pub const zwp_linux_buffer_params_v1_interface: wl_interface = .{ + .name = "zwp_linux_buffer_params_v1", + .version = 3, + .method_count = params_requests.len, + .methods = ¶ms_requests, + .event_count = params_events.len, + .events = ¶ms_events, +}; + +// The loaded wl_buffer_interface is copied here before any linux-dmabuf request is made. +// SAFETY: load assigns every global pointer before any protocol wrapper is used. +var wl_buffer_interface_storage: wl_interface = undefined; + // SAFETY: load assigns every function pointer before any protocol wrapper is used. -pub var wl_display_dispatch: *const fn (*wl_display) callconv(.c) c_int = undefined; +pub var wl_display_cancel_read: *const fn (*wl_display) callconv(.c) void = undefined; +// SAFETY: load assigns every function pointer before any protocol wrapper is used. +pub var wl_display_create_queue: *const fn (*wl_display) callconv(.c) ?*wl_event_queue = undefined; +// SAFETY: load assigns every function pointer before any protocol wrapper is used. +pub var wl_display_dispatch_queue: *const fn (*wl_display, *wl_event_queue) callconv(.c) c_int = undefined; +// SAFETY: load assigns every function pointer before any protocol wrapper is used. +pub var wl_display_dispatch_queue_pending: *const fn (*wl_display, *wl_event_queue) callconv(.c) c_int = undefined; +// SAFETY: load assigns every function pointer before any protocol wrapper is used. +pub var wl_display_get_fd: *const fn (*wl_display) callconv(.c) c_int = undefined; +// SAFETY: load assigns every function pointer before any protocol wrapper is used. +pub var wl_display_prepare_read_queue: *const fn (*wl_display, *wl_event_queue) callconv(.c) c_int = undefined; +// SAFETY: load assigns every function pointer before any protocol wrapper is used. +pub var wl_display_read_events: *const fn (*wl_display) callconv(.c) c_int = undefined; +// SAFETY: load assigns every function pointer before any protocol wrapper is used. +pub var wl_display_roundtrip_queue: *const fn (*wl_display, *wl_event_queue) callconv(.c) c_int = undefined; +// SAFETY: load assigns every function pointer before any protocol wrapper is used. +pub var wl_event_queue_destroy: *const fn (*wl_event_queue) callconv(.c) void = undefined; // SAFETY: load assigns every function pointer before any protocol wrapper is used. pub var wl_proxy_marshal_flags: *const fn (*wl_proxy, u32, ?*const wl_interface, u32, u32, ...) callconv(.c) ?*wl_proxy = undefined; // SAFETY: load assigns every function pointer before any protocol wrapper is used. @@ -55,53 +138,72 @@ pub var wl_proxy_get_version: *const fn (*wl_proxy) callconv(.c) u32 = undefined // SAFETY: load assigns every function pointer before any protocol wrapper is used. pub var wl_proxy_add_listener: *const fn (*wl_proxy, **const fn (void) void, ?*anyopaque) callconv(.c) c_int = undefined; // SAFETY: load assigns every function pointer before any protocol wrapper is used. +pub var wl_proxy_destroy: *const fn (*wl_proxy) callconv(.c) void = undefined; +// SAFETY: load assigns every function pointer before any protocol wrapper is used. +pub var wl_proxy_set_queue: *const fn (*wl_proxy, ?*wl_event_queue) callconv(.c) void = undefined; +// SAFETY: load assigns every function pointer before any protocol wrapper is used. pub var wl_display_flush: *const fn (*wl_display) callconv(.c) c_int = undefined; // SAFETY: load resolves every interface pointer before a surface can be created. pub var wl_buffer_interface: *wl_interface = undefined; // SAFETY: load resolves every interface pointer before a surface can be created. pub var wl_registry_interface: *wl_interface = undefined; -// SAFETY: load resolves every interface pointer before a surface can be created. -pub var wl_shm_interface: *wl_interface = undefined; -// SAFETY: load resolves every interface pointer before a surface can be created. -pub var wl_shm_pool_interface: *wl_interface = undefined; // SAFETY: load initializes the module before it can be closed or queried. -pub var module: std.DynLib = undefined; - -pub var ref_count = std.atomic.Value(usize).init(0); +var module: std.DynLib = undefined; +var ref_count = std.atomic.Value(usize).init(0); +var load_mutex: lib.SpinMutex = .{}; pub fn load() VkError!void { - if (ref_count.load(.monotonic) != 0) + load_mutex.lock(); + defer load_mutex.unlock(); + + if (ref_count.load(.monotonic) != 0) { + _ = ref_count.fetchAdd(1, .monotonic); return; + } - module = std.DynLib.open("libwayland-client.so.0") catch return VkError.Unknown; + module = std.DynLib.open("libwayland-client.so.0") catch { + std.log.scoped(.WaylandClient).err("Could not open 'libwayland-client.so.0'", .{}); + return VkError.InitializationFailed; + }; errdefer module.close(); - errdefer std.log.scoped(.WaylandClient).err("Could not open 'libwayland-client.so.0': {s}", .{std.c.dlerror() orelse "unknown error"}); - wl_display_dispatch = module.lookup(@TypeOf(wl_display_dispatch), "wl_display_dispatch") orelse return VkError.Unknown; - wl_proxy_marshal_flags = module.lookup(@TypeOf(wl_proxy_marshal_flags), "wl_proxy_marshal_flags") orelse return VkError.Unknown; - wl_proxy_get_version = module.lookup(@TypeOf(wl_proxy_get_version), "wl_proxy_get_version") orelse return VkError.Unknown; - wl_proxy_add_listener = module.lookup(@TypeOf(wl_proxy_add_listener), "wl_proxy_add_listener") orelse return VkError.Unknown; - wl_display_flush = module.lookup(@TypeOf(wl_display_flush), "wl_display_flush") orelse return VkError.Unknown; + wl_display_cancel_read = module.lookup(@TypeOf(wl_display_cancel_read), "wl_display_cancel_read") orelse return VkError.InitializationFailed; + wl_display_create_queue = module.lookup(@TypeOf(wl_display_create_queue), "wl_display_create_queue") orelse return VkError.InitializationFailed; + wl_display_dispatch_queue = module.lookup(@TypeOf(wl_display_dispatch_queue), "wl_display_dispatch_queue") orelse return VkError.InitializationFailed; + wl_display_dispatch_queue_pending = module.lookup(@TypeOf(wl_display_dispatch_queue_pending), "wl_display_dispatch_queue_pending") orelse return VkError.InitializationFailed; + wl_display_get_fd = module.lookup(@TypeOf(wl_display_get_fd), "wl_display_get_fd") orelse return VkError.InitializationFailed; + wl_display_prepare_read_queue = module.lookup(@TypeOf(wl_display_prepare_read_queue), "wl_display_prepare_read_queue") orelse return VkError.InitializationFailed; + wl_display_read_events = module.lookup(@TypeOf(wl_display_read_events), "wl_display_read_events") orelse return VkError.InitializationFailed; + wl_display_roundtrip_queue = module.lookup(@TypeOf(wl_display_roundtrip_queue), "wl_display_roundtrip_queue") orelse return VkError.InitializationFailed; + wl_event_queue_destroy = module.lookup(@TypeOf(wl_event_queue_destroy), "wl_event_queue_destroy") orelse return VkError.InitializationFailed; + wl_proxy_marshal_flags = module.lookup(@TypeOf(wl_proxy_marshal_flags), "wl_proxy_marshal_flags") orelse return VkError.InitializationFailed; + wl_proxy_get_version = module.lookup(@TypeOf(wl_proxy_get_version), "wl_proxy_get_version") orelse return VkError.InitializationFailed; + wl_proxy_add_listener = module.lookup(@TypeOf(wl_proxy_add_listener), "wl_proxy_add_listener") orelse return VkError.InitializationFailed; + wl_proxy_destroy = module.lookup(@TypeOf(wl_proxy_destroy), "wl_proxy_destroy") orelse return VkError.InitializationFailed; + wl_proxy_set_queue = module.lookup(@TypeOf(wl_proxy_set_queue), "wl_proxy_set_queue") orelse return VkError.InitializationFailed; + wl_display_flush = module.lookup(@TypeOf(wl_display_flush), "wl_display_flush") orelse return VkError.InitializationFailed; - wl_buffer_interface = module.lookup(*wl_interface, "wl_buffer_interface") orelse return VkError.Unknown; - wl_registry_interface = module.lookup(*wl_interface, "wl_registry_interface") orelse return VkError.Unknown; - wl_shm_interface = module.lookup(*wl_interface, "wl_shm_interface") orelse return VkError.Unknown; - wl_shm_pool_interface = module.lookup(*wl_interface, "wl_shm_pool_interface") orelse return VkError.Unknown; + wl_buffer_interface = module.lookup(*wl_interface, "wl_buffer_interface") orelse return VkError.InitializationFailed; + wl_registry_interface = module.lookup(*wl_interface, "wl_registry_interface") orelse return VkError.InitializationFailed; + wl_buffer_interface_storage = wl_buffer_interface.*; _ = ref_count.fetchAdd(1, .monotonic); - std.log.scoped(.WaylandClient).debug("Loaded wayland client lib", .{}); + std.log.scoped(.WaylandClient).debug("Loaded Wayland client", .{}); } pub fn unload() void { + load_mutex.lock(); + defer load_mutex.unlock(); + if (ref_count.fetchSub(1, .release) == 1) { module.close(); - std.log.scoped(.WaylandClient).debug("Unloaded wayland client lib", .{}); + std.log.scoped(.WaylandClient).debug("Unloaded Wayland client", .{}); } } -pub fn wl_registry_bind(registry: *wl_registry, name: u32, interface: *const wl_interface, version: u32) ?*wl_proxy { +pub fn wlRegistryBind(registry: *wl_registry, name: u32, interface: *const wl_interface, version: u32) ?*wl_proxy { return wl_proxy_marshal_flags( @ptrCast(@alignCast(registry)), wl_registry_bind_opcode, @@ -115,7 +217,7 @@ pub fn wl_registry_bind(registry: *wl_registry, name: u32, interface: *const wl_ ); } -pub fn wl_display_get_registry(display: *wl_display) ?*wl_registry { +pub fn wlDisplayGetRegistry(display: *wl_display) ?*wl_registry { return @ptrCast(@alignCast(wl_proxy_marshal_flags( @ptrCast(@alignCast(display)), wl_display_get_registry_opcode, @@ -126,60 +228,102 @@ pub fn wl_display_get_registry(display: *wl_display) ?*wl_registry { ))); } -pub fn wl_registry_add_listener(registry: *wl_registry, listener: *const wl_registry_listener, data: ?*anyopaque) c_int { +pub fn wlRegistryAddListener(registry: *wl_registry, listener: *const wl_registry_listener, data: ?*anyopaque) c_int { return wl_proxy_add_listener(@ptrCast(@alignCast(registry)), @ptrCast(@alignCast(@constCast(listener))), data); } -pub fn wl_shm_create_pool(shm: *wl_shm, fd: i32, size: i32) callconv(.c) ?*wl_shm_pool { - return @ptrCast(@alignCast(wl_proxy_marshal_flags( - @ptrCast(@alignCast(shm)), - wl_shm_create_pool_opcode, - wl_shm_pool_interface, - wl_proxy_get_version(@ptrCast(@alignCast(shm))), - 0, - @as(?*anyopaque, null), - fd, - size, - ))); +pub fn wlProxyAssignQueue(proxy: *anyopaque, queue: *wl_event_queue) void { + wl_proxy_set_queue(@ptrCast(@alignCast(proxy)), queue); } -pub fn wl_shm_pool_destroy(shm_pool: *wl_shm_pool) void { +pub fn wlRegistryDestroy(registry: *wl_registry) void { + wl_proxy_destroy(@ptrCast(@alignCast(registry))); +} + +pub fn wlBufferAddListener(buffer: *wl_buffer, listener: *const wl_buffer_listener, data: ?*anyopaque) c_int { + return wl_proxy_add_listener(@ptrCast(@alignCast(buffer)), @ptrCast(@alignCast(@constCast(listener))), data); +} + +pub fn zwpLinuxDmabufV1AddListener(dmabuf: *zwp_linux_dmabuf_v1, listener: *const zwp_linux_dmabuf_v1_listener, data: ?*anyopaque) c_int { + return wl_proxy_add_listener(@ptrCast(@alignCast(dmabuf)), @ptrCast(@alignCast(@constCast(listener))), data); +} + +pub fn zwpLinuxBufferParamsV1AddListener(params: *zwp_linux_buffer_params_v1, listener: *const zwp_linux_buffer_params_v1_listener, data: ?*anyopaque) c_int { + return wl_proxy_add_listener(@ptrCast(@alignCast(params)), @ptrCast(@alignCast(@constCast(listener))), data); +} + +pub fn zwpLinuxDmabufV1Destroy(dmabuf: *zwp_linux_dmabuf_v1) void { _ = wl_proxy_marshal_flags( - @ptrCast(@alignCast(shm_pool)), - wl_shm_pool_destroy_opcode, + @ptrCast(@alignCast(dmabuf)), + zwp_linux_dmabuf_v1_destroy_opcode, null, - wl_proxy_get_version(@ptrCast(@alignCast(shm_pool))), - @bitCast(@as(c_int, @as(c_int, 1) << @intCast(@as(c_int, 0)))), + wl_proxy_get_version(@ptrCast(@alignCast(dmabuf))), + wl_marshal_flag_destroy, ); } -pub fn wl_shm_pool_create_buffer(shm_pool: *wl_shm_pool, offset: i32, width: i32, height: i32, stride: i32, format: u32) ?*wl_buffer { +pub fn zwpLinuxDmabufV1CreateParams(dmabuf: *zwp_linux_dmabuf_v1) ?*zwp_linux_buffer_params_v1 { return @ptrCast(@alignCast(wl_proxy_marshal_flags( - @ptrCast(@alignCast(shm_pool)), - wl_shm_pool_create_buffer_opcode, - wl_buffer_interface, - wl_proxy_get_version(@ptrCast(@alignCast(shm_pool))), + @ptrCast(@alignCast(dmabuf)), + zwp_linux_dmabuf_v1_create_params_opcode, + &zwp_linux_buffer_params_v1_interface, + wl_proxy_get_version(@ptrCast(@alignCast(dmabuf))), 0, @as(?*anyopaque, null), - offset, - width, - height, - stride, - format, ))); } -pub fn wl_buffer_destroy(buffer: *wl_buffer) void { +pub fn zwpLinuxBufferParamsV1Destroy(params: *zwp_linux_buffer_params_v1) void { + _ = wl_proxy_marshal_flags( + @ptrCast(@alignCast(params)), + zwp_linux_buffer_params_v1_destroy_opcode, + null, + wl_proxy_get_version(@ptrCast(@alignCast(params))), + wl_marshal_flag_destroy, + ); +} + +pub fn zwpLinuxBufferParamsV1Add(params: *zwp_linux_buffer_params_v1, fd: i32, plane_index: u32, offset: u32, stride: u32, modifier: u64) void { + _ = wl_proxy_marshal_flags( + @ptrCast(@alignCast(params)), + zwp_linux_buffer_params_v1_add_opcode, + null, + wl_proxy_get_version(@ptrCast(@alignCast(params))), + 0, + fd, + plane_index, + offset, + stride, + @as(u32, @truncate(modifier >> 32)), + @as(u32, @truncate(modifier)), + ); +} + +pub fn zwpLinuxBufferParamsV1Create(params: *zwp_linux_buffer_params_v1, width: i32, height: i32, format: u32, flags: u32) void { + _ = wl_proxy_marshal_flags( + @ptrCast(@alignCast(params)), + zwp_linux_buffer_params_v1_create_opcode, + null, + wl_proxy_get_version(@ptrCast(@alignCast(params))), + 0, + width, + height, + format, + flags, + ); +} + +pub fn wlBufferDestroy(buffer: *wl_buffer) void { _ = wl_proxy_marshal_flags( @ptrCast(@alignCast(buffer)), wl_buffer_destroy_opcode, null, wl_proxy_get_version(@ptrCast(@alignCast(buffer))), - @bitCast(@as(c_int, @as(c_int, 1) << @intCast(@as(c_int, 0)))), + wl_marshal_flag_destroy, ); } -pub fn wl_surface_attach(surface: *wl_surface, buffer: *wl_buffer, x: i32, y: i32) void { +pub fn wlSurfaceAttach(surface: *wl_surface, buffer: *wl_buffer, x: i32, y: i32) void { _ = wl_proxy_marshal_flags( @ptrCast(@alignCast(surface)), wl_surface_attach_opcode, @@ -192,7 +336,7 @@ pub fn wl_surface_attach(surface: *wl_surface, buffer: *wl_buffer, x: i32, y: i3 ); } -pub fn wl_surface_damage(surface: *wl_surface, x: i32, y: i32, width: i32, height: i32) void { +pub fn wlSurfaceDamage(surface: *wl_surface, x: i32, y: i32, width: i32, height: i32) void { _ = wl_proxy_marshal_flags( @ptrCast(@alignCast(surface)), wl_surface_damage_opcode, @@ -206,7 +350,7 @@ pub fn wl_surface_damage(surface: *wl_surface, x: i32, y: i32, width: i32, heigh ); } -pub fn wl_surface_commit(surface: *wl_surface) void { +pub fn wlSurfaceCommit(surface: *wl_surface) void { _ = wl_proxy_marshal_flags( @ptrCast(@alignCast(surface)), wl_surface_commit_opcode, diff --git a/src/vulkan/wsi/clients/xcb.zig b/src/vulkan/wsi/clients/xcb.zig new file mode 100644 index 0000000..92c97f0 --- /dev/null +++ b/src/vulkan/wsi/clients/xcb.zig @@ -0,0 +1,342 @@ +//! Minimal dynamically loaded XCB client used by the XCB WSI backend. + +const std = @import("std"); +const vk = @import("vulkan"); +const lib = @import("../../lib.zig"); + +const VkError = lib.VkError; + +pub const Connection = vk.xcb_connection_t; +pub const Window = vk.xcb_window_t; +pub const Drawable = u32; +pub const Gcontext = u32; + +const z_pixmap: u8 = 2; +const put_image_request_size: u32 = 24; + +const VoidCookie = extern struct { + sequence: c_uint, +}; + +const GetGeometryCookie = extern struct { + sequence: c_uint, +}; + +const GenericError = opaque {}; +const Setup = opaque {}; +const Screen = opaque {}; + +const Depth = extern struct { + depth: u8, + pad0: u8, + visuals_len: u16, + pad1: [4]u8, +}; + +const Format = extern struct { + depth: u8, + bits_per_pixel: u8, + scanline_pad: u8, + pad0: [5]u8, +}; + +const VisualType = extern struct { + visual_id: vk.xcb_visualid_t, + class: u8, + bits_per_rgb_value: u8, + colormap_entries: u16, + red_mask: u32, + green_mask: u32, + blue_mask: u32, + pad0: [4]u8, +}; + +const DepthIterator = extern struct { + data: ?*Depth, + rem: c_int, + index: c_int, +}; + +const FormatIterator = extern struct { + data: ?*Format, + rem: c_int, + index: c_int, +}; + +const ScreenIterator = extern struct { + data: ?*Screen, + rem: c_int, + index: c_int, +}; + +const VisualTypeIterator = extern struct { + data: ?*VisualType, + rem: c_int, + index: c_int, +}; + +const GetGeometryReply = extern struct { + response_type: u8, + depth: u8, + sequence: u16, + length: u32, + root: Window, + x: i16, + y: i16, + width: u16, + height: u16, + border_width: u16, + pad0: [2]u8, +}; + +pub const Geometry = struct { + width: u32, + height: u32, + depth: u8, +}; + +// SAFETY: load assigns every function pointer before any helper is used. +var xcb_connection_has_error: *const fn (*Connection) callconv(.c) c_int = undefined; +// SAFETY: load assigns every function pointer before any helper is used. +var xcb_create_gc_checked: *const fn (*Connection, Gcontext, Drawable, u32, ?*const anyopaque) callconv(.c) VoidCookie = undefined; +// SAFETY: load assigns every function pointer before any helper is used. +var xcb_depth_next: *const fn (*DepthIterator) callconv(.c) void = undefined; +// SAFETY: load assigns every function pointer before any helper is used. +var xcb_depth_visuals_iterator: *const fn (*const Depth) callconv(.c) VisualTypeIterator = undefined; +// SAFETY: load assigns every function pointer before any helper is used. +var xcb_flush: *const fn (*Connection) callconv(.c) c_int = undefined; +// SAFETY: load assigns every function pointer before any helper is used. +var xcb_format_next: *const fn (*FormatIterator) callconv(.c) void = undefined; +// SAFETY: load assigns every function pointer before any helper is used. +var xcb_free_gc: *const fn (*Connection, Gcontext) callconv(.c) VoidCookie = undefined; +// SAFETY: load assigns every function pointer before any helper is used. +var xcb_generate_id: *const fn (*Connection) callconv(.c) u32 = undefined; +// SAFETY: load assigns every function pointer before any helper is used. +var xcb_get_geometry: *const fn (*Connection, Drawable) callconv(.c) GetGeometryCookie = undefined; +// SAFETY: load assigns every function pointer before any helper is used. +var xcb_get_geometry_reply: *const fn (*Connection, GetGeometryCookie, *?*GenericError) callconv(.c) ?*GetGeometryReply = undefined; +// SAFETY: load assigns every function pointer before any helper is used. +var xcb_get_maximum_request_length: *const fn (*Connection) callconv(.c) u32 = undefined; +// SAFETY: load assigns every function pointer before any helper is used. +var xcb_get_setup: *const fn (*Connection) callconv(.c) ?*const Setup = undefined; +// SAFETY: load assigns every function pointer before any helper is used. +var xcb_put_image: *const fn (*Connection, u8, Drawable, Gcontext, u16, u16, i16, i16, u8, u8, u32, [*]const u8) callconv(.c) VoidCookie = undefined; +// SAFETY: load assigns every function pointer before any helper is used. +var xcb_request_check: *const fn (*Connection, VoidCookie) callconv(.c) ?*GenericError = undefined; +// SAFETY: load assigns every function pointer before any helper is used. +var xcb_screen_allowed_depths_iterator: *const fn (*const Screen) callconv(.c) DepthIterator = undefined; +// SAFETY: load assigns every function pointer before any helper is used. +var xcb_screen_next: *const fn (*ScreenIterator) callconv(.c) void = undefined; +// SAFETY: load assigns every function pointer before any helper is used. +var xcb_setup_pixmap_formats_iterator: *const fn (*const Setup) callconv(.c) FormatIterator = undefined; +// SAFETY: load assigns every function pointer before any helper is used. +var xcb_setup_roots_iterator: *const fn (*const Setup) callconv(.c) ScreenIterator = undefined; +// SAFETY: load assigns every function pointer before any helper is used. +var xcb_visualtype_next: *const fn (*VisualTypeIterator) callconv(.c) void = undefined; + +// SAFETY: load initializes the module before it can be closed. +var module: std.DynLib = undefined; +var ref_count = std.atomic.Value(usize).init(0); +var load_mutex: lib.SpinMutex = .{}; + +pub fn load() VkError!void { + load_mutex.lock(); + defer load_mutex.unlock(); + + if (ref_count.load(.monotonic) != 0) { + _ = ref_count.fetchAdd(1, .monotonic); + return; + } + + module = std.DynLib.open("libxcb.so.1") catch { + std.log.scoped(.XcbClient).err("Could not open 'libxcb.so.1'", .{}); + return VkError.InitializationFailed; + }; + errdefer module.close(); + + xcb_connection_has_error = module.lookup(@TypeOf(xcb_connection_has_error), "xcb_connection_has_error") orelse return VkError.InitializationFailed; + xcb_create_gc_checked = module.lookup(@TypeOf(xcb_create_gc_checked), "xcb_create_gc_checked") orelse return VkError.InitializationFailed; + xcb_depth_next = module.lookup(@TypeOf(xcb_depth_next), "xcb_depth_next") orelse return VkError.InitializationFailed; + xcb_depth_visuals_iterator = module.lookup(@TypeOf(xcb_depth_visuals_iterator), "xcb_depth_visuals_iterator") orelse return VkError.InitializationFailed; + xcb_flush = module.lookup(@TypeOf(xcb_flush), "xcb_flush") orelse return VkError.InitializationFailed; + xcb_format_next = module.lookup(@TypeOf(xcb_format_next), "xcb_format_next") orelse return VkError.InitializationFailed; + xcb_free_gc = module.lookup(@TypeOf(xcb_free_gc), "xcb_free_gc") orelse return VkError.InitializationFailed; + xcb_generate_id = module.lookup(@TypeOf(xcb_generate_id), "xcb_generate_id") orelse return VkError.InitializationFailed; + xcb_get_geometry = module.lookup(@TypeOf(xcb_get_geometry), "xcb_get_geometry") orelse return VkError.InitializationFailed; + xcb_get_geometry_reply = module.lookup(@TypeOf(xcb_get_geometry_reply), "xcb_get_geometry_reply") orelse return VkError.InitializationFailed; + xcb_get_maximum_request_length = module.lookup(@TypeOf(xcb_get_maximum_request_length), "xcb_get_maximum_request_length") orelse return VkError.InitializationFailed; + xcb_get_setup = module.lookup(@TypeOf(xcb_get_setup), "xcb_get_setup") orelse return VkError.InitializationFailed; + xcb_put_image = module.lookup(@TypeOf(xcb_put_image), "xcb_put_image") orelse return VkError.InitializationFailed; + xcb_request_check = module.lookup(@TypeOf(xcb_request_check), "xcb_request_check") orelse return VkError.InitializationFailed; + xcb_screen_allowed_depths_iterator = module.lookup(@TypeOf(xcb_screen_allowed_depths_iterator), "xcb_screen_allowed_depths_iterator") orelse return VkError.InitializationFailed; + xcb_screen_next = module.lookup(@TypeOf(xcb_screen_next), "xcb_screen_next") orelse return VkError.InitializationFailed; + xcb_setup_pixmap_formats_iterator = module.lookup(@TypeOf(xcb_setup_pixmap_formats_iterator), "xcb_setup_pixmap_formats_iterator") orelse return VkError.InitializationFailed; + xcb_setup_roots_iterator = module.lookup(@TypeOf(xcb_setup_roots_iterator), "xcb_setup_roots_iterator") orelse return VkError.InitializationFailed; + xcb_visualtype_next = module.lookup(@TypeOf(xcb_visualtype_next), "xcb_visualtype_next") orelse return VkError.InitializationFailed; + + _ = ref_count.fetchAdd(1, .monotonic); + std.log.scoped(.XcbClient).debug("Loaded XCB client", .{}); +} + +pub fn unload() void { + load_mutex.lock(); + defer load_mutex.unlock(); + + if (ref_count.fetchSub(1, .release) == 1) { + module.close(); + std.log.scoped(.XcbClient).debug("Unloaded XCB client", .{}); + } +} + +pub fn checkConnection(connection: *Connection) VkError!void { + if (xcb_connection_has_error(connection) != 0) + return VkError.SurfaceLostKhr; +} + +pub fn supportsVisual(connection: *Connection, visual_id: vk.xcb_visualid_t) VkError!bool { + try checkConnection(connection); + const setup = xcb_get_setup(connection) orelse return false; + + var screen_iterator = xcb_setup_roots_iterator(setup); + while (screen_iterator.rem > 0) : (xcb_screen_next(&screen_iterator)) { + const screen = screen_iterator.data orelse break; + var depth_iterator = xcb_screen_allowed_depths_iterator(screen); + while (depth_iterator.rem > 0) : (xcb_depth_next(&depth_iterator)) { + const depth = depth_iterator.data orelse break; + var visual_iterator = xcb_depth_visuals_iterator(depth); + while (visual_iterator.rem > 0) : (xcb_visualtype_next(&visual_iterator)) { + const visual = visual_iterator.data orelse break; + if (visual.visual_id != visual_id) + continue; + + if ((depth.depth != 24 and depth.depth != 32) or + visual.red_mask != 0x00ff_0000 or + visual.green_mask != 0x0000_ff00 or + visual.blue_mask != 0x0000_00ff) + return false; + + var format_iterator = xcb_setup_pixmap_formats_iterator(setup); + while (format_iterator.rem > 0) : (xcb_format_next(&format_iterator)) { + const format = format_iterator.data orelse break; + if (format.depth == depth.depth and + format.bits_per_pixel == 32 and + format.scanline_pad != 0 and + 32 % format.scanline_pad == 0) + return true; + } + return false; + } + } + } + + return false; +} + +pub fn getGeometry(connection: *Connection, drawable: Drawable) VkError!Geometry { + try checkConnection(connection); + + var protocol_error: ?*GenericError = null; + const reply = xcb_get_geometry_reply(connection, xcb_get_geometry(connection, drawable), &protocol_error) orelse { + if (protocol_error) |err| std.c.free(err); + return VkError.SurfaceLostKhr; + }; + defer std.c.free(reply); + if (protocol_error) |err| { + std.c.free(err); + return VkError.SurfaceLostKhr; + } + + return .{ .width = reply.width, .height = reply.height, .depth = reply.depth }; +} + +pub fn createGc(connection: *Connection, drawable: Drawable) VkError!Gcontext { + const gc = xcb_generate_id(connection); + if (gc == std.math.maxInt(u32)) + return VkError.OutOfHostMemory; + + const error_reply = xcb_request_check(connection, xcb_create_gc_checked(connection, gc, drawable, 0, null)); + if (error_reply) |err| { + std.c.free(err); + return VkError.SurfaceLostKhr; + } + return gc; +} + +pub fn freeGc(connection: *Connection, gc: Gcontext) void { + _ = xcb_free_gc(connection, gc); + _ = xcb_flush(connection); +} + +pub fn maximumImagePayload(connection: *Connection) VkError!usize { + const request_units = xcb_get_maximum_request_length(connection); + const request_bytes = std.math.mul(u32, request_units, 4) catch return VkError.OutOfHostMemory; + if (request_bytes <= put_image_request_size + 4) + return VkError.InitializationFailed; + return request_bytes - put_image_request_size; +} + +pub fn putImage(connection: *Connection, drawable: Drawable, gc: Gcontext, depth: u8, width: u32, height: u32, row_size: usize, data: []const u8, maximum_payload: usize) VkError!void { + try checkConnection(connection); + + const protocol_width = std.math.cast(u16, width) orelse return VkError.OutOfDeviceMemory; + const protocol_height = std.math.cast(u16, height) orelse return VkError.OutOfDeviceMemory; + const required_size = std.math.mul(usize, row_size, height) catch return VkError.OutOfHostMemory; + if (data.len < required_size or row_size != @as(usize, width) * 4 or maximum_payload < 4) + return VkError.ValidationFailed; + + if (row_size <= maximum_payload) { + const rows_per_request = @max(@as(usize, 1), @min(maximum_payload / row_size, std.math.maxInt(u16))); + var y: usize = 0; + while (y < protocol_height) { + const row_count = @min(rows_per_request, @as(usize, protocol_height) - y); + const offset = y * row_size; + const byte_count = row_count * row_size; + _ = xcb_put_image( + connection, + z_pixmap, + drawable, + gc, + protocol_width, + @intCast(row_count), + 0, + @intCast(y), + 0, + depth, + @intCast(byte_count), + data[offset..][0..byte_count].ptr, + ); + y += row_count; + } + } else { + const pixels_per_request = maximum_payload / 4; + if (pixels_per_request == 0) + return VkError.InitializationFailed; + + for (0..protocol_height) |y| { + var x: usize = 0; + while (x < protocol_width) { + const pixel_count = @min(pixels_per_request, @as(usize, protocol_width) - x); + const offset = y * row_size + x * 4; + const byte_count = pixel_count * 4; + _ = xcb_put_image( + connection, + z_pixmap, + drawable, + gc, + @intCast(pixel_count), + 1, + @intCast(x), + @intCast(y), + 0, + depth, + @intCast(byte_count), + data[offset..][0..byte_count].ptr, + ); + x += pixel_count; + } + } + } + + if (xcb_flush(connection) <= 0) + try checkConnection(connection); +} diff --git a/src/vulkan/wsi/clients/xlib.zig b/src/vulkan/wsi/clients/xlib.zig new file mode 100644 index 0000000..8c623eb --- /dev/null +++ b/src/vulkan/wsi/clients/xlib.zig @@ -0,0 +1,222 @@ +//! Minimal dynamically loaded Xlib client used by the Xlib WSI backend. + +const std = @import("std"); +const vk = @import("vulkan"); +const lib = @import("../../lib.zig"); + +const VkError = lib.VkError; + +pub const Display = vk.Display; +pub const Window = vk.Window; +pub const Drawable = c_ulong; +pub const Visual = opaque {}; +pub const Screen = opaque {}; +pub const GC = opaque {}; + +const z_pixmap: c_int = 2; +const lsb_first: c_int = 0; +const visual_id_mask: c_long = 0x1; + +pub const XImage = extern struct { + width: c_int, + height: c_int, + xoffset: c_int, + format: c_int, + data: [*c]u8, + byte_order: c_int, + bitmap_unit: c_int, + bitmap_bit_order: c_int, + bitmap_pad: c_int, + depth: c_int, + bytes_per_line: c_int, + bits_per_pixel: c_int, + red_mask: c_ulong, + green_mask: c_ulong, + blue_mask: c_ulong, + obdata: ?*anyopaque, + f: extern struct { + create_image: ?*const fn (*Display, *Visual, c_uint, c_int, c_int, [*c]u8, c_uint, c_uint, c_int, c_int) callconv(.c) ?*XImage, + destroy_image: *const fn (*XImage) callconv(.c) c_int, + get_pixel: ?*const fn (*XImage, c_int, c_int) callconv(.c) c_ulong, + put_pixel: ?*const fn (*XImage, c_int, c_int, c_ulong) callconv(.c) c_int, + sub_image: ?*const fn (*XImage, c_int, c_int, c_uint, c_uint) callconv(.c) ?*XImage, + add_pixel: ?*const fn (*XImage, c_long) callconv(.c) c_int, + }, +}; + +const VisualInfo = extern struct { + visual: *Visual, + visualid: c_ulong, + screen: c_int, + depth: c_int, + class: c_int, + red_mask: c_ulong, + green_mask: c_ulong, + blue_mask: c_ulong, + colormap_size: c_int, + bits_per_rgb: c_int, +}; + +const WindowAttributes = extern struct { + x: c_int, + y: c_int, + width: c_int, + height: c_int, + border_width: c_int, + depth: c_int, + visual: *Visual, + root: Window, + class: c_int, + bit_gravity: c_int, + win_gravity: c_int, + backing_store: c_int, + backing_planes: c_ulong, + backing_pixel: c_ulong, + save_under: c_int, + colormap: c_ulong, + map_installed: c_int, + map_state: c_int, + all_event_masks: c_long, + your_event_mask: c_long, + do_not_propagate_mask: c_long, + override_redirect: c_int, + screen: *Screen, +}; + +pub const Geometry = struct { + width: u32, + height: u32, + depth: u32, + visual: *Visual, +}; + +// SAFETY: load assigns every function pointer before any helper is used. +var XCreateGC: *const fn (*Display, Drawable, c_ulong, ?*anyopaque) callconv(.c) ?*GC = undefined; +// SAFETY: load assigns every function pointer before any helper is used. +var XCreateImage: *const fn (*Display, *Visual, c_uint, c_int, c_int, [*c]u8, c_uint, c_uint, c_int, c_int) callconv(.c) ?*XImage = undefined; +// SAFETY: load assigns every function pointer before any helper is used. +var XFlush: *const fn (*Display) callconv(.c) c_int = undefined; +// SAFETY: load assigns every function pointer before any helper is used. +var XFree: *const fn (?*anyopaque) callconv(.c) c_int = undefined; +// SAFETY: load assigns every function pointer before any helper is used. +var XFreeGC: *const fn (*Display, *GC) callconv(.c) c_int = undefined; +// SAFETY: load assigns every function pointer before any helper is used. +var XGetVisualInfo: *const fn (*Display, c_long, *VisualInfo, *c_int) callconv(.c) ?[*]VisualInfo = undefined; +// SAFETY: load assigns every function pointer before any helper is used. +var XGetWindowAttributes: *const fn (*Display, Window, *WindowAttributes) callconv(.c) c_int = undefined; +// SAFETY: load assigns every function pointer before any helper is used. +var XPutImage: *const fn (*Display, Drawable, *GC, *XImage, c_int, c_int, c_int, c_int, c_uint, c_uint) callconv(.c) c_int = undefined; + +// SAFETY: load initializes the module before it can be closed. +var module: std.DynLib = undefined; +var ref_count = std.atomic.Value(usize).init(0); +var load_mutex: lib.SpinMutex = .{}; + +pub fn load() VkError!void { + load_mutex.lock(); + defer load_mutex.unlock(); + + if (ref_count.load(.monotonic) != 0) { + _ = ref_count.fetchAdd(1, .monotonic); + return; + } + + module = std.DynLib.open("libX11.so.6") catch { + std.log.scoped(.XlibClient).err("Could not open 'libX11.so.6'", .{}); + return VkError.InitializationFailed; + }; + errdefer module.close(); + + XCreateGC = module.lookup(@TypeOf(XCreateGC), "XCreateGC") orelse return VkError.InitializationFailed; + XCreateImage = module.lookup(@TypeOf(XCreateImage), "XCreateImage") orelse return VkError.InitializationFailed; + XFlush = module.lookup(@TypeOf(XFlush), "XFlush") orelse return VkError.InitializationFailed; + XFree = module.lookup(@TypeOf(XFree), "XFree") orelse return VkError.InitializationFailed; + XFreeGC = module.lookup(@TypeOf(XFreeGC), "XFreeGC") orelse return VkError.InitializationFailed; + XGetVisualInfo = module.lookup(@TypeOf(XGetVisualInfo), "XGetVisualInfo") orelse return VkError.InitializationFailed; + XGetWindowAttributes = module.lookup(@TypeOf(XGetWindowAttributes), "XGetWindowAttributes") orelse return VkError.InitializationFailed; + XPutImage = module.lookup(@TypeOf(XPutImage), "XPutImage") orelse return VkError.InitializationFailed; + + _ = ref_count.fetchAdd(1, .monotonic); + std.log.scoped(.XlibClient).debug("Loaded Xlib client", .{}); +} + +pub fn unload() void { + load_mutex.lock(); + defer load_mutex.unlock(); + + if (ref_count.fetchSub(1, .release) == 1) { + module.close(); + std.log.scoped(.XlibClient).debug("Unloaded Xlib client", .{}); + } +} + +pub fn supportsVisual(display: *Display, visual_id: vk.VisualID) bool { + var template = std.mem.zeroes(VisualInfo); + template.visualid = visual_id; + + var visual_count: c_int = 0; + const visual_infos = XGetVisualInfo(display, visual_id_mask, &template, &visual_count) orelse return false; + defer _ = XFree(@ptrCast(visual_infos)); + if (visual_count <= 0) + return false; + + for (visual_infos[0..@intCast(visual_count)]) |visual_info| { + if ((visual_info.depth == 24 or visual_info.depth == 32) and + visual_info.red_mask == 0x00ff_0000 and + visual_info.green_mask == 0x0000_ff00 and + visual_info.blue_mask == 0x0000_00ff) + return true; + } + + return false; +} + +pub fn getGeometry(display: *Display, window: Window) VkError!Geometry { + // SAFETY: will be overwritten by XGetWindowAttributes + var attributes: WindowAttributes = undefined; + if (XGetWindowAttributes(display, window, &attributes) == 0) + return VkError.SurfaceLostKhr; + if (attributes.width <= 0 or attributes.height <= 0 or attributes.depth <= 0) + return VkError.SurfaceLostKhr; + + return .{ + .width = @intCast(attributes.width), + .height = @intCast(attributes.height), + .depth = @intCast(attributes.depth), + .visual = attributes.visual, + }; +} + +pub fn createGc(display: *Display, drawable: Drawable) VkError!*GC { + return XCreateGC(display, drawable, 0, null) orelse VkError.OutOfHostMemory; +} + +pub fn freeGc(display: *Display, gc: *GC) void { + _ = XFreeGC(display, gc); +} + +pub fn createImage(display: *Display, visual: *Visual, depth: u32, width: u32, height: u32, row_size: usize, data: []u8) VkError!*XImage { + const bytes_per_line = std.math.cast(c_int, row_size) orelse return VkError.OutOfHostMemory; + const image = XCreateImage(display, visual, depth, z_pixmap, 0, data.ptr, width, height, 32, bytes_per_line) orelse return VkError.OutOfHostMemory; + errdefer destroyImage(image); + + if (image.byte_order != lsb_first or + image.bits_per_pixel != 32 or + image.bytes_per_line != bytes_per_line or + image.red_mask != 0x00ff_0000 or + image.green_mask != 0x0000_ff00 or + image.blue_mask != 0x0000_00ff) + return VkError.FormatNotSupported; + + return image; +} + +pub fn destroyImage(image: *XImage) void { + image.data = null; + _ = image.f.destroy_image(image); +} + +pub fn putImage(display: *Display, drawable: Drawable, gc: *GC, image: *XImage, width: u32, height: u32) void { + _ = XPutImage(display, drawable, gc, image, 0, 0, 0, 0, width, height); + _ = XFlush(display); +} diff --git a/src/vulkan/wsi/scale.zig b/src/vulkan/wsi/scale.zig new file mode 100644 index 0000000..15cfa3f --- /dev/null +++ b/src/vulkan/wsi/scale.zig @@ -0,0 +1,35 @@ +const std = @import("std"); + +pub const Error = error{ + InvalidExtent, + InvalidBuffer, +}; + +pub fn scaleBgra8Nearest( + source: []const u8, + source_width: usize, + source_height: usize, + destination: []u8, + destination_width: usize, + destination_height: usize, +) Error!void { + if (source_width == 0 or source_height == 0 or destination_width == 0 or destination_height == 0) + return Error.InvalidExtent; + + const source_pixel_count = std.math.mul(usize, source_width, source_height) catch return Error.InvalidBuffer; + const source_size = std.math.mul(usize, source_pixel_count, 4) catch return Error.InvalidBuffer; + const destination_pixel_count = std.math.mul(usize, destination_width, destination_height) catch return Error.InvalidBuffer; + const destination_size = std.math.mul(usize, destination_pixel_count, 4) catch return Error.InvalidBuffer; + if (source.len < source_size or destination.len < destination_size) + return Error.InvalidBuffer; + + for (0..destination_height) |destination_y| { + const source_y = destination_y * source_height / destination_height; + for (0..destination_width) |destination_x| { + const source_x = destination_x * source_width / destination_width; + const source_offset = (source_y * source_width + source_x) * 4; + const destination_offset = (destination_y * destination_width + destination_x) * 4; + @memcpy(destination[destination_offset..][0..4], source[source_offset..][0..4]); + } + } +}