487 lines
18 KiB
Zig
487 lines
18 KiB
Zig
const std = @import("std");
|
|
const glfw = @import("glfw");
|
|
const vk = @import("vulkan");
|
|
const resources = @import("resources");
|
|
const Allocator = std.mem.Allocator;
|
|
const GraphicsContext = @import("graphics_context.zig").GraphicsContext;
|
|
const Swapchain = @import("swapchain.zig").Swapchain;
|
|
const Vertex = @import("vertex.zig").Vertex;
|
|
|
|
// TODO: model loading!
|
|
const vertices = [_]Vertex{
|
|
.{ .pos = .{ 0, -0.5 }, .color = .{ 1, 0, 0 } },
|
|
.{ .pos = .{ 0.5, 0.5 }, .color = .{ 0, 1, 0 } },
|
|
.{ .pos = .{ -0.5, 0.5 }, .color = .{ 0, 0, 1 } },
|
|
};
|
|
|
|
pub const VulkanRenderer = struct {
|
|
gc: *const GraphicsContext,
|
|
allocator: Allocator,
|
|
window: *glfw.Window,
|
|
swapchain: Swapchain,
|
|
render_pass: vk.RenderPass,
|
|
pipeline_layout: vk.PipelineLayout,
|
|
pipeline: vk.Pipeline,
|
|
cmdbufs: []vk.CommandBuffer,
|
|
framebuffers: []vk.Framebuffer,
|
|
pool: vk.CommandPool,
|
|
buffer: vk.Buffer,
|
|
memory: vk.DeviceMemory,
|
|
viewport: vk.Viewport,
|
|
scissor: vk.Rect2D,
|
|
window_resized: bool,
|
|
new_extent: vk.Extent2D,
|
|
|
|
pub fn init(gc: *const GraphicsContext, allocator: Allocator, extent: vk.Extent2D, window: *glfw.Window) !VulkanRenderer {
|
|
var self: VulkanRenderer = undefined;
|
|
|
|
self.window = window;
|
|
self.allocator = allocator;
|
|
window.setUserPointer(&self);
|
|
window.setFramebufferSizeCallback(resizeCallback);
|
|
|
|
self.gc = gc;
|
|
self.swapchain = try Swapchain.init(gc, allocator, extent);
|
|
|
|
self.pipeline_layout = try self.gc.vkd.createPipelineLayout(self.gc.dev, &.{
|
|
.flags = .{},
|
|
.set_layout_count = 0,
|
|
.p_set_layouts = undefined,
|
|
.push_constant_range_count = 0,
|
|
.p_push_constant_ranges = undefined,
|
|
}, null);
|
|
|
|
self.render_pass = try self.createRenderPass();
|
|
|
|
self.pipeline = try self.createPipeline();
|
|
|
|
self.framebuffers = try self.createFramebuffers();
|
|
|
|
self.pool = try gc.vkd.createCommandPool(gc.dev, &.{
|
|
.flags = .{},
|
|
.queue_family_index = gc.graphics_queue.family,
|
|
}, null);
|
|
|
|
self.buffer = try gc.vkd.createBuffer(gc.dev, &.{
|
|
.flags = .{},
|
|
.size = @sizeOf(@TypeOf(vertices)),
|
|
.usage = .{ .transfer_dst_bit = true, .vertex_buffer_bit = true },
|
|
.sharing_mode = .exclusive,
|
|
.queue_family_index_count = 0,
|
|
.p_queue_family_indices = undefined,
|
|
}, null);
|
|
const mem_reqs = gc.vkd.getBufferMemoryRequirements(gc.dev, self.buffer);
|
|
self.memory = try gc.allocate(mem_reqs, .{ .device_local_bit = true });
|
|
try gc.vkd.bindBufferMemory(gc.dev, self.buffer, self.memory, 0);
|
|
|
|
try self.uploadVertices();
|
|
|
|
self.cmdbufs = try self.createCommandBuffers(extent);
|
|
|
|
return self;
|
|
}
|
|
|
|
pub fn reinit(self: *VulkanRenderer, extent: vk.Extent2D) !void {
|
|
self.swapchain.recreate(extent);
|
|
}
|
|
|
|
pub fn deinit(self: *VulkanRenderer) void {
|
|
// todo: log?
|
|
self.swapchain.waitForAllFences() catch {};
|
|
|
|
self.destroyCommandBuffers();
|
|
self.gc.vkd.freeMemory(self.gc.dev, self.memory, null);
|
|
self.gc.vkd.destroyBuffer(self.gc.dev, self.buffer, null);
|
|
self.gc.vkd.destroyCommandPool(self.gc.dev, self.pool, null);
|
|
self.destroyFramebuffers();
|
|
self.gc.vkd.destroyPipeline(self.gc.dev, self.pipeline, null);
|
|
self.gc.vkd.destroyRenderPass(self.gc.dev, self.render_pass, null);
|
|
self.gc.vkd.destroyPipelineLayout(self.gc.dev, self.pipeline_layout, null);
|
|
|
|
self.swapchain.deinit();
|
|
}
|
|
|
|
pub fn drawFrame(self: *VulkanRenderer) !void {
|
|
const cmdbuf = self.cmdbufs[self.swapchain.image_index];
|
|
|
|
const state = self.swapchain.present(cmdbuf) catch |err| switch (err) {
|
|
error.OutOfDateKHR => Swapchain.PresentState.suboptimal,
|
|
else => |narrow| return narrow,
|
|
};
|
|
|
|
if (state == .suboptimal or self.window_resized) {
|
|
// todo: fn
|
|
self.destroyFramebuffers();
|
|
|
|
self.gc.vkd.destroyPipeline(self.gc.dev, self.pipeline, null);
|
|
self.gc.vkd.destroyPipelineLayout(self.gc.dev, self.pipeline_layout, null);
|
|
self.gc.vkd.destroyRenderPass(self.gc.dev, self.render_pass, null);
|
|
|
|
try self.swapchain.recreate(self.new_extent);
|
|
|
|
// todo: dedupe creation logic
|
|
self.pipeline_layout = try self.gc.vkd.createPipelineLayout(self.gc.dev, &.{
|
|
.flags = .{},
|
|
.set_layout_count = 0,
|
|
.p_set_layouts = undefined,
|
|
.push_constant_range_count = 0,
|
|
.p_push_constant_ranges = undefined,
|
|
}, null);
|
|
|
|
self.render_pass = try self.createRenderPass();
|
|
self.pipeline = try self.createPipeline();
|
|
self.framebuffers = try self.createFramebuffers();
|
|
self.window_resized = false;
|
|
}
|
|
}
|
|
|
|
fn createRenderPass(self: *VulkanRenderer) !vk.RenderPass {
|
|
const color_attachment = vk.AttachmentDescription{
|
|
.flags = .{},
|
|
.format = self.swapchain.surface_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{
|
|
.flags = .{},
|
|
.pipeline_bind_point = .graphics,
|
|
.input_attachment_count = 0,
|
|
.p_input_attachments = undefined,
|
|
.color_attachment_count = 1,
|
|
.p_color_attachments = @ptrCast([*]const vk.AttachmentReference, &color_attachment_ref),
|
|
.p_resolve_attachments = null,
|
|
.p_depth_stencil_attachment = null,
|
|
.preserve_attachment_count = 0,
|
|
.p_preserve_attachments = undefined,
|
|
};
|
|
|
|
return try self.gc.vkd.createRenderPass(self.gc.dev, &.{
|
|
.flags = .{},
|
|
.attachment_count = 1,
|
|
.p_attachments = @ptrCast([*]const vk.AttachmentDescription, &color_attachment),
|
|
.subpass_count = 1,
|
|
.p_subpasses = @ptrCast([*]const vk.SubpassDescription, &subpass),
|
|
.dependency_count = 0,
|
|
.p_dependencies = undefined,
|
|
}, null);
|
|
}
|
|
|
|
fn createPipeline(self: *VulkanRenderer) !vk.Pipeline {
|
|
const vert = try self.gc.vkd.createShaderModule(self.gc.dev, &.{
|
|
.flags = .{},
|
|
.code_size = resources.triangle_vert.len,
|
|
.p_code = @ptrCast([*]const u32, resources.triangle_vert),
|
|
}, null);
|
|
defer self.gc.vkd.destroyShaderModule(self.gc.dev, vert, null);
|
|
|
|
const frag = try self.gc.vkd.createShaderModule(self.gc.dev, &.{
|
|
.flags = .{},
|
|
.code_size = resources.triangle_frag.len,
|
|
.p_code = @ptrCast([*]const u32, resources.triangle_frag),
|
|
}, null);
|
|
defer self.gc.vkd.destroyShaderModule(self.gc.dev, frag, null);
|
|
|
|
const pssci = [_]vk.PipelineShaderStageCreateInfo{
|
|
.{
|
|
.flags = .{},
|
|
.stage = .{ .vertex_bit = true },
|
|
.module = vert,
|
|
.p_name = "main",
|
|
.p_specialization_info = null,
|
|
},
|
|
.{
|
|
.flags = .{},
|
|
.stage = .{ .fragment_bit = true },
|
|
.module = frag,
|
|
.p_name = "main",
|
|
.p_specialization_info = null,
|
|
},
|
|
};
|
|
|
|
const pvisci = vk.PipelineVertexInputStateCreateInfo{
|
|
.flags = .{},
|
|
.vertex_binding_description_count = 1,
|
|
.p_vertex_binding_descriptions = @ptrCast([*]const vk.VertexInputBindingDescription, &Vertex.binding_description),
|
|
.vertex_attribute_description_count = Vertex.attribute_description.len,
|
|
.p_vertex_attribute_descriptions = &Vertex.attribute_description,
|
|
};
|
|
|
|
const piasci = vk.PipelineInputAssemblyStateCreateInfo{
|
|
.flags = .{},
|
|
.topology = .triangle_list,
|
|
.primitive_restart_enable = vk.FALSE,
|
|
};
|
|
|
|
const pvsci = vk.PipelineViewportStateCreateInfo{
|
|
.flags = .{},
|
|
.viewport_count = 1,
|
|
.p_viewports = undefined, // set in createCommandBuffers iwth cmdSetViewport
|
|
.scissor_count = 1,
|
|
.p_scissors = undefined, //set in createCommandBuffers
|
|
};
|
|
|
|
const prsci = vk.PipelineRasterizationStateCreateInfo{
|
|
.flags = .{},
|
|
.depth_clamp_enable = vk.FALSE,
|
|
.rasterizer_discard_enable = vk.FALSE,
|
|
.polygon_mode = .fill,
|
|
.cull_mode = .{ .back_bit = true },
|
|
.front_face = .clockwise,
|
|
.depth_bias_enable = vk.FALSE,
|
|
.depth_bias_constant_factor = 0,
|
|
.depth_bias_clamp = 0,
|
|
.depth_bias_slope_factor = 0,
|
|
.line_width = 1,
|
|
};
|
|
|
|
const pmsci = vk.PipelineMultisampleStateCreateInfo{
|
|
.flags = .{},
|
|
.rasterization_samples = .{ .@"1_bit" = true },
|
|
.sample_shading_enable = vk.FALSE,
|
|
.min_sample_shading = 1,
|
|
.p_sample_mask = null,
|
|
.alpha_to_coverage_enable = vk.FALSE,
|
|
.alpha_to_one_enable = vk.FALSE,
|
|
};
|
|
|
|
const pcbas = vk.PipelineColorBlendAttachmentState{
|
|
.blend_enable = vk.FALSE,
|
|
.src_color_blend_factor = .one,
|
|
.dst_color_blend_factor = .zero,
|
|
.color_blend_op = .add,
|
|
.src_alpha_blend_factor = .one,
|
|
.dst_alpha_blend_factor = .zero,
|
|
.alpha_blend_op = .add,
|
|
.color_write_mask = .{ .r_bit = true, .g_bit = true, .b_bit = true, .a_bit = true },
|
|
};
|
|
|
|
const pcbsci = vk.PipelineColorBlendStateCreateInfo{
|
|
.flags = .{},
|
|
.logic_op_enable = vk.FALSE,
|
|
.logic_op = .copy,
|
|
.attachment_count = 1,
|
|
.p_attachments = @ptrCast([*]const vk.PipelineColorBlendAttachmentState, &pcbas),
|
|
.blend_constants = [_]f32{ 0, 0, 0, 0 },
|
|
};
|
|
|
|
const dynstate = [_]vk.DynamicState{ .viewport, .scissor };
|
|
const pdsci = vk.PipelineDynamicStateCreateInfo{
|
|
.flags = .{},
|
|
.dynamic_state_count = dynstate.len,
|
|
.p_dynamic_states = &dynstate,
|
|
};
|
|
|
|
const gpci = vk.GraphicsPipelineCreateInfo{
|
|
.flags = .{},
|
|
.stage_count = 2,
|
|
.p_stages = &pssci,
|
|
.p_vertex_input_state = &pvisci,
|
|
.p_input_assembly_state = &piasci,
|
|
.p_tessellation_state = null,
|
|
.p_viewport_state = &pvsci,
|
|
.p_rasterization_state = &prsci,
|
|
.p_multisample_state = &pmsci,
|
|
.p_depth_stencil_state = null,
|
|
.p_color_blend_state = &pcbsci,
|
|
.p_dynamic_state = &pdsci,
|
|
.layout = self.pipeline_layout,
|
|
.render_pass = self.render_pass,
|
|
.subpass = 0,
|
|
.base_pipeline_handle = .null_handle,
|
|
.base_pipeline_index = -1,
|
|
};
|
|
|
|
var pipeline: vk.Pipeline = undefined;
|
|
_ = try self.gc.vkd.createGraphicsPipelines(
|
|
self.gc.dev,
|
|
.null_handle,
|
|
1,
|
|
@ptrCast([*]const vk.GraphicsPipelineCreateInfo, &gpci),
|
|
null,
|
|
@ptrCast([*]vk.Pipeline, &pipeline),
|
|
);
|
|
return pipeline;
|
|
}
|
|
|
|
fn createFramebuffers(self: *VulkanRenderer) ![]vk.Framebuffer {
|
|
const framebuffers = try self.allocator.alloc(vk.Framebuffer, self.swapchain.swap_images.len);
|
|
errdefer self.allocator.free(framebuffers);
|
|
|
|
var i: usize = 0;
|
|
errdefer for (self.framebuffers[0..i]) |fb| self.gc.vkd.destroyFramebuffer(self.gc.dev, fb, null);
|
|
|
|
for (framebuffers) |*fb| {
|
|
fb.* = try self.gc.vkd.createFramebuffer(self.gc.dev, &.{
|
|
.flags = .{},
|
|
.render_pass = self.render_pass,
|
|
.attachment_count = 1,
|
|
.p_attachments = @ptrCast([*]const vk.ImageView, &self.swapchain.swap_images[i].view),
|
|
.width = self.swapchain.extent.width,
|
|
.height = self.swapchain.extent.height,
|
|
.layers = 1,
|
|
}, null);
|
|
i += 1;
|
|
}
|
|
|
|
return framebuffers;
|
|
}
|
|
|
|
fn createCommandBuffers(self: *VulkanRenderer, extent: vk.Extent2D) ![]vk.CommandBuffer {
|
|
const cmdbufs = try self.allocator.alloc(vk.CommandBuffer, self.framebuffers.len);
|
|
errdefer self.allocator.free(cmdbufs);
|
|
|
|
try self.gc.vkd.allocateCommandBuffers(self.gc.dev, &.{
|
|
.command_pool = self.pool,
|
|
.level = .primary,
|
|
.command_buffer_count = @truncate(u32, cmdbufs.len),
|
|
}, cmdbufs.ptr);
|
|
errdefer self.gc.vkd.freeCommandBuffers(self.gc.dev, self.pool, @truncate(u32, cmdbufs.len), cmdbufs.ptr);
|
|
|
|
const clear = vk.ClearValue{
|
|
.color = .{ .float_32 = .{ 0, 0, 0, 1 } },
|
|
};
|
|
|
|
self.viewport = vk.Viewport{
|
|
.x = 0,
|
|
.y = 0,
|
|
.width = @intToFloat(f32, extent.width),
|
|
.height = @intToFloat(f32, extent.height),
|
|
.min_depth = 0,
|
|
.max_depth = 1,
|
|
};
|
|
|
|
self.scissor = vk.Rect2D{
|
|
.offset = .{ .x = 0, .y = 0 },
|
|
.extent = extent,
|
|
};
|
|
|
|
for (cmdbufs) |cmdbuf, i| {
|
|
_ = i;
|
|
try self.gc.vkd.beginCommandBuffer(cmdbuf, &.{
|
|
.flags = .{},
|
|
.p_inheritance_info = null,
|
|
});
|
|
|
|
self.gc.vkd.cmdSetViewport(cmdbuf, 0, 1, @ptrCast([*]const vk.Viewport, &self.viewport));
|
|
self.gc.vkd.cmdSetScissor(cmdbuf, 0, 1, @ptrCast([*]const vk.Rect2D, &self.scissor));
|
|
|
|
// This needs to be a separate definition - see https://github.com/ziglang/zig/issues/7627.
|
|
const render_area = vk.Rect2D{
|
|
.offset = .{ .x = 0, .y = 0 },
|
|
.extent = extent,
|
|
};
|
|
|
|
self.gc.vkd.cmdBeginRenderPass(cmdbuf, &.{
|
|
.render_pass = self.render_pass,
|
|
.framebuffer = self.framebuffers[i],
|
|
.render_area = render_area,
|
|
.clear_value_count = 1,
|
|
.p_clear_values = @ptrCast([*]const vk.ClearValue, &clear),
|
|
}, .@"inline");
|
|
|
|
self.gc.vkd.cmdBindPipeline(cmdbuf, .graphics, self.pipeline);
|
|
const offset = [_]vk.DeviceSize{0};
|
|
self.gc.vkd.cmdBindVertexBuffers(cmdbuf, 0, 1, @ptrCast([*]const vk.Buffer, &self.buffer), &offset);
|
|
self.gc.vkd.cmdDraw(cmdbuf, vertices.len, 1, 0, 0);
|
|
|
|
self.gc.vkd.cmdEndRenderPass(cmdbuf);
|
|
try self.gc.vkd.endCommandBuffer(cmdbuf);
|
|
}
|
|
|
|
return cmdbufs;
|
|
}
|
|
|
|
fn destroyCommandBuffers(self: *VulkanRenderer) void {
|
|
self.gc.vkd.freeCommandBuffers(self.gc.dev, self.pool, @truncate(u32, self.cmdbufs.len), self.cmdbufs.ptr);
|
|
self.allocator.free(self.cmdbufs);
|
|
}
|
|
|
|
fn destroyFramebuffers(self: *VulkanRenderer) void {
|
|
for (self.framebuffers) |fb| self.gc.vkd.destroyFramebuffer(self.gc.dev, fb, null);
|
|
self.allocator.free(self.framebuffers);
|
|
}
|
|
|
|
fn copyBuffer(self: *VulkanRenderer, dst: vk.Buffer, src: vk.Buffer, size: vk.DeviceSize) !void {
|
|
var cmdbuf: vk.CommandBuffer = undefined;
|
|
try self.gc.vkd.allocateCommandBuffers(self.gc.dev, &.{
|
|
.command_pool = self.pool,
|
|
.level = .primary,
|
|
.command_buffer_count = 1,
|
|
}, @ptrCast([*]vk.CommandBuffer, &cmdbuf));
|
|
defer self.gc.vkd.freeCommandBuffers(self.gc.dev, self.pool, 1, @ptrCast([*]const vk.CommandBuffer, &cmdbuf));
|
|
|
|
try self.gc.vkd.beginCommandBuffer(cmdbuf, &.{
|
|
.flags = .{ .one_time_submit_bit = true },
|
|
.p_inheritance_info = null,
|
|
});
|
|
|
|
const region = vk.BufferCopy{
|
|
.src_offset = 0,
|
|
.dst_offset = 0,
|
|
.size = size,
|
|
};
|
|
self.gc.vkd.cmdCopyBuffer(cmdbuf, src, dst, 1, @ptrCast([*]const vk.BufferCopy, ®ion));
|
|
|
|
try self.gc.vkd.endCommandBuffer(cmdbuf);
|
|
|
|
const si = vk.SubmitInfo{
|
|
.wait_semaphore_count = 0,
|
|
.p_wait_semaphores = undefined,
|
|
.p_wait_dst_stage_mask = undefined,
|
|
.command_buffer_count = 1,
|
|
.p_command_buffers = @ptrCast([*]const vk.CommandBuffer, &cmdbuf),
|
|
.signal_semaphore_count = 0,
|
|
.p_signal_semaphores = undefined,
|
|
};
|
|
try self.gc.vkd.queueSubmit(self.gc.graphics_queue.handle, 1, @ptrCast([*]const vk.SubmitInfo, &si), .null_handle);
|
|
try self.gc.vkd.queueWaitIdle(self.gc.graphics_queue.handle);
|
|
}
|
|
|
|
fn uploadVertices(self: *VulkanRenderer) !void {
|
|
const staging_buffer = try self.gc.vkd.createBuffer(self.gc.dev, &.{
|
|
.flags = .{},
|
|
.size = @sizeOf(@TypeOf(vertices)),
|
|
.usage = .{ .transfer_src_bit = true },
|
|
.sharing_mode = .exclusive,
|
|
.queue_family_index_count = 0,
|
|
.p_queue_family_indices = undefined,
|
|
}, null);
|
|
|
|
defer self.gc.vkd.destroyBuffer(self.gc.dev, staging_buffer, null);
|
|
const mem_reqs = self.gc.vkd.getBufferMemoryRequirements(self.gc.dev, staging_buffer);
|
|
const staging_memory = try self.gc.allocate(mem_reqs, .{ .host_visible_bit = true, .host_coherent_bit = true });
|
|
defer self.gc.vkd.freeMemory(self.gc.dev, staging_memory, null);
|
|
try self.gc.vkd.bindBufferMemory(self.gc.dev, staging_buffer, staging_memory, 0);
|
|
|
|
{
|
|
const data = try self.gc.vkd.mapMemory(self.gc.dev, staging_memory, 0, vk.WHOLE_SIZE, .{});
|
|
defer self.gc.vkd.unmapMemory(self.gc.dev, staging_memory);
|
|
|
|
const gpu_vertices = @ptrCast([*]Vertex, @alignCast(@alignOf(Vertex), data));
|
|
for (vertices) |vertex, i| {
|
|
gpu_vertices[i] = vertex;
|
|
}
|
|
}
|
|
|
|
try self.copyBuffer(self.buffer, staging_buffer, @sizeOf(@TypeOf(vertices)));
|
|
}
|
|
};
|
|
|
|
fn resizeCallback(window: glfw.Window, width: u32, height: u32) void {
|
|
if (window.getUserPointer(VulkanRenderer)) |renderer| {
|
|
renderer.window_resized = true;
|
|
renderer.new_extent.width = width;
|
|
renderer.new_extent.height = height;
|
|
}
|
|
}
|