[WSI] implementing xcb and xlib surfaces
Mirror Gitea refs to GitHub / mirror (push) Successful in 13s
Test / build_and_test (push) Successful in 6m2s
Build / build (push) Successful in 7m0s

This commit is contained in:
2026-08-26 21:38:56 +02:00
parent 2525dded67
commit 148ed9b441
15 changed files with 1829 additions and 151 deletions
+4 -4
View File
@@ -129,8 +129,8 @@ Assume thou that functions lacking in this array are, for now, not intended to b
| vkCreateSwapchainKHR | ✅ Implemented | | vkCreateSwapchainKHR | ✅ Implemented |
| vkCreateWaylandSurfaceKHR | ✅ Implemented | | vkCreateWaylandSurfaceKHR | ✅ Implemented |
| vkCreateWin32SurfaceKHR | ⚙️ WIP | | vkCreateWin32SurfaceKHR | ⚙️ WIP |
| vkCreateXcbSurfaceKHR | ⚙️ WIP | | vkCreateXcbSurfaceKHR | ✅ Implemented |
| vkCreateXlibSurfaceKHR | ⚙️ WIP | | vkCreateXlibSurfaceKHR | ✅ Implemented |
| vkDestroyBuffer | ✅ Implemented | | vkDestroyBuffer | ✅ Implemented |
| vkDestroyBufferView | ✅ Implemented | | vkDestroyBufferView | ✅ Implemented |
| vkDestroyCommandPool | ✅ Implemented | | vkDestroyCommandPool | ✅ Implemented |
@@ -194,8 +194,8 @@ Assume thou that functions lacking in this array are, for now, not intended to b
| vkGetPhysicalDeviceSurfaceSupportKHR | ✅ Implemented | | vkGetPhysicalDeviceSurfaceSupportKHR | ✅ Implemented |
| vkGetPhysicalDeviceWaylandPresentationSupportKHR | ✅ Implemented | | vkGetPhysicalDeviceWaylandPresentationSupportKHR | ✅ Implemented |
| vkGetPhysicalDeviceWin32PresentationSupportKHR | ⚙️ WIP | | vkGetPhysicalDeviceWin32PresentationSupportKHR | ⚙️ WIP |
| vkGetPhysicalDeviceXcbPresentationSupportKHR | ⚙️ WIP | | vkGetPhysicalDeviceXcbPresentationSupportKHR | ✅ Implemented |
| vkGetPhysicalDeviceXlibPresentationSupportKHR | ⚙️ WIP | | vkGetPhysicalDeviceXlibPresentationSupportKHR | ✅ Implemented |
| vkGetPipelineCacheData | ✅ Implemented | | vkGetPipelineCacheData | ✅ Implemented |
| vkGetQueryPoolResults | ✅ Implemented | | vkGetQueryPoolResults | ✅ Implemented |
| vkGetRenderAreaGranularity | ✅ Implemented | | vkGetRenderAreaGranularity | ✅ Implemented |
+2
View File
@@ -31,6 +31,8 @@ pub const extensions = [_]vk.ExtensionProperties{
castExtension(vk.extensions.khr_get_physical_device_properties_2), castExtension(vk.extensions.khr_get_physical_device_properties_2),
castExtension(vk.extensions.khr_surface), castExtension(vk.extensions.khr_surface),
castExtension(vk.extensions.khr_wayland_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 { pub fn create(allocator: std.mem.Allocator, infos: *const vk.InstanceCreateInfo) VkError!*Interface {
+2
View File
@@ -32,6 +32,8 @@ pub const extensions = [_]vk.ExtensionProperties{
castExtension(vk.extensions.khr_get_physical_device_properties_2), castExtension(vk.extensions.khr_get_physical_device_properties_2),
castExtension(vk.extensions.khr_surface), castExtension(vk.extensions.khr_surface),
castExtension(vk.extensions.khr_wayland_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 { pub fn create(allocator: std.mem.Allocator, infos: *const vk.InstanceCreateInfo) VkError!*Interface {
+2 -2
View File
@@ -29,8 +29,8 @@ pub const extensions = [_]vk.ExtensionProperties{
castExtension(vk.extensions.khr_get_physical_device_properties_2), castExtension(vk.extensions.khr_get_physical_device_properties_2),
castExtension(vk.extensions.khr_surface), castExtension(vk.extensions.khr_surface),
castExtension(vk.extensions.khr_wayland_surface), castExtension(vk.extensions.khr_wayland_surface),
//castExtension(vk.extensions.khr_xlib_surface), castExtension(vk.extensions.khr_xlib_surface),
//castExtension(vk.extensions.khr_xcb_surface), castExtension(vk.extensions.khr_xcb_surface),
}; };
pub fn create(allocator: std.mem.Allocator, infos: *const vk.InstanceCreateInfo) VkError!*Interface { pub fn create(allocator: std.mem.Allocator, infos: *const vk.InstanceCreateInfo) VkError!*Interface {
+87
View File
@@ -52,7 +52,14 @@ const has_wayland = switch (builtin.os.tag) {
else => false, 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 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 { inline fn entryPointBeginLogTrace(comptime scope: @EnumLiteral()) void {
std.log.scoped(scope).debug("Calling {s}...", .{@tagName(scope)}); 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(.{ const instance_pfn_map = std.StaticStringMap(vk.PfnVoidFunction).initComptime(.{
functionMapEntryPoint("vkCreateWaylandSurfaceKHR"), functionMapEntryPoint("vkCreateWaylandSurfaceKHR"),
functionMapEntryPoint("vkCreateXlibSurfaceKHR"),
functionMapEntryPoint("vkCreateXcbSurfaceKHR"),
functionMapEntryPoint("vkDestroyInstance"), functionMapEntryPoint("vkDestroyInstance"),
functionMapEntryPoint("vkDestroySurfaceKHR"), functionMapEntryPoint("vkDestroySurfaceKHR"),
functionMapEntryPoint("vkEnumeratePhysicalDeviceGroups"), functionMapEntryPoint("vkEnumeratePhysicalDeviceGroups"),
@@ -131,6 +140,8 @@ const physical_device_pfn_map = std.StaticStringMap(vk.PfnVoidFunction).initComp
functionMapEntryPoint("vkGetPhysicalDeviceSurfacePresentModesKHR"), functionMapEntryPoint("vkGetPhysicalDeviceSurfacePresentModesKHR"),
functionMapEntryPoint("vkGetPhysicalDeviceSurfaceSupportKHR"), functionMapEntryPoint("vkGetPhysicalDeviceSurfaceSupportKHR"),
functionMapEntryPoint("vkGetPhysicalDeviceWaylandPresentationSupportKHR"), functionMapEntryPoint("vkGetPhysicalDeviceWaylandPresentationSupportKHR"),
functionMapEntryPoint("vkGetPhysicalDeviceXlibPresentationSupportKHR"),
functionMapEntryPoint("vkGetPhysicalDeviceXcbPresentationSupportKHR"),
}); });
const device_pfn_map = block: { 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 { pub export fn apeDestroySurfaceKHR(p_instance: vk.Instance, p_surface: vk.SurfaceKHR, callbacks: ?*const vk.AllocationCallbacks) callconv(vk.vulkan_call_conv) void {
entryPointBeginLogTrace(.vkDestroySurfaceKHR); entryPointBeginLogTrace(.vkDestroySurfaceKHR);
defer entryPointEndLogTrace(); 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 { 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); entryPointBeginLogTrace(.vkGetSwapchainImagesKHR);
defer entryPointEndLogTrace(); defer entryPointEndLogTrace();
+5
View File
@@ -31,6 +31,7 @@ pub const VTable = struct {
getCapabilities: *const fn (*const Self, *vk.SurfaceCapabilitiesKHR) VkError!void, getCapabilities: *const fn (*const Self, *vk.SurfaceCapabilitiesKHR) VkError!void,
attachImage: *const fn (*Self, std.mem.Allocator, *PresentImage) VkError!void, attachImage: *const fn (*Self, std.mem.Allocator, *PresentImage) VkError!void,
detachImage: *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, 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); 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 { pub inline fn presentImage(self: *Self, allocator: std.mem.Allocator, image: *PresentImage) VkError!void {
try self.vtable.presentImage(self, allocator, image); try self.vtable.presentImage(self, allocator, image);
} }
+31 -18
View File
@@ -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 { 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 (self.surface) |surface|
if (image.state == .available) { try surface.waitForImage(timeout);
image.state = .drawing;
index.* = @intCast(i); if (try self.acquireAvailableImage(semaphore, fence, index))
if (semaphore) |s| return;
try s.signal();
if (fence) |f|
try f.signal();
return;
}
}
return if (timeout > 0) VkError.Timeout else VkError.NotReady; 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 { pub fn present(self: *Self, index: usize) VkError!void {
const allocator = self.owner.host_allocator.allocator(); const allocator = self.owner.host_allocator.allocator();
@@ -114,19 +127,19 @@ pub fn detachSurface(self: *Self) VkError!void {
if (self.surface) |surface| { if (self.surface) |surface| {
surface.swapchain = null; surface.swapchain = null;
for (self.images) |*image| { for (self.images) |*image|
if (image.state == .available) try surface.detachImage(allocator, image);
try surface.detachImage(allocator, image);
}
} }
self.surface = null; self.surface = null;
} }
pub fn destroy(self: *Self, allocator: std.mem.Allocator) void { pub fn destroy(self: *Self, allocator: std.mem.Allocator) void {
if (self.surface) |surface|
surface.swapchain = null;
for (self.images) |*image| { 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"); surface.detachImage(allocator, image) catch @panic("Caught an error while handling an error");
}
image.deinit(allocator); image.deinit(allocator);
} }
allocator.free(self.images); allocator.free(self.images);
+283 -54
View File
@@ -2,6 +2,7 @@ const std = @import("std");
const vk = @import("vulkan"); const vk = @import("vulkan");
const lib = @import("../lib.zig"); const lib = @import("../lib.zig");
const gbm = @import("clients/gbm.zig");
const wayland = @import("clients/wayland.zig"); const wayland = @import("clients/wayland.zig");
const PresentImage = @import("PresentImage.zig"); const PresentImage = @import("PresentImage.zig");
@@ -13,19 +14,31 @@ pub const Interface = @import("SurfaceKHR.zig");
const WaylandImage = struct { const WaylandImage = struct {
buffer: *wayland.wl_buffer, buffer: *wayland.wl_buffer,
data: []align(std.heap.page_size_min) u8, dma_buf: gbm.Buffer,
staging: []u8,
image: *PresentImage,
width: u32, width: u32,
height: 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 { fn wlRegistryHandleGlobal(data: ?*anyopaque, registry: ?*wayland.wl_registry, name: u32, interface: [*c]const u8, version: u32) callconv(.c) void {
const pshm: *?*wayland.wl_shm = @ptrCast(@alignCast(data orelse return)); const self: *Self = @ptrCast(@alignCast(data orelse return));
if (std.mem.eql(u8, std.mem.span(interface), "wl_shm")) { if (!std.mem.eql(u8, std.mem.span(interface), "zwp_linux_dmabuf_v1") or version < 2 or self.dmabuf != null)
if (wayland.wl_registry_bind(registry orelse return, name, wayland.wl_shm_interface, 1)) |shm| { return;
pshm.* = @ptrCast(@alignCast(shm));
} 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 {} 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, .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, interface: Interface,
display: *wayland.wl_display, display: *wayland.wl_display,
surface: *wayland.wl_surface, 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), image_map: std.AutoHashMapUnmanaged(*PresentImage, *WaylandImage),
pub fn create(instance: *Instance, allocator: std.mem.Allocator, info: *const vk.WaylandSurfaceCreateInfoKHR) VkError!*Interface { 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); errdefer allocator.destroy(self);
try wayland.load(); try wayland.load();
errdefer wayland.unload();
try gbm.load();
errdefer gbm.unload();
var interface = try Interface.init(instance, allocator); var interface = try Interface.init(instance, allocator);
interface.vtable = &.{ interface.vtable = &.{
.destroy = destroy, .destroy = destroy,
.getCapabilities = getCapabilities, .getCapabilities = getCapabilities,
.attachImage = attachImage, .attachImage = attachImage,
.detachImage = detachImage, .detachImage = detachImage,
.waitForImage = waitForImage,
.presentImage = presentImage, .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.* = .{ self.* = .{
.interface = interface, .interface = interface,
.display = info.display, .display = info.display,
.surface = info.surface, .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, .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; const registry = wayland.wlDisplayGetRegistry(self.display) orelse return VkError.InitializationFailed;
_ = wayland.wl_registry_add_listener(registry, &wl_registry_listener, @ptrCast(&self.shm)); defer wayland.wlRegistryDestroy(registry);
_ = wayland.wl_display_dispatch(@ptrCast(self.display)); wayland.wlProxyAssignQueue(registry, self.event_queue);
if (self.shm == null) return VkError.Unknown;
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; return &self.interface;
} }
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void { pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); 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); 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); allocator.destroy(self);
gbm.unload();
wayland.unload(); wayland.unload();
} }
pub fn getCapabilities(interface: *const Interface, capabilities: *vk.SurfaceCapabilitiesKHR) VkError!void { pub fn getCapabilities(interface: *const Interface, capabilities: *vk.SurfaceCapabilitiesKHR) VkError!void {
// No-op
_ = interface; _ = 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 { 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)) if (self.image_map.contains(image))
return; return;
const width: u32 = image.image.extent.width; const width = image.image.extent.width;
const height: u32 = image.image.extent.height; 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 row_size = std.math.mul(usize, width, 4) catch return VkError.OutOfHostMemory;
const size: usize = @as(usize, stride) * @as(usize, height); const staging_size = std.math.mul(usize, row_size, height) catch return VkError.OutOfHostMemory;
const wl_image = allocator.create(WaylandImage) catch return VkError.OutOfHostMemory; const wl_image = allocator.create(WaylandImage) catch return VkError.OutOfHostMemory;
errdefer allocator.destroy(wl_image); 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); defer _ = std.c.close(fd);
const data = std.posix.mmap(null, size, .{ .READ = true, .WRITE = true }, .{ .TYPE = .SHARED }, fd, 0) catch return VkError.OutOfHostMemory; const dmabuf = self.dmabuf orelse return VkError.InitializationFailed;
errdefer std.posix.munmap(data); const params = wayland.zwpLinuxDmabufV1CreateParams(dmabuf) orelse return VkError.OutOfHostMemory;
defer wayland.zwpLinuxBufferParamsV1Destroy(params);
const shm = self.shm orelse return VkError.Unknown; var creation_result: BufferCreationResult = .{};
const pool = wayland.wl_shm_create_pool(shm, fd, @intCast(size)) orelse return VkError.Unknown; if (wayland.zwpLinuxBufferParamsV1AddListener(params, &linux_buffer_params_listener, &creation_result) != 0)
defer wayland.wl_shm_pool_destroy(pool); 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; wayland.zwpLinuxBufferParamsV1Add(params, fd, 0, 0, dma_buf.stride, protocol_modifier);
errdefer wayland.wl_buffer_destroy(buffer); 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.* = .{ wl_image.* = .{
.buffer = buffer, .buffer = buffer,
.data = data, .dma_buf = dma_buf,
.staging = staging,
.image = image,
.width = width, .width = width,
.height = height, .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; 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 { pub fn detachImage(interface: *Interface, allocator: std.mem.Allocator, image: *PresentImage) VkError!void {
const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
const entry = self.image_map.fetchRemove(image) orelse return; const entry = self.image_map.fetchRemove(image) orelse return;
const wl_image = entry.value; destroyWaylandImage(allocator, entry.value);
wayland.wl_buffer_destroy(wl_image.buffer);
std.posix.munmap(wl_image.data);
allocator.destroy(wl_image);
} }
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)); 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; 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 }, .aspect_mask = .{ .color_bit = true },
.mip_level = 0, .mip_level = 0,
.base_array_layer = 0, .base_array_layer = 0,
.layer_count = 1, .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)); const mapping = try wl_image.dma_buf.mapWrite();
wayland.wl_surface_commit(@ptrCast(self.surface)); 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 { fn hasAvailableImage(self: *const Self) bool {
const name = "ape_vk_wayland_surface"; var image_iterator = self.image_map.keyIterator();
while (image_iterator.next()) |image| {
const fd = std.posix.memfd_create(name, std.posix.FD_CLOEXEC) catch return VkError.Unknown; if (image.*.state == .available)
errdefer std.c.close(fd); return true;
}
_ = std.c.ftruncate(fd, @intCast(size)); return false;
}
return fd;
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);
} }
+194
View File
@@ -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);
}
+194
View File
@@ -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);
}
+209
View File
@@ -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);
}
+217 -73
View File
@@ -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 std = @import("std");
const vk = @import("vulkan"); const vk = @import("vulkan");
@@ -7,34 +7,52 @@ const lib = @import("../../lib.zig");
const VkError = lib.VkError; const VkError = lib.VkError;
pub const wl_registry_listener = extern struct { 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: ?*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, wl_registry: ?*wl_registry, name: 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_buffer_listener = extern struct {
pub const wl_display_get_registry_opcode: c_int = 1; release: ?*const fn (data: ?*anyopaque, buffer: ?*wl_buffer) callconv(.c) void = null,
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 zwp_linux_dmabuf_v1_listener = extern struct {
pub const wl_shm_pool_create_buffer_opcode: c_int = 0; format: ?*const fn (data: ?*anyopaque, dmabuf: ?*zwp_linux_dmabuf_v1, format: u32) callconv(.c) void = null,
pub const wl_shm_pool_destroy_opcode: c_int = 1; modifier: ?*const fn (data: ?*anyopaque, dmabuf: ?*zwp_linux_dmabuf_v1, format: u32, modifier_hi: u32, modifier_lo: u32) callconv(.c) void = null,
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 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_buffer = opaque {};
pub const wl_callback = opaque {}; pub const wl_event_queue = opaque {};
pub const wl_registry = opaque {}; pub const wl_registry = opaque {};
pub const wl_shm = opaque {};
pub const wl_shm_pool = opaque {};
pub const wl_proxy = 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_display = vk.wl_display;
pub const wl_surface = vk.wl_surface; pub const wl_surface = vk.wl_surface;
pub const wl_message = extern struct { pub const wl_message = extern struct {
name: ?[*:0]const u8 = null, name: ?[*:0]const u8 = null,
signature: [*c]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 { pub const wl_interface = extern struct {
@@ -46,8 +64,73 @@ pub const wl_interface = extern struct {
events: ?[*]const wl_message = null, 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 = &params_requests,
.event_count = params_events.len,
.events = &params_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. // 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. // 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; 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. // 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. // 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; 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. // 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; 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. // SAFETY: load resolves every interface pointer before a surface can be created.
pub var wl_buffer_interface: *wl_interface = undefined; pub var wl_buffer_interface: *wl_interface = undefined;
// SAFETY: load resolves every interface pointer before a surface can be created. // SAFETY: load resolves every interface pointer before a surface can be created.
pub var wl_registry_interface: *wl_interface = undefined; 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. // SAFETY: load initializes the module before it can be closed or queried.
pub var module: std.DynLib = undefined; var module: std.DynLib = undefined;
var ref_count = std.atomic.Value(usize).init(0);
pub var ref_count = std.atomic.Value(usize).init(0); var load_mutex: lib.SpinMutex = .{};
pub fn load() VkError!void { 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; 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 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_display_cancel_read = module.lookup(@TypeOf(wl_display_cancel_read), "wl_display_cancel_read") orelse return VkError.InitializationFailed;
wl_proxy_marshal_flags = module.lookup(@TypeOf(wl_proxy_marshal_flags), "wl_proxy_marshal_flags") orelse return VkError.Unknown; wl_display_create_queue = module.lookup(@TypeOf(wl_display_create_queue), "wl_display_create_queue") orelse return VkError.InitializationFailed;
wl_proxy_get_version = module.lookup(@TypeOf(wl_proxy_get_version), "wl_proxy_get_version") orelse return VkError.Unknown; wl_display_dispatch_queue = module.lookup(@TypeOf(wl_display_dispatch_queue), "wl_display_dispatch_queue") orelse return VkError.InitializationFailed;
wl_proxy_add_listener = module.lookup(@TypeOf(wl_proxy_add_listener), "wl_proxy_add_listener") orelse return VkError.Unknown; wl_display_dispatch_queue_pending = module.lookup(@TypeOf(wl_display_dispatch_queue_pending), "wl_display_dispatch_queue_pending") orelse return VkError.InitializationFailed;
wl_display_flush = module.lookup(@TypeOf(wl_display_flush), "wl_display_flush") orelse return VkError.Unknown; 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_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.Unknown; wl_registry_interface = module.lookup(*wl_interface, "wl_registry_interface") orelse return VkError.InitializationFailed;
wl_shm_interface = module.lookup(*wl_interface, "wl_shm_interface") orelse return VkError.Unknown; wl_buffer_interface_storage = wl_buffer_interface.*;
wl_shm_pool_interface = module.lookup(*wl_interface, "wl_shm_pool_interface") orelse return VkError.Unknown;
_ = ref_count.fetchAdd(1, .monotonic); _ = 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 { pub fn unload() void {
load_mutex.lock();
defer load_mutex.unlock();
if (ref_count.fetchSub(1, .release) == 1) { if (ref_count.fetchSub(1, .release) == 1) {
module.close(); 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( return wl_proxy_marshal_flags(
@ptrCast(@alignCast(registry)), @ptrCast(@alignCast(registry)),
wl_registry_bind_opcode, 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( return @ptrCast(@alignCast(wl_proxy_marshal_flags(
@ptrCast(@alignCast(display)), @ptrCast(@alignCast(display)),
wl_display_get_registry_opcode, 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); 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 { pub fn wlProxyAssignQueue(proxy: *anyopaque, queue: *wl_event_queue) void {
return @ptrCast(@alignCast(wl_proxy_marshal_flags( wl_proxy_set_queue(@ptrCast(@alignCast(proxy)), queue);
@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 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( _ = wl_proxy_marshal_flags(
@ptrCast(@alignCast(shm_pool)), @ptrCast(@alignCast(dmabuf)),
wl_shm_pool_destroy_opcode, zwp_linux_dmabuf_v1_destroy_opcode,
null, null,
wl_proxy_get_version(@ptrCast(@alignCast(shm_pool))), wl_proxy_get_version(@ptrCast(@alignCast(dmabuf))),
@bitCast(@as(c_int, @as(c_int, 1) << @intCast(@as(c_int, 0)))), 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( return @ptrCast(@alignCast(wl_proxy_marshal_flags(
@ptrCast(@alignCast(shm_pool)), @ptrCast(@alignCast(dmabuf)),
wl_shm_pool_create_buffer_opcode, zwp_linux_dmabuf_v1_create_params_opcode,
wl_buffer_interface, &zwp_linux_buffer_params_v1_interface,
wl_proxy_get_version(@ptrCast(@alignCast(shm_pool))), wl_proxy_get_version(@ptrCast(@alignCast(dmabuf))),
0, 0,
@as(?*anyopaque, null), @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( _ = wl_proxy_marshal_flags(
@ptrCast(@alignCast(buffer)), @ptrCast(@alignCast(buffer)),
wl_buffer_destroy_opcode, wl_buffer_destroy_opcode,
null, null,
wl_proxy_get_version(@ptrCast(@alignCast(buffer))), 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( _ = wl_proxy_marshal_flags(
@ptrCast(@alignCast(surface)), @ptrCast(@alignCast(surface)),
wl_surface_attach_opcode, 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( _ = wl_proxy_marshal_flags(
@ptrCast(@alignCast(surface)), @ptrCast(@alignCast(surface)),
wl_surface_damage_opcode, 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( _ = wl_proxy_marshal_flags(
@ptrCast(@alignCast(surface)), @ptrCast(@alignCast(surface)),
wl_surface_commit_opcode, wl_surface_commit_opcode,
+342
View File
@@ -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);
}
+222
View File
@@ -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);
}
+35
View File
@@ -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]);
}
}
}