Refactor Vulkan setup phase 3
This commit is contained in:
parent
6e18542b2b
commit
ed1b9db61e
@ -1,7 +1,11 @@
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec2 in_position;
|
||||
vec2 positions[3] = vec2[](
|
||||
vec2(0.0, -0.5),
|
||||
vec2(0.5, 0.5),
|
||||
vec2(-0.5, 0.5)
|
||||
);
|
||||
|
||||
void main() {
|
||||
gl_Position = vec4(in_position, 0.0, 1.0);
|
||||
gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
|
||||
}
|
||||
|
||||
750
src/main.zig
750
src/main.zig
@ -2,54 +2,22 @@ const std = @import("std");
|
||||
const glfw = @import("zglfw");
|
||||
const vk = @import("vulkan");
|
||||
|
||||
const window_mod = @import("window.zig");
|
||||
const commands_mod = @import("vulkan/commands.zig");
|
||||
const context_mod = @import("vulkan/context.zig");
|
||||
const device_mod = @import("vulkan/device.zig");
|
||||
const frame_mod = @import("vulkan/frame.zig");
|
||||
const framebuffers_mod = @import("vulkan/framebuffers.zig");
|
||||
const render_pass_mod = @import("vulkan/render_pass.zig");
|
||||
const swapchain_mod = @import("vulkan/swapchain.zig");
|
||||
const sync_mod = @import("vulkan/sync.zig");
|
||||
|
||||
// The build script compiles these GLSL files to SPIR-V and exposes them as
|
||||
// anonymous imports. They are currently only loaded and printed; the program
|
||||
// does not create shader modules or a graphics pipeline yet.
|
||||
const square_vert_spv = @embedFile("square_vertex_shader");
|
||||
const square_frag_spv = @embedFile("square_fragment_shader");
|
||||
|
||||
const VulkanContext = struct {
|
||||
base: vk.BaseWrapper,
|
||||
instance: vk.Instance,
|
||||
vki: vk.InstanceWrapper,
|
||||
|
||||
fn destroy(self: *const VulkanContext) void {
|
||||
self.vki.destroyInstance(self.instance, null);
|
||||
}
|
||||
};
|
||||
|
||||
const LogicalDeviceContext = struct {
|
||||
physical_device: vk.PhysicalDevice,
|
||||
graphics_queue_family_index: u32,
|
||||
device: vk.Device,
|
||||
vkd: vk.DeviceWrapper,
|
||||
graphics_queue: vk.Queue,
|
||||
|
||||
fn destroy(self: *const LogicalDeviceContext) void {
|
||||
self.vkd.destroyDevice(self.device, null);
|
||||
}
|
||||
};
|
||||
|
||||
const SwapchainContext = struct {
|
||||
swapchain: vk.SwapchainKHR,
|
||||
images: []vk.Image,
|
||||
image_views: []vk.ImageView,
|
||||
format: vk.SurfaceFormatKHR,
|
||||
present_mode: vk.PresentModeKHR,
|
||||
extent: vk.Extent2D,
|
||||
image_count: u32,
|
||||
allocator: std.mem.Allocator,
|
||||
|
||||
fn destroy(self: *const SwapchainContext, ldc: *const LogicalDeviceContext) void {
|
||||
for (self.image_views) |image_view| {
|
||||
ldc.vkd.destroyImageView(ldc.device, image_view, null);
|
||||
}
|
||||
self.allocator.free(self.image_views);
|
||||
self.allocator.free(self.images);
|
||||
ldc.vkd.destroySwapchainKHR(ldc.device, self.swapchain, null);
|
||||
}
|
||||
};
|
||||
|
||||
pub fn main() !void {
|
||||
std.debug.print("zig-chess bootstrap\n", .{});
|
||||
std.debug.print("vertex shader bytes: {}\n", .{square_vert_spv.len});
|
||||
@ -58,8 +26,7 @@ pub fn main() !void {
|
||||
// ---------------------------------------------------------------------
|
||||
// Window bootstrap
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
const window = try initWindow(800, 600, "zig-chess");
|
||||
const window = try window_mod.initWindow(800, 600, "zig-chess");
|
||||
defer glfw.terminate();
|
||||
defer window.destroy();
|
||||
|
||||
@ -69,16 +36,11 @@ pub fn main() !void {
|
||||
// ---------------------------------------------------------------------
|
||||
// Vulkan instance setup
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
const vc = try initInstance("zig-chess");
|
||||
const vc = try context_mod.initInstance("zig-chess");
|
||||
defer vc.destroy();
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Window surface
|
||||
//
|
||||
// This connects the platform window to Vulkan presentation. Swapchain
|
||||
// support and present-capable queue families are queried against this
|
||||
// surface.
|
||||
// ---------------------------------------------------------------------
|
||||
var surface: vk.SurfaceKHR = undefined;
|
||||
try glfw.createWindowSurface(vc.instance, window, null, &surface);
|
||||
@ -88,10 +50,9 @@ pub fn main() !void {
|
||||
// ---------------------------------------------------------------------
|
||||
// Physical device and queue-family discovery
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
const physical_devices = try vc.vki.enumeratePhysicalDevicesAlloc(vc.instance, std.heap.page_allocator);
|
||||
defer std.heap.page_allocator.free(physical_devices);
|
||||
//try debugPhysicalGPUs(vc, physical_devices, surface);
|
||||
// try device_mod.debugPhysicalGPUs(vc, physical_devices, surface);
|
||||
|
||||
// TODO(refactor): this is intentionally temporary and machine-specific.
|
||||
// Replace it with selection logic that searches for a device/queue pair
|
||||
@ -112,642 +73,133 @@ pub fn main() !void {
|
||||
// ---------------------------------------------------------------------
|
||||
// Logical device and queue
|
||||
// ---------------------------------------------------------------------
|
||||
const ldc = try initLogicalDevice(vc, selected_physical_device, graphics_queue_family_index);
|
||||
const ldc = try device_mod.initLogicalDevice(vc, selected_physical_device, graphics_queue_family_index);
|
||||
defer ldc.destroy();
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Swapchain setup
|
||||
// Swapchain-dependent rendering resources
|
||||
// ---------------------------------------------------------------------
|
||||
const swapchain_context = try initSwapchain(vc, ldc, surface, window, std.heap.page_allocator);
|
||||
var swapchain_context = try swapchain_mod.initSwapchain(vc, ldc, surface, window, std.heap.page_allocator);
|
||||
defer swapchain_context.destroy(&ldc);
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Render pass
|
||||
//
|
||||
// This render pass has one color attachment: the current swapchain image.
|
||||
// load_op=.clear means each frame starts by clearing the image; final
|
||||
// layout present_src_khr means the image is ready for presentation.
|
||||
//
|
||||
// Refactor direction: this can become createRenderPass(device, format).
|
||||
// Later, when drawing pieces or UI, this may gain depth/stencil or change
|
||||
// if we move to dynamic rendering.
|
||||
// ---------------------------------------------------------------------
|
||||
const color_attachment = vk.AttachmentDescription{
|
||||
.format = swapchain_context.format.format,
|
||||
.samples = .{ .@"1_bit" = true },
|
||||
.load_op = .clear,
|
||||
.store_op = .store,
|
||||
.stencil_load_op = .dont_care,
|
||||
.stencil_store_op = .dont_care,
|
||||
.initial_layout = .undefined,
|
||||
.final_layout = .present_src_khr,
|
||||
};
|
||||
var render_pass_context = try render_pass_mod.initRenderPass(ldc, swapchain_context.format.format);
|
||||
defer render_pass_context.destroy(&ldc);
|
||||
|
||||
const color_attachment_ref = vk.AttachmentReference{
|
||||
.attachment = 0,
|
||||
.layout = .color_attachment_optimal,
|
||||
};
|
||||
|
||||
const subpass = vk.SubpassDescription{
|
||||
.pipeline_bind_point = .graphics,
|
||||
.color_attachment_count = 1,
|
||||
.p_color_attachments = @ptrCast(&color_attachment_ref),
|
||||
};
|
||||
|
||||
const subpass_dependency = vk.SubpassDependency{
|
||||
.src_subpass = vk.SUBPASS_EXTERNAL,
|
||||
.dst_subpass = 0,
|
||||
.src_stage_mask = .{
|
||||
.color_attachment_output_bit = true,
|
||||
},
|
||||
.src_access_mask = .{},
|
||||
.dst_stage_mask = .{
|
||||
.color_attachment_output_bit = true,
|
||||
},
|
||||
.dst_access_mask = .{
|
||||
.color_attachment_write_bit = true,
|
||||
},
|
||||
};
|
||||
|
||||
const render_pass_create_info = vk.RenderPassCreateInfo{
|
||||
.attachment_count = 1,
|
||||
.p_attachments = @ptrCast(&color_attachment),
|
||||
.subpass_count = 1,
|
||||
.p_subpasses = @ptrCast(&subpass),
|
||||
.dependency_count = 1,
|
||||
.p_dependencies = @ptrCast(&subpass_dependency),
|
||||
};
|
||||
|
||||
const render_pass = try ldc.vkd.createRenderPass(ldc.device, &render_pass_create_info, null);
|
||||
defer ldc.vkd.destroyRenderPass(ldc.device, render_pass, null);
|
||||
|
||||
std.debug.print("created render pass\n", .{});
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Framebuffers
|
||||
//
|
||||
// A framebuffer binds the render pass attachment description to a concrete
|
||||
// image view. We need one framebuffer for each swapchain image view.
|
||||
// ---------------------------------------------------------------------
|
||||
const framebuffers = try std.heap.page_allocator.alloc(
|
||||
vk.Framebuffer,
|
||||
swapchain_context.image_views.len,
|
||||
var framebuffer_context = try framebuffers_mod.initFramebuffers(
|
||||
ldc,
|
||||
render_pass_context,
|
||||
swapchain_context,
|
||||
std.heap.page_allocator,
|
||||
);
|
||||
defer std.heap.page_allocator.free(framebuffers);
|
||||
defer framebuffer_context.destroy(&ldc);
|
||||
|
||||
for (swapchain_context.image_views, 0..) |image_view, i| {
|
||||
const attachments = [_]vk.ImageView{image_view};
|
||||
|
||||
const framebuffer_create_info = vk.FramebufferCreateInfo{
|
||||
.render_pass = render_pass,
|
||||
.attachment_count = attachments.len,
|
||||
.p_attachments = &attachments,
|
||||
.width = swapchain_context.extent.width,
|
||||
.height = swapchain_context.extent.height,
|
||||
.layers = 1,
|
||||
};
|
||||
|
||||
framebuffers[i] = try ldc.vkd.createFramebuffer(
|
||||
ldc.device,
|
||||
&framebuffer_create_info,
|
||||
null,
|
||||
);
|
||||
}
|
||||
|
||||
defer {
|
||||
for (framebuffers) |framebuffer| {
|
||||
ldc.vkd.destroyFramebuffer(ldc.device, framebuffer, null);
|
||||
}
|
||||
}
|
||||
|
||||
std.debug.print("created framebuffers: {}\n", .{framebuffers.len});
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Command pool and command buffers
|
||||
//
|
||||
// Command buffers record GPU work. Right now each swapchain image gets one
|
||||
// pre-recorded command buffer that only clears the image.
|
||||
//
|
||||
// Refactor direction: for frame generation, introduce recordCommandBuffer()
|
||||
// and call it per frame after acquiring the image. That will make dynamic
|
||||
// board drawing, highlights, and resize handling easier to reason about.
|
||||
// ---------------------------------------------------------------------
|
||||
const command_pool_create_info = vk.CommandPoolCreateInfo{
|
||||
.flags = .{
|
||||
.reset_command_buffer_bit = true,
|
||||
},
|
||||
.queue_family_index = ldc.graphics_queue_family_index,
|
||||
};
|
||||
|
||||
const command_pool = try ldc.vkd.createCommandPool(
|
||||
ldc.device,
|
||||
&command_pool_create_info,
|
||||
null,
|
||||
var command_context = try commands_mod.initCommandBuffers(
|
||||
ldc,
|
||||
render_pass_context,
|
||||
framebuffer_context,
|
||||
swapchain_context,
|
||||
std.heap.page_allocator,
|
||||
);
|
||||
defer ldc.vkd.destroyCommandPool(ldc.device, command_pool, null);
|
||||
|
||||
std.debug.print("created command pool\n", .{});
|
||||
|
||||
const command_buffers = try std.heap.page_allocator.alloc(
|
||||
vk.CommandBuffer,
|
||||
framebuffers.len,
|
||||
);
|
||||
defer std.heap.page_allocator.free(command_buffers);
|
||||
|
||||
const command_buffer_allocate_info = vk.CommandBufferAllocateInfo{
|
||||
.command_pool = command_pool,
|
||||
.level = .primary,
|
||||
.command_buffer_count = @intCast(command_buffers.len),
|
||||
};
|
||||
|
||||
try ldc.vkd.allocateCommandBuffers(
|
||||
ldc.device,
|
||||
&command_buffer_allocate_info,
|
||||
command_buffers.ptr,
|
||||
);
|
||||
|
||||
std.debug.print("allocated command buffers: {}\n", .{command_buffers.len});
|
||||
|
||||
for (command_buffers, 0..) |command_buffer, i| {
|
||||
const begin_info = vk.CommandBufferBeginInfo{};
|
||||
|
||||
try ldc.vkd.beginCommandBuffer(command_buffer, &begin_info);
|
||||
|
||||
// This is the only "drawing" currently happening: begin a render pass
|
||||
// and clear the swapchain image. The embedded shaders are not used yet.
|
||||
const clear_color = vk.ClearValue{
|
||||
.color = .{
|
||||
.float_32 = .{ 0.02, 0.02, 0.08, 1.0 },
|
||||
},
|
||||
};
|
||||
|
||||
const render_pass_begin_info = vk.RenderPassBeginInfo{
|
||||
.render_pass = render_pass,
|
||||
.framebuffer = framebuffers[i],
|
||||
.render_area = .{
|
||||
.offset = .{ .x = 0, .y = 0 },
|
||||
.extent = swapchain_context.extent,
|
||||
},
|
||||
.clear_value_count = 1,
|
||||
.p_clear_values = @ptrCast(&clear_color),
|
||||
};
|
||||
|
||||
ldc.vkd.cmdBeginRenderPass(
|
||||
command_buffer,
|
||||
&render_pass_begin_info,
|
||||
.@"inline",
|
||||
);
|
||||
|
||||
ldc.vkd.cmdEndRenderPass(command_buffer);
|
||||
|
||||
try ldc.vkd.endCommandBuffer(command_buffer);
|
||||
}
|
||||
|
||||
std.debug.print("recorded command buffers\n", .{});
|
||||
defer command_context.destroy(&ldc);
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Synchronization objects
|
||||
//
|
||||
// The image-available semaphore is signaled when acquireNextImageKHR has a
|
||||
// swapchain image ready. The render-finished semaphore is signaled when GPU
|
||||
// rendering completes and presentation may wait on it. The fence lets the
|
||||
// CPU wait until submitted GPU work for this frame is done.
|
||||
//
|
||||
// Refactor direction: use arrays for 2 frames in flight, e.g.
|
||||
// image_available[2], render_finished[2], in_flight_fences[2].
|
||||
// ---------------------------------------------------------------------
|
||||
const semaphore_create_info = vk.SemaphoreCreateInfo{};
|
||||
|
||||
const image_available_semaphore = try ldc.vkd.createSemaphore(
|
||||
ldc.device,
|
||||
&semaphore_create_info,
|
||||
null,
|
||||
);
|
||||
defer ldc.vkd.destroySemaphore(ldc.device, image_available_semaphore, null);
|
||||
|
||||
const render_finished_semaphore = try ldc.vkd.createSemaphore(
|
||||
ldc.device,
|
||||
&semaphore_create_info,
|
||||
null,
|
||||
);
|
||||
defer ldc.vkd.destroySemaphore(ldc.device, render_finished_semaphore, null);
|
||||
|
||||
const fence_create_info = vk.FenceCreateInfo{
|
||||
.flags = .{
|
||||
.signaled_bit = true,
|
||||
},
|
||||
};
|
||||
|
||||
const in_flight_fence = try ldc.vkd.createFence(
|
||||
ldc.device,
|
||||
&fence_create_info,
|
||||
null,
|
||||
);
|
||||
defer ldc.vkd.destroyFence(ldc.device, in_flight_fence, null);
|
||||
|
||||
std.debug.print("created synchronization objects\n", .{});
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Single-frame acquire/submit/present
|
||||
//
|
||||
// This renders exactly one frame before entering the event loop. To turn
|
||||
// this into frame generation, move this whole block into drawFrame() and
|
||||
// call it from the window loop below.
|
||||
//
|
||||
// Per-frame shape:
|
||||
// 1. wait/reset the in-flight fence
|
||||
// 2. acquire the next swapchain image
|
||||
// 3. submit the command buffer for that image
|
||||
// 4. present that image
|
||||
//
|
||||
// Later this block must handle out-of-date/suboptimal swapchains and call
|
||||
// recreateSwapchainResources().
|
||||
// ---------------------------------------------------------------------
|
||||
const wait_fences = [_]vk.Fence{in_flight_fence};
|
||||
_ = try ldc.vkd.waitForFences(ldc.device, &wait_fences, .true, std.math.maxInt(u64));
|
||||
try ldc.vkd.resetFences(ldc.device, &wait_fences);
|
||||
|
||||
const acquire_result = try ldc.vkd.acquireNextImageKHR(
|
||||
ldc.device,
|
||||
swapchain_context.swapchain,
|
||||
std.math.maxInt(u64),
|
||||
image_available_semaphore,
|
||||
.null_handle,
|
||||
);
|
||||
|
||||
const image_index = acquire_result.image_index;
|
||||
std.debug.print("acquired swapchain image: {}\n", .{image_index});
|
||||
|
||||
const wait_semaphores = [_]vk.Semaphore{image_available_semaphore};
|
||||
const wait_stages = [_]vk.PipelineStageFlags{
|
||||
.{
|
||||
.color_attachment_output_bit = true,
|
||||
},
|
||||
};
|
||||
const signal_semaphores = [_]vk.Semaphore{render_finished_semaphore};
|
||||
const submit_command_buffers = [_]vk.CommandBuffer{
|
||||
command_buffers[image_index],
|
||||
};
|
||||
|
||||
const submit_info = vk.SubmitInfo{
|
||||
.wait_semaphore_count = wait_semaphores.len,
|
||||
.p_wait_semaphores = &wait_semaphores,
|
||||
.p_wait_dst_stage_mask = &wait_stages,
|
||||
.command_buffer_count = submit_command_buffers.len,
|
||||
.p_command_buffers = &submit_command_buffers,
|
||||
.signal_semaphore_count = signal_semaphores.len,
|
||||
.p_signal_semaphores = &signal_semaphores,
|
||||
};
|
||||
|
||||
try ldc.vkd.queueSubmit(ldc.graphics_queue, &[_]vk.SubmitInfo{submit_info}, in_flight_fence);
|
||||
|
||||
const present_swapchains = [_]vk.SwapchainKHR{swapchain_context.swapchain};
|
||||
const present_image_indices = [_]u32{image_index};
|
||||
|
||||
const present_info = vk.PresentInfoKHR{
|
||||
.wait_semaphore_count = signal_semaphores.len,
|
||||
.p_wait_semaphores = &signal_semaphores,
|
||||
.swapchain_count = present_swapchains.len,
|
||||
.p_swapchains = &present_swapchains,
|
||||
.p_image_indices = &present_image_indices,
|
||||
};
|
||||
|
||||
_ = try ldc.vkd.queuePresentKHR(ldc.graphics_queue, &present_info);
|
||||
|
||||
std.debug.print("presented one frame\n", .{});
|
||||
const sync_context = try sync_mod.initSyncObjects(ldc);
|
||||
defer sync_context.destroy(&ldc);
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Event loop
|
||||
//
|
||||
// Currently this only keeps the window alive after the one presented frame.
|
||||
// Next rendering milestone: call drawFrame() each iteration after polling
|
||||
// events, then wait for the device to be idle before cleanup on exit.
|
||||
// ---------------------------------------------------------------------
|
||||
while (!window.shouldClose()) {
|
||||
glfw.pollEvents();
|
||||
const result = try frame_mod.drawFrame(ldc, swapchain_context, command_context, sync_context);
|
||||
if (result == .Recreate) {
|
||||
try recreateSwapchain(
|
||||
vc,
|
||||
ldc,
|
||||
surface,
|
||||
window,
|
||||
&swapchain_context,
|
||||
&render_pass_context,
|
||||
&framebuffer_context,
|
||||
&command_context,
|
||||
std.heap.page_allocator,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
try ldc.vkd.deviceWaitIdle(ldc.device);
|
||||
}
|
||||
|
||||
fn initWindow(x: c_int, y: c_int, name: [:0]const u8) !*glfw.Window {
|
||||
try glfw.init();
|
||||
errdefer glfw.terminate();
|
||||
|
||||
glfw.windowHint(.client_api, .no_api);
|
||||
const window = try glfw.Window.create(
|
||||
x,
|
||||
y,
|
||||
name,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
|
||||
std.debug.print("GLFW platform: {any}\n", .{glfw.getPlatform()});
|
||||
std.debug.print("Vulkan supported by GLFW: {}\n", .{glfw.isVulkanSupported()});
|
||||
|
||||
const size = window.getSize();
|
||||
const fb_size = window.getFramebufferSize();
|
||||
|
||||
std.debug.print("Window size: {}x{}\n", .{ size[0], size[1] });
|
||||
std.debug.print("Framebuffer size: {}x{}\n", .{ fb_size[0], fb_size[1] });
|
||||
std.debug.print("Window visible: {}\n", .{window.getAttribute(.visible)});
|
||||
|
||||
return window;
|
||||
}
|
||||
|
||||
fn initInstance(name: [:0]const u8) !VulkanContext {
|
||||
const base = vk.BaseWrapper.load(glfw.getInstanceProcAddress);
|
||||
const required_extensions = try glfw.getRequiredInstanceExtensions();
|
||||
|
||||
std.debug.print("Required instance extensions:\n", .{});
|
||||
for (required_extensions) |extension| {
|
||||
std.debug.print(" {s}\n", .{extension});
|
||||
}
|
||||
const app_info = vk.ApplicationInfo{
|
||||
.p_application_name = name,
|
||||
.application_version = 1,
|
||||
.p_engine_name = name,
|
||||
.engine_version = 1,
|
||||
.api_version = @bitCast(vk.makeApiVersion(0, 1, 2, 0)),
|
||||
};
|
||||
|
||||
const instance_create_info = vk.InstanceCreateInfo{
|
||||
.p_application_info = &app_info,
|
||||
.enabled_extension_count = @intCast(required_extensions.len),
|
||||
.pp_enabled_extension_names = required_extensions.ptr,
|
||||
};
|
||||
|
||||
const instance = try base.createInstance(&instance_create_info, null);
|
||||
|
||||
std.debug.print("Created Vulkan Instance", .{});
|
||||
|
||||
const vki = vk.InstanceWrapper.load(instance, base.dispatch.vkGetInstanceProcAddr.?);
|
||||
|
||||
return .{
|
||||
.instance = instance,
|
||||
.base = base,
|
||||
.vki = vki,
|
||||
};
|
||||
}
|
||||
|
||||
fn initLogicalDevice(
|
||||
vc: VulkanContext,
|
||||
physical_device: vk.PhysicalDevice,
|
||||
graphics_queue_family_index: u32,
|
||||
) !LogicalDeviceContext {
|
||||
const queue_priority: f32 = 1.0;
|
||||
|
||||
const queue_create_info = vk.DeviceQueueCreateInfo{
|
||||
.queue_family_index = graphics_queue_family_index,
|
||||
.queue_count = 1,
|
||||
.p_queue_priorities = @ptrCast(&queue_priority),
|
||||
};
|
||||
|
||||
const device_extensions = [_][*:0]const u8{
|
||||
"VK_KHR_swapchain",
|
||||
};
|
||||
|
||||
const device_create_info = vk.DeviceCreateInfo{
|
||||
.queue_create_info_count = 1,
|
||||
.p_queue_create_infos = @ptrCast(&queue_create_info),
|
||||
.enabled_extension_count = device_extensions.len,
|
||||
.pp_enabled_extension_names = &device_extensions,
|
||||
};
|
||||
|
||||
const device = try vc.vki.createDevice(physical_device, &device_create_info, null);
|
||||
errdefer vc.vki.destroyDevice(device, null);
|
||||
std.debug.print("created logical device\n", .{});
|
||||
|
||||
const vkd = vk.DeviceWrapper.load(device, vc.vki.dispatch.vkGetDeviceProcAddr.?);
|
||||
const graphics_queue = vkd.getDeviceQueue(device, graphics_queue_family_index, 0);
|
||||
std.debug.print("retrieved graphics queue\n", .{});
|
||||
|
||||
return .{
|
||||
.physical_device = physical_device,
|
||||
.graphics_queue_family_index = graphics_queue_family_index,
|
||||
.device = device,
|
||||
.vkd = vkd,
|
||||
.graphics_queue = graphics_queue,
|
||||
};
|
||||
}
|
||||
|
||||
fn initSwapchain(
|
||||
vc: VulkanContext,
|
||||
ldc: LogicalDeviceContext,
|
||||
fn recreateSwapchain(
|
||||
vc: context_mod.VulkanContext,
|
||||
ldc: device_mod.LogicalDeviceContext,
|
||||
surface: vk.SurfaceKHR,
|
||||
window: *glfw.Window,
|
||||
swapchain_context: *swapchain_mod.SwapchainContext,
|
||||
render_pass_context: *render_pass_mod.RenderPassContext,
|
||||
framebuffer_context: *framebuffers_mod.FramebufferContext,
|
||||
command_context: *commands_mod.CommandContext,
|
||||
allocator: std.mem.Allocator,
|
||||
) !SwapchainContext {
|
||||
const surface_caps = try vc.vki.getPhysicalDeviceSurfaceCapabilitiesKHR(
|
||||
ldc.physical_device,
|
||||
surface,
|
||||
);
|
||||
|
||||
std.debug.print(
|
||||
"surface current extent: {}x{}\n",
|
||||
.{
|
||||
surface_caps.current_extent.width,
|
||||
surface_caps.current_extent.height,
|
||||
},
|
||||
);
|
||||
|
||||
std.debug.print(
|
||||
"surface min/max image count: {}/{}\n",
|
||||
.{
|
||||
surface_caps.min_image_count,
|
||||
surface_caps.max_image_count,
|
||||
},
|
||||
);
|
||||
|
||||
const surface_formats = try vc.vki.getPhysicalDeviceSurfaceFormatsAllocKHR(
|
||||
ldc.physical_device,
|
||||
surface,
|
||||
allocator,
|
||||
);
|
||||
defer allocator.free(surface_formats);
|
||||
|
||||
std.debug.print("surface formats: {}\n", .{surface_formats.len});
|
||||
for (surface_formats, 0..) |format, i| {
|
||||
std.debug.print(
|
||||
" format {}: format={any}, color_space={any}\n",
|
||||
.{ i, format.format, format.color_space },
|
||||
);
|
||||
}
|
||||
|
||||
const present_modes = try vc.vki.getPhysicalDeviceSurfacePresentModesAllocKHR(
|
||||
ldc.physical_device,
|
||||
surface,
|
||||
allocator,
|
||||
);
|
||||
defer allocator.free(present_modes);
|
||||
|
||||
std.debug.print("present modes: {}\n", .{present_modes.len});
|
||||
for (present_modes, 0..) |mode, i| {
|
||||
std.debug.print(" present mode {}: {any}\n", .{ i, mode });
|
||||
}
|
||||
|
||||
var chosen_surface_format = surface_formats[0];
|
||||
for (surface_formats) |format| {
|
||||
if (format.format == .b8g8r8a8_srgb and
|
||||
format.color_space == .srgb_nonlinear_khr)
|
||||
{
|
||||
chosen_surface_format = format;
|
||||
) !void {
|
||||
// A minimized window can report a zero-sized framebuffer. Vulkan swapchain
|
||||
// extents must be non-zero, so wait here until the window has drawable size
|
||||
// again before trying to recreate swapchain-dependent resources.
|
||||
while (!window.shouldClose()) {
|
||||
const framebuffer_size = window.getFramebufferSize();
|
||||
if (framebuffer_size[0] != 0 and framebuffer_size[1] != 0) {
|
||||
break;
|
||||
}
|
||||
glfw.waitEvents();
|
||||
}
|
||||
|
||||
var chosen_present_mode: vk.PresentModeKHR = .fifo_khr;
|
||||
for (present_modes) |mode| {
|
||||
if (mode == .fifo_khr) {
|
||||
chosen_present_mode = mode;
|
||||
break;
|
||||
}
|
||||
if (window.shouldClose()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const framebuffer_size = window.getFramebufferSize();
|
||||
// Make sure the GPU is no longer using the old swapchain, framebuffers, or
|
||||
// command buffers before we destroy them.
|
||||
try ldc.vkd.deviceWaitIdle(ldc.device);
|
||||
|
||||
const chosen_extent = if (surface_caps.current_extent.width != std.math.maxInt(u32))
|
||||
surface_caps.current_extent
|
||||
else
|
||||
vk.Extent2D{
|
||||
.width = @intCast(framebuffer_size[0]),
|
||||
.height = @intCast(framebuffer_size[1]),
|
||||
};
|
||||
// Build the replacement resource chain first. If any allocation/creation
|
||||
// fails, the old contexts are still intact and their existing defers remain
|
||||
// valid during error unwinding.
|
||||
const new_swapchain_context = try swapchain_mod.initSwapchain(vc, ldc, surface, window, allocator);
|
||||
errdefer new_swapchain_context.destroy(&ldc);
|
||||
|
||||
var chosen_image_count = surface_caps.min_image_count + 1;
|
||||
if (surface_caps.max_image_count != 0 and chosen_image_count > surface_caps.max_image_count) {
|
||||
chosen_image_count = surface_caps.max_image_count;
|
||||
}
|
||||
const new_render_pass_context = try render_pass_mod.initRenderPass(ldc, new_swapchain_context.format.format);
|
||||
errdefer new_render_pass_context.destroy(&ldc);
|
||||
|
||||
std.debug.print(
|
||||
"chosen swapchain format={any}, color_space={any}\n",
|
||||
.{ chosen_surface_format.format, chosen_surface_format.color_space },
|
||||
);
|
||||
std.debug.print("chosen present mode={any}\n", .{chosen_present_mode});
|
||||
std.debug.print(
|
||||
"chosen extent={}x{}\n",
|
||||
.{ chosen_extent.width, chosen_extent.height },
|
||||
);
|
||||
std.debug.print("chosen image count={}\n", .{chosen_image_count});
|
||||
|
||||
const swapchain_create_info = vk.SwapchainCreateInfoKHR{
|
||||
.surface = surface,
|
||||
.min_image_count = chosen_image_count,
|
||||
.image_format = chosen_surface_format.format,
|
||||
.image_color_space = chosen_surface_format.color_space,
|
||||
.image_extent = chosen_extent,
|
||||
.image_array_layers = 1,
|
||||
.image_usage = .{
|
||||
.color_attachment_bit = true,
|
||||
},
|
||||
.image_sharing_mode = .exclusive,
|
||||
.pre_transform = surface_caps.current_transform,
|
||||
.composite_alpha = .{
|
||||
.opaque_bit_khr = true,
|
||||
},
|
||||
.present_mode = chosen_present_mode,
|
||||
.clipped = .true,
|
||||
};
|
||||
|
||||
const swapchain = try ldc.vkd.createSwapchainKHR(ldc.device, &swapchain_create_info, null);
|
||||
errdefer ldc.vkd.destroySwapchainKHR(ldc.device, swapchain, null);
|
||||
std.debug.print("created swapchain\n", .{});
|
||||
|
||||
const swapchain_images = try ldc.vkd.getSwapchainImagesAllocKHR(
|
||||
ldc.device,
|
||||
swapchain,
|
||||
const new_framebuffer_context = try framebuffers_mod.initFramebuffers(
|
||||
ldc,
|
||||
new_render_pass_context,
|
||||
new_swapchain_context,
|
||||
allocator,
|
||||
);
|
||||
errdefer allocator.free(swapchain_images);
|
||||
std.debug.print("swapchain images: {}\n", .{swapchain_images.len});
|
||||
errdefer new_framebuffer_context.destroy(&ldc);
|
||||
|
||||
const swapchain_image_views = try allocator.alloc(vk.ImageView, swapchain_images.len);
|
||||
errdefer allocator.free(swapchain_image_views);
|
||||
const new_command_context = try commands_mod.initCommandBuffers(
|
||||
ldc,
|
||||
new_render_pass_context,
|
||||
new_framebuffer_context,
|
||||
new_swapchain_context,
|
||||
allocator,
|
||||
);
|
||||
errdefer new_command_context.destroy(&ldc);
|
||||
|
||||
var created_image_view_count: usize = 0;
|
||||
errdefer {
|
||||
for (swapchain_image_views[0..created_image_view_count]) |image_view| {
|
||||
ldc.vkd.destroyImageView(ldc.device, image_view, null);
|
||||
}
|
||||
}
|
||||
// Now that the full replacement chain exists, release the old chain in
|
||||
// dependency order: command buffers/pool -> framebuffers -> render pass ->
|
||||
// swapchain/image views/images.
|
||||
command_context.destroy(&ldc);
|
||||
framebuffer_context.destroy(&ldc);
|
||||
render_pass_context.destroy(&ldc);
|
||||
swapchain_context.destroy(&ldc);
|
||||
|
||||
for (swapchain_images, 0..) |image, i| {
|
||||
const image_view_create_info = vk.ImageViewCreateInfo{
|
||||
.image = image,
|
||||
.view_type = .@"2d",
|
||||
.format = chosen_surface_format.format,
|
||||
.components = .{
|
||||
.r = .identity,
|
||||
.g = .identity,
|
||||
.b = .identity,
|
||||
.a = .identity,
|
||||
},
|
||||
.subresource_range = .{
|
||||
.aspect_mask = .{
|
||||
.color_bit = true,
|
||||
},
|
||||
.base_mip_level = 0,
|
||||
.level_count = 1,
|
||||
.base_array_layer = 0,
|
||||
.layer_count = 1,
|
||||
},
|
||||
};
|
||||
swapchain_context.* = new_swapchain_context;
|
||||
render_pass_context.* = new_render_pass_context;
|
||||
framebuffer_context.* = new_framebuffer_context;
|
||||
command_context.* = new_command_context;
|
||||
|
||||
swapchain_image_views[i] = try ldc.vkd.createImageView(
|
||||
ldc.device,
|
||||
&image_view_create_info,
|
||||
null,
|
||||
);
|
||||
created_image_view_count += 1;
|
||||
}
|
||||
|
||||
std.debug.print("created swapchain image views: {}\n", .{swapchain_image_views.len});
|
||||
|
||||
return .{
|
||||
.swapchain = swapchain,
|
||||
.images = swapchain_images,
|
||||
.image_views = swapchain_image_views,
|
||||
.format = chosen_surface_format,
|
||||
.present_mode = chosen_present_mode,
|
||||
.extent = chosen_extent,
|
||||
.image_count = chosen_image_count,
|
||||
.allocator = allocator,
|
||||
};
|
||||
}
|
||||
|
||||
fn debugPhysicalGPUs(vc: VulkanContext, physical_devices: []vk.PhysicalDevice, surface: vk.SurfaceKHR) !void {
|
||||
std.debug.print("physical devices: {}\n", .{physical_devices.len});
|
||||
|
||||
for (physical_devices, 0..) |physical_device, i| {
|
||||
const props = vc.vki.getPhysicalDeviceProperties(physical_device);
|
||||
std.debug.print("device {}: {s}\n", .{ i, std.mem.sliceTo(&props.device_name, 0) });
|
||||
const queue_families = try vc.vki.getPhysicalDeviceQueueFamilyPropertiesAlloc(
|
||||
physical_device,
|
||||
std.heap.page_allocator,
|
||||
);
|
||||
defer std.heap.page_allocator.free(queue_families);
|
||||
|
||||
for (queue_families, 0..) |queue_family, queue_index| {
|
||||
const supports_graphics = queue_family.queue_flags.graphics_bit;
|
||||
const supports_compute = queue_family.queue_flags.compute_bit;
|
||||
const supports_transfer = queue_family.queue_flags.transfer_bit;
|
||||
|
||||
const supports_present = try vc.vki.getPhysicalDeviceSurfaceSupportKHR(
|
||||
physical_device,
|
||||
@intCast(queue_index),
|
||||
surface,
|
||||
);
|
||||
|
||||
std.debug.print(
|
||||
" queue {}: count={}, graphics={}, compute={}, transfer={}, present={}\n",
|
||||
.{
|
||||
queue_index,
|
||||
queue_family.queue_count,
|
||||
supports_graphics,
|
||||
supports_compute,
|
||||
supports_transfer,
|
||||
supports_present,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
std.debug.print("recreated swapchain resources\n", .{});
|
||||
}
|
||||
|
||||
101
src/vulkan/commands.zig
Normal file
101
src/vulkan/commands.zig
Normal file
@ -0,0 +1,101 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
|
||||
const device = @import("device.zig");
|
||||
const framebuffers = @import("framebuffers.zig");
|
||||
const render_pass = @import("render_pass.zig");
|
||||
const swapchain = @import("swapchain.zig");
|
||||
|
||||
pub const CommandContext = struct {
|
||||
command_pool: vk.CommandPool,
|
||||
command_buffers: []vk.CommandBuffer,
|
||||
allocator: std.mem.Allocator,
|
||||
|
||||
pub fn destroy(self: *const CommandContext, ldc: *const device.LogicalDeviceContext) void {
|
||||
self.allocator.free(self.command_buffers);
|
||||
ldc.vkd.destroyCommandPool(ldc.device, self.command_pool, null);
|
||||
}
|
||||
};
|
||||
|
||||
pub fn initCommandBuffers(
|
||||
ldc: device.LogicalDeviceContext,
|
||||
render_pass_context: render_pass.RenderPassContext,
|
||||
framebuffer_context: framebuffers.FramebufferContext,
|
||||
swapchain_context: swapchain.SwapchainContext,
|
||||
allocator: std.mem.Allocator,
|
||||
) !CommandContext {
|
||||
const command_pool_create_info = vk.CommandPoolCreateInfo{
|
||||
.flags = .{
|
||||
.reset_command_buffer_bit = true,
|
||||
},
|
||||
.queue_family_index = ldc.graphics_queue_family_index,
|
||||
};
|
||||
|
||||
const command_pool = try ldc.vkd.createCommandPool(
|
||||
ldc.device,
|
||||
&command_pool_create_info,
|
||||
null,
|
||||
);
|
||||
errdefer ldc.vkd.destroyCommandPool(ldc.device, command_pool, null);
|
||||
std.debug.print("created command pool\n", .{});
|
||||
|
||||
const command_buffers = try allocator.alloc(vk.CommandBuffer, framebuffer_context.framebuffers.len);
|
||||
errdefer allocator.free(command_buffers);
|
||||
|
||||
const command_buffer_allocate_info = vk.CommandBufferAllocateInfo{
|
||||
.command_pool = command_pool,
|
||||
.level = .primary,
|
||||
.command_buffer_count = @intCast(command_buffers.len),
|
||||
};
|
||||
|
||||
try ldc.vkd.allocateCommandBuffers(
|
||||
ldc.device,
|
||||
&command_buffer_allocate_info,
|
||||
command_buffers.ptr,
|
||||
);
|
||||
|
||||
std.debug.print("allocated command buffers: {}\n", .{command_buffers.len});
|
||||
|
||||
for (command_buffers, 0..) |command_buffer, i| {
|
||||
const begin_info = vk.CommandBufferBeginInfo{};
|
||||
|
||||
try ldc.vkd.beginCommandBuffer(command_buffer, &begin_info);
|
||||
|
||||
// This is the only "drawing" currently happening: begin a render pass
|
||||
// and clear the swapchain image. The embedded shaders are not used yet.
|
||||
const clear_color = vk.ClearValue{
|
||||
.color = .{
|
||||
.float_32 = .{ 0.02, 0.02, 0.08, 1.0 },
|
||||
},
|
||||
};
|
||||
|
||||
const render_pass_begin_info = vk.RenderPassBeginInfo{
|
||||
.render_pass = render_pass_context.render_pass,
|
||||
.framebuffer = framebuffer_context.framebuffers[i],
|
||||
.render_area = .{
|
||||
.offset = .{ .x = 0, .y = 0 },
|
||||
.extent = swapchain_context.extent,
|
||||
},
|
||||
.clear_value_count = 1,
|
||||
.p_clear_values = @ptrCast(&clear_color),
|
||||
};
|
||||
|
||||
ldc.vkd.cmdBeginRenderPass(
|
||||
command_buffer,
|
||||
&render_pass_begin_info,
|
||||
.@"inline",
|
||||
);
|
||||
|
||||
ldc.vkd.cmdEndRenderPass(command_buffer);
|
||||
|
||||
try ldc.vkd.endCommandBuffer(command_buffer);
|
||||
}
|
||||
|
||||
std.debug.print("recorded command buffers\n", .{});
|
||||
|
||||
return .{
|
||||
.command_pool = command_pool,
|
||||
.command_buffers = command_buffers,
|
||||
.allocator = allocator,
|
||||
};
|
||||
}
|
||||
47
src/vulkan/context.zig
Normal file
47
src/vulkan/context.zig
Normal file
@ -0,0 +1,47 @@
|
||||
const std = @import("std");
|
||||
const glfw = @import("zglfw");
|
||||
const vk = @import("vulkan");
|
||||
|
||||
pub const VulkanContext = struct {
|
||||
base: vk.BaseWrapper,
|
||||
instance: vk.Instance,
|
||||
vki: vk.InstanceWrapper,
|
||||
|
||||
pub fn destroy(self: *const VulkanContext) void {
|
||||
self.vki.destroyInstance(self.instance, null);
|
||||
}
|
||||
};
|
||||
|
||||
pub fn initInstance(name: [:0]const u8) !VulkanContext {
|
||||
const base = vk.BaseWrapper.load(glfw.getInstanceProcAddress);
|
||||
const required_extensions = try glfw.getRequiredInstanceExtensions();
|
||||
|
||||
std.debug.print("Required instance extensions:\n", .{});
|
||||
for (required_extensions) |extension| {
|
||||
std.debug.print(" {s}\n", .{extension});
|
||||
}
|
||||
const app_info = vk.ApplicationInfo{
|
||||
.p_application_name = name,
|
||||
.application_version = 1,
|
||||
.p_engine_name = name,
|
||||
.engine_version = 1,
|
||||
.api_version = @bitCast(vk.makeApiVersion(0, 1, 2, 0)),
|
||||
};
|
||||
|
||||
const instance_create_info = vk.InstanceCreateInfo{
|
||||
.p_application_info = &app_info,
|
||||
.enabled_extension_count = @intCast(required_extensions.len),
|
||||
.pp_enabled_extension_names = required_extensions.ptr,
|
||||
};
|
||||
|
||||
const instance = try base.createInstance(&instance_create_info, null);
|
||||
std.debug.print("Created Vulkan Instance\n", .{});
|
||||
|
||||
const vki = vk.InstanceWrapper.load(instance, base.dispatch.vkGetInstanceProcAddr.?);
|
||||
|
||||
return .{
|
||||
.instance = instance,
|
||||
.base = base,
|
||||
.vki = vki,
|
||||
};
|
||||
}
|
||||
99
src/vulkan/device.zig
Normal file
99
src/vulkan/device.zig
Normal file
@ -0,0 +1,99 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
|
||||
const context = @import("context.zig");
|
||||
|
||||
pub const LogicalDeviceContext = struct {
|
||||
physical_device: vk.PhysicalDevice,
|
||||
graphics_queue_family_index: u32,
|
||||
device: vk.Device,
|
||||
vkd: vk.DeviceWrapper,
|
||||
graphics_queue: vk.Queue,
|
||||
|
||||
pub fn destroy(self: *const LogicalDeviceContext) void {
|
||||
self.vkd.destroyDevice(self.device, null);
|
||||
}
|
||||
};
|
||||
|
||||
pub fn initLogicalDevice(
|
||||
vc: context.VulkanContext,
|
||||
physical_device: vk.PhysicalDevice,
|
||||
graphics_queue_family_index: u32,
|
||||
) !LogicalDeviceContext {
|
||||
const queue_priority: f32 = 1.0;
|
||||
|
||||
const queue_create_info = vk.DeviceQueueCreateInfo{
|
||||
.queue_family_index = graphics_queue_family_index,
|
||||
.queue_count = 1,
|
||||
.p_queue_priorities = @ptrCast(&queue_priority),
|
||||
};
|
||||
|
||||
const device_extensions = [_][*:0]const u8{
|
||||
"VK_KHR_swapchain",
|
||||
};
|
||||
|
||||
const device_create_info = vk.DeviceCreateInfo{
|
||||
.queue_create_info_count = 1,
|
||||
.p_queue_create_infos = @ptrCast(&queue_create_info),
|
||||
.enabled_extension_count = device_extensions.len,
|
||||
.pp_enabled_extension_names = &device_extensions,
|
||||
};
|
||||
|
||||
const device = try vc.vki.createDevice(physical_device, &device_create_info, null);
|
||||
errdefer vc.vki.destroyDevice(device, null);
|
||||
std.debug.print("created logical device\n", .{});
|
||||
|
||||
const vkd = vk.DeviceWrapper.load(device, vc.vki.dispatch.vkGetDeviceProcAddr.?);
|
||||
const graphics_queue = vkd.getDeviceQueue(device, graphics_queue_family_index, 0);
|
||||
std.debug.print("retrieved graphics queue\n", .{});
|
||||
|
||||
return .{
|
||||
.physical_device = physical_device,
|
||||
.graphics_queue_family_index = graphics_queue_family_index,
|
||||
.device = device,
|
||||
.vkd = vkd,
|
||||
.graphics_queue = graphics_queue,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn debugPhysicalGPUs(
|
||||
vc: context.VulkanContext,
|
||||
physical_devices: []vk.PhysicalDevice,
|
||||
surface: vk.SurfaceKHR,
|
||||
) !void {
|
||||
std.debug.print("physical devices: {}\n", .{physical_devices.len});
|
||||
|
||||
for (physical_devices, 0..) |physical_device, i| {
|
||||
const props = vc.vki.getPhysicalDeviceProperties(physical_device);
|
||||
std.debug.print("device {}: {s}\n", .{ i, std.mem.sliceTo(&props.device_name, 0) });
|
||||
const queue_families = try vc.vki.getPhysicalDeviceQueueFamilyPropertiesAlloc(
|
||||
physical_device,
|
||||
std.heap.page_allocator,
|
||||
);
|
||||
defer std.heap.page_allocator.free(queue_families);
|
||||
|
||||
for (queue_families, 0..) |queue_family, queue_index| {
|
||||
const supports_graphics = queue_family.queue_flags.graphics_bit;
|
||||
const supports_compute = queue_family.queue_flags.compute_bit;
|
||||
const supports_transfer = queue_family.queue_flags.transfer_bit;
|
||||
|
||||
const supports_present = try vc.vki.getPhysicalDeviceSurfaceSupportKHR(
|
||||
physical_device,
|
||||
@intCast(queue_index),
|
||||
surface,
|
||||
);
|
||||
|
||||
std.debug.print(
|
||||
" queue {}: count={}, graphics={}, compute={}, transfer={}, present={}\n",
|
||||
.{
|
||||
queue_index,
|
||||
queue_family.queue_count,
|
||||
supports_graphics,
|
||||
supports_compute,
|
||||
supports_transfer,
|
||||
supports_present,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
86
src/vulkan/frame.zig
Normal file
86
src/vulkan/frame.zig
Normal file
@ -0,0 +1,86 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
|
||||
const commands = @import("commands.zig");
|
||||
const device = @import("device.zig");
|
||||
const swapchain = @import("swapchain.zig");
|
||||
const sync = @import("sync.zig");
|
||||
|
||||
pub const FrameResult = enum {
|
||||
OK,
|
||||
Recreate,
|
||||
};
|
||||
|
||||
pub fn drawFrame(
|
||||
ldc: device.LogicalDeviceContext,
|
||||
swapchain_context: swapchain.SwapchainContext,
|
||||
command_context: commands.CommandContext,
|
||||
sync_context: sync.SyncContext,
|
||||
) !FrameResult {
|
||||
const wait_fences = [_]vk.Fence{sync_context.in_flight_fence};
|
||||
_ = try ldc.vkd.waitForFences(ldc.device, &wait_fences, .true, std.math.maxInt(u64));
|
||||
|
||||
const image_result = ldc.vkd.acquireNextImageKHR(
|
||||
ldc.device,
|
||||
swapchain_context.swapchain,
|
||||
std.math.maxInt(u64),
|
||||
sync_context.image_available_semaphore,
|
||||
.null_handle,
|
||||
) catch |err| switch (err) {
|
||||
error.OutOfDateKHR => return .Recreate,
|
||||
else => return err,
|
||||
};
|
||||
|
||||
const image_index = image_result.image_index;
|
||||
const should_recreate_after_present = image_result.result == .suboptimal_khr;
|
||||
std.debug.print("Acquired swapchain image: {}\n", .{image_index});
|
||||
|
||||
try ldc.vkd.resetFences(ldc.device, &wait_fences);
|
||||
|
||||
const wait_semaphores = [_]vk.Semaphore{sync_context.image_available_semaphore};
|
||||
const wait_stages = [_]vk.PipelineStageFlags{
|
||||
.{
|
||||
.color_attachment_output_bit = true,
|
||||
},
|
||||
};
|
||||
|
||||
const signal_semaphores = [_]vk.Semaphore{sync_context.render_finished_semaphore};
|
||||
const submit_command_buffers = [_]vk.CommandBuffer{
|
||||
command_context.command_buffers[image_index],
|
||||
};
|
||||
|
||||
const submit_info = vk.SubmitInfo{
|
||||
.wait_semaphore_count = wait_semaphores.len,
|
||||
.p_wait_semaphores = &wait_semaphores,
|
||||
.p_wait_dst_stage_mask = &wait_stages,
|
||||
.command_buffer_count = submit_command_buffers.len,
|
||||
.p_command_buffers = &submit_command_buffers,
|
||||
.signal_semaphore_count = signal_semaphores.len,
|
||||
.p_signal_semaphores = &signal_semaphores,
|
||||
};
|
||||
|
||||
try ldc.vkd.queueSubmit(ldc.graphics_queue, &[_]vk.SubmitInfo{submit_info}, sync_context.in_flight_fence);
|
||||
|
||||
const present_swapchains = [_]vk.SwapchainKHR{swapchain_context.swapchain};
|
||||
const present_image_indices = [_]u32{image_index};
|
||||
|
||||
const present_info = vk.PresentInfoKHR{
|
||||
.wait_semaphore_count = signal_semaphores.len,
|
||||
.p_wait_semaphores = &signal_semaphores,
|
||||
.swapchain_count = present_swapchains.len,
|
||||
.p_swapchains = &present_swapchains,
|
||||
.p_image_indices = &present_image_indices,
|
||||
};
|
||||
|
||||
const present_result = ldc.vkd.queuePresentKHR(ldc.graphics_queue, &present_info) catch |err| switch (err) {
|
||||
error.OutOfDateKHR => return .Recreate,
|
||||
else => return err,
|
||||
};
|
||||
std.debug.print("Presented one frame\n", .{});
|
||||
|
||||
if (should_recreate_after_present or present_result == .suboptimal_khr) {
|
||||
return FrameResult.Recreate;
|
||||
}
|
||||
|
||||
return FrameResult.OK;
|
||||
}
|
||||
62
src/vulkan/framebuffers.zig
Normal file
62
src/vulkan/framebuffers.zig
Normal file
@ -0,0 +1,62 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
|
||||
const device = @import("device.zig");
|
||||
const render_pass = @import("render_pass.zig");
|
||||
const swapchain = @import("swapchain.zig");
|
||||
|
||||
pub const FramebufferContext = struct {
|
||||
framebuffers: []vk.Framebuffer,
|
||||
allocator: std.mem.Allocator,
|
||||
|
||||
pub fn destroy(self: *const FramebufferContext, ldc: *const device.LogicalDeviceContext) void {
|
||||
for (self.framebuffers) |framebuffer| {
|
||||
ldc.vkd.destroyFramebuffer(ldc.device, framebuffer, null);
|
||||
}
|
||||
self.allocator.free(self.framebuffers);
|
||||
}
|
||||
};
|
||||
|
||||
pub fn initFramebuffers(
|
||||
ldc: device.LogicalDeviceContext,
|
||||
render_pass_context: render_pass.RenderPassContext,
|
||||
swapchain_context: swapchain.SwapchainContext,
|
||||
allocator: std.mem.Allocator,
|
||||
) !FramebufferContext {
|
||||
const framebuffers = try allocator.alloc(vk.Framebuffer, swapchain_context.image_views.len);
|
||||
errdefer allocator.free(framebuffers);
|
||||
|
||||
var created_framebuffer_count: usize = 0;
|
||||
errdefer {
|
||||
for (framebuffers[0..created_framebuffer_count]) |framebuffer| {
|
||||
ldc.vkd.destroyFramebuffer(ldc.device, framebuffer, null);
|
||||
}
|
||||
}
|
||||
|
||||
for (swapchain_context.image_views, 0..) |image_view, i| {
|
||||
const attachments = [_]vk.ImageView{image_view};
|
||||
|
||||
const framebuffer_create_info = vk.FramebufferCreateInfo{
|
||||
.render_pass = render_pass_context.render_pass,
|
||||
.attachment_count = attachments.len,
|
||||
.p_attachments = &attachments,
|
||||
.width = swapchain_context.extent.width,
|
||||
.height = swapchain_context.extent.height,
|
||||
.layers = 1,
|
||||
};
|
||||
|
||||
framebuffers[i] = try ldc.vkd.createFramebuffer(
|
||||
ldc.device,
|
||||
&framebuffer_create_info,
|
||||
null,
|
||||
);
|
||||
created_framebuffer_count += 1;
|
||||
}
|
||||
|
||||
std.debug.print("created framebuffers: {}\n", .{framebuffers.len});
|
||||
|
||||
return .{
|
||||
.framebuffers = framebuffers,
|
||||
.allocator = allocator,
|
||||
};
|
||||
}
|
||||
14
src/vulkan/pipeline.zig
Normal file
14
src/vulkan/pipeline.zig
Normal file
@ -0,0 +1,14 @@
|
||||
const vk = @import("vulkan");
|
||||
|
||||
const device = @import("device.zig");
|
||||
|
||||
pub fn createShaderModule(ldc: device.LogicalDeviceContext, spv: []const u8) !vk.ShaderModule {
|
||||
if (spv.len % 4 != 0) return error.InvalidSpirVSize;
|
||||
|
||||
const create_info = vk.ShaderModuleCreateInfo{
|
||||
.code_size = spv.len,
|
||||
.p_code = @ptrCast(@alignCast(spv.ptr)),
|
||||
};
|
||||
|
||||
return try ldc.vkd.createShaderModule(ldc.device, &create_info, null);
|
||||
}
|
||||
65
src/vulkan/render_pass.zig
Normal file
65
src/vulkan/render_pass.zig
Normal file
@ -0,0 +1,65 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
|
||||
const device = @import("device.zig");
|
||||
|
||||
pub const RenderPassContext = struct {
|
||||
render_pass: vk.RenderPass,
|
||||
|
||||
pub fn destroy(self: *const RenderPassContext, ldc: *const device.LogicalDeviceContext) void {
|
||||
ldc.vkd.destroyRenderPass(ldc.device, self.render_pass, null);
|
||||
}
|
||||
};
|
||||
|
||||
pub fn initRenderPass(ldc: device.LogicalDeviceContext, format: vk.Format) !RenderPassContext {
|
||||
const color_attachment = vk.AttachmentDescription{
|
||||
.format = format,
|
||||
.samples = .{ .@"1_bit" = true },
|
||||
.load_op = .clear,
|
||||
.store_op = .store,
|
||||
.stencil_load_op = .dont_care,
|
||||
.stencil_store_op = .dont_care,
|
||||
.initial_layout = .undefined,
|
||||
.final_layout = .present_src_khr,
|
||||
};
|
||||
|
||||
const color_attachment_ref = vk.AttachmentReference{
|
||||
.attachment = 0,
|
||||
.layout = .color_attachment_optimal,
|
||||
};
|
||||
|
||||
const subpass = vk.SubpassDescription{
|
||||
.pipeline_bind_point = .graphics,
|
||||
.color_attachment_count = 1,
|
||||
.p_color_attachments = @ptrCast(&color_attachment_ref),
|
||||
};
|
||||
|
||||
const subpass_dependency = vk.SubpassDependency{
|
||||
.src_subpass = vk.SUBPASS_EXTERNAL,
|
||||
.dst_subpass = 0,
|
||||
.src_stage_mask = .{
|
||||
.color_attachment_output_bit = true,
|
||||
},
|
||||
.src_access_mask = .{},
|
||||
.dst_stage_mask = .{
|
||||
.color_attachment_output_bit = true,
|
||||
},
|
||||
.dst_access_mask = .{
|
||||
.color_attachment_write_bit = true,
|
||||
},
|
||||
};
|
||||
|
||||
const render_pass_create_info = vk.RenderPassCreateInfo{
|
||||
.attachment_count = 1,
|
||||
.p_attachments = @ptrCast(&color_attachment),
|
||||
.subpass_count = 1,
|
||||
.p_subpasses = @ptrCast(&subpass),
|
||||
.dependency_count = 1,
|
||||
.p_dependencies = @ptrCast(&subpass_dependency),
|
||||
};
|
||||
|
||||
const render_pass = try ldc.vkd.createRenderPass(ldc.device, &render_pass_create_info, null);
|
||||
std.debug.print("created render pass\n", .{});
|
||||
|
||||
return .{ .render_pass = render_pass };
|
||||
}
|
||||
197
src/vulkan/swapchain.zig
Normal file
197
src/vulkan/swapchain.zig
Normal file
@ -0,0 +1,197 @@
|
||||
const std = @import("std");
|
||||
const glfw = @import("zglfw");
|
||||
const vk = @import("vulkan");
|
||||
|
||||
const context = @import("context.zig");
|
||||
const device = @import("device.zig");
|
||||
|
||||
pub const SwapchainContext = struct {
|
||||
swapchain: vk.SwapchainKHR,
|
||||
images: []vk.Image,
|
||||
image_views: []vk.ImageView,
|
||||
format: vk.SurfaceFormatKHR,
|
||||
present_mode: vk.PresentModeKHR,
|
||||
extent: vk.Extent2D,
|
||||
image_count: u32,
|
||||
allocator: std.mem.Allocator,
|
||||
|
||||
pub fn destroy(self: *const SwapchainContext, ldc: *const device.LogicalDeviceContext) void {
|
||||
for (self.image_views) |image_view| {
|
||||
ldc.vkd.destroyImageView(ldc.device, image_view, null);
|
||||
}
|
||||
self.allocator.free(self.image_views);
|
||||
self.allocator.free(self.images);
|
||||
ldc.vkd.destroySwapchainKHR(ldc.device, self.swapchain, null);
|
||||
}
|
||||
};
|
||||
|
||||
pub fn initSwapchain(
|
||||
vc: context.VulkanContext,
|
||||
ldc: device.LogicalDeviceContext,
|
||||
surface: vk.SurfaceKHR,
|
||||
window: *glfw.Window,
|
||||
allocator: std.mem.Allocator,
|
||||
) !SwapchainContext {
|
||||
const surface_caps = try vc.vki.getPhysicalDeviceSurfaceCapabilitiesKHR(
|
||||
ldc.physical_device,
|
||||
surface,
|
||||
);
|
||||
|
||||
std.debug.print(
|
||||
"surface current extent: {}x{}\n",
|
||||
.{
|
||||
surface_caps.current_extent.width,
|
||||
surface_caps.current_extent.height,
|
||||
},
|
||||
);
|
||||
|
||||
std.debug.print(
|
||||
"surface min/max image count: {}/{}\n",
|
||||
.{
|
||||
surface_caps.min_image_count,
|
||||
surface_caps.max_image_count,
|
||||
},
|
||||
);
|
||||
|
||||
const surface_formats = try vc.vki.getPhysicalDeviceSurfaceFormatsAllocKHR(
|
||||
ldc.physical_device,
|
||||
surface,
|
||||
allocator,
|
||||
);
|
||||
defer allocator.free(surface_formats);
|
||||
|
||||
const present_modes = try vc.vki.getPhysicalDeviceSurfacePresentModesAllocKHR(
|
||||
ldc.physical_device,
|
||||
surface,
|
||||
allocator,
|
||||
);
|
||||
defer allocator.free(present_modes);
|
||||
|
||||
var chosen_surface_format = surface_formats[0];
|
||||
for (surface_formats) |format| {
|
||||
if (format.format == .b8g8r8a8_srgb and
|
||||
format.color_space == .srgb_nonlinear_khr)
|
||||
{
|
||||
chosen_surface_format = format;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var chosen_present_mode: vk.PresentModeKHR = .fifo_khr;
|
||||
for (present_modes) |mode| {
|
||||
if (mode == .fifo_khr) {
|
||||
chosen_present_mode = mode;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const framebuffer_size = window.getFramebufferSize();
|
||||
|
||||
const chosen_extent = if (surface_caps.current_extent.width != std.math.maxInt(u32))
|
||||
surface_caps.current_extent
|
||||
else
|
||||
vk.Extent2D{
|
||||
.width = @intCast(framebuffer_size[0]),
|
||||
.height = @intCast(framebuffer_size[1]),
|
||||
};
|
||||
|
||||
var chosen_image_count = surface_caps.min_image_count + 1;
|
||||
if (surface_caps.max_image_count != 0 and chosen_image_count > surface_caps.max_image_count) {
|
||||
chosen_image_count = surface_caps.max_image_count;
|
||||
}
|
||||
|
||||
std.debug.print(
|
||||
"chosen swapchain format={any}, color_space={any}\n",
|
||||
.{ chosen_surface_format.format, chosen_surface_format.color_space },
|
||||
);
|
||||
std.debug.print("chosen present mode={any}\n", .{chosen_present_mode});
|
||||
std.debug.print(
|
||||
"chosen extent={}x{}\n",
|
||||
.{ chosen_extent.width, chosen_extent.height },
|
||||
);
|
||||
std.debug.print("chosen image count={}\n", .{chosen_image_count});
|
||||
|
||||
const swapchain_create_info = vk.SwapchainCreateInfoKHR{
|
||||
.surface = surface,
|
||||
.min_image_count = chosen_image_count,
|
||||
.image_format = chosen_surface_format.format,
|
||||
.image_color_space = chosen_surface_format.color_space,
|
||||
.image_extent = chosen_extent,
|
||||
.image_array_layers = 1,
|
||||
.image_usage = .{
|
||||
.color_attachment_bit = true,
|
||||
},
|
||||
.image_sharing_mode = .exclusive,
|
||||
.pre_transform = surface_caps.current_transform,
|
||||
.composite_alpha = .{
|
||||
.opaque_bit_khr = true,
|
||||
},
|
||||
.present_mode = chosen_present_mode,
|
||||
.clipped = .true,
|
||||
};
|
||||
|
||||
const swapchain = try ldc.vkd.createSwapchainKHR(ldc.device, &swapchain_create_info, null);
|
||||
errdefer ldc.vkd.destroySwapchainKHR(ldc.device, swapchain, null);
|
||||
std.debug.print("created swapchain\n", .{});
|
||||
|
||||
const swapchain_images = try ldc.vkd.getSwapchainImagesAllocKHR(
|
||||
ldc.device,
|
||||
swapchain,
|
||||
allocator,
|
||||
);
|
||||
errdefer allocator.free(swapchain_images);
|
||||
std.debug.print("swapchain images: {}\n", .{swapchain_images.len});
|
||||
|
||||
const swapchain_image_views = try allocator.alloc(vk.ImageView, swapchain_images.len);
|
||||
errdefer allocator.free(swapchain_image_views);
|
||||
|
||||
var created_image_view_count: usize = 0;
|
||||
errdefer {
|
||||
for (swapchain_image_views[0..created_image_view_count]) |image_view| {
|
||||
ldc.vkd.destroyImageView(ldc.device, image_view, null);
|
||||
}
|
||||
}
|
||||
|
||||
for (swapchain_images, 0..) |image, i| {
|
||||
const image_view_create_info = vk.ImageViewCreateInfo{
|
||||
.image = image,
|
||||
.view_type = .@"2d",
|
||||
.format = chosen_surface_format.format,
|
||||
.components = .{
|
||||
.r = .identity,
|
||||
.g = .identity,
|
||||
.b = .identity,
|
||||
.a = .identity,
|
||||
},
|
||||
.subresource_range = .{
|
||||
.aspect_mask = .{
|
||||
.color_bit = true,
|
||||
},
|
||||
.base_mip_level = 0,
|
||||
.level_count = 1,
|
||||
.base_array_layer = 0,
|
||||
.layer_count = 1,
|
||||
},
|
||||
};
|
||||
|
||||
swapchain_image_views[i] = try ldc.vkd.createImageView(
|
||||
ldc.device,
|
||||
&image_view_create_info,
|
||||
null,
|
||||
);
|
||||
created_image_view_count += 1;
|
||||
}
|
||||
|
||||
std.debug.print("created swapchain image views: {}\n", .{swapchain_image_views.len});
|
||||
|
||||
return .{
|
||||
.swapchain = swapchain,
|
||||
.images = swapchain_images,
|
||||
.image_views = swapchain_image_views,
|
||||
.format = chosen_surface_format,
|
||||
.present_mode = chosen_present_mode,
|
||||
.extent = chosen_extent,
|
||||
.image_count = chosen_image_count,
|
||||
.allocator = allocator,
|
||||
};
|
||||
}
|
||||
54
src/vulkan/sync.zig
Normal file
54
src/vulkan/sync.zig
Normal file
@ -0,0 +1,54 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
|
||||
const device = @import("device.zig");
|
||||
|
||||
pub const SyncContext = struct {
|
||||
image_available_semaphore: vk.Semaphore,
|
||||
render_finished_semaphore: vk.Semaphore,
|
||||
in_flight_fence: vk.Fence,
|
||||
|
||||
pub fn destroy(self: *const SyncContext, ldc: *const device.LogicalDeviceContext) void {
|
||||
ldc.vkd.destroyFence(ldc.device, self.in_flight_fence, null);
|
||||
ldc.vkd.destroySemaphore(ldc.device, self.render_finished_semaphore, null);
|
||||
ldc.vkd.destroySemaphore(ldc.device, self.image_available_semaphore, null);
|
||||
}
|
||||
};
|
||||
|
||||
pub fn initSyncObjects(ldc: device.LogicalDeviceContext) !SyncContext {
|
||||
const semaphore_create_info = vk.SemaphoreCreateInfo{};
|
||||
|
||||
const image_available_semaphore = try ldc.vkd.createSemaphore(
|
||||
ldc.device,
|
||||
&semaphore_create_info,
|
||||
null,
|
||||
);
|
||||
errdefer ldc.vkd.destroySemaphore(ldc.device, image_available_semaphore, null);
|
||||
|
||||
const render_finished_semaphore = try ldc.vkd.createSemaphore(
|
||||
ldc.device,
|
||||
&semaphore_create_info,
|
||||
null,
|
||||
);
|
||||
errdefer ldc.vkd.destroySemaphore(ldc.device, render_finished_semaphore, null);
|
||||
|
||||
const fence_create_info = vk.FenceCreateInfo{
|
||||
.flags = .{
|
||||
.signaled_bit = true,
|
||||
},
|
||||
};
|
||||
|
||||
const in_flight_fence = try ldc.vkd.createFence(
|
||||
ldc.device,
|
||||
&fence_create_info,
|
||||
null,
|
||||
);
|
||||
|
||||
std.debug.print("created synchronization objects\n", .{});
|
||||
|
||||
return .{
|
||||
.image_available_semaphore = image_available_semaphore,
|
||||
.render_finished_semaphore = render_finished_semaphore,
|
||||
.in_flight_fence = in_flight_fence,
|
||||
};
|
||||
}
|
||||
28
src/window.zig
Normal file
28
src/window.zig
Normal file
@ -0,0 +1,28 @@
|
||||
const std = @import("std");
|
||||
const glfw = @import("zglfw");
|
||||
|
||||
pub fn initWindow(x: c_int, y: c_int, name: [:0]const u8) !*glfw.Window {
|
||||
try glfw.init();
|
||||
errdefer glfw.terminate();
|
||||
|
||||
glfw.windowHint(.client_api, .no_api);
|
||||
const window = try glfw.Window.create(
|
||||
x,
|
||||
y,
|
||||
name,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
|
||||
std.debug.print("GLFW platform: {any}\n", .{glfw.getPlatform()});
|
||||
std.debug.print("Vulkan supported by GLFW: {}\n", .{glfw.isVulkanSupported()});
|
||||
|
||||
const size = window.getSize();
|
||||
const fb_size = window.getFramebufferSize();
|
||||
|
||||
std.debug.print("Window size: {}x{}\n", .{ size[0], size[1] });
|
||||
std.debug.print("Framebuffer size: {}x{}\n", .{ fb_size[0], fb_size[1] });
|
||||
std.debug.print("Window visible: {}\n", .{window.getAttribute(.visible)});
|
||||
|
||||
return window;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user