summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authors-ol <s-ol@users.noreply.github.com>2021-03-09 17:40:29 +0000
committers-ol <s-ol@users.noreply.github.com>2021-03-09 17:40:29 +0000
commit40c7bc1f0f58d3e19d9b4d610454745cfbb005aa (patch)
tree709c7e99854d92988ca5d296afd539559a13761c /src
parentintial commit (diff)
downloadopenxPriments-40c7bc1f0f58d3e19d9b4d610454745cfbb005aa.tar.gz
openxPriments-40c7bc1f0f58d3e19d9b4d610454745cfbb005aa.zip
Vulkan wip
Diffstat (limited to 'src')
-rw-r--r--src/c.zig6
-rw-r--r--src/graphics.zig554
-rw-r--r--src/main.zig434
-rw-r--r--src/renderdoc.zig77
-rw-r--r--src/xrvk.zig93
5 files changed, 891 insertions, 273 deletions
diff --git a/src/c.zig b/src/c.zig
deleted file mode 100644
index 31521b1..0000000
--- a/src/c.zig
+++ /dev/null
@@ -1,6 +0,0 @@
-const xr = @import("openxr");
-const vk = @import("vulkan");
-
-pub extern fn xrGetInstanceProcAddr(instance: xr.Instance, procname: [*:0]const u8, function: *xr.PfnVoidFunction) xr.Result;
-pub extern fn vkGetInstanceProcAddr(instance: vk.Instance, procname: [*:0]const u8) vk.PfnVoidFunction;
-pub extern fn vkGetDeviceProcAddr(instance: vk.Device, procname: [*:0]const u8) vk.PfnVoidFunction;
diff --git a/src/graphics.zig b/src/graphics.zig
index 40b85f5..03fb71d 100644
--- a/src/graphics.zig
+++ b/src/graphics.zig
@@ -1,140 +1,472 @@
const std = @import("std");
-const xr = @import("openxr");
-const vk = @import("vulkan");
-const c = @import("c.zig");
+usingnamespace @import("xrvk.zig");
const Allocator = std.mem.Allocator;
-var vk_binding: xr.GraphicsBindingVulkan2KHR = undefined;
-var vk_queue: vk.Queue = undefined;
+pub const Graphics = struct {
+ xri: xr.InstanceDispatch,
+ vki: vk.InstanceDispatch,
+ vkd: vk.DeviceDispatch,
-const InstanceDispatch = struct {
- vkDestroyInstance: vk.PfnDestroyInstance,
- vkGetPhysicalDeviceQueueFamilyProperties: vk.PfnGetPhysicalDeviceQueueFamilyProperties,
- usingnamespace vk.InstanceWrapper(@This());
-};
+ instance: vk.Instance,
+ device: vk.Device,
+ physical_device: vk.PhysicalDevice,
-const DeviceDispatch = struct {
- vkDestroyDevice: vk.PfnDestroyDevice,
- vkGetDeviceQueue: vk.PfnGetDeviceQueue,
- usingnamespace vk.DeviceWrapper(@This());
-};
+ queue: vk.Queue,
+ family_index: u32,
+ command_pool: vk.CommandPool,
+
+ allocator: *Allocator,
+
+ pub fn init(allocator: *Allocator, xri: xr.InstanceDispatch, instance: xr.Instance, system: xr.SystemId) !Graphics {
+ var self: Graphics = undefined;
+
+ self.xri = xri;
+ self.allocator = allocator;
+
+ var result: xr.VkResult = undefined;
+
+ var requirements = xr.GraphicsRequirementsVulkan2KHR.empty();
+ try xri.getVulkanGraphicsRequirements2KHR(instance, system, &requirements);
+
+ // CREATE INSTANCE
+
+ const app_info = vk.ApplicationInfo{
+ .p_application_name = "test",
+ .application_version = vk.makeVersion(0, 0, 0),
+ .p_engine_name = "test",
+ .engine_version = vk.makeVersion(0, 0, 0),
+ .api_version = vk.API_VERSION_1_2,
+ };
-pub fn get_binding(xri: anytype, instance: xr.Instance, system: xr.SystemId, allocator: *Allocator) !*c_void {
- var result: xr.VkResult = undefined;
- vk_binding = xr.GraphicsBindingVulkan2KHR.empty();
-
- var requirements = xr.GraphicsRequirementsVulkan2KHR.empty();
- try xri.getVulkanGraphicsRequirements2KHR(instance, system, &requirements);
-
- // CREATE INSTANCE
-
- const app_info = vk.ApplicationInfo{
- .p_application_name = "test",
- .application_version = vk.makeVersion(0, 0, 0),
- .p_engine_name = "test",
- .engine_version = vk.makeVersion(0, 0, 0),
- .api_version = vk.API_VERSION_1_2,
- };
-
- const instance_create_info = vk.InstanceCreateInfo{
- .flags = .{},
- .p_application_info = &app_info,
- .enabled_layer_count = 0,
- .pp_enabled_layer_names = undefined,
- .enabled_extension_count = 0,
- .pp_enabled_extension_names = @ptrCast([*]const [*:0]const u8, ""),
- };
-
- try xri.createVulkanInstanceKHR(
- instance,
- .{
+ const instance_layers = [_][*:0]const u8{
+ "VK_LAYER_KHRONOS_validation",
+ };
+ const instance_extensions = [_][*:0]const u8{
+ "VK_EXT_debug_utils",
+ };
+
+ const instance_create_info = vk.InstanceCreateInfo{
+ .flags = .{},
+ .p_application_info = &app_info,
+ .enabled_layer_count = instance_layers.len,
+ .pp_enabled_layer_names = &instance_layers,
+ .enabled_extension_count = instance_extensions.len,
+ .pp_enabled_extension_names = &instance_extensions,
+ };
+
+ try xri.createVulkanInstanceKHR(instance, .{
.create_flags = .{},
.system_id = system,
- .pfn_get_instance_proc_addr = c.vkGetInstanceProcAddr,
+ .pfn_get_instance_proc_addr = vk.getInstanceProcAddr,
.vulkan_create_info = &instance_create_info,
.vulkan_allocator = null,
// .vulkan_allocator = &vk_allocator,
- },
- &vk_binding.instance,
- &result
- );
- const vki = try InstanceDispatch.load(vk_binding.instance, c.vkGetInstanceProcAddr);
- // defer vki.destroyInstance(inst) catch unreachable;
+ }, &self.instance, &result);
+ self.vki = try vk.InstanceDispatch.load(self.instance, vk.getInstanceProcAddr);
+ errdefer self.vki.destroyInstance(self.instance, null);
- // CREATE PHYSICAL DEVICE
+ // CREATE PHYSICAL DEVICE
- try xri.getVulkanGraphicsDevice2KHR(
- instance,
- .{ .system_id = system, .vulkan_instance = vk_binding.instance },
- &vk_binding.physical_device
- );
+ try xri.getVulkanGraphicsDevice2KHR(instance, .{ .system_id = system, .vulkan_instance = self.instance }, &self.physical_device);
- // CHOOSE QUEUE
+ // CHOOSE QUEUE
- var family_count: u32 = undefined;
- vki.getPhysicalDeviceQueueFamilyProperties(vk_binding.physical_device, &family_count, null);
+ var family_count: u32 = undefined;
+ self.vki.getPhysicalDeviceQueueFamilyProperties(self.physical_device, &family_count, null);
- const families = try allocator.alloc(vk.QueueFamilyProperties, family_count);
- defer allocator.free(families);
- vki.getPhysicalDeviceQueueFamilyProperties(vk_binding.physical_device, &family_count, families.ptr);
+ const families = try allocator.alloc(vk.QueueFamilyProperties, family_count);
+ defer allocator.free(families);
+ self.vki.getPhysicalDeviceQueueFamilyProperties(self.physical_device, &family_count, families.ptr);
- const family_index = for (families) |properties, i| {
- const family = @intCast(u32, i);
+ self.family_index = for (families) |properties, i| {
+ const family = @intCast(u32, i);
- if (properties.queue_flags.contains(.{.graphics_bit = true})) {
- break family;
- }
- } else {
- return error.NoGraphicsFamily;
- };
+ if (properties.queue_flags.contains(.{ .graphics_bit = true })) {
+ break family;
+ }
+ } else {
+ return error.NoGraphicsFamily;
+ };
+
+ const priority = [_]f32{0};
+ const queue_create_info = [_]vk.DeviceQueueCreateInfo{
+ .{
+ .flags = .{},
+ .queue_count = 1,
+ .p_queue_priorities = &priority,
+ .queue_family_index = self.family_index,
+ },
+ };
- const priority = [_]f32{0};
- const queue_create_info = [_]vk.DeviceQueueCreateInfo{
- .{
+ // CREATE DEVICE
+
+ const device_create_info = vk.DeviceCreateInfo{
.flags = .{},
- .queue_count = 1,
- .p_queue_priorities = &priority,
- .queue_family_index = family_index,
- },
- };
-
- // CREATE DEVICE
-
- const zero = [_:0]u8{0};
- const device_create_info = vk.DeviceCreateInfo{
- .flags = .{},
- .queue_create_info_count = queue_create_info.len,
- .p_queue_create_infos = &queue_create_info,
- .enabled_layer_count = 0,
- .pp_enabled_layer_names = @ptrCast([*]const [*:0]const u8, &zero),
- .enabled_extension_count = 0,
- .pp_enabled_extension_names = @ptrCast([*]const [*:0]const u8, &zero),
- .p_enabled_features = null,
- };
-
- try xri.createVulkanDeviceKHR(
- instance,
- .{
+ .queue_create_info_count = queue_create_info.len,
+ .p_queue_create_infos = &queue_create_info,
+ .enabled_layer_count = 0,
+ .pp_enabled_layer_names = undefined,
+ .enabled_extension_count = 0,
+ .pp_enabled_extension_names = undefined,
+ .p_enabled_features = null,
+ };
+
+ try xri.createVulkanDeviceKHR(instance, .{
.system_id = system,
.create_flags = .{},
- .pfn_get_instance_proc_addr = c.vkGetInstanceProcAddr,
- .vulkan_physical_device = vk_binding.physical_device,
+ .pfn_get_instance_proc_addr = vk.getInstanceProcAddr,
+ .vulkan_physical_device = self.physical_device,
.vulkan_create_info = &device_create_info,
.vulkan_allocator = null,
// .vulkan_allocator = &vk_allocator,
- },
- &vk_binding.device,
- &result
- );
- const vkd = try DeviceDispatch.load(vk_binding.device, c.vkGetDeviceProcAddr);
- // defer vki.destroyDevice(vk_binding.device) catch unreachable;
+ }, &self.device, &result);
+ self.vkd = try vk.DeviceDispatch.load(self.device, vk.getDeviceProcAddr);
+ errdefer self.vkd.destroyDevice(self.device, null);
+
+ // GET QUEUE
+
+ self.queue = self.vkd.getDeviceQueue(self.device, self.family_index, 0);
+
+ // CREATE COMMAND POOL
+ self.command_pool = try self.vkd.createCommandPool(
+ self.device,
+ .{
+ .flags = .{ .reset_command_buffer_bit = true },
+ .queue_family_index = self.family_index
+ },
+ null
+ );
+ errdefer self.vkd.destroyCommandPool(self.device, self.command_pool, null);
+
+ return self;
+ }
+
+ pub fn deinit(self: *const Graphics) void {
+ self.vkd.destroyCommandPool(self.device, self.command_pool, null);
+ self.vkd.destroyDevice(self.device, null);
+ self.vki.destroyInstance(self.instance, null);
+ }
+
+ fn debug_callback(
+ msg_severity: vk.c.VkDebugUtilsMessageSeverityFlagBitsEXT,
+ msg_type: vk.c.VkDebugUtilsMessageTypeFlagsEXT,
+ c_data: ?*const vk.c.VkDebugUtilsMessengerCallbackDataEXT,
+ c_self: ?*c_void
+ ) callconv(.C) vk.Bool32 {
+ const level = if (msg_severity == .VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) "error" else "warn";
+ const self = @ptrCast(*Graphics, @alignCast(@alignOf(Graphics), c_self));
+
+ if (c_data) |data| {
+ std.debug.print("[{}] {}: {}\n", .{
+ level,
+ "??",
+ // @as([*:0]const u8, data.pMessageIdName),
+ @as([*:0]const u8, data.pMessage),
+ });
+ }
+ return 0;
+ }
+
+ pub fn setup_debugging(self: *Graphics) !void {
+ if (self.vki.vkCreateDebugUtilsMessengerEXT) |pfn| {
+ var messenger: vk.c.VkDebugUtilsMessengerEXT = undefined;
+ const messenger_create_info = vk.c.VkDebugUtilsMessengerCreateInfoEXT{
+ .sType = .VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT,
+ .pNext = null,
+ .flags = 0,
+ .messageSeverity = 0x1110,
+ .messageType = 0x3,
+ .pfnUserCallback = @This().debug_callback,
+ .pUserData = @ptrCast(*c_void, self),
+ };
+
+ _ = pfn(
+ @intToPtr(vk.c.VkInstance, @enumToInt(self.instance)),
+ &messenger_create_info,
+ null,
+ &messenger
+ );
+ } else {
+ return error.FunctionUnsupported;
+ }
+ }
+
+ pub fn get_binding(self: *const Graphics) xr.GraphicsBindingVulkan2KHR {
+ return .{
+ .instance = self.instance,
+ .device = self.device,
+ .physical_device = self.physical_device,
+ .queue_family_index = self.family_index,
+ .queue_index = 0,
+ };
+ }
+
+ pub fn render_view(self: *const Graphics, view: xr.CompositionLayerProjectionView, swapchain: Swapchain, i: u32) !void {
+ const buf = swapchain.command_buffers[0];
+ const fence = swapchain.fence;
+
+ // reset
+ try self.vkd.resetFences(self.device, 1, @ptrCast([*]const vk.Fence, &fence));
+ try self.vkd.resetCommandBuffer(buf, .{});
+
+ // begin
+ try self.vkd.beginCommandBuffer(buf, .{
+ .flags = .{},
+ .p_inheritance_info = null,
+ });
+
+ // main
+
+ if (false) {
+ const image_ranges = [_]vk.ImageSubresourceRange{
+ .{
+ .aspect_mask = .{ .color_bit = true },
+ .base_mip_level = 0,
+ .level_count = vk.REMAINING_MIP_LEVELS,
+ .base_array_layer = 0,
+ .layer_count = vk.REMAINING_ARRAY_LAYERS,
+ },
+ };
+
+ var image_barrier = [_]vk.ImageMemoryBarrier{
+ .{
+ .src_access_mask = .{},
+ .dst_access_mask = .{ .transfer_write_bit = true },
+ .old_layout = .color_attachment_optimal,
+ .new_layout = .transfer_dst_optimal,
+ .src_queue_family_index = self.family_index,
+ .dst_queue_family_index = self.family_index,
+ .image = undefined,
+ .subresource_range = image_ranges[0],
+ },
+ .{
+ .src_access_mask = .{ .transfer_write_bit = true },
+ .dst_access_mask = .{},
+ .old_layout = .transfer_dst_optimal,
+ .new_layout = .color_attachment_optimal,
+ .src_queue_family_index = self.family_index,
+ .dst_queue_family_index = self.family_index,
+ .image = undefined,
+ .subresource_range = image_ranges[0],
+ },
+ };
+ image_barrier[0].image = swapchain.images[i];
+ image_barrier[1].image = swapchain.images[i];
+
+ self.vkd.cmdPipelineBarrier(
+ buf,
+ .{ .transfer_bit = true }, .{ .transfer_bit = true }, .{},
+ 0, @ptrCast([*]vk.MemoryBarrier, &image_barrier),
+ 0, @ptrCast([*]vk.BufferMemoryBarrier, &image_barrier),
+ 1, image_barrier[0..]
+ );
+
+ self.vkd.cmdClearColorImage(
+ buf, swapchain.images[i], .transfer_dst_optimal,
+ .{ .uint_32 = .{ @intCast(u32, (i * 80)) % 256, 30, 34, 255 } },
+ image_ranges.len, &image_ranges
+ );
+
+ self.vkd.cmdPipelineBarrier(
+ buf,
+ .{ .transfer_bit = true }, .{ .transfer_bit = true }, .{},
+ 0, @ptrCast([*]vk.MemoryBarrier, &image_barrier),
+ 0, @ptrCast([*]vk.BufferMemoryBarrier, &image_barrier),
+ 1, image_barrier[1..]
+ );
+ } else {
+
- // GET QUEUE
+ }
+
+ // end
+ try self.vkd.endCommandBuffer(buf);
+
+ // exec
+ std.debug.print("using command buffer {}\n", .{ i });
+ const submits = [_]vk.SubmitInfo{
+ .{
+ .wait_semaphore_count = 0,
+ .p_wait_semaphores = undefined,
+ .p_wait_dst_stage_mask = undefined,
+
+ .command_buffer_count = 1,
+ .p_command_buffers = swapchain.command_buffers[0..].ptr,
+
+ .signal_semaphore_count = 0,
+ .p_signal_semaphores = undefined,
+ },
+ };
+ try self.vkd.queueSubmit(self.queue, submits.len, &submits, swapchain.fence);
+
+ // wait
+ while (true) {
+ const res = try self.vkd.waitForFences(self.device, 1, @ptrCast([*]const vk.Fence, &fence), vk.TRUE, 1_000_000_000);
+ if (res == .success) {
+ break;
+ }
+ std.debug.print("timed out waiting for fence...\n", .{});
+ }
+ }
+};
+
+pub const Swapchain = struct {
+ handle: xr.Swapchain,
+ images: []vk.Image,
+ image_rect: xr.Rect2Di,
+ command_buffers: []vk.CommandBuffer,
+ fence: vk.Fence,
+
+ graphics: *const Graphics,
+
+ pub fn init(gfx: *const Graphics, session: xr.Session, config: xr.ViewConfigurationView) !Swapchain {
+ var self: Swapchain = undefined;
- vk_queue = vkd.getDeviceQueue(vk_binding.device, family_index, 0);
- vk_binding.queue_family_index = family_index;
- vk_binding.queue_index = 0;
+ self.graphics = gfx;
+
+ // xri.enumerateSwapchainFormats(...);
+
+ self.image_rect = .{
+ .offset = .{},
+ .extent = .{
+ .width = @intCast(i32, config.recommended_image_rect_width),
+ .height = @intCast(i32, config.recommended_image_rect_height),
+ },
+ };
+
+ const create_result = try gfx.xri.createSwapchain(session, .{
+ .create_flags = .{},
+ .usage_flags = .{
+ .color_attachment_bit = true,
+ // .depth_stencil_attachment_bit = true,
+ .transfer_dst_bit = true,
+ .sampled_bit = true,
+ },
+ .format = @enumToInt(vk.Format.r8g8b8a8_unorm),
+ .sample_count = config.recommended_swapchain_sample_count,
+ .width = config.recommended_image_rect_width,
+ .height = config.recommended_image_rect_height,
+ .face_count = 1,
+ .array_size = 1,
+ .mip_count = 1,
+ });
+ self.handle = create_result.swapchain;
+
+ const image_result = try gfx.xri.enumerateSwapchainImages(self.handle, 0, null);
+ const images = try gfx.allocator.alloc(xr.SwapchainImageVulkan2KHR, image_result.image_count_output);
+ defer gfx.allocator.free(images);
+
+ _ = try gfx.xri.enumerateSwapchainImages(self.handle, image_result.image_count_output, @ptrCast([*]xr.SwapchainImageBaseHeader, images.ptr));
+
+ self.images = try gfx.allocator.alloc(vk.Image, images.len);
+ errdefer gfx.allocator.free(self.images);
+
+ for (images) |xr_image, i| {
+ self.images[i] = xr_image.image;
+ }
- return @ptrCast(*c_void, &vk_binding);
-}
+ self.command_buffers = try gfx.allocator.alloc(vk.CommandBuffer, images.len);
+ errdefer gfx.allocator.free(self.command_buffers);
+ try gfx.vkd.allocateCommandBuffers(gfx.device, .{
+ .command_pool = gfx.command_pool,
+ .command_buffer_count = image_result.image_count_output,
+ .level = .primary,
+ }, self.command_buffers.ptr);
+ std.debug.print( "allocating command buffers: {}\n", .{ image_result.image_count_output });
+
+ std.debug.print( "created swapchain {}x{}, SS{}, {} images\n", .{
+ config.recommended_image_rect_width,
+ config.recommended_image_rect_height,
+ config.recommended_swapchain_sample_count,
+ images.len,
+ });
+
+ self.fence = try gfx.vkd.createFence(gfx.device, .{ .flags = .{} }, null);
+
+ // try self.record_buffers();
+
+ return self;
+ }
+
+ fn record_buffers(self: Swapchain) !void {
+ const gfx = self.graphics;
+
+ const image_ranges = [_]vk.ImageSubresourceRange{
+ .{
+ .aspect_mask = .{ .color_bit = true },
+ .base_mip_level = 0,
+ .level_count = 1,
+ .base_array_layer = 0,
+ .layer_count = 1,
+ },
+ };
+
+ var image_barrier = [_]vk.ImageMemoryBarrier{
+ .{
+ .src_access_mask = .{},
+ .dst_access_mask = .{ .transfer_write_bit = true },
+ .old_layout = .color_attachment_optimal,
+ .new_layout = .general,
+ .src_queue_family_index = gfx.family_index,
+ .dst_queue_family_index = gfx.family_index,
+ .image = undefined,
+ .subresource_range = image_ranges[0],
+ },
+ .{
+ .src_access_mask = .{ .transfer_write_bit = true },
+ .dst_access_mask = .{},
+ .old_layout = .general,
+ .new_layout = .color_attachment_optimal,
+ .src_queue_family_index = gfx.family_index,
+ .dst_queue_family_index = gfx.family_index,
+ .image = undefined,
+ .subresource_range = image_ranges[0],
+ },
+ };
+
+ for (self.command_buffers) |buf, i| {
+ std.debug.print("recording buffer {}\n", .{ i });
+ try gfx.vkd.beginCommandBuffer(buf, .{
+ .flags = .{ .simultaneous_use_bit = true },
+ .p_inheritance_info = null,
+ });
+
+ image_barrier[0].image = self.images[i];
+ image_barrier[1].image = self.images[i];
+
+ gfx.vkd.cmdPipelineBarrier(
+ buf,
+ .{ .transfer_bit = true }, .{ .transfer_bit = true }, .{},
+ 0, @ptrCast([*]vk.MemoryBarrier, &image_barrier),
+ 0, @ptrCast([*]vk.BufferMemoryBarrier, &image_barrier),
+ 1, image_barrier[0..]
+ );
+
+ gfx.vkd.cmdClearColorImage(
+ buf, self.images[i], .general,
+ .{ .float32 = .{ i / 4.0, 1.0, 0.5, 1.0 } },
+ image_ranges.len, &image_ranges
+ );
+
+ gfx.vkd.cmdPipelineBarrier(
+ buf,
+ .{ .transfer_bit = true }, .{ .transfer_bit = true }, .{},
+ 0, @ptrCast([*]vk.MemoryBarrier, &image_barrier),
+ 0, @ptrCast([*]vk.BufferMemoryBarrier, &image_barrier),
+ 1, image_barrier[1..]
+ );
+
+
+ try gfx.vkd.endCommandBuffer(buf);
+ }
+ }
+
+ pub fn deinit(self: Swapchain) void {
+ const gfx = self.graphics;
+
+ gfx.allocator.free(self.images);
+ gfx.allocator.free(self.command_buffers);
+ gfx.xri.destroySwapchain(self.handle) catch {};
+ }
+};
diff --git a/src/main.zig b/src/main.zig
index b7c7174..1d10253 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -1,179 +1,301 @@
+usingnamespace @import("xrvk.zig");
const std = @import("std");
-const xr = @import("openxr");
-const c = @import("c.zig");
+const gfx = @import("graphics.zig");
+const renderdoc = @import("renderdoc.zig");
const Allocator = std.mem.Allocator;
-const graphics = @import("./graphics.zig");
+pub fn main() !void {
+ try renderdoc.load_api();
+ var session = try Session.init(std.heap.page_allocator);
+ try session.loop();
+ session.deinit();
+}
-const BaseDispatch = struct {
- xrCreateInstance: xr.PfnCreateInstance,
- usingnamespace xr.BaseWrapper(@This());
-};
+const Session = struct {
+ xrb: xr.BaseDispatch,
+ xri: xr.InstanceDispatch,
-const InstanceDispatch = struct {
- xrDestroyInstance: xr.PfnDestroyInstance,
- xrGetSystem: xr.PfnGetSystem,
- xrGetSystemProperties: xr.PfnGetSystemProperties,
- xrCreateSession: xr.PfnCreateSession,
- xrBeginSession: xr.PfnBeginSession,
- xrEndSession: xr.PfnEndSession,
- xrWaitFrame: xr.PfnWaitFrame,
- xrBeginFrame: xr.PfnBeginFrame,
- xrEndFrame: xr.PfnEndFrame,
- xrPollEvent: xr.PfnPollEvent,
- xrLocateViews: xr.PfnLocateViews,
- xrCreateReferenceSpace: xr.PfnCreateReferenceSpace,
- xrCreateVulkanInstanceKHR: xr.PfnCreateVulkanInstanceKHR,
- xrGetVulkanGraphicsDevice2KHR: xr.PfnGetVulkanGraphicsDevice2KHR,
- xrGetVulkanGraphicsRequirements2KHR: xr.PfnGetVulkanGraphicsRequirements2KHR,
- xrCreateVulkanDeviceKHR: xr.PfnCreateVulkanDeviceKHR,
- usingnamespace xr.InstanceWrapper(@This());
-};
+ instance: xr.Instance,
+ session: xr.Session,
+ system: xr.SystemId,
-fn xrGetProcAddr(instance: xr.Instance, name: [*:0]const u8) !xr.PfnVoidFunction {
- var out: xr.PfnVoidFunction = undefined;
- const result = c.xrGetInstanceProcAddr(instance, name, &out);
- return switch (result) {
- .success => out,
- .error_handle_invalid => error.HandleInvalid,
- .error_instance_lost => error.InstanceLost,
- .error_runtime_failure => error.RuntimeFailure,
- .error_out_of_memory => error.OutOfMemory,
- .error_function_unsupported => error.FunctionUnsupported,
- .error_validation_failure => error.ValidationFailure,
- else => error.Unknown,
- };
-}
+ allocator: *Allocator,
-pub fn main() !void {
- const allocator = std.heap.page_allocator;
-
- var name: [128]u8 = undefined;
- std.mem.copy(u8, name[0..], "openxr-zig-test" ++ [_]u8{0});
- const zero = [_:0]u8{0};
-
- const extensions = [_][*:0]const u8 {
- "XR_KHR_vulkan_enable2",
- };
+ state: xr.SessionState,
+ views: ?ViewInfo,
+ graphics: gfx.Graphics,
+ layers: []*const xr.CompositionLayerBaseHeader,
+ scene_space: xr.Space,
- const xrb = try BaseDispatch.load(xrGetProcAddr);
- const inst = try xrb.createInstance(.{
- .create_flags = .{},
- .application_info = .{
- .application_name = name,
- .application_version = 0,
- .engine_name = name,
- .engine_version = 0,
- .api_version = xr.makeVersion(1, 0, 0),
- },
- .enabled_api_layer_count = 0,
- .enabled_api_layer_names = @ptrCast([*]const [*:0]const u8, &zero),
- .enabled_extension_count = extensions.len,
- .enabled_extension_names = &extensions,
- });
-
- const xri = try InstanceDispatch.load(inst, xrGetProcAddr);
- defer xri.destroyInstance(inst) catch unreachable;
-
- const system = try xri.getSystem(inst, .{ .form_factor = .head_mounted_display });
-
- var system_properties = xr.SystemProperties.empty();
- try xri.getSystemProperties(inst, system, &system_properties);
-
- std.debug.print(
- \\system {}:
- \\ vendor Id: {}
- \\ systemName: {}
- \\ gfx
- \\ max swapchain image resolution: {}x{}
- \\ max layer count: {}
- \\ tracking
- \\ orientation tracking: {}
- \\ positional tracking: {}
- \\
- , .{
- system,
- system_properties.vendor_id,
- system_properties.system_name,
- system_properties.graphics_properties.max_swapchain_image_width,
- system_properties.graphics_properties.max_swapchain_image_height,
- system_properties.graphics_properties.max_layer_count,
- system_properties.tracking_properties.orientation_tracking,
- system_properties.tracking_properties.position_tracking,
- });
-
- const session = try xri.createSession(inst, .{
- .next = try graphics.get_binding(xri, inst, system, allocator),
- .create_flags = .{},
- .system_id = system,
- });
-
- _ = try xri.beginSession(session, .{
- .primary_view_configuration_type = .primary_stereo,
- });
-
- const sceneSpace = (try xri.createReferenceSpace(session, .{
- .reference_space_type = .stage,
- .pose_in_reference_space = .{
- .orientation = .{ .w = 1 },
- },
- })).space;
-
- var projViews = [_]xr.CompositionLayerProjectionView{xr.CompositionLayerProjectionView.empty()} ** 2;
-
- const compositionLayer = xr.CompositionLayerProjection{
- .layer_flags = .{ .blend_texture_source_alpha_bit = true },
- .space = sceneSpace,
- .view_count = 2,
- .views = &projViews,
- };
- const layers = [_]*const xr.CompositionLayerBaseHeader{
- @ptrCast(*const xr.CompositionLayerBaseHeader, &compositionLayer),
+ first_render: bool = true,
+
+ const ViewInfo = struct {
+ configurations: []xr.ViewConfigurationView,
+ projections: []xr.CompositionLayerProjectionView,
+ swapchains: []gfx.Swapchain,
};
- while (true) {
- // todo: fix API
- var frameWaitInfo = xr.FrameWaitInfo.empty();
- var frameState = xr.FrameState.empty();
+ pub fn init(allocator: *Allocator) !Session {
+ var name: [128]u8 = undefined;
+ std.mem.copy(u8, name[0..], "openxr-zig-test" ++ [_]u8{0});
+ const zero = [_:0]u8{};
- _ = try xri.waitFrame(session, &frameWaitInfo, &frameState);
+ const layers = [_][*:0]const u8{
+ "XR_APILAYER_LUNARG_core_validation",
+ };
+
+ const extensions = [_][*:0]const u8{
+ "XR_KHR_vulkan_enable2",
+ };
- _ = try xri.beginFrame(session, &xr.FrameBeginInfo{});
+ const xrb = try xr.BaseDispatch.load(xr.getProcAddr);
+ const instance = try xrb.createInstance(.{
+ .create_flags = .{},
+ .application_info = .{
+ .application_name = name,
+ .application_version = 0,
+ .engine_name = name,
+ .engine_version = 0,
+ .api_version = xr.makeVersion(1, 0, 0),
+ },
+ .enabled_api_layer_count = 0,
+ .enabled_api_layer_names = @ptrCast([*]const [*:0]const u8, &zero),
+ .enabled_extension_count = extensions.len,
+ .enabled_extension_names = &extensions,
+ });
- var layer_count: u32 = 0;
+ const xri = try xr.InstanceDispatch.load(instance, xr.getProcAddr);
+ errdefer xri.destroyInstance(instance) catch unreachable;
- if (frameState.should_render > 0) {
- var views = [_]xr.View{xr.View.empty()} ** 2;
- var viewState = xr.ViewState.empty();
+ const system = try xri.getSystem(instance, .{ .form_factor = .head_mounted_display });
- _ = try xri.locateViews(session, .{
- .view_configuration_type = .primary_stereo,
- .display_time = frameState.predicted_display_time,
- .space = sceneSpace,
- }, &viewState, 2, &views);
+ var system_properties = xr.SystemProperties.empty();
+ try xri.getSystemProperties(instance, system, &system_properties);
- std.debug.print("views: {}\n", .{ views });
+ std.debug.print(
+ \\system {}:
+ \\ vendor Id: {}
+ \\ systemName: {}
+ \\ gfx
+ \\ max swapchain image resolution: {}x{}
+ \\ max layer count: {}
+ \\ tracking
+ \\ orientation tracking: {}
+ \\ positional tracking: {}
+ \\
+ , .{
+ system,
+ system_properties.vendor_id,
+ system_properties.system_name,
+ system_properties.graphics_properties.max_swapchain_image_width,
+ system_properties.graphics_properties.max_swapchain_image_height,
+ system_properties.graphics_properties.max_layer_count,
+ system_properties.tracking_properties.orientation_tracking,
+ system_properties.tracking_properties.position_tracking,
+ });
+
+ var graphics = try gfx.Graphics.init(allocator, xri, instance, system);
+ try graphics.setup_debugging();
+
+ const binding = graphics.get_binding();
+ const session = try xri.createSession(instance, .{
+ .next = &binding,
+ .create_flags = .{},
+ .system_id = system,
+ });
+ errdefer xri.destroySession(session) catch unreachable;
- for (views) |view, i| {
- projViews[i].pose = view.pose;
- projViews[i].fov = view.fov;
- projViews[i].sub_image = .{
- .swapchain = xr.Swapchain.null_handle,
- .image_rect = .{},
- .image_array_index = 0,
- };
+ const scene_space = (try xri.createReferenceSpace(session, .{
+ .reference_space_type = .stage,
+ .pose_in_reference_space = .{
+ .orientation = .{ .w = 1 },
+ },
+ })).space;
+
+ return Session{
+ .xrb = xrb,
+ .xri = xri,
+
+ .instance = instance,
+ .session = session,
+ .system = system,
+ .allocator = allocator,
+
+ .state = .idle,
+ .views = null,
+ .graphics = graphics,
+ .scene_space = scene_space,
+ .layers = &[_]*const xr.CompositionLayerBaseHeader{},
+ };
+ }
+
+ pub fn deinit(self: *const Session) void {
+ if (self.views) |views| {
+ for (views.swapchains) |swapchain| {
+ swapchain.deinit();
}
+ self.allocator.free(views.configurations);
+ self.allocator.free(views.projections);
+ self.allocator.free(views.swapchains);
+ }
- layer_count = 1;
+ for (self.layers) |layer| {
+ self.allocator.destroy(layer);
}
- _ = try xri.endFrame(session, .{
- .display_time = frameState.predicted_display_time,
- .environment_blend_mode = .@"opaque",
- .layer_count = layer_count,
- .layers = &layers,
- });
+ _ = self.xri.destroySession(self.session) catch null;
+ _ = self.xri.destroyInstance(self.instance) catch null;
}
- try xri.endSession(session);
-}
+ pub fn loop(self: *Session) !void {
+ const view_count = try self.xri.enumerateViewConfigurationViews(self.instance, self.system, .primary_stereo, 0, null);
+ const views = ViewInfo{
+ .configurations = try self.allocator.alloc(xr.ViewConfigurationView, view_count),
+ .projections = try self.allocator.alloc(xr.CompositionLayerProjectionView, view_count),
+ .swapchains = try self.allocator.alloc(gfx.Swapchain, view_count),
+ };
+
+ _ = try self.xri.enumerateViewConfigurationViews(self.instance, self.system, .primary_stereo, view_count, views.configurations.ptr);
+
+ for (views.configurations) |config, i| {
+ if (renderdoc.api) |api| api.StartFrameCapture(null, null);
+ views.projections[i] = xr.CompositionLayerProjectionView.empty();
+ views.swapchains[i] = try gfx.Swapchain.init(&self.graphics, self.session, config);
+ if (renderdoc.api) |api| _ = api.EndFrameCapture(null, null);
+ }
+
+ const composition_layer = try self.allocator.create(xr.CompositionLayerProjection);
+ composition_layer.* = .{
+ .layer_flags = .{ .blend_texture_source_alpha_bit = true },
+ .space = self.scene_space,
+ .view_count = view_count,
+ .views = views.projections.ptr,
+ };
+
+ self.views = views;
+ self.layers = try self.allocator.alloc(*xr.CompositionLayerBaseHeader, 1);
+ self.layers[0] = @ptrCast(*const xr.CompositionLayerBaseHeader, composition_layer);
+
+ while (true) main: {
+ while (true) {
+ var event = xr.EventDataBuffer.empty();
+ const poll_result = try self.xri.pollEvent(self.instance, &event);
+
+ if (poll_result == .event_unavailable) {
+ break;
+ }
+
+ switch (event.type) {
+ .event_data_session_state_changed => {
+ const state_event = @ptrCast(*const xr.EventDataSessionStateChanged, &event);
+ std.debug.print("session state {} → {}\n", .{ self.state, state_event.state });
+ self.state = state_event.state;
+ switch (self.state) {
+ .ready => {
+ _ = try self.xri.beginSession(self.session, .{ .primary_view_configuration_type = .primary_stereo });
+ },
+ .stopping => {
+ _ = try self.xri.endSession(self.session);
+ break :main;
+ },
+ .idle, .synchronized, .visible, .focused, .exiting => {},
+ .loss_pending => return error.LossPending,
+ else => return error.InvalidState,
+ }
+ },
+ .event_data_interaction_profile_changed, .event_data_reference_space_change_pending => {
+ std.debug.print("skipping event: {}\n", .{event.type});
+ },
+
+ .event_data_instance_loss_pending => break,
+ .event_data_events_lost => return error.EventsLost,
+
+ else => {
+ std.debug.print("unknown event: {}\n", .{event.type});
+ },
+ }
+ }
+
+ if (!try self.render_frame()) {
+ std.os.nanosleep(0, 250_000_000);
+ }
+ }
+ }
+
+ pub fn render_frame(self: *Session) !bool {
+ if (self.views) |views| {
+ var frame_state = xr.FrameState.empty();
+ _ = try self.xri.waitFrame(self.session, &xr.FrameWaitInfo{}, &frame_state);
+
+ _ = try self.xri.beginFrame(self.session, &xr.FrameBeginInfo{});
+
+ var layer_count: u32 = 0;
+
+ if (frame_state.should_render > 0) {
+ std.debug.print("renderin'\n", .{});
+ if (self.first_render) if (renderdoc.api) |api| api.StartFrameCapture(null, null);
+
+ var located_views = [_]xr.View{xr.View.empty()} ** 2;
+ var view_state = xr.ViewState.empty();
+
+ _ = try self.xri.locateViews(self.session, .{
+ .view_configuration_type = .primary_stereo,
+ .display_time = frame_state.predicted_display_time,
+ .space = self.scene_space,
+ }, &view_state, 2, &located_views);
+
+ if (!view_state.view_state_flags.contains(.{ .position_valid_bit = true, .orientation_valid_bit = true })) {
+ return error.InvalidPositionOrOrientation;
+ }
+
+ for (located_views) |located_view, i| {
+ const swapchain = views.swapchains[i];
+ const acquire_result = try self.xri.acquireSwapchainImage(swapchain.handle, &xr.SwapchainImageAcquireInfo.empty());
+
+ _ = try self.xri.waitSwapchainImage(swapchain.handle, .{ .timeout = -1 });
+
+ views.projections[i] = .{
+ .pose = located_view.pose,
+ .fov = located_view.fov,
+ .sub_image = .{
+ .swapchain = swapchain.handle,
+ .image_rect = swapchain.image_rect,
+ .image_array_index = 0,
+ },
+ };
+
+ std.debug.print("submitting view {}: {}\n", .{ i, views.projections[i].sub_image });
+
+ try self.graphics.render_view(views.projections[i], swapchain, acquire_result.index);
+
+ _ = try self.xri.releaseSwapchainImage(swapchain.handle, &xr.SwapchainImageReleaseInfo.empty());
+ }
+
+ _ = try self.xri.endFrame(self.session, .{
+ .display_time = frame_state.predicted_display_time,
+ .environment_blend_mode = .@"opaque",
+ .layer_count = @intCast(u32, self.layers.len),
+ .layers = self.layers.ptr,
+ });
+
+ if (self.first_render) {
+ if (renderdoc.api) |api| _ = api.EndFrameCapture(null, null);
+ self.first_render = false;
+ }
+
+ return true;
+ }
+
+ _ = try self.xri.endFrame(self.session, .{
+ .display_time = frame_state.predicted_display_time,
+ .environment_blend_mode = .@"opaque",
+ .layer_count = 0,
+ .layers = null,
+ });
+
+ return false;
+ }
+
+ unreachable;
+ }
+};
diff --git a/src/renderdoc.zig b/src/renderdoc.zig
new file mode 100644
index 0000000..48cbb16
--- /dev/null
+++ b/src/renderdoc.zig
@@ -0,0 +1,77 @@
+const std = @import("std");
+const c = @cImport({
+ @cInclude("renderdoc_app.h");
+ @cInclude("dlfcn.h");
+});
+
+fn fix_fields(comptime T: type) type {
+ const TypeId = std.builtin.TypeId;
+ var ti = @typeInfo(T);
+
+ const num_fields = ti.Struct.fields.len;
+ var fields = [_]TypeId.StructField{undefined} ** num_fields;
+ for (ti.Struct.fields) |field, i| {
+ var field_ti = @typeInfo(field.field_type);
+
+ switch (field_ti) {
+ .Optional => |opt| {
+ fields[i] = TypeId.StructField{
+ .name = field.name,
+ .field_type = opt.child,
+ .default_value = @as(opt.child, fields[i].default_value),
+ .is_comptime = field.is_comptime,
+ .alignment = field.alignment,
+ };
+ },
+ else => {
+ fields[i] = field;
+ }
+ }
+ }
+
+ ti.Struct.fields = fields[0..];
+ return @Type(.{
+ .Struct = .{
+ .layout = .Extern,
+ .fields = fields,
+ .decls = [_]TypeId.Declaration{},
+ .is_tuple = false,
+ },
+ });
+}
+
+const API = struct {
+ StartFrameCapture: fn (c.RENDERDOC_DevicePointer, c.RENDERDOC_WindowHandle) callconv(.C) void,
+ EndFrameCapture: fn (c.RENDERDOC_DevicePointer, c.RENDERDOC_WindowHandle) callconv(.C) u32,
+};
+
+pub var api: ?API = null;
+
+extern fn RENDERDOC_GetAPI(c.RENDERDOC_Version, [*c]?*c_void) callconv(.C) c_int;
+
+pub fn load_api() !void {
+ var tmp_api: ?*c.RENDERDOC_API_1_1_2 = null;
+ if (RENDERDOC_GetAPI(.eRENDERDOC_API_Version_1_1_2, @ptrCast([*c]?*c_void, &tmp_api)) != 1) {
+ return error.Unknown;
+ }
+
+ if (tmp_api) |_api| {
+ var local_api : API = undefined;
+ var have_all: bool = true;
+ if (_api.StartFrameCapture) |fp| {
+ local_api.StartFrameCapture = fp;
+ } else {
+ return error.NoStart;
+ }
+
+ if (_api.EndFrameCapture) |fp| {
+ local_api.EndFrameCapture = fp;
+ } else {
+ return error.NoEnd;
+ }
+
+ api = local_api;
+ } else {
+ return error.ApiEmpty;
+ }
+}
diff --git a/src/xrvk.zig b/src/xrvk.zig
new file mode 100644
index 0000000..a433b36
--- /dev/null
+++ b/src/xrvk.zig
@@ -0,0 +1,93 @@
+const rvk = @import("vulkan");
+const rxr = @import("openxr");
+
+pub extern fn xrGetInstanceProcAddr(instance: xr.Instance, procname: [*:0]const u8, function: *xr.PfnVoidFunction) xr.Result;
+pub extern fn vkGetInstanceProcAddr(instance: vk.Instance, procname: [*:0]const u8) vk.PfnVoidFunction;
+pub extern fn vkGetDeviceProcAddr(instance: vk.Device, procname: [*:0]const u8) vk.PfnVoidFunction;
+
+pub const vk = struct {
+ usingnamespace rvk;
+
+ pub const c = @cImport(@cInclude("vulkan/vulkan_core.h"));
+
+ pub const getInstanceProcAddr = vkGetInstanceProcAddr;
+ pub const getDeviceProcAddr = vkGetDeviceProcAddr;
+
+ pub const InstanceDispatch = struct {
+ vkDestroyInstance: PfnDestroyInstance,
+ vkGetPhysicalDeviceQueueFamilyProperties: PfnGetPhysicalDeviceQueueFamilyProperties,
+ vkCreateDebugUtilsMessengerEXT: c.PFN_vkCreateDebugUtilsMessengerEXT,
+ usingnamespace InstanceWrapper(@This());
+ };
+
+ pub const DeviceDispatch = struct {
+ vkDestroyDevice: PfnDestroyDevice,
+ vkGetDeviceQueue: PfnGetDeviceQueue,
+ vkQueueSubmit: PfnQueueSubmit,
+ vkCreateCommandPool: PfnCreateCommandPool,
+ vkDestroyCommandPool: PfnDestroyCommandPool,
+ vkAllocateCommandBuffers: PfnAllocateCommandBuffers,
+ vkBeginCommandBuffer: PfnBeginCommandBuffer,
+ vkResetCommandBuffer: PfnResetCommandBuffer,
+ vkEndCommandBuffer: PfnEndCommandBuffer,
+ vkCmdClearColorImage: PfnCmdClearColorImage,
+ vkCmdPipelineBarrier: PfnCmdPipelineBarrier,
+ vkCreateFence: PfnCreateFence,
+ vkWaitForFences: PfnWaitForFences,
+ vkResetFences: PfnResetFences,
+ vkDestroyFence: PfnDestroyFence,
+ usingnamespace DeviceWrapper(@This());
+ };
+};
+
+pub const xr = struct {
+ usingnamespace rxr;
+
+ pub const BaseDispatch = struct {
+ xrCreateInstance: PfnCreateInstance,
+ usingnamespace BaseWrapper(@This());
+ };
+
+ pub const InstanceDispatch = struct {
+ xrDestroyInstance: PfnDestroyInstance,
+ xrGetSystem: PfnGetSystem,
+ xrGetSystemProperties: PfnGetSystemProperties,
+ xrCreateSession: PfnCreateSession,
+ xrDestroySession: PfnDestroySession,
+ xrBeginSession: PfnBeginSession,
+ xrEndSession: PfnEndSession,
+ xrWaitFrame: PfnWaitFrame,
+ xrBeginFrame: PfnBeginFrame,
+ xrEndFrame: PfnEndFrame,
+ xrPollEvent: PfnPollEvent,
+ xrLocateViews: PfnLocateViews,
+ xrCreateReferenceSpace: PfnCreateReferenceSpace,
+ xrCreateVulkanInstanceKHR: PfnCreateVulkanInstanceKHR,
+ xrGetVulkanGraphicsDevice2KHR: PfnGetVulkanGraphicsDevice2KHR,
+ xrGetVulkanGraphicsRequirements2KHR: PfnGetVulkanGraphicsRequirements2KHR,
+ xrCreateVulkanDeviceKHR: PfnCreateVulkanDeviceKHR,
+ xrEnumerateViewConfigurationViews: PfnEnumerateViewConfigurationViews,
+ xrCreateSwapchain: PfnCreateSwapchain,
+ xrDestroySwapchain: PfnDestroySwapchain,
+ xrEnumerateSwapchainImages: PfnEnumerateSwapchainImages,
+ xrAcquireSwapchainImage: PfnAcquireSwapchainImage,
+ xrWaitSwapchainImage: PfnWaitSwapchainImage,
+ xrReleaseSwapchainImage: PfnReleaseSwapchainImage,
+ usingnamespace InstanceWrapper(@This());
+ };
+
+ pub fn getProcAddr(instance: Instance, name: [*:0]const u8) !PfnVoidFunction {
+ var out: PfnVoidFunction = undefined;
+ const result = xrGetInstanceProcAddr(instance, name, &out);
+ return switch (result) {
+ .success => out,
+ .error_handle_invalid => error.HandleInvalid,
+ .error_instance_lost => error.InstanceLost,
+ .error_runtime_failure => error.RuntimeFailure,
+ .error_out_of_memory => error.OutOfMemory,
+ .error_function_unsupported => error.FunctionUnsupported,
+ .error_validation_failure => error.ValidationFailure,
+ else => error.Unknown,
+ };
+ }
+};