[WSI] implementing xcb and xlib surfaces
This commit is contained in:
@@ -129,8 +129,8 @@ Assume thou that functions lacking in this array are, for now, not intended to b
|
||||
| vkCreateSwapchainKHR | ✅ Implemented |
|
||||
| vkCreateWaylandSurfaceKHR | ✅ Implemented |
|
||||
| vkCreateWin32SurfaceKHR | ⚙️ WIP |
|
||||
| vkCreateXcbSurfaceKHR | ⚙️ WIP |
|
||||
| vkCreateXlibSurfaceKHR | ⚙️ WIP |
|
||||
| vkCreateXcbSurfaceKHR | ✅ Implemented |
|
||||
| vkCreateXlibSurfaceKHR | ✅ Implemented |
|
||||
| vkDestroyBuffer | ✅ Implemented |
|
||||
| vkDestroyBufferView | ✅ Implemented |
|
||||
| vkDestroyCommandPool | ✅ Implemented |
|
||||
@@ -194,8 +194,8 @@ Assume thou that functions lacking in this array are, for now, not intended to b
|
||||
| vkGetPhysicalDeviceSurfaceSupportKHR | ✅ Implemented |
|
||||
| vkGetPhysicalDeviceWaylandPresentationSupportKHR | ✅ Implemented |
|
||||
| vkGetPhysicalDeviceWin32PresentationSupportKHR | ⚙️ WIP |
|
||||
| vkGetPhysicalDeviceXcbPresentationSupportKHR | ⚙️ WIP |
|
||||
| vkGetPhysicalDeviceXlibPresentationSupportKHR | ⚙️ WIP |
|
||||
| vkGetPhysicalDeviceXcbPresentationSupportKHR | ✅ Implemented |
|
||||
| vkGetPhysicalDeviceXlibPresentationSupportKHR | ✅ Implemented |
|
||||
| vkGetPipelineCacheData | ✅ Implemented |
|
||||
| vkGetQueryPoolResults | ✅ Implemented |
|
||||
| vkGetRenderAreaGranularity | ✅ Implemented |
|
||||
|
||||
@@ -31,6 +31,8 @@ pub const extensions = [_]vk.ExtensionProperties{
|
||||
castExtension(vk.extensions.khr_get_physical_device_properties_2),
|
||||
castExtension(vk.extensions.khr_surface),
|
||||
castExtension(vk.extensions.khr_wayland_surface),
|
||||
castExtension(vk.extensions.khr_xlib_surface),
|
||||
castExtension(vk.extensions.khr_xcb_surface),
|
||||
};
|
||||
|
||||
pub fn create(allocator: std.mem.Allocator, infos: *const vk.InstanceCreateInfo) VkError!*Interface {
|
||||
|
||||
@@ -32,6 +32,8 @@ pub const extensions = [_]vk.ExtensionProperties{
|
||||
castExtension(vk.extensions.khr_get_physical_device_properties_2),
|
||||
castExtension(vk.extensions.khr_surface),
|
||||
castExtension(vk.extensions.khr_wayland_surface),
|
||||
castExtension(vk.extensions.khr_xlib_surface),
|
||||
castExtension(vk.extensions.khr_xcb_surface),
|
||||
};
|
||||
|
||||
pub fn create(allocator: std.mem.Allocator, infos: *const vk.InstanceCreateInfo) VkError!*Interface {
|
||||
|
||||
@@ -29,8 +29,8 @@ pub const extensions = [_]vk.ExtensionProperties{
|
||||
castExtension(vk.extensions.khr_get_physical_device_properties_2),
|
||||
castExtension(vk.extensions.khr_surface),
|
||||
castExtension(vk.extensions.khr_wayland_surface),
|
||||
//castExtension(vk.extensions.khr_xlib_surface),
|
||||
//castExtension(vk.extensions.khr_xcb_surface),
|
||||
castExtension(vk.extensions.khr_xlib_surface),
|
||||
castExtension(vk.extensions.khr_xcb_surface),
|
||||
};
|
||||
|
||||
pub fn create(allocator: std.mem.Allocator, infos: *const vk.InstanceCreateInfo) VkError!*Interface {
|
||||
|
||||
@@ -52,7 +52,14 @@ const has_wayland = switch (builtin.os.tag) {
|
||||
else => false,
|
||||
};
|
||||
|
||||
const has_x11 = switch (builtin.os.tag) {
|
||||
.linux, .freebsd, .netbsd, .openbsd, .dragonfly => true,
|
||||
else => false,
|
||||
};
|
||||
|
||||
pub const WaylandSurfaceKHR = if (has_wayland) @import("wsi/WaylandSurfaceKHR.zig") else null;
|
||||
pub const XlibSurfaceKHR = if (has_x11) @import("wsi/XlibSurfaceKHR.zig") else null;
|
||||
pub const XcbSurfaceKHR = if (has_x11) @import("wsi/XcbSurfaceKHR.zig") else null;
|
||||
|
||||
inline fn entryPointBeginLogTrace(comptime scope: @EnumLiteral()) void {
|
||||
std.log.scoped(scope).debug("Calling {s}...", .{@tagName(scope)});
|
||||
@@ -100,6 +107,8 @@ const global_pfn_map = std.StaticStringMap(vk.PfnVoidFunction).initComptime(.{
|
||||
|
||||
const instance_pfn_map = std.StaticStringMap(vk.PfnVoidFunction).initComptime(.{
|
||||
functionMapEntryPoint("vkCreateWaylandSurfaceKHR"),
|
||||
functionMapEntryPoint("vkCreateXlibSurfaceKHR"),
|
||||
functionMapEntryPoint("vkCreateXcbSurfaceKHR"),
|
||||
functionMapEntryPoint("vkDestroyInstance"),
|
||||
functionMapEntryPoint("vkDestroySurfaceKHR"),
|
||||
functionMapEntryPoint("vkEnumeratePhysicalDeviceGroups"),
|
||||
@@ -131,6 +140,8 @@ const physical_device_pfn_map = std.StaticStringMap(vk.PfnVoidFunction).initComp
|
||||
functionMapEntryPoint("vkGetPhysicalDeviceSurfacePresentModesKHR"),
|
||||
functionMapEntryPoint("vkGetPhysicalDeviceSurfaceSupportKHR"),
|
||||
functionMapEntryPoint("vkGetPhysicalDeviceWaylandPresentationSupportKHR"),
|
||||
functionMapEntryPoint("vkGetPhysicalDeviceXlibPresentationSupportKHR"),
|
||||
functionMapEntryPoint("vkGetPhysicalDeviceXcbPresentationSupportKHR"),
|
||||
});
|
||||
|
||||
const device_pfn_map = block: {
|
||||
@@ -2406,6 +2417,42 @@ pub export fn apeCreateWaylandSurfaceKHR(p_instance: vk.Instance, info: *const v
|
||||
}
|
||||
}
|
||||
|
||||
pub export fn apeCreateXlibSurfaceKHR(p_instance: vk.Instance, info: *const vk.XlibSurfaceCreateInfoKHR, callbacks: ?*const vk.AllocationCallbacks, p_surface: *vk.SurfaceKHR) callconv(vk.vulkan_call_conv) vk.Result {
|
||||
if (comptime has_x11) {
|
||||
entryPointBeginLogTrace(.vkCreateXlibSurfaceKHR);
|
||||
defer entryPointEndLogTrace();
|
||||
|
||||
if (info.s_type != .xlib_surface_create_info_khr)
|
||||
return .error_validation_failed;
|
||||
|
||||
const allocator = VulkanAllocator.init(callbacks, .object).allocator();
|
||||
const instance = Dispatchable(Instance).fromHandleObject(p_instance) catch |err| return toVkResult(err);
|
||||
const surface = XlibSurfaceKHR.create(instance, allocator, info) catch |err| return toVkResult(err);
|
||||
p_surface.* = wrapNonDispatchable(SurfaceKHR, allocator, surface, vk.SurfaceKHR) catch |err| return toVkResult(err);
|
||||
return .success;
|
||||
} else {
|
||||
return .error_unknown;
|
||||
}
|
||||
}
|
||||
|
||||
pub export fn apeCreateXcbSurfaceKHR(p_instance: vk.Instance, info: *const vk.XcbSurfaceCreateInfoKHR, callbacks: ?*const vk.AllocationCallbacks, p_surface: *vk.SurfaceKHR) callconv(vk.vulkan_call_conv) vk.Result {
|
||||
if (comptime has_x11) {
|
||||
entryPointBeginLogTrace(.vkCreateXcbSurfaceKHR);
|
||||
defer entryPointEndLogTrace();
|
||||
|
||||
if (info.s_type != .xcb_surface_create_info_khr)
|
||||
return .error_validation_failed;
|
||||
|
||||
const allocator = VulkanAllocator.init(callbacks, .object).allocator();
|
||||
const instance = Dispatchable(Instance).fromHandleObject(p_instance) catch |err| return toVkResult(err);
|
||||
const surface = XcbSurfaceKHR.create(instance, allocator, info) catch |err| return toVkResult(err);
|
||||
p_surface.* = wrapNonDispatchable(SurfaceKHR, allocator, surface, vk.SurfaceKHR) catch |err| return toVkResult(err);
|
||||
return .success;
|
||||
} else {
|
||||
return .error_unknown;
|
||||
}
|
||||
}
|
||||
|
||||
pub export fn apeDestroySurfaceKHR(p_instance: vk.Instance, p_surface: vk.SurfaceKHR, callbacks: ?*const vk.AllocationCallbacks) callconv(vk.vulkan_call_conv) void {
|
||||
entryPointBeginLogTrace(.vkDestroySurfaceKHR);
|
||||
defer entryPointEndLogTrace();
|
||||
@@ -2481,6 +2528,46 @@ pub export fn apeGetPhysicalDeviceWaylandPresentationSupportKHR(p_physical_devic
|
||||
}
|
||||
}
|
||||
|
||||
pub export fn apeGetPhysicalDeviceXlibPresentationSupportKHR(p_physical_device: vk.PhysicalDevice, queue_family_index: u32, display: *vk.Display, visual_id: vk.VisualID) callconv(vk.vulkan_call_conv) vk.Bool32 {
|
||||
if (comptime has_x11) {
|
||||
entryPointBeginLogTrace(.vkGetPhysicalDeviceXlibPresentationSupportKHR);
|
||||
defer entryPointEndLogTrace();
|
||||
|
||||
const physical_device = Dispatchable(PhysicalDevice).fromHandleObject(p_physical_device) catch |err| {
|
||||
errorLogger(err);
|
||||
return .false;
|
||||
};
|
||||
const queue_index = std.math.cast(usize, queue_family_index) orelse return .false;
|
||||
if (queue_index >= physical_device.queue_family_props.items.len or
|
||||
physical_device.queue_family_props.items[queue_index].queue_count == 0)
|
||||
return .false;
|
||||
|
||||
return if (XlibSurfaceKHR.supportsPresentation(display, visual_id)) .true else .false;
|
||||
} else {
|
||||
return .false;
|
||||
}
|
||||
}
|
||||
|
||||
pub export fn apeGetPhysicalDeviceXcbPresentationSupportKHR(p_physical_device: vk.PhysicalDevice, queue_family_index: u32, connection: *vk.xcb_connection_t, visual_id: vk.xcb_visualid_t) callconv(vk.vulkan_call_conv) vk.Bool32 {
|
||||
if (comptime has_x11) {
|
||||
entryPointBeginLogTrace(.vkGetPhysicalDeviceXcbPresentationSupportKHR);
|
||||
defer entryPointEndLogTrace();
|
||||
|
||||
const physical_device = Dispatchable(PhysicalDevice).fromHandleObject(p_physical_device) catch |err| {
|
||||
errorLogger(err);
|
||||
return .false;
|
||||
};
|
||||
const queue_index = std.math.cast(usize, queue_family_index) orelse return .false;
|
||||
if (queue_index >= physical_device.queue_family_props.items.len or
|
||||
physical_device.queue_family_props.items[queue_index].queue_count == 0)
|
||||
return .false;
|
||||
|
||||
return if (XcbSurfaceKHR.supportsPresentation(connection, visual_id)) .true else .false;
|
||||
} else {
|
||||
return .false;
|
||||
}
|
||||
}
|
||||
|
||||
pub export fn apeGetSwapchainImagesKHR(p_device: vk.Device, p_swapchain: vk.SwapchainKHR, count: *u32, p_images: ?[*]vk.Image) callconv(vk.vulkan_call_conv) vk.Result {
|
||||
entryPointBeginLogTrace(.vkGetSwapchainImagesKHR);
|
||||
defer entryPointEndLogTrace();
|
||||
|
||||
@@ -31,6 +31,7 @@ pub const VTable = struct {
|
||||
getCapabilities: *const fn (*const Self, *vk.SurfaceCapabilitiesKHR) VkError!void,
|
||||
attachImage: *const fn (*Self, std.mem.Allocator, *PresentImage) VkError!void,
|
||||
detachImage: *const fn (*Self, std.mem.Allocator, *PresentImage) VkError!void,
|
||||
waitForImage: *const fn (*Self, u64) VkError!void,
|
||||
presentImage: *const fn (*Self, std.mem.Allocator, *PresentImage) VkError!void,
|
||||
};
|
||||
|
||||
@@ -81,6 +82,10 @@ pub inline fn detachImage(self: *Self, allocator: std.mem.Allocator, image: *Pre
|
||||
try self.vtable.detachImage(self, allocator, image);
|
||||
}
|
||||
|
||||
pub inline fn waitForImage(self: *Self, timeout: u64) VkError!void {
|
||||
try self.vtable.waitForImage(self, timeout);
|
||||
}
|
||||
|
||||
pub inline fn presentImage(self: *Self, allocator: std.mem.Allocator, image: *PresentImage) VkError!void {
|
||||
try self.vtable.presentImage(self, allocator, image);
|
||||
}
|
||||
|
||||
@@ -82,23 +82,36 @@ pub fn create(device: *Device, allocator: std.mem.Allocator, surface: *SurfaceKH
|
||||
}
|
||||
|
||||
pub fn getNextImage(self: *const Self, timeout: u64, semaphore: ?*BinarySemaphore, fence: ?*Fence, index: *u32) VkError!void {
|
||||
// TODO: handle timeout correctly
|
||||
// TODO: handle finite timeouts correctly
|
||||
if (try self.acquireAvailableImage(semaphore, fence, index))
|
||||
return;
|
||||
|
||||
for (self.images, 0..) |*image, i| {
|
||||
if (image.state == .available) {
|
||||
image.state = .drawing;
|
||||
index.* = @intCast(i);
|
||||
if (semaphore) |s|
|
||||
try s.signal();
|
||||
if (fence) |f|
|
||||
try f.signal();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (self.surface) |surface|
|
||||
try surface.waitForImage(timeout);
|
||||
|
||||
if (try self.acquireAvailableImage(semaphore, fence, index))
|
||||
return;
|
||||
|
||||
return if (timeout > 0) VkError.Timeout else VkError.NotReady;
|
||||
}
|
||||
|
||||
fn acquireAvailableImage(self: *const Self, semaphore: ?*BinarySemaphore, fence: ?*Fence, index: *u32) VkError!bool {
|
||||
for (self.images, 0..) |*image, i| {
|
||||
if (image.state != .available)
|
||||
continue;
|
||||
|
||||
image.state = .drawing;
|
||||
index.* = @intCast(i);
|
||||
if (semaphore) |s|
|
||||
try s.signal();
|
||||
if (fence) |f|
|
||||
try f.signal();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn present(self: *Self, index: usize) VkError!void {
|
||||
const allocator = self.owner.host_allocator.allocator();
|
||||
|
||||
@@ -114,19 +127,19 @@ pub fn detachSurface(self: *Self) VkError!void {
|
||||
|
||||
if (self.surface) |surface| {
|
||||
surface.swapchain = null;
|
||||
for (self.images) |*image| {
|
||||
if (image.state == .available)
|
||||
try surface.detachImage(allocator, image);
|
||||
}
|
||||
for (self.images) |*image|
|
||||
try surface.detachImage(allocator, image);
|
||||
}
|
||||
self.surface = null;
|
||||
}
|
||||
|
||||
pub fn destroy(self: *Self, allocator: std.mem.Allocator) void {
|
||||
if (self.surface) |surface|
|
||||
surface.swapchain = null;
|
||||
|
||||
for (self.images) |*image| {
|
||||
if (self.surface) |surface| {
|
||||
if (self.surface) |surface|
|
||||
surface.detachImage(allocator, image) catch @panic("Caught an error while handling an error");
|
||||
}
|
||||
image.deinit(allocator);
|
||||
}
|
||||
allocator.free(self.images);
|
||||
|
||||
@@ -2,6 +2,7 @@ const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
|
||||
const lib = @import("../lib.zig");
|
||||
const gbm = @import("clients/gbm.zig");
|
||||
const wayland = @import("clients/wayland.zig");
|
||||
const PresentImage = @import("PresentImage.zig");
|
||||
|
||||
@@ -13,19 +14,31 @@ pub const Interface = @import("SurfaceKHR.zig");
|
||||
|
||||
const WaylandImage = struct {
|
||||
buffer: *wayland.wl_buffer,
|
||||
data: []align(std.heap.page_size_min) u8,
|
||||
dma_buf: gbm.Buffer,
|
||||
staging: []u8,
|
||||
image: *PresentImage,
|
||||
width: u32,
|
||||
height: u32,
|
||||
stride: u32,
|
||||
row_size: usize,
|
||||
};
|
||||
|
||||
fn wlRegistryHandleGlobal(data: ?*anyopaque, registry: ?*wayland.wl_registry, name: u32, interface: [*c]const u8, _: u32) callconv(.c) void {
|
||||
const pshm: *?*wayland.wl_shm = @ptrCast(@alignCast(data orelse return));
|
||||
if (std.mem.eql(u8, std.mem.span(interface), "wl_shm")) {
|
||||
if (wayland.wl_registry_bind(registry orelse return, name, wayland.wl_shm_interface, 1)) |shm| {
|
||||
pshm.* = @ptrCast(@alignCast(shm));
|
||||
}
|
||||
}
|
||||
fn wlRegistryHandleGlobal(data: ?*anyopaque, registry: ?*wayland.wl_registry, name: u32, interface: [*c]const u8, version: u32) callconv(.c) void {
|
||||
const self: *Self = @ptrCast(@alignCast(data orelse return));
|
||||
if (!std.mem.eql(u8, std.mem.span(interface), "zwp_linux_dmabuf_v1") or version < 2 or self.dmabuf != null)
|
||||
return;
|
||||
|
||||
const bind_version = @min(version, @as(u32, wayland.zwp_linux_dmabuf_v1_interface.version));
|
||||
const proxy = wayland.wlRegistryBind(registry orelse return, name, &wayland.zwp_linux_dmabuf_v1_interface, bind_version) orelse {
|
||||
self.protocol_failed = true;
|
||||
return;
|
||||
};
|
||||
|
||||
const dmabuf: *wayland.zwp_linux_dmabuf_v1 = @ptrCast(@alignCast(proxy));
|
||||
wayland.wlProxyAssignQueue(dmabuf, self.event_queue);
|
||||
self.dmabuf = dmabuf;
|
||||
self.dmabuf_version = bind_version;
|
||||
if (wayland.zwpLinuxDmabufV1AddListener(dmabuf, &linux_dmabuf_listener, self) != 0)
|
||||
self.protocol_failed = true;
|
||||
}
|
||||
|
||||
fn wlRegistryHandleGlobalRemove(_: ?*anyopaque, _: ?*wayland.wl_registry, _: u32) callconv(.c) void {}
|
||||
@@ -35,10 +48,75 @@ const wl_registry_listener: wayland.wl_registry_listener = .{
|
||||
.global_remove = wlRegistryHandleGlobalRemove,
|
||||
};
|
||||
|
||||
fn linuxDmabufHandleFormat(data: ?*anyopaque, _: ?*wayland.zwp_linux_dmabuf_v1, format: u32) callconv(.c) void {
|
||||
const self: *Self = @ptrCast(@alignCast(data orelse return));
|
||||
if (format != gbm.drm_format_argb8888)
|
||||
return;
|
||||
|
||||
self.supports_argb8888 = true;
|
||||
if (self.dmabuf_version < 3)
|
||||
self.supports_implicit_modifier = true;
|
||||
}
|
||||
|
||||
fn linuxDmabufHandleModifier(data: ?*anyopaque, _: ?*wayland.zwp_linux_dmabuf_v1, format: u32, modifier_hi: u32, modifier_lo: u32) callconv(.c) void {
|
||||
const self: *Self = @ptrCast(@alignCast(data orelse return));
|
||||
if (format != gbm.drm_format_argb8888)
|
||||
return;
|
||||
|
||||
const modifier = (@as(u64, modifier_hi) << 32) | modifier_lo;
|
||||
switch (modifier) {
|
||||
gbm.drm_format_modifier_linear => self.supports_linear_modifier = true,
|
||||
gbm.drm_format_modifier_invalid => self.supports_implicit_modifier = true,
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
const linux_dmabuf_listener: wayland.zwp_linux_dmabuf_v1_listener = .{
|
||||
.format = linuxDmabufHandleFormat,
|
||||
.modifier = linuxDmabufHandleModifier,
|
||||
};
|
||||
|
||||
const BufferCreationResult = struct {
|
||||
buffer: ?*wayland.wl_buffer = null,
|
||||
failed: bool = false,
|
||||
};
|
||||
|
||||
fn linuxBufferParamsHandleCreated(data: ?*anyopaque, _: ?*wayland.zwp_linux_buffer_params_v1, buffer: ?*wayland.wl_buffer) callconv(.c) void {
|
||||
const result: *BufferCreationResult = @ptrCast(@alignCast(data orelse return));
|
||||
result.buffer = buffer;
|
||||
result.failed = buffer == null;
|
||||
}
|
||||
|
||||
fn linuxBufferParamsHandleFailed(data: ?*anyopaque, _: ?*wayland.zwp_linux_buffer_params_v1) callconv(.c) void {
|
||||
const result: *BufferCreationResult = @ptrCast(@alignCast(data orelse return));
|
||||
result.failed = true;
|
||||
}
|
||||
|
||||
const linux_buffer_params_listener: wayland.zwp_linux_buffer_params_v1_listener = .{
|
||||
.created = linuxBufferParamsHandleCreated,
|
||||
.failed = linuxBufferParamsHandleFailed,
|
||||
};
|
||||
|
||||
fn wlBufferHandleRelease(data: ?*anyopaque, _: ?*wayland.wl_buffer) callconv(.c) void {
|
||||
const wl_image: *WaylandImage = @ptrCast(@alignCast(data orelse return));
|
||||
wl_image.image.state = .available;
|
||||
}
|
||||
|
||||
const wl_buffer_listener: wayland.wl_buffer_listener = .{
|
||||
.release = wlBufferHandleRelease,
|
||||
};
|
||||
|
||||
interface: Interface,
|
||||
display: *wayland.wl_display,
|
||||
surface: *wayland.wl_surface,
|
||||
shm: ?*wayland.wl_shm,
|
||||
event_queue: *wayland.wl_event_queue,
|
||||
dmabuf: ?*wayland.zwp_linux_dmabuf_v1,
|
||||
dmabuf_version: u32,
|
||||
supports_argb8888: bool,
|
||||
supports_linear_modifier: bool,
|
||||
supports_implicit_modifier: bool,
|
||||
protocol_failed: bool,
|
||||
gbm_device: ?gbm.Device,
|
||||
image_map: std.AutoHashMapUnmanaged(*PresentImage, *WaylandImage),
|
||||
|
||||
pub fn create(instance: *Instance, allocator: std.mem.Allocator, info: *const vk.WaylandSurfaceCreateInfoKHR) VkError!*Interface {
|
||||
@@ -46,44 +124,90 @@ pub fn create(instance: *Instance, allocator: std.mem.Allocator, info: *const vk
|
||||
errdefer allocator.destroy(self);
|
||||
|
||||
try wayland.load();
|
||||
errdefer wayland.unload();
|
||||
try gbm.load();
|
||||
errdefer gbm.unload();
|
||||
|
||||
var interface = try Interface.init(instance, allocator);
|
||||
|
||||
interface.vtable = &.{
|
||||
.destroy = destroy,
|
||||
.getCapabilities = getCapabilities,
|
||||
.attachImage = attachImage,
|
||||
.detachImage = detachImage,
|
||||
.waitForImage = waitForImage,
|
||||
.presentImage = presentImage,
|
||||
};
|
||||
|
||||
const event_queue = wayland.wl_display_create_queue(info.display) orelse return VkError.OutOfHostMemory;
|
||||
errdefer wayland.wl_event_queue_destroy(event_queue);
|
||||
|
||||
self.* = .{
|
||||
.interface = interface,
|
||||
.display = info.display,
|
||||
.surface = info.surface,
|
||||
.shm = null,
|
||||
.event_queue = event_queue,
|
||||
.dmabuf = null,
|
||||
.dmabuf_version = 0,
|
||||
.supports_argb8888 = false,
|
||||
.supports_linear_modifier = false,
|
||||
.supports_implicit_modifier = false,
|
||||
.protocol_failed = false,
|
||||
.gbm_device = null,
|
||||
.image_map = .empty,
|
||||
};
|
||||
errdefer if (self.dmabuf) |dmabuf| wayland.zwpLinuxDmabufV1Destroy(dmabuf);
|
||||
|
||||
const registry = wayland.wl_display_get_registry(@ptrCast(self.display)) orelse return VkError.Unknown;
|
||||
_ = wayland.wl_registry_add_listener(registry, &wl_registry_listener, @ptrCast(&self.shm));
|
||||
_ = wayland.wl_display_dispatch(@ptrCast(self.display));
|
||||
if (self.shm == null) return VkError.Unknown;
|
||||
const registry = wayland.wlDisplayGetRegistry(self.display) orelse return VkError.InitializationFailed;
|
||||
defer wayland.wlRegistryDestroy(registry);
|
||||
wayland.wlProxyAssignQueue(registry, self.event_queue);
|
||||
|
||||
if (wayland.wlRegistryAddListener(registry, &wl_registry_listener, self) != 0)
|
||||
return VkError.InitializationFailed;
|
||||
|
||||
// The first roundtrip receives registry globals. The second receives the
|
||||
// format and modifier events emitted in response to binding linux-dmabuf.
|
||||
if (wayland.wl_display_roundtrip_queue(self.display, self.event_queue) < 0 or self.dmabuf == null or self.protocol_failed)
|
||||
return VkError.InitializationFailed;
|
||||
if (wayland.wl_display_roundtrip_queue(self.display, self.event_queue) < 0 or self.protocol_failed)
|
||||
return VkError.InitializationFailed;
|
||||
|
||||
if (!self.supports_argb8888 or (!self.supports_linear_modifier and !self.supports_implicit_modifier)) {
|
||||
std.log.scoped(.WaylandSurface).err("The compositor does not advertise a usable DMA-BUF ARGB8888 format", .{});
|
||||
return VkError.FormatNotSupported;
|
||||
}
|
||||
|
||||
return &self.interface;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
|
||||
if (interface.swapchain) |swapchain|
|
||||
swapchain.surface = null;
|
||||
|
||||
var image_iterator = self.image_map.valueIterator();
|
||||
while (image_iterator.next()) |wl_image|
|
||||
destroyWaylandImage(allocator, wl_image.*);
|
||||
self.image_map.deinit(allocator);
|
||||
|
||||
if (self.gbm_device) |*device|
|
||||
device.deinit();
|
||||
if (self.dmabuf) |dmabuf|
|
||||
wayland.zwpLinuxDmabufV1Destroy(dmabuf);
|
||||
wayland.wl_event_queue_destroy(self.event_queue);
|
||||
|
||||
allocator.destroy(self);
|
||||
gbm.unload();
|
||||
wayland.unload();
|
||||
}
|
||||
|
||||
pub fn getCapabilities(interface: *const Interface, capabilities: *vk.SurfaceCapabilitiesKHR) VkError!void {
|
||||
// No-op
|
||||
_ = interface;
|
||||
_ = capabilities;
|
||||
capabilities.min_image_count = 2;
|
||||
capabilities.max_image_extent = .{
|
||||
.width = std.math.maxInt(i32),
|
||||
.height = std.math.maxInt(i32),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn attachImage(interface: *Interface, allocator: std.mem.Allocator, image: *PresentImage) VkError!void {
|
||||
@@ -92,78 +216,183 @@ pub fn attachImage(interface: *Interface, allocator: std.mem.Allocator, image: *
|
||||
if (self.image_map.contains(image))
|
||||
return;
|
||||
|
||||
const width: u32 = image.image.extent.width;
|
||||
const height: u32 = image.image.extent.height;
|
||||
const width = image.image.extent.width;
|
||||
const height = image.image.extent.height;
|
||||
if (width > std.math.maxInt(i32) or height > std.math.maxInt(i32))
|
||||
return VkError.OutOfDeviceMemory;
|
||||
|
||||
const stride: u32 = @intCast(image.image.getRowPitchMemSizeForMipLevel(.{ .color_bit = true }, 0));
|
||||
const size: usize = @as(usize, stride) * @as(usize, height);
|
||||
const row_size = std.math.mul(usize, width, 4) catch return VkError.OutOfHostMemory;
|
||||
const staging_size = std.math.mul(usize, row_size, height) catch return VkError.OutOfHostMemory;
|
||||
|
||||
const wl_image = allocator.create(WaylandImage) catch return VkError.OutOfHostMemory;
|
||||
errdefer allocator.destroy(wl_image);
|
||||
|
||||
const fd = try createShmFile(size);
|
||||
const staging = allocator.alloc(u8, staging_size) catch return VkError.OutOfHostMemory;
|
||||
errdefer allocator.free(staging);
|
||||
|
||||
if (self.gbm_device == null)
|
||||
self.gbm_device = try gbm.Device.open();
|
||||
const device = &(self.gbm_device orelse unreachable);
|
||||
|
||||
var dma_buf = try device.createBuffer(width, height);
|
||||
errdefer dma_buf.deinit();
|
||||
|
||||
const protocol_modifier: u64 = if (dma_buf.modifier == gbm.drm_format_modifier_linear and self.supports_linear_modifier)
|
||||
gbm.drm_format_modifier_linear
|
||||
else if (self.supports_implicit_modifier)
|
||||
gbm.drm_format_modifier_invalid
|
||||
else
|
||||
return VkError.FormatNotSupported;
|
||||
|
||||
const fd = try dma_buf.exportFd();
|
||||
defer _ = std.c.close(fd);
|
||||
|
||||
const data = std.posix.mmap(null, size, .{ .READ = true, .WRITE = true }, .{ .TYPE = .SHARED }, fd, 0) catch return VkError.OutOfHostMemory;
|
||||
errdefer std.posix.munmap(data);
|
||||
const dmabuf = self.dmabuf orelse return VkError.InitializationFailed;
|
||||
const params = wayland.zwpLinuxDmabufV1CreateParams(dmabuf) orelse return VkError.OutOfHostMemory;
|
||||
defer wayland.zwpLinuxBufferParamsV1Destroy(params);
|
||||
|
||||
const shm = self.shm orelse return VkError.Unknown;
|
||||
const pool = wayland.wl_shm_create_pool(shm, fd, @intCast(size)) orelse return VkError.Unknown;
|
||||
defer wayland.wl_shm_pool_destroy(pool);
|
||||
var creation_result: BufferCreationResult = .{};
|
||||
if (wayland.zwpLinuxBufferParamsV1AddListener(params, &linux_buffer_params_listener, &creation_result) != 0)
|
||||
return VkError.InitializationFailed;
|
||||
|
||||
const buffer = wayland.wl_shm_pool_create_buffer(pool, 0, @intCast(width), @intCast(height), @intCast(stride), wayland.wl_shm_format_argb8888) orelse return VkError.Unknown;
|
||||
errdefer wayland.wl_buffer_destroy(buffer);
|
||||
wayland.zwpLinuxBufferParamsV1Add(params, fd, 0, 0, dma_buf.stride, protocol_modifier);
|
||||
wayland.zwpLinuxBufferParamsV1Create(params, @intCast(width), @intCast(height), gbm.drm_format_argb8888, 0);
|
||||
if (wayland.wl_display_roundtrip_queue(self.display, self.event_queue) < 0)
|
||||
return VkError.SurfaceLostKhr;
|
||||
if (creation_result.failed)
|
||||
return VkError.FormatNotSupported;
|
||||
|
||||
const buffer = creation_result.buffer orelse return VkError.Unknown;
|
||||
errdefer wayland.wlBufferDestroy(buffer);
|
||||
wayland.wlProxyAssignQueue(buffer, self.event_queue);
|
||||
|
||||
wl_image.* = .{
|
||||
.buffer = buffer,
|
||||
.data = data,
|
||||
.dma_buf = dma_buf,
|
||||
.staging = staging,
|
||||
.image = image,
|
||||
.width = width,
|
||||
.height = height,
|
||||
.stride = stride,
|
||||
.row_size = row_size,
|
||||
};
|
||||
|
||||
if (wayland.wlBufferAddListener(buffer, &wl_buffer_listener, wl_image) != 0)
|
||||
return VkError.InitializationFailed;
|
||||
|
||||
self.image_map.put(allocator, image, wl_image) catch return VkError.OutOfHostMemory;
|
||||
}
|
||||
|
||||
pub fn detachImage(interface: *Interface, allocator: std.mem.Allocator, image: *PresentImage) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
const entry = self.image_map.fetchRemove(image) orelse return;
|
||||
const wl_image = entry.value;
|
||||
|
||||
wayland.wl_buffer_destroy(wl_image.buffer);
|
||||
std.posix.munmap(wl_image.data);
|
||||
allocator.destroy(wl_image);
|
||||
destroyWaylandImage(allocator, entry.value);
|
||||
}
|
||||
|
||||
pub fn presentImage(interface: *Interface, allocator: std.mem.Allocator, image: *PresentImage) VkError!void {
|
||||
pub fn waitForImage(interface: *Interface, timeout: u64) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
_ = allocator;
|
||||
|
||||
if (wayland.wl_display_dispatch_queue_pending(self.display, self.event_queue) < 0)
|
||||
return VkError.SurfaceLostKhr;
|
||||
if (hasAvailableImage(self) or timeout == 0)
|
||||
return;
|
||||
|
||||
if (timeout == std.math.maxInt(u64)) {
|
||||
while (!hasAvailableImage(self)) {
|
||||
if (wayland.wl_display_dispatch_queue(self.display, self.event_queue) < 0)
|
||||
return VkError.SurfaceLostKhr;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const io = interface.owner.io();
|
||||
const deadline = std.Io.Clock.Timestamp.fromNow(io, .{
|
||||
.raw = .fromNanoseconds(@intCast(@min(timeout, std.math.maxInt(i64)))),
|
||||
.clock = .awake,
|
||||
});
|
||||
|
||||
while (!hasAvailableImage(self)) {
|
||||
while (wayland.wl_display_prepare_read_queue(self.display, self.event_queue) != 0) {
|
||||
if (wayland.wl_display_dispatch_queue_pending(self.display, self.event_queue) < 0)
|
||||
return VkError.SurfaceLostKhr;
|
||||
if (hasAvailableImage(self))
|
||||
return;
|
||||
}
|
||||
|
||||
const remaining_ns = deadline.durationFromNow(io).raw.nanoseconds;
|
||||
if (remaining_ns <= 0) {
|
||||
wayland.wl_display_cancel_read(self.display);
|
||||
return;
|
||||
}
|
||||
|
||||
_ = wayland.wl_display_flush(self.display);
|
||||
var poll_fds = [_]std.posix.pollfd{.{
|
||||
.fd = wayland.wl_display_get_fd(self.display),
|
||||
.events = std.posix.POLL.IN,
|
||||
.revents = 0,
|
||||
}};
|
||||
const rounded_ms = @divTrunc(@as(u64, @intCast(remaining_ns)) + std.time.ns_per_ms - 1, std.time.ns_per_ms);
|
||||
const timeout_ms: i32 = @intCast(@min(rounded_ms, std.math.maxInt(i32)));
|
||||
const ready_count = std.posix.poll(&poll_fds, timeout_ms) catch {
|
||||
wayland.wl_display_cancel_read(self.display);
|
||||
return VkError.SurfaceLostKhr;
|
||||
};
|
||||
if (ready_count == 0) {
|
||||
wayland.wl_display_cancel_read(self.display);
|
||||
return;
|
||||
}
|
||||
|
||||
if (wayland.wl_display_read_events(self.display) < 0)
|
||||
return VkError.SurfaceLostKhr;
|
||||
if (wayland.wl_display_dispatch_queue_pending(self.display, self.event_queue) < 0)
|
||||
return VkError.SurfaceLostKhr;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn presentImage(interface: *Interface, _: std.mem.Allocator, image: *PresentImage) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
errdefer image.state = .available;
|
||||
|
||||
const wl_image = self.image_map.get(image) orelse return VkError.Unknown;
|
||||
|
||||
try image.image.copyToMemory(wl_image.data, .{
|
||||
try image.image.copyToMemory(wl_image.staging, .{
|
||||
.aspect_mask = .{ .color_bit = true },
|
||||
.mip_level = 0,
|
||||
.base_array_layer = 0,
|
||||
.layer_count = 1,
|
||||
});
|
||||
|
||||
wayland.wl_surface_attach(@ptrCast(self.surface), wl_image.buffer, 0, 0);
|
||||
wayland.wl_surface_damage(@ptrCast(self.surface), 0, 0, @intCast(wl_image.width), @intCast(wl_image.height));
|
||||
wayland.wl_surface_commit(@ptrCast(self.surface));
|
||||
{
|
||||
const mapping = try wl_image.dma_buf.mapWrite();
|
||||
defer wl_image.dma_buf.unmap(mapping);
|
||||
|
||||
_ = wayland.wl_display_flush(@ptrCast(self.display));
|
||||
for (0..wl_image.height) |row| {
|
||||
const source_offset = row * wl_image.row_size;
|
||||
const destination_offset = row * mapping.stride;
|
||||
@memcpy(
|
||||
mapping.data[destination_offset..][0..wl_image.row_size],
|
||||
wl_image.staging[source_offset..][0..wl_image.row_size],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
image.state = .available;
|
||||
wayland.wlSurfaceAttach(self.surface, wl_image.buffer, 0, 0);
|
||||
wayland.wlSurfaceDamage(self.surface, 0, 0, @intCast(wl_image.width), @intCast(wl_image.height));
|
||||
wayland.wlSurfaceCommit(self.surface);
|
||||
_ = wayland.wl_display_flush(self.display);
|
||||
}
|
||||
|
||||
fn createShmFile(size: usize) VkError!std.posix.fd_t {
|
||||
const name = "ape_vk_wayland_surface";
|
||||
|
||||
const fd = std.posix.memfd_create(name, std.posix.FD_CLOEXEC) catch return VkError.Unknown;
|
||||
errdefer std.c.close(fd);
|
||||
|
||||
_ = std.c.ftruncate(fd, @intCast(size));
|
||||
|
||||
return fd;
|
||||
fn hasAvailableImage(self: *const Self) bool {
|
||||
var image_iterator = self.image_map.keyIterator();
|
||||
while (image_iterator.next()) |image| {
|
||||
if (image.*.state == .available)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn destroyWaylandImage(allocator: std.mem.Allocator, wl_image: *WaylandImage) void {
|
||||
wayland.wlBufferDestroy(wl_image.buffer);
|
||||
wl_image.dma_buf.deinit();
|
||||
allocator.free(wl_image.staging);
|
||||
allocator.destroy(wl_image);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Minimal wayland client protocol declarations and loader
|
||||
//! Minimal Wayland client and linux-dmabuf protocol declarations and loader.
|
||||
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
@@ -7,34 +7,52 @@ const lib = @import("../../lib.zig");
|
||||
const VkError = lib.VkError;
|
||||
|
||||
pub const wl_registry_listener = extern struct {
|
||||
global: ?*const fn (data: ?*anyopaque, wl_registry: ?*wl_registry, name: u32, interface: [*c]const u8, version: u32) callconv(.c) void = null,
|
||||
global_remove: ?*const fn (data: ?*anyopaque, wl_registry: ?*wl_registry, name: u32) callconv(.c) void = null,
|
||||
global: ?*const fn (data: ?*anyopaque, registry: ?*wl_registry, name: u32, interface: [*c]const u8, version: u32) callconv(.c) void = null,
|
||||
global_remove: ?*const fn (data: ?*anyopaque, registry: ?*wl_registry, name: u32) callconv(.c) void = null,
|
||||
};
|
||||
|
||||
pub const wl_buffer_destroy_opcode: c_int = 0;
|
||||
pub const wl_display_get_registry_opcode: c_int = 1;
|
||||
pub const wl_registry_bind_opcode: c_int = 0;
|
||||
pub const wl_shm_create_pool_opcode: c_int = 0;
|
||||
pub const wl_shm_format_argb8888: c_int = 0;
|
||||
pub const wl_shm_pool_create_buffer_opcode: c_int = 0;
|
||||
pub const wl_shm_pool_destroy_opcode: c_int = 1;
|
||||
pub const wl_surface_attach_opcode: c_int = 1;
|
||||
pub const wl_surface_commit_opcode: c_int = 6;
|
||||
pub const wl_surface_damage_opcode: c_int = 2;
|
||||
pub const wl_buffer_listener = extern struct {
|
||||
release: ?*const fn (data: ?*anyopaque, buffer: ?*wl_buffer) callconv(.c) void = null,
|
||||
};
|
||||
|
||||
pub const zwp_linux_dmabuf_v1_listener = extern struct {
|
||||
format: ?*const fn (data: ?*anyopaque, dmabuf: ?*zwp_linux_dmabuf_v1, format: u32) callconv(.c) void = null,
|
||||
modifier: ?*const fn (data: ?*anyopaque, dmabuf: ?*zwp_linux_dmabuf_v1, format: u32, modifier_hi: u32, modifier_lo: u32) callconv(.c) void = null,
|
||||
};
|
||||
|
||||
pub const zwp_linux_buffer_params_v1_listener = extern struct {
|
||||
created: ?*const fn (data: ?*anyopaque, params: ?*zwp_linux_buffer_params_v1, buffer: ?*wl_buffer) callconv(.c) void = null,
|
||||
failed: ?*const fn (data: ?*anyopaque, params: ?*zwp_linux_buffer_params_v1) callconv(.c) void = null,
|
||||
};
|
||||
|
||||
pub const wl_buffer_destroy_opcode: u32 = 0;
|
||||
pub const wl_display_get_registry_opcode: u32 = 1;
|
||||
pub const wl_registry_bind_opcode: u32 = 0;
|
||||
pub const wl_surface_attach_opcode: u32 = 1;
|
||||
pub const wl_surface_commit_opcode: u32 = 6;
|
||||
pub const wl_surface_damage_opcode: u32 = 2;
|
||||
|
||||
pub const zwp_linux_dmabuf_v1_destroy_opcode: u32 = 0;
|
||||
pub const zwp_linux_dmabuf_v1_create_params_opcode: u32 = 1;
|
||||
pub const zwp_linux_buffer_params_v1_destroy_opcode: u32 = 0;
|
||||
pub const zwp_linux_buffer_params_v1_add_opcode: u32 = 1;
|
||||
pub const zwp_linux_buffer_params_v1_create_opcode: u32 = 2;
|
||||
|
||||
const wl_marshal_flag_destroy: u32 = 1;
|
||||
|
||||
pub const wl_buffer = opaque {};
|
||||
pub const wl_callback = opaque {};
|
||||
pub const wl_event_queue = opaque {};
|
||||
pub const wl_registry = opaque {};
|
||||
pub const wl_shm = opaque {};
|
||||
pub const wl_shm_pool = opaque {};
|
||||
pub const wl_proxy = opaque {};
|
||||
pub const zwp_linux_dmabuf_v1 = opaque {};
|
||||
pub const zwp_linux_buffer_params_v1 = opaque {};
|
||||
pub const wl_display = vk.wl_display;
|
||||
pub const wl_surface = vk.wl_surface;
|
||||
|
||||
pub const wl_message = extern struct {
|
||||
name: ?[*:0]const u8 = null,
|
||||
signature: [*c]const u8 = null,
|
||||
types: [*c]*const wl_interface = null,
|
||||
types: [*c]const ?*const wl_interface = null,
|
||||
};
|
||||
|
||||
pub const wl_interface = extern struct {
|
||||
@@ -46,8 +64,73 @@ pub const wl_interface = extern struct {
|
||||
events: ?[*]const wl_message = null,
|
||||
};
|
||||
|
||||
const no_types = [_]?*const wl_interface{null};
|
||||
const dmabuf_create_params_types = [_]?*const wl_interface{&zwp_linux_buffer_params_v1_interface};
|
||||
const params_create_immed_types = [_]?*const wl_interface{ &wl_buffer_interface_storage, null, null, null, null };
|
||||
const params_created_types = [_]?*const wl_interface{&wl_buffer_interface_storage};
|
||||
|
||||
const dmabuf_requests = [_]wl_message{
|
||||
.{ .name = "destroy", .signature = "", .types = no_types[0..].ptr },
|
||||
.{ .name = "create_params", .signature = "n", .types = dmabuf_create_params_types[0..].ptr },
|
||||
};
|
||||
|
||||
const dmabuf_events = [_]wl_message{
|
||||
.{ .name = "format", .signature = "u", .types = no_types[0..].ptr },
|
||||
.{ .name = "modifier", .signature = "3uuu", .types = no_types[0..].ptr },
|
||||
};
|
||||
|
||||
pub const zwp_linux_dmabuf_v1_interface: wl_interface = .{
|
||||
.name = "zwp_linux_dmabuf_v1",
|
||||
.version = 3,
|
||||
.method_count = dmabuf_requests.len,
|
||||
.methods = &dmabuf_requests,
|
||||
.event_count = dmabuf_events.len,
|
||||
.events = &dmabuf_events,
|
||||
};
|
||||
|
||||
const params_requests = [_]wl_message{
|
||||
.{ .name = "destroy", .signature = "", .types = no_types[0..].ptr },
|
||||
.{ .name = "add", .signature = "huuuuu", .types = no_types[0..].ptr },
|
||||
.{ .name = "create", .signature = "iiuu", .types = no_types[0..].ptr },
|
||||
.{ .name = "create_immed", .signature = "2niiuu", .types = params_create_immed_types[0..].ptr },
|
||||
};
|
||||
|
||||
const params_events = [_]wl_message{
|
||||
.{ .name = "created", .signature = "n", .types = params_created_types[0..].ptr },
|
||||
.{ .name = "failed", .signature = "", .types = no_types[0..].ptr },
|
||||
};
|
||||
|
||||
pub const zwp_linux_buffer_params_v1_interface: wl_interface = .{
|
||||
.name = "zwp_linux_buffer_params_v1",
|
||||
.version = 3,
|
||||
.method_count = params_requests.len,
|
||||
.methods = ¶ms_requests,
|
||||
.event_count = params_events.len,
|
||||
.events = ¶ms_events,
|
||||
};
|
||||
|
||||
// The loaded wl_buffer_interface is copied here before any linux-dmabuf request is made.
|
||||
// SAFETY: load assigns every global pointer before any protocol wrapper is used.
|
||||
var wl_buffer_interface_storage: wl_interface = undefined;
|
||||
|
||||
// SAFETY: load assigns every function pointer before any protocol wrapper is used.
|
||||
pub var wl_display_dispatch: *const fn (*wl_display) callconv(.c) c_int = undefined;
|
||||
pub var wl_display_cancel_read: *const fn (*wl_display) callconv(.c) void = undefined;
|
||||
// SAFETY: load assigns every function pointer before any protocol wrapper is used.
|
||||
pub var wl_display_create_queue: *const fn (*wl_display) callconv(.c) ?*wl_event_queue = undefined;
|
||||
// SAFETY: load assigns every function pointer before any protocol wrapper is used.
|
||||
pub var wl_display_dispatch_queue: *const fn (*wl_display, *wl_event_queue) callconv(.c) c_int = undefined;
|
||||
// SAFETY: load assigns every function pointer before any protocol wrapper is used.
|
||||
pub var wl_display_dispatch_queue_pending: *const fn (*wl_display, *wl_event_queue) callconv(.c) c_int = undefined;
|
||||
// SAFETY: load assigns every function pointer before any protocol wrapper is used.
|
||||
pub var wl_display_get_fd: *const fn (*wl_display) callconv(.c) c_int = undefined;
|
||||
// SAFETY: load assigns every function pointer before any protocol wrapper is used.
|
||||
pub var wl_display_prepare_read_queue: *const fn (*wl_display, *wl_event_queue) callconv(.c) c_int = undefined;
|
||||
// SAFETY: load assigns every function pointer before any protocol wrapper is used.
|
||||
pub var wl_display_read_events: *const fn (*wl_display) callconv(.c) c_int = undefined;
|
||||
// SAFETY: load assigns every function pointer before any protocol wrapper is used.
|
||||
pub var wl_display_roundtrip_queue: *const fn (*wl_display, *wl_event_queue) callconv(.c) c_int = undefined;
|
||||
// SAFETY: load assigns every function pointer before any protocol wrapper is used.
|
||||
pub var wl_event_queue_destroy: *const fn (*wl_event_queue) callconv(.c) void = undefined;
|
||||
// SAFETY: load assigns every function pointer before any protocol wrapper is used.
|
||||
pub var wl_proxy_marshal_flags: *const fn (*wl_proxy, u32, ?*const wl_interface, u32, u32, ...) callconv(.c) ?*wl_proxy = undefined;
|
||||
// SAFETY: load assigns every function pointer before any protocol wrapper is used.
|
||||
@@ -55,53 +138,72 @@ pub var wl_proxy_get_version: *const fn (*wl_proxy) callconv(.c) u32 = undefined
|
||||
// SAFETY: load assigns every function pointer before any protocol wrapper is used.
|
||||
pub var wl_proxy_add_listener: *const fn (*wl_proxy, **const fn (void) void, ?*anyopaque) callconv(.c) c_int = undefined;
|
||||
// SAFETY: load assigns every function pointer before any protocol wrapper is used.
|
||||
pub var wl_proxy_destroy: *const fn (*wl_proxy) callconv(.c) void = undefined;
|
||||
// SAFETY: load assigns every function pointer before any protocol wrapper is used.
|
||||
pub var wl_proxy_set_queue: *const fn (*wl_proxy, ?*wl_event_queue) callconv(.c) void = undefined;
|
||||
// SAFETY: load assigns every function pointer before any protocol wrapper is used.
|
||||
pub var wl_display_flush: *const fn (*wl_display) callconv(.c) c_int = undefined;
|
||||
|
||||
// SAFETY: load resolves every interface pointer before a surface can be created.
|
||||
pub var wl_buffer_interface: *wl_interface = undefined;
|
||||
// SAFETY: load resolves every interface pointer before a surface can be created.
|
||||
pub var wl_registry_interface: *wl_interface = undefined;
|
||||
// SAFETY: load resolves every interface pointer before a surface can be created.
|
||||
pub var wl_shm_interface: *wl_interface = undefined;
|
||||
// SAFETY: load resolves every interface pointer before a surface can be created.
|
||||
pub var wl_shm_pool_interface: *wl_interface = undefined;
|
||||
|
||||
// SAFETY: load initializes the module before it can be closed or queried.
|
||||
pub var module: std.DynLib = undefined;
|
||||
|
||||
pub var ref_count = std.atomic.Value(usize).init(0);
|
||||
var module: std.DynLib = undefined;
|
||||
var ref_count = std.atomic.Value(usize).init(0);
|
||||
var load_mutex: lib.SpinMutex = .{};
|
||||
|
||||
pub fn load() VkError!void {
|
||||
if (ref_count.load(.monotonic) != 0)
|
||||
load_mutex.lock();
|
||||
defer load_mutex.unlock();
|
||||
|
||||
if (ref_count.load(.monotonic) != 0) {
|
||||
_ = ref_count.fetchAdd(1, .monotonic);
|
||||
return;
|
||||
}
|
||||
|
||||
module = std.DynLib.open("libwayland-client.so.0") catch return VkError.Unknown;
|
||||
module = std.DynLib.open("libwayland-client.so.0") catch {
|
||||
std.log.scoped(.WaylandClient).err("Could not open 'libwayland-client.so.0'", .{});
|
||||
return VkError.InitializationFailed;
|
||||
};
|
||||
errdefer module.close();
|
||||
errdefer std.log.scoped(.WaylandClient).err("Could not open 'libwayland-client.so.0': {s}", .{std.c.dlerror() orelse "unknown error"});
|
||||
|
||||
wl_display_dispatch = module.lookup(@TypeOf(wl_display_dispatch), "wl_display_dispatch") orelse return VkError.Unknown;
|
||||
wl_proxy_marshal_flags = module.lookup(@TypeOf(wl_proxy_marshal_flags), "wl_proxy_marshal_flags") orelse return VkError.Unknown;
|
||||
wl_proxy_get_version = module.lookup(@TypeOf(wl_proxy_get_version), "wl_proxy_get_version") orelse return VkError.Unknown;
|
||||
wl_proxy_add_listener = module.lookup(@TypeOf(wl_proxy_add_listener), "wl_proxy_add_listener") orelse return VkError.Unknown;
|
||||
wl_display_flush = module.lookup(@TypeOf(wl_display_flush), "wl_display_flush") orelse return VkError.Unknown;
|
||||
wl_display_cancel_read = module.lookup(@TypeOf(wl_display_cancel_read), "wl_display_cancel_read") orelse return VkError.InitializationFailed;
|
||||
wl_display_create_queue = module.lookup(@TypeOf(wl_display_create_queue), "wl_display_create_queue") orelse return VkError.InitializationFailed;
|
||||
wl_display_dispatch_queue = module.lookup(@TypeOf(wl_display_dispatch_queue), "wl_display_dispatch_queue") orelse return VkError.InitializationFailed;
|
||||
wl_display_dispatch_queue_pending = module.lookup(@TypeOf(wl_display_dispatch_queue_pending), "wl_display_dispatch_queue_pending") orelse return VkError.InitializationFailed;
|
||||
wl_display_get_fd = module.lookup(@TypeOf(wl_display_get_fd), "wl_display_get_fd") orelse return VkError.InitializationFailed;
|
||||
wl_display_prepare_read_queue = module.lookup(@TypeOf(wl_display_prepare_read_queue), "wl_display_prepare_read_queue") orelse return VkError.InitializationFailed;
|
||||
wl_display_read_events = module.lookup(@TypeOf(wl_display_read_events), "wl_display_read_events") orelse return VkError.InitializationFailed;
|
||||
wl_display_roundtrip_queue = module.lookup(@TypeOf(wl_display_roundtrip_queue), "wl_display_roundtrip_queue") orelse return VkError.InitializationFailed;
|
||||
wl_event_queue_destroy = module.lookup(@TypeOf(wl_event_queue_destroy), "wl_event_queue_destroy") orelse return VkError.InitializationFailed;
|
||||
wl_proxy_marshal_flags = module.lookup(@TypeOf(wl_proxy_marshal_flags), "wl_proxy_marshal_flags") orelse return VkError.InitializationFailed;
|
||||
wl_proxy_get_version = module.lookup(@TypeOf(wl_proxy_get_version), "wl_proxy_get_version") orelse return VkError.InitializationFailed;
|
||||
wl_proxy_add_listener = module.lookup(@TypeOf(wl_proxy_add_listener), "wl_proxy_add_listener") orelse return VkError.InitializationFailed;
|
||||
wl_proxy_destroy = module.lookup(@TypeOf(wl_proxy_destroy), "wl_proxy_destroy") orelse return VkError.InitializationFailed;
|
||||
wl_proxy_set_queue = module.lookup(@TypeOf(wl_proxy_set_queue), "wl_proxy_set_queue") orelse return VkError.InitializationFailed;
|
||||
wl_display_flush = module.lookup(@TypeOf(wl_display_flush), "wl_display_flush") orelse return VkError.InitializationFailed;
|
||||
|
||||
wl_buffer_interface = module.lookup(*wl_interface, "wl_buffer_interface") orelse return VkError.Unknown;
|
||||
wl_registry_interface = module.lookup(*wl_interface, "wl_registry_interface") orelse return VkError.Unknown;
|
||||
wl_shm_interface = module.lookup(*wl_interface, "wl_shm_interface") orelse return VkError.Unknown;
|
||||
wl_shm_pool_interface = module.lookup(*wl_interface, "wl_shm_pool_interface") orelse return VkError.Unknown;
|
||||
wl_buffer_interface = module.lookup(*wl_interface, "wl_buffer_interface") orelse return VkError.InitializationFailed;
|
||||
wl_registry_interface = module.lookup(*wl_interface, "wl_registry_interface") orelse return VkError.InitializationFailed;
|
||||
wl_buffer_interface_storage = wl_buffer_interface.*;
|
||||
|
||||
_ = ref_count.fetchAdd(1, .monotonic);
|
||||
std.log.scoped(.WaylandClient).debug("Loaded wayland client lib", .{});
|
||||
std.log.scoped(.WaylandClient).debug("Loaded Wayland client", .{});
|
||||
}
|
||||
|
||||
pub fn unload() void {
|
||||
load_mutex.lock();
|
||||
defer load_mutex.unlock();
|
||||
|
||||
if (ref_count.fetchSub(1, .release) == 1) {
|
||||
module.close();
|
||||
std.log.scoped(.WaylandClient).debug("Unloaded wayland client lib", .{});
|
||||
std.log.scoped(.WaylandClient).debug("Unloaded Wayland client", .{});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn wl_registry_bind(registry: *wl_registry, name: u32, interface: *const wl_interface, version: u32) ?*wl_proxy {
|
||||
pub fn wlRegistryBind(registry: *wl_registry, name: u32, interface: *const wl_interface, version: u32) ?*wl_proxy {
|
||||
return wl_proxy_marshal_flags(
|
||||
@ptrCast(@alignCast(registry)),
|
||||
wl_registry_bind_opcode,
|
||||
@@ -115,7 +217,7 @@ pub fn wl_registry_bind(registry: *wl_registry, name: u32, interface: *const wl_
|
||||
);
|
||||
}
|
||||
|
||||
pub fn wl_display_get_registry(display: *wl_display) ?*wl_registry {
|
||||
pub fn wlDisplayGetRegistry(display: *wl_display) ?*wl_registry {
|
||||
return @ptrCast(@alignCast(wl_proxy_marshal_flags(
|
||||
@ptrCast(@alignCast(display)),
|
||||
wl_display_get_registry_opcode,
|
||||
@@ -126,60 +228,102 @@ pub fn wl_display_get_registry(display: *wl_display) ?*wl_registry {
|
||||
)));
|
||||
}
|
||||
|
||||
pub fn wl_registry_add_listener(registry: *wl_registry, listener: *const wl_registry_listener, data: ?*anyopaque) c_int {
|
||||
pub fn wlRegistryAddListener(registry: *wl_registry, listener: *const wl_registry_listener, data: ?*anyopaque) c_int {
|
||||
return wl_proxy_add_listener(@ptrCast(@alignCast(registry)), @ptrCast(@alignCast(@constCast(listener))), data);
|
||||
}
|
||||
|
||||
pub fn wl_shm_create_pool(shm: *wl_shm, fd: i32, size: i32) callconv(.c) ?*wl_shm_pool {
|
||||
return @ptrCast(@alignCast(wl_proxy_marshal_flags(
|
||||
@ptrCast(@alignCast(shm)),
|
||||
wl_shm_create_pool_opcode,
|
||||
wl_shm_pool_interface,
|
||||
wl_proxy_get_version(@ptrCast(@alignCast(shm))),
|
||||
0,
|
||||
@as(?*anyopaque, null),
|
||||
fd,
|
||||
size,
|
||||
)));
|
||||
pub fn wlProxyAssignQueue(proxy: *anyopaque, queue: *wl_event_queue) void {
|
||||
wl_proxy_set_queue(@ptrCast(@alignCast(proxy)), queue);
|
||||
}
|
||||
|
||||
pub fn wl_shm_pool_destroy(shm_pool: *wl_shm_pool) void {
|
||||
pub fn wlRegistryDestroy(registry: *wl_registry) void {
|
||||
wl_proxy_destroy(@ptrCast(@alignCast(registry)));
|
||||
}
|
||||
|
||||
pub fn wlBufferAddListener(buffer: *wl_buffer, listener: *const wl_buffer_listener, data: ?*anyopaque) c_int {
|
||||
return wl_proxy_add_listener(@ptrCast(@alignCast(buffer)), @ptrCast(@alignCast(@constCast(listener))), data);
|
||||
}
|
||||
|
||||
pub fn zwpLinuxDmabufV1AddListener(dmabuf: *zwp_linux_dmabuf_v1, listener: *const zwp_linux_dmabuf_v1_listener, data: ?*anyopaque) c_int {
|
||||
return wl_proxy_add_listener(@ptrCast(@alignCast(dmabuf)), @ptrCast(@alignCast(@constCast(listener))), data);
|
||||
}
|
||||
|
||||
pub fn zwpLinuxBufferParamsV1AddListener(params: *zwp_linux_buffer_params_v1, listener: *const zwp_linux_buffer_params_v1_listener, data: ?*anyopaque) c_int {
|
||||
return wl_proxy_add_listener(@ptrCast(@alignCast(params)), @ptrCast(@alignCast(@constCast(listener))), data);
|
||||
}
|
||||
|
||||
pub fn zwpLinuxDmabufV1Destroy(dmabuf: *zwp_linux_dmabuf_v1) void {
|
||||
_ = wl_proxy_marshal_flags(
|
||||
@ptrCast(@alignCast(shm_pool)),
|
||||
wl_shm_pool_destroy_opcode,
|
||||
@ptrCast(@alignCast(dmabuf)),
|
||||
zwp_linux_dmabuf_v1_destroy_opcode,
|
||||
null,
|
||||
wl_proxy_get_version(@ptrCast(@alignCast(shm_pool))),
|
||||
@bitCast(@as(c_int, @as(c_int, 1) << @intCast(@as(c_int, 0)))),
|
||||
wl_proxy_get_version(@ptrCast(@alignCast(dmabuf))),
|
||||
wl_marshal_flag_destroy,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn wl_shm_pool_create_buffer(shm_pool: *wl_shm_pool, offset: i32, width: i32, height: i32, stride: i32, format: u32) ?*wl_buffer {
|
||||
pub fn zwpLinuxDmabufV1CreateParams(dmabuf: *zwp_linux_dmabuf_v1) ?*zwp_linux_buffer_params_v1 {
|
||||
return @ptrCast(@alignCast(wl_proxy_marshal_flags(
|
||||
@ptrCast(@alignCast(shm_pool)),
|
||||
wl_shm_pool_create_buffer_opcode,
|
||||
wl_buffer_interface,
|
||||
wl_proxy_get_version(@ptrCast(@alignCast(shm_pool))),
|
||||
@ptrCast(@alignCast(dmabuf)),
|
||||
zwp_linux_dmabuf_v1_create_params_opcode,
|
||||
&zwp_linux_buffer_params_v1_interface,
|
||||
wl_proxy_get_version(@ptrCast(@alignCast(dmabuf))),
|
||||
0,
|
||||
@as(?*anyopaque, null),
|
||||
offset,
|
||||
width,
|
||||
height,
|
||||
stride,
|
||||
format,
|
||||
)));
|
||||
}
|
||||
|
||||
pub fn wl_buffer_destroy(buffer: *wl_buffer) void {
|
||||
pub fn zwpLinuxBufferParamsV1Destroy(params: *zwp_linux_buffer_params_v1) void {
|
||||
_ = wl_proxy_marshal_flags(
|
||||
@ptrCast(@alignCast(params)),
|
||||
zwp_linux_buffer_params_v1_destroy_opcode,
|
||||
null,
|
||||
wl_proxy_get_version(@ptrCast(@alignCast(params))),
|
||||
wl_marshal_flag_destroy,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn zwpLinuxBufferParamsV1Add(params: *zwp_linux_buffer_params_v1, fd: i32, plane_index: u32, offset: u32, stride: u32, modifier: u64) void {
|
||||
_ = wl_proxy_marshal_flags(
|
||||
@ptrCast(@alignCast(params)),
|
||||
zwp_linux_buffer_params_v1_add_opcode,
|
||||
null,
|
||||
wl_proxy_get_version(@ptrCast(@alignCast(params))),
|
||||
0,
|
||||
fd,
|
||||
plane_index,
|
||||
offset,
|
||||
stride,
|
||||
@as(u32, @truncate(modifier >> 32)),
|
||||
@as(u32, @truncate(modifier)),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn zwpLinuxBufferParamsV1Create(params: *zwp_linux_buffer_params_v1, width: i32, height: i32, format: u32, flags: u32) void {
|
||||
_ = wl_proxy_marshal_flags(
|
||||
@ptrCast(@alignCast(params)),
|
||||
zwp_linux_buffer_params_v1_create_opcode,
|
||||
null,
|
||||
wl_proxy_get_version(@ptrCast(@alignCast(params))),
|
||||
0,
|
||||
width,
|
||||
height,
|
||||
format,
|
||||
flags,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn wlBufferDestroy(buffer: *wl_buffer) void {
|
||||
_ = wl_proxy_marshal_flags(
|
||||
@ptrCast(@alignCast(buffer)),
|
||||
wl_buffer_destroy_opcode,
|
||||
null,
|
||||
wl_proxy_get_version(@ptrCast(@alignCast(buffer))),
|
||||
@bitCast(@as(c_int, @as(c_int, 1) << @intCast(@as(c_int, 0)))),
|
||||
wl_marshal_flag_destroy,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn wl_surface_attach(surface: *wl_surface, buffer: *wl_buffer, x: i32, y: i32) void {
|
||||
pub fn wlSurfaceAttach(surface: *wl_surface, buffer: *wl_buffer, x: i32, y: i32) void {
|
||||
_ = wl_proxy_marshal_flags(
|
||||
@ptrCast(@alignCast(surface)),
|
||||
wl_surface_attach_opcode,
|
||||
@@ -192,7 +336,7 @@ pub fn wl_surface_attach(surface: *wl_surface, buffer: *wl_buffer, x: i32, y: i3
|
||||
);
|
||||
}
|
||||
|
||||
pub fn wl_surface_damage(surface: *wl_surface, x: i32, y: i32, width: i32, height: i32) void {
|
||||
pub fn wlSurfaceDamage(surface: *wl_surface, x: i32, y: i32, width: i32, height: i32) void {
|
||||
_ = wl_proxy_marshal_flags(
|
||||
@ptrCast(@alignCast(surface)),
|
||||
wl_surface_damage_opcode,
|
||||
@@ -206,7 +350,7 @@ pub fn wl_surface_damage(surface: *wl_surface, x: i32, y: i32, width: i32, heigh
|
||||
);
|
||||
}
|
||||
|
||||
pub fn wl_surface_commit(surface: *wl_surface) void {
|
||||
pub fn wlSurfaceCommit(surface: *wl_surface) void {
|
||||
_ = wl_proxy_marshal_flags(
|
||||
@ptrCast(@alignCast(surface)),
|
||||
wl_surface_commit_opcode,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user