Skip to content

Instantly share code, notes, and snippets.

@shakesoda
Created April 5, 2020 20:02
Show Gist options
  • Save shakesoda/28eb50428eb683ce677b8b1903a57473 to your computer and use it in GitHub Desktop.
Save shakesoda/28eb50428eb683ce677b8b1903a57473 to your computer and use it in GitHub Desktop.
zig vulkan triangle breaks compiler
$ zig build
broken LLVM module found: Call parameter type does not match function signature!
%35 = getelementptr inbounds { %"[][*]const u8", i16 }, { %"[][*]const u8", i16 }* %0, i32 0, i32 0, !dbg !8239
%"[][*:0]const u8"* call fastcc void @"std.array_list.AlignedArrayList([*:0]const u8,null).toOwnedSlice"(%"[][*]const u8"* sret %35, %"std.array_list.AlignedArrayList([*:0]const u8,null)"* %extensions), !dbg !8239
This is a bug in the Zig compiler.
Unable to dump stack trace: debug info stripped
how-does-vulkan...The following command exited with error code 3:
$ zig version
0.5.0+1568470c4
const std = @import("std");
const assert = std.debug.assert;
const mem = std.mem;
const Allocator = mem.Allocator;
const c = @cImport({
@cInclude("vulkan/vulkan.h");
@cInclude("GLFW/glfw3.h");
});
const WIDTH = 800;
const HEIGHT = 600;
const MAX_FRAMES_IN_FLIGHT = 2;
const enableValidationLayers = std.debug.runtime_safety;
const validationLayers = [_][*:0]const u8{"VK_LAYER_LUNARG_standard_validation"};
const deviceExtensions = [_][*:0]const u8{c.VK_KHR_SWAPCHAIN_EXTENSION_NAME};
var currentFrame: usize = 0;
var instance: c.VkInstance = undefined;
var callback: c.VkDebugReportCallbackEXT = undefined;
var surface: c.VkSurfaceKHR = undefined;
var physicalDevice: c.VkPhysicalDevice = undefined;
var global_device: c.VkDevice = undefined;
var graphicsQueue: c.VkQueue = undefined;
var presentQueue: c.VkQueue = undefined;
var swapChainImages: []c.VkImage = undefined;
var swapChain: c.VkSwapchainKHR = undefined;
var swapChainImageFormat: c.VkFormat = undefined;
var swapChainExtent: c.VkExtent2D = undefined;
var swapChainImageViews: []c.VkImageView = undefined;
var renderPass: c.VkRenderPass = undefined;
var pipelineLayout: c.VkPipelineLayout = undefined;
var graphicsPipeline: c.VkPipeline = undefined;
var swapChainFramebuffers: []c.VkFramebuffer = undefined;
var commandPool: c.VkCommandPool = undefined;
var commandBuffers: []c.VkCommandBuffer = undefined;
var imageAvailableSemaphores: [MAX_FRAMES_IN_FLIGHT]c.VkSemaphore = undefined;
var renderFinishedSemaphores: [MAX_FRAMES_IN_FLIGHT]c.VkSemaphore = undefined;
var inFlightFences: [MAX_FRAMES_IN_FLIGHT]c.VkFence = undefined;
const QueueFamilyIndices = struct {
graphicsFamily: ?u32,
presentFamily: ?u32,
fn init() QueueFamilyIndices {
return QueueFamilyIndices{
.graphicsFamily = null,
.presentFamily = null,
};
}
fn isComplete(self: QueueFamilyIndices) bool {
return self.graphicsFamily != null and self.presentFamily != null;
}
};
const SwapChainSupportDetails = struct {
capabilities: c.VkSurfaceCapabilitiesKHR,
formats: std.ArrayList(c.VkSurfaceFormatKHR),
presentModes: std.ArrayList(c.VkPresentModeKHR),
fn init(allocator: *Allocator) SwapChainSupportDetails {
var result = SwapChainSupportDetails{
.capabilities = std.mem.zeroes(c.VkSurfaceCapabilitiesKHR),
.formats = std.ArrayList(c.VkSurfaceFormatKHR).init(allocator),
.presentModes = std.ArrayList(c.VkPresentModeKHR).init(allocator),
};
// const slice = @sliceToBytes((*[1]c.VkSurfaceCapabilitiesKHR)(&result.capabilities)[0..1]);
// std.mem.set(u8, slice, 0);
return result;
}
fn deinit(self: *SwapChainSupportDetails) void {
self.formats.deinit();
self.presentModes.deinit();
}
};
pub fn main() !void {
if (c.glfwInit() == 0) return error.GlfwInitFailed;
defer c.glfwTerminate();
c.glfwWindowHint(c.GLFW_CLIENT_API, c.GLFW_NO_API);
c.glfwWindowHint(c.GLFW_RESIZABLE, c.GLFW_FALSE);
const window = c.glfwCreateWindow(WIDTH, HEIGHT, "Zig Vulkan Triangle", null, null) orelse return error.GlfwCreateWindowFailed;
defer c.glfwDestroyWindow(window);
const allocator = std.heap.c_allocator;
try initVulkan(allocator, window);
while (c.glfwWindowShouldClose(window) == 0) {
c.glfwPollEvents();
try drawFrame();
}
try checkSuccess(c.vkDeviceWaitIdle(global_device));
cleanup();
}
fn cleanup() void {
var i: usize = 0;
while (i < MAX_FRAMES_IN_FLIGHT) : (i += 1) {
c.vkDestroySemaphore(global_device, renderFinishedSemaphores[i], null);
c.vkDestroySemaphore(global_device, imageAvailableSemaphores[i], null);
c.vkDestroyFence(global_device, inFlightFences[i], null);
}
c.vkDestroyCommandPool(global_device, commandPool, null);
for (swapChainFramebuffers) |framebuffer| {
c.vkDestroyFramebuffer(global_device, framebuffer, null);
}
c.vkDestroyPipeline(global_device, graphicsPipeline, null);
c.vkDestroyPipelineLayout(global_device, pipelineLayout, null);
c.vkDestroyRenderPass(global_device, renderPass, null);
for (swapChainImageViews) |imageView| {
c.vkDestroyImageView(global_device, imageView, null);
}
c.vkDestroySwapchainKHR(global_device, swapChain, null);
c.vkDestroyDevice(global_device, null);
if (enableValidationLayers) {
DestroyDebugReportCallbackEXT(null);
}
c.vkDestroySurfaceKHR(instance, surface, null);
c.vkDestroyInstance(instance, null);
}
fn initVulkan(allocator: *Allocator, window: *c.GLFWwindow) !void {
try createInstance(allocator);
try setupDebugCallback();
try createSurface(window);
try pickPhysicalDevice(allocator);
try createLogicalDevice(allocator);
try createSwapChain(allocator);
try createImageViews(allocator);
try createRenderPass();
try createGraphicsPipeline(allocator);
try createFramebuffers(allocator);
try createCommandPool(allocator);
try createCommandBuffers(allocator);
try createSyncObjects();
}
// automate setting up these things, it's really error prone.
fn VulkanObject(T: var) T {
var ret = std.mem.zeroes(T);
ret.sType = switch (T) {
c.VkDeviceQueueCreateInfo => .VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
c.VkSwapchainCreateInfoKHR => .VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
c.VkPresentInfoKHR => .VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
c.VkDebugReportCallbackCreateInfoEXT => .VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT,
c.VkDeviceCreateInfo => .VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
c.VkImageViewCreateInfo => .VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
c.VkPipelineLayoutCreateInfo => .VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
c.VkCommandBufferAllocateInfo => .VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
c.VkCommandBufferBeginInfo => .VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
c.VkRenderPassBeginInfo => .VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
c.VkApplicationInfo => .VK_STRUCTURE_TYPE_APPLICATION_INFO,
c.VkInstanceCreateInfo => .VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
c.VkPipelineShaderStageCreateInfo => .VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
c.VkPipelineVertexInputStateCreateInfo => .VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
c.VkPipelineInputAssemblyStateCreateInfo => .VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
c.VkPipelineViewportStateCreateInfo => .VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
c.VkPipelineRasterizationStateCreateInfo => .VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
c.VkPipelineMultisampleStateCreateInfo => .VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
c.VkGraphicsPipelineCreateInfo => .VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
c.VkRenderPassCreateInfo => .VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
c.VkSubmitInfo => .VK_STRUCTURE_TYPE_SUBMIT_INFO,
c.VkSemaphoreCreateInfo => .VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
c.VkFenceCreateInfo => .VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
c.VkCommandPoolCreateInfo => .VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
c.VkFramebufferCreateInfo => .VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
c.VkShaderModuleCreateInfo => .VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
c.VkPipelineColorBlendStateCreateInfo => .VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
else => unreachable
};
return ret;
}
fn createCommandBuffers(allocator: *Allocator) !void {
commandBuffers = try allocator.alloc(c.VkCommandBuffer, swapChainFramebuffers.len);
var allocInfo = VulkanObject(c.VkCommandBufferAllocateInfo);
allocInfo.commandPool = commandPool;
allocInfo.level = .VK_COMMAND_BUFFER_LEVEL_PRIMARY;
allocInfo.commandBufferCount = @intCast(u32, commandBuffers.len);
try checkSuccess(c.vkAllocateCommandBuffers(global_device, &allocInfo, commandBuffers.ptr));
for (commandBuffers) |command_buffer, i| {
var beginInfo = VulkanObject(c.VkCommandBufferBeginInfo);
beginInfo.flags = c.VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT;
try checkSuccess(c.vkBeginCommandBuffer(commandBuffers[i], &beginInfo));
const clearColor = c.VkClearValue{ .color = c.VkClearColorValue{ .float32 = [_]f32{ 0.0, 0.0, 0.0, 1.0 } } };
var renderPassInfo = VulkanObject(c.VkRenderPassBeginInfo);
renderPassInfo.renderPass = renderPass;
renderPassInfo.framebuffer = swapChainFramebuffers[i];
renderPassInfo.renderArea = c.VkRect2D{
.offset = c.VkOffset2D{ .x = 0, .y = 0 },
.extent = swapChainExtent,
};
renderPassInfo.clearValueCount = 1;
renderPassInfo.pClearValues = &clearColor;
c.vkCmdBeginRenderPass(commandBuffers[i], &renderPassInfo, .VK_SUBPASS_CONTENTS_INLINE);
{
c.vkCmdBindPipeline(commandBuffers[i], .VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline);
c.vkCmdDraw(commandBuffers[i], 3, 1, 0, 0);
}
c.vkCmdEndRenderPass(commandBuffers[i]);
try checkSuccess(c.vkEndCommandBuffer(commandBuffers[i]));
}
}
fn createSyncObjects() !void {
const semaphoreInfo = VulkanObject(c.VkSemaphoreCreateInfo);
var fenceInfo = VulkanObject(c.VkFenceCreateInfo);
fenceInfo.flags = c.VK_FENCE_CREATE_SIGNALED_BIT;
var i: usize = 0;
while (i < MAX_FRAMES_IN_FLIGHT) : (i += 1) {
try checkSuccess(c.vkCreateSemaphore(global_device, &semaphoreInfo, null, &imageAvailableSemaphores[i]));
try checkSuccess(c.vkCreateSemaphore(global_device, &semaphoreInfo, null, &renderFinishedSemaphores[i]));
try checkSuccess(c.vkCreateFence(global_device, &fenceInfo, null, &inFlightFences[i]));
}
}
fn createCommandPool(allocator: *Allocator) !void {
const queueFamilyIndices = try findQueueFamilies(allocator, physicalDevice);
var poolInfo = VulkanObject(c.VkCommandPoolCreateInfo);
poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.?;
try checkSuccess(c.vkCreateCommandPool(global_device, &poolInfo, null, &commandPool));
}
fn createFramebuffers(allocator: *Allocator) !void {
swapChainFramebuffers = try allocator.alloc(c.VkFramebuffer, swapChainImageViews.len);
for (swapChainImageViews) |swap_chain_image_view, i| {
const attachments = [_]c.VkImageView{swap_chain_image_view};
var framebufferInfo = VulkanObject(c.VkFramebufferCreateInfo);
framebufferInfo.renderPass = renderPass;
framebufferInfo.attachmentCount = 1;
framebufferInfo.pAttachments = &attachments;
framebufferInfo.width = swapChainExtent.width;
framebufferInfo.height = swapChainExtent.height;
framebufferInfo.layers = 1;
try checkSuccess(c.vkCreateFramebuffer(global_device, &framebufferInfo, null, &swapChainFramebuffers[i]));
}
}
fn createShaderModule(code: []align(@alignOf(u32)) const u8) !c.VkShaderModule {
var createInfo = VulkanObject(c.VkShaderModuleCreateInfo);
createInfo.codeSize = code.len;
createInfo.pCode = @ptrCast([*:0]const u32, &code[0..:0]);
var shaderModule: c.VkShaderModule = undefined;
try checkSuccess(c.vkCreateShaderModule(global_device, &createInfo, null, &shaderModule));
return shaderModule;
}
fn createGraphicsPipeline(allocator: *Allocator) !void {
const vertShaderCode = try std.fs.cwd().readFileAllocAligned(allocator, "shaders/vert.spv", std.math.maxInt(u32), @alignOf(u32));
defer allocator.free(vertShaderCode);
const fragShaderCode = try std.fs.cwd().readFileAllocAligned(allocator, "shaders/frag.spv", std.math.maxInt(u32), @alignOf(u32));
defer allocator.free(fragShaderCode);
const vertShaderModule = try createShaderModule(vertShaderCode);
const fragShaderModule = try createShaderModule(fragShaderCode);
var vertShaderStageInfo = VulkanObject(c.VkPipelineShaderStageCreateInfo);
vertShaderStageInfo.stage = .VK_SHADER_STAGE_VERTEX_BIT;
vertShaderStageInfo.module = vertShaderModule;
vertShaderStageInfo.pName = "main";
var fragShaderStageInfo = VulkanObject(c.VkPipelineShaderStageCreateInfo);
fragShaderStageInfo.stage = .VK_SHADER_STAGE_FRAGMENT_BIT;
fragShaderStageInfo.module = fragShaderModule;
fragShaderStageInfo.pName = "main";
const shaderStages = [_]c.VkPipelineShaderStageCreateInfo{ vertShaderStageInfo, fragShaderStageInfo };
const vertexInputInfo = VulkanObject(c.VkPipelineVertexInputStateCreateInfo);
var inputAssembly = VulkanObject(c.VkPipelineInputAssemblyStateCreateInfo);
inputAssembly.topology = .VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
inputAssembly.primitiveRestartEnable = c.VK_FALSE;
const viewport = [_]c.VkViewport{c.VkViewport{
.x = 0.0,
.y = 0.0,
.width = @intToFloat(f32, swapChainExtent.width),
.height = @intToFloat(f32, swapChainExtent.height),
.minDepth = 0.0,
.maxDepth = 1.0,
}};
const scissor = [_]c.VkRect2D{c.VkRect2D{
.offset = c.VkOffset2D{ .x = 0, .y = 0 },
.extent = swapChainExtent,
}};
var viewportState = VulkanObject(c.VkPipelineViewportStateCreateInfo);
viewportState.viewportCount = 1;
viewportState.pViewports = &viewport;
viewportState.scissorCount = 1;
viewportState.pScissors = &scissor;
var rasterizer = VulkanObject(c.VkPipelineRasterizationStateCreateInfo);
rasterizer.depthClampEnable = c.VK_FALSE;
rasterizer.rasterizerDiscardEnable = c.VK_FALSE;
rasterizer.polygonMode = .VK_POLYGON_MODE_FILL;
rasterizer.lineWidth = 1.0;
// rasterizer.cullMode = @intCast(u32, @enumToInt(c.VK_CULL_MODE_BACK_BIT));
rasterizer.cullMode = c.VK_CULL_MODE_BACK_BIT;
rasterizer.frontFace = .VK_FRONT_FACE_CLOCKWISE;
rasterizer.depthBiasEnable = c.VK_FALSE;
var multisampling = VulkanObject(c.VkPipelineMultisampleStateCreateInfo);
multisampling.sampleShadingEnable = c.VK_FALSE;
multisampling.rasterizationSamples = .VK_SAMPLE_COUNT_1_BIT;
const colorBlendAttachment = c.VkPipelineColorBlendAttachmentState{
.colorWriteMask = c.VK_COLOR_COMPONENT_R_BIT | c.VK_COLOR_COMPONENT_G_BIT | c.VK_COLOR_COMPONENT_B_BIT | c.VK_COLOR_COMPONENT_A_BIT,
.blendEnable = c.VK_FALSE,
.srcColorBlendFactor = .VK_BLEND_FACTOR_ZERO,
.dstColorBlendFactor = .VK_BLEND_FACTOR_ZERO,
.colorBlendOp = .VK_BLEND_OP_ADD,
.srcAlphaBlendFactor = .VK_BLEND_FACTOR_ZERO,
.dstAlphaBlendFactor = .VK_BLEND_FACTOR_ZERO,
.alphaBlendOp = .VK_BLEND_OP_ADD,
};
var colorBlending = VulkanObject(c.VkPipelineColorBlendStateCreateInfo);
colorBlending.logicOpEnable = c.VK_FALSE;
colorBlending.logicOp = .VK_LOGIC_OP_COPY;
colorBlending.attachmentCount = 1;
colorBlending.pAttachments = &colorBlendAttachment;
colorBlending.blendConstants = [_]f32{ 0, 0, 0, 0 };
const pipelineLayoutInfo = VulkanObject(c.VkPipelineLayoutCreateInfo);
try checkSuccess(c.vkCreatePipelineLayout(global_device, &pipelineLayoutInfo, null, &pipelineLayout));
var pipelineInfoReal = VulkanObject(c.VkGraphicsPipelineCreateInfo);
pipelineInfoReal.stageCount = @intCast(u32, shaderStages.len);
pipelineInfoReal.pStages = &shaderStages;
pipelineInfoReal.pVertexInputState = &vertexInputInfo;
pipelineInfoReal.pInputAssemblyState = &inputAssembly;
pipelineInfoReal.pViewportState = &viewportState;
pipelineInfoReal.pRasterizationState = &rasterizer;
pipelineInfoReal.pMultisampleState = &multisampling;
pipelineInfoReal.pColorBlendState = &colorBlending;
pipelineInfoReal.layout = pipelineLayout;
pipelineInfoReal.renderPass = renderPass;
const pipelineInfo = [_]c.VkGraphicsPipelineCreateInfo{ pipelineInfoReal };
try checkSuccess(c.vkCreateGraphicsPipelines(
global_device,
null,
@intCast(u32, pipelineInfo.len),
&pipelineInfo,
null,
&graphicsPipeline,
));
c.vkDestroyShaderModule(global_device, fragShaderModule, null);
c.vkDestroyShaderModule(global_device, vertShaderModule, null);
}
fn createRenderPass() !void {
const colorAttachment = c.VkAttachmentDescription{
.format = swapChainImageFormat,
.samples = .VK_SAMPLE_COUNT_1_BIT,
.loadOp = .VK_ATTACHMENT_LOAD_OP_CLEAR,
.storeOp = .VK_ATTACHMENT_STORE_OP_STORE,
.stencilLoadOp = .VK_ATTACHMENT_LOAD_OP_DONT_CARE,
.stencilStoreOp = .VK_ATTACHMENT_STORE_OP_DONT_CARE,
.initialLayout = .VK_IMAGE_LAYOUT_UNDEFINED,
.finalLayout = .VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
.flags = 0,
};
const colorAttachmentRef = c.VkAttachmentReference{
.attachment = 0,
.layout = .VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
};
const subpass = [_]c.VkSubpassDescription{c.VkSubpassDescription{
.pipelineBindPoint = .VK_PIPELINE_BIND_POINT_GRAPHICS,
.colorAttachmentCount = 1,
.pColorAttachments = &colorAttachmentRef,
.flags = 0,
.inputAttachmentCount = 0,
.pInputAttachments = null,
.pResolveAttachments = null,
.pDepthStencilAttachment = null,
.preserveAttachmentCount = 0,
.pPreserveAttachments = null,
}};
const dependency = [_]c.VkSubpassDependency{c.VkSubpassDependency{
.srcSubpass = c.VK_SUBPASS_EXTERNAL,
.dstSubpass = 0,
.srcStageMask = c.VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
.srcAccessMask = 0,
.dstStageMask = c.VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
.dstAccessMask = c.VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | c.VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
.dependencyFlags = 0,
}};
var renderPassInfo = VulkanObject(c.VkRenderPassCreateInfo);
renderPassInfo.attachmentCount = 1;
renderPassInfo.pAttachments = &colorAttachment;
renderPassInfo.subpassCount = 1;
renderPassInfo.pSubpasses = &subpass;
renderPassInfo.dependencyCount = 1;
renderPassInfo.pDependencies = &dependency;
try checkSuccess(c.vkCreateRenderPass(global_device, &renderPassInfo, null, &renderPass));
}
fn createImageViews(allocator: *Allocator) !void {
swapChainImageViews = try allocator.alloc(c.VkImageView, swapChainImages.len);
errdefer allocator.free(swapChainImageViews);
for (swapChainImages) |swap_chain_image, i| {
var createInfo = VulkanObject(c.VkImageViewCreateInfo);
createInfo.image = swap_chain_image;
createInfo.viewType = .VK_IMAGE_VIEW_TYPE_2D;
createInfo.format = swapChainImageFormat;
createInfo.components = c.VkComponentMapping{
.r = .VK_COMPONENT_SWIZZLE_IDENTITY,
.g = .VK_COMPONENT_SWIZZLE_IDENTITY,
.b = .VK_COMPONENT_SWIZZLE_IDENTITY,
.a = .VK_COMPONENT_SWIZZLE_IDENTITY,
};
createInfo.subresourceRange = c.VkImageSubresourceRange{
.aspectMask = c.VK_IMAGE_ASPECT_COLOR_BIT,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1,
};
try checkSuccess(c.vkCreateImageView(global_device, &createInfo, null, &swapChainImageViews[i]));
}
}
fn chooseSwapSurfaceFormat(availableFormats: []c.VkSurfaceFormatKHR) c.VkSurfaceFormatKHR {
if (availableFormats.len == 1 and availableFormats[0].format == .VK_FORMAT_UNDEFINED) {
return c.VkSurfaceFormatKHR{
.format = .VK_FORMAT_B8G8R8A8_UNORM,
.colorSpace = .VK_COLOR_SPACE_SRGB_NONLINEAR_KHR,
};
}
for (availableFormats) |availableFormat| {
if (availableFormat.format == .VK_FORMAT_B8G8R8A8_UNORM and
availableFormat.colorSpace == .VK_COLOR_SPACE_SRGB_NONLINEAR_KHR)
{
return availableFormat;
}
}
return availableFormats[0];
}
fn chooseSwapPresentMode(availablePresentModes: []c.VkPresentModeKHR) c.VkPresentModeKHR {
var bestMode: c.VkPresentModeKHR = .VK_PRESENT_MODE_FIFO_KHR;
for (availablePresentModes) |availablePresentMode| {
if (availablePresentMode == .VK_PRESENT_MODE_MAILBOX_KHR) {
return availablePresentMode;
} else if (availablePresentMode == .VK_PRESENT_MODE_IMMEDIATE_KHR) {
bestMode = availablePresentMode;
}
}
return bestMode;
}
fn chooseSwapExtent(capabilities: c.VkSurfaceCapabilitiesKHR) c.VkExtent2D {
if (capabilities.currentExtent.width != std.math.maxInt(u32)) {
return capabilities.currentExtent;
} else {
var actualExtent = c.VkExtent2D{
.width = WIDTH,
.height = HEIGHT,
};
actualExtent.width = std.math.max(capabilities.minImageExtent.width, std.math.min(capabilities.maxImageExtent.width, actualExtent.width));
actualExtent.height = std.math.max(capabilities.minImageExtent.height, std.math.min(capabilities.maxImageExtent.height, actualExtent.height));
return actualExtent;
}
}
fn createSwapChain(allocator: *Allocator) !void {
var swapChainSupport = try querySwapChainSupport(allocator, physicalDevice);
defer swapChainSupport.deinit();
const surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats.toSlice());
const presentMode = chooseSwapPresentMode(swapChainSupport.presentModes.toSlice());
const extent = chooseSwapExtent(swapChainSupport.capabilities);
var imageCount: u32 = swapChainSupport.capabilities.minImageCount + 1;
if (swapChainSupport.capabilities.maxImageCount > 0 and
imageCount > swapChainSupport.capabilities.maxImageCount)
{
imageCount = swapChainSupport.capabilities.maxImageCount;
}
const indices = try findQueueFamilies(allocator, physicalDevice);
const queueFamilyIndices = [_]u32{ indices.graphicsFamily.?, indices.presentFamily.? };
const different_families = indices.graphicsFamily.? != indices.presentFamily.?;
var createInfo = VulkanObject(c.VkSwapchainCreateInfoKHR);
createInfo.surface = surface;
createInfo.minImageCount = imageCount;
createInfo.imageFormat = surfaceFormat.format;
createInfo.imageColorSpace = surfaceFormat.colorSpace;
createInfo.imageExtent = extent;
createInfo.imageArrayLayers = 1;
createInfo.imageUsage = c.VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
createInfo.imageSharingMode = if (different_families) .VK_SHARING_MODE_CONCURRENT else .VK_SHARING_MODE_EXCLUSIVE;
createInfo.queueFamilyIndexCount = if (different_families) 2 else 0;
createInfo.pQueueFamilyIndices = if (different_families) &queueFamilyIndices else &([_]u32{ 0, 0 });
createInfo.preTransform = swapChainSupport.capabilities.currentTransform;
createInfo.compositeAlpha = .VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
createInfo.presentMode = presentMode;
createInfo.clipped = c.VK_TRUE;
try checkSuccess(c.vkCreateSwapchainKHR(global_device, &createInfo, null, &swapChain));
try checkSuccess(c.vkGetSwapchainImagesKHR(global_device, swapChain, &imageCount, null));
swapChainImages = try allocator.alloc(c.VkImage, imageCount);
try checkSuccess(c.vkGetSwapchainImagesKHR(global_device, swapChain, &imageCount, swapChainImages.ptr));
swapChainImageFormat = surfaceFormat.format;
swapChainExtent = extent;
}
fn createLogicalDevice(allocator: *Allocator) !void {
const indices = try findQueueFamilies(allocator, physicalDevice);
var queueCreateInfos = std.ArrayList(c.VkDeviceQueueCreateInfo).init(allocator);
defer queueCreateInfos.deinit();
const all_queue_families = [_]u32{ indices.graphicsFamily.?, indices.presentFamily.? };
const uniqueQueueFamilies = if (indices.graphicsFamily.? == indices.presentFamily.?)
all_queue_families[0..1]
else
all_queue_families[0..2];
var queuePriority: f32 = 1.0;
for (uniqueQueueFamilies) |queueFamily| {
var queueCreateInfo = VulkanObject(c.VkDeviceQueueCreateInfo);
queueCreateInfo.queueFamilyIndex = queueFamily;
queueCreateInfo.queueCount = 1;
queueCreateInfo.pQueuePriorities = &queuePriority;
try queueCreateInfos.append(queueCreateInfo);
}
const deviceFeatures = c.VkPhysicalDeviceFeatures{
.robustBufferAccess = 0,
.fullDrawIndexUint32 = 0,
.imageCubeArray = 0,
.independentBlend = 0,
.geometryShader = 0,
.tessellationShader = 0,
.sampleRateShading = 0,
.dualSrcBlend = 0,
.logicOp = 0,
.multiDrawIndirect = 0,
.drawIndirectFirstInstance = 0,
.depthClamp = 0,
.depthBiasClamp = 0,
.fillModeNonSolid = 0,
.depthBounds = 0,
.wideLines = 0,
.largePoints = 0,
.alphaToOne = 0,
.multiViewport = 0,
.samplerAnisotropy = 0,
.textureCompressionETC2 = 0,
.textureCompressionASTC_LDR = 0,
.textureCompressionBC = 0,
.occlusionQueryPrecise = 0,
.pipelineStatisticsQuery = 0,
.vertexPipelineStoresAndAtomics = 0,
.fragmentStoresAndAtomics = 0,
.shaderTessellationAndGeometryPointSize = 0,
.shaderImageGatherExtended = 0,
.shaderStorageImageExtendedFormats = 0,
.shaderStorageImageMultisample = 0,
.shaderStorageImageReadWithoutFormat = 0,
.shaderStorageImageWriteWithoutFormat = 0,
.shaderUniformBufferArrayDynamicIndexing = 0,
.shaderSampledImageArrayDynamicIndexing = 0,
.shaderStorageBufferArrayDynamicIndexing = 0,
.shaderStorageImageArrayDynamicIndexing = 0,
.shaderClipDistance = 0,
.shaderCullDistance = 0,
.shaderFloat64 = 0,
.shaderInt64 = 0,
.shaderInt16 = 0,
.shaderResourceResidency = 0,
.shaderResourceMinLod = 0,
.sparseBinding = 0,
.sparseResidencyBuffer = 0,
.sparseResidencyImage2D = 0,
.sparseResidencyImage3D = 0,
.sparseResidency2Samples = 0,
.sparseResidency4Samples = 0,
.sparseResidency8Samples = 0,
.sparseResidency16Samples = 0,
.sparseResidencyAliased = 0,
.variableMultisampleRate = 0,
.inheritedQueries = 0,
};
var createInfo = VulkanObject(c.VkDeviceCreateInfo);
createInfo.queueCreateInfoCount = @intCast(u32, queueCreateInfos.len);
createInfo.pQueueCreateInfos = queueCreateInfos.items.ptr;
createInfo.pEnabledFeatures = &deviceFeatures;
createInfo.enabledExtensionCount = @intCast(u32, deviceExtensions.len);
createInfo.ppEnabledExtensionNames = &deviceExtensions;
createInfo.enabledLayerCount = if (enableValidationLayers) @intCast(u32, validationLayers.len) else 0;
createInfo.ppEnabledLayerNames = if (enableValidationLayers) &validationLayers else null;
try checkSuccess(c.vkCreateDevice(physicalDevice, &createInfo, null, &global_device));
c.vkGetDeviceQueue(global_device, indices.graphicsFamily.?, 0, &graphicsQueue);
c.vkGetDeviceQueue(global_device, indices.presentFamily.?, 0, &presentQueue);
}
fn pickPhysicalDevice(allocator: *Allocator) !void {
var deviceCount: u32 = 0;
try checkSuccess(c.vkEnumeratePhysicalDevices(instance, &deviceCount, null));
if (deviceCount == 0) {
return error.FailedToFindGPUsWithVulkanSupport;
}
const devices = try allocator.alloc(c.VkPhysicalDevice, deviceCount);
defer allocator.free(devices);
try checkSuccess(c.vkEnumeratePhysicalDevices(instance, &deviceCount, devices.ptr));
physicalDevice = for (devices) |device| {
if (try isDeviceSuitable(allocator, device)) {
break device;
}
} else return error.FailedToFindSuitableGPU;
}
fn findQueueFamilies(allocator: *Allocator, device: c.VkPhysicalDevice) !QueueFamilyIndices {
var indices = QueueFamilyIndices.init();
var queueFamilyCount: u32 = 0;
c.vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, null);
const queueFamilies = try allocator.alloc(c.VkQueueFamilyProperties, queueFamilyCount);
defer allocator.free(queueFamilies);
c.vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.ptr);
var i: u32 = 0;
for (queueFamilies) |queueFamily| {
if (queueFamily.queueCount > 0 and
queueFamily.queueFlags & @intCast(u32, c.VK_QUEUE_GRAPHICS_BIT) != 0)
{
indices.graphicsFamily = i;
}
var presentSupport: c.VkBool32 = 0;
try checkSuccess(c.vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport));
if (queueFamily.queueCount > 0 and presentSupport != 0) {
indices.presentFamily = i;
}
if (indices.isComplete()) {
break;
}
i += 1;
}
return indices;
}
fn isDeviceSuitable(allocator: *Allocator, device: c.VkPhysicalDevice) !bool {
const indices = try findQueueFamilies(allocator, device);
const extensionsSupported = try checkDeviceExtensionSupport(allocator, device);
var swapChainAdequate = false;
if (extensionsSupported) {
var swapChainSupport = try querySwapChainSupport(allocator, device);
defer swapChainSupport.deinit();
swapChainAdequate = swapChainSupport.formats.len != 0 and swapChainSupport.presentModes.len != 0;
}
return indices.isComplete() and extensionsSupported and swapChainAdequate;
}
fn querySwapChainSupport(allocator: *Allocator, device: c.VkPhysicalDevice) !SwapChainSupportDetails {
var details = SwapChainSupportDetails.init(allocator);
try checkSuccess(c.vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities));
var formatCount: u32 = undefined;
try checkSuccess(c.vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, null));
if (formatCount != 0) {
try details.formats.resize(formatCount);
try checkSuccess(c.vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.items.ptr));
}
var presentModeCount: u32 = undefined;
try checkSuccess(c.vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, null));
if (presentModeCount != 0) {
try details.presentModes.resize(presentModeCount);
try checkSuccess(c.vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.items.ptr));
}
return details;
}
fn checkDeviceExtensionSupport(allocator: *Allocator, device: c.VkPhysicalDevice) !bool {
var extensionCount: u32 = undefined;
try checkSuccess(c.vkEnumerateDeviceExtensionProperties(device, null, &extensionCount, null));
const availableExtensions = try allocator.alloc(c.VkExtensionProperties, extensionCount);
defer allocator.free(availableExtensions);
try checkSuccess(c.vkEnumerateDeviceExtensionProperties(device, null, &extensionCount, availableExtensions.ptr));
var requiredExtensions = std.HashMap([*:0]const u8, void, hash_cstr, eql_cstr).init(allocator);
defer requiredExtensions.deinit();
for (deviceExtensions) |device_ext| {
_ = try requiredExtensions.put(device_ext, {});
}
for (availableExtensions) |extension| {
const name: [*:0]const u8 = @ptrCast([*:0]const u8, &extension.extensionName);
_ = requiredExtensions.remove(name);
}
return requiredExtensions.count() == 0;
}
fn createSurface(window: *c.GLFWwindow) !void {
if (c.glfwCreateWindowSurface(instance, window, null, &surface) != @intToEnum(c.VkResult, c.VK_SUCCESS)) {
return error.FailedToCreateWindowSurface;
}
}
// TODO https://github.com/ziglang/zig/issues/661
// Doesn't work on Windows until the above is fixed, because
// this function needs to be stdcallcc on Windows.
fn debugCallback(
flags: c.VkDebugReportFlagsEXT,
objType: c.VkDebugReportObjectTypeEXT,
obj: u64,
location: usize,
code: i32,
layerPrefix: ?[*:0]const u8,
msg: ?[*:0]const u8,
userData: ?*c_void,
) callconv(.C) c.VkBool32 {
std.debug.warn("validation layer: {s}\n", .{msg});
return c.VK_FALSE;
}
fn setupDebugCallback() error{FailedToSetUpDebugCallback}!void {
if (!enableValidationLayers) return;
var createInfo = VulkanObject(c.VkDebugReportCallbackCreateInfoEXT);
createInfo.flags = c.VK_DEBUG_REPORT_ERROR_BIT_EXT | c.VK_DEBUG_REPORT_WARNING_BIT_EXT;
createInfo.pfnCallback = debugCallback;
if (CreateDebugReportCallbackEXT(&createInfo, null, &callback) != @intToEnum(c.VkResult, c.VK_SUCCESS)) {
return error.FailedToSetUpDebugCallback;
}
}
fn DestroyDebugReportCallbackEXT(
pAllocator: ?*const c.VkAllocationCallbacks,
) void {
const func = @ptrCast(c.PFN_vkDestroyDebugReportCallbackEXT, c.vkGetInstanceProcAddr(
instance,
"vkDestroyDebugReportCallbackEXT",
)) orelse unreachable;
func(instance, callback, pAllocator);
}
fn CreateDebugReportCallbackEXT(
pCreateInfo: *const c.VkDebugReportCallbackCreateInfoEXT,
pAllocator: ?*const c.VkAllocationCallbacks,
pCallback: *c.VkDebugReportCallbackEXT,
) c.VkResult {
const func = @ptrCast(c.PFN_vkCreateDebugReportCallbackEXT, c.vkGetInstanceProcAddr(
instance,
"vkCreateDebugReportCallbackEXT",
)) orelse return @intToEnum(c.VkResult, c.VK_ERROR_EXTENSION_NOT_PRESENT);
return func(instance, pCreateInfo, pAllocator, pCallback);
}
fn createInstance(allocator: *Allocator) !void {
if (enableValidationLayers) {
if (!(try checkValidationLayerSupport(allocator))) {
return error.ValidationLayerRequestedButNotAvailable;
}
}
var appInfo = VulkanObject(c.VkApplicationInfo);
appInfo.pApplicationName = "Hello Triangle";
appInfo.applicationVersion = c.VK_MAKE_VERSION(1, 0, 0);
appInfo.pEngineName = "No Engine";
appInfo.engineVersion = c.VK_MAKE_VERSION(1, 0, 0);
appInfo.apiVersion = c.VK_API_VERSION_1_0;
const extensions = try getRequiredExtensions(allocator);
defer allocator.free(extensions);
var createInfo = VulkanObject(c.VkInstanceCreateInfo);
createInfo.pApplicationInfo = &appInfo;
createInfo.enabledExtensionCount = @intCast(u32, extensions.len);
createInfo.ppEnabledExtensionNames = extensions.ptr;
createInfo.enabledLayerCount = if (enableValidationLayers) @intCast(u32, validationLayers.len) else 0;
createInfo.ppEnabledLayerNames = if (enableValidationLayers) &validationLayers else null;
try checkSuccess(c.vkCreateInstance(&createInfo, null, &instance));
}
/// caller must free returned memory
fn getRequiredExtensions(allocator: *Allocator) ![][*]const u8 {
var glfwExtensionCount: u32 = 0;
const glfwExtensions: [*]const?[*:0]const u8 = c.glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
// var glfwExtensions: [*]const [*:0]const u8 = c.glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
var extensions = std.ArrayList([*:0]const u8).init(allocator);
errdefer extensions.deinit();
var i: u32 = 0;
while (i < glfwExtensionCount) : (i += 1) {
if (glfwExtensions[i]) |ext| {
try extensions.append(ext);
}
}
if (enableValidationLayers) {
try extensions.append(c.VK_EXT_DEBUG_REPORT_EXTENSION_NAME);
}
return extensions.toOwnedSlice();
}
fn checkSuccess(result: c.VkResult) !void {
switch (@enumToInt(result)) {
c.VK_SUCCESS => {},
else => return error.Unexpected,
}
}
fn checkValidationLayerSupport(allocator: *Allocator) !bool {
var layerCount: u32 = undefined;
try checkSuccess(c.vkEnumerateInstanceLayerProperties(&layerCount, null));
var availableLayers = try allocator.alloc(c.VkLayerProperties, layerCount);
defer allocator.free(availableLayers);
try checkSuccess(c.vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.ptr));
for (validationLayers) |layerName| {
var layerFound = false;
for (availableLayers) |*layerProperties| {
const name: [*:0]const u8 = @ptrCast([*:0]const u8, &layerProperties.layerName);
if (std.cstr.cmp(layerName, name) == 0) {
layerFound = true;
break;
}
}
if (!layerFound) {
return false;
}
}
return true;
}
fn drawFrame() !void {
try checkSuccess(c.vkWaitForFences(global_device, 1, &inFlightFences[currentFrame], c.VK_TRUE, std.math.maxInt(u64)));
try checkSuccess(c.vkResetFences(global_device, 1, &inFlightFences[currentFrame]));
var imageIndex: u32 = undefined;
try checkSuccess(c.vkAcquireNextImageKHR(global_device, swapChain, std.math.maxInt(u64), imageAvailableSemaphores[currentFrame], null, &imageIndex));
var waitSemaphores = [_]c.VkSemaphore{imageAvailableSemaphores[currentFrame]};
var waitStages = [_]c.VkPipelineStageFlags{c.VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};
const signalSemaphores = [_]c.VkSemaphore{renderFinishedSemaphores[currentFrame]};
var submitInfoReal = VulkanObject(c.VkSubmitInfo);
submitInfoReal.waitSemaphoreCount = 1;
submitInfoReal.pWaitSemaphores = &waitSemaphores;
submitInfoReal.pWaitDstStageMask = &waitStages;
submitInfoReal.commandBufferCount = 1;
submitInfoReal.pCommandBuffers = commandBuffers.ptr + imageIndex;
submitInfoReal.signalSemaphoreCount = 1;
submitInfoReal.pSignalSemaphores = &signalSemaphores;
var submitInfo = [_]c.VkSubmitInfo{submitInfoReal};
try checkSuccess(c.vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]));
const swapChains = [_]c.VkSwapchainKHR{swapChain};
var presentInfo = VulkanObject(c.VkPresentInfoKHR);
presentInfo.waitSemaphoreCount = 1;
presentInfo.pWaitSemaphores = &signalSemaphores;
presentInfo.swapchainCount = 1;
presentInfo.pSwapchains = &swapChains;
presentInfo.pImageIndices = &imageIndex;
try checkSuccess(c.vkQueuePresentKHR(presentQueue, &presentInfo));
currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
}
fn hash_cstr(a: [*:0]const u8) u32 {
// FNV 32-bit hash
var h: u32 = 2166136261;
var i: usize = 0;
while (a[i] != 0) : (i += 1) {
h ^= a[i];
h *%= 16777619;
}
return h;
}
fn eql_cstr(a: [*:0]const u8, b: [*:0]const u8) bool {
return std.cstr.cmp(a, b) == 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment