summaryrefslogtreecommitdiffstats
path: root/vulkan.c
diff options
context:
space:
mode:
Diffstat (limited to 'vulkan.c')
-rw-r--r--vulkan.c839
1 files changed, 839 insertions, 0 deletions
diff --git a/vulkan.c b/vulkan.c
new file mode 100644
index 0000000..420be16
--- /dev/null
+++ b/vulkan.c
@@ -0,0 +1,839 @@
+#include <vulkan/vulkan.h>
+#include <GLFW/glfw3.h>
+#include <assert.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "eprintf.h"
+#include "nelem.h"
+#include "shaders.h"
+#include "strlist.h"
+#include "validation.h"
+
+#define LIST_REALLOC(l) (l)->list = erealloc((l)->list, (l)->count * sizeof *(l)->list)
+#define LIST_FOREACH(l, i) for (uint32_t i = 0; i < (l)->count; i++)
+
+enum {
+ WINWIDTH = 800,
+ WINHEIGHT = 600,
+};
+static const char *NAME = "Vulkan Test";
+
+enum qfams {
+ QF_GRAPHICS,
+ QF_PRESENT,
+ QF_MAX,
+};
+
+const char *devexts[] = {
+ VK_KHR_SWAPCHAIN_EXTENSION_NAME,
+};
+
+struct swp {
+ VkSwapchainKHR swp;
+ struct swpimgs {
+ VkImage *list;
+ uint32_t count;
+ } imgs;
+ VkFormat format;
+ VkExtent2D extent;
+};
+
+struct ppln {
+ VkPipeline ppln;
+ VkPipelineLayout layout;
+};
+
+struct views {
+ VkImageView *list;
+ uint32_t count;
+};
+
+struct fbs {
+ VkFramebuffer *list;
+ uint32_t count;
+};
+
+struct cmdbufs {
+ VkCommandBuffer *list;
+ uint32_t count;
+};
+
+struct swpdtl {
+ VkSurfaceCapabilitiesKHR caps;
+ struct swpfmts {
+ VkSurfaceFormatKHR *list;
+ uint32_t count;
+ } fmts;
+ struct swppmodes {
+ VkPresentModeKHR *list;
+ uint32_t count;
+ } pmodes;
+};
+
+static GLFWwindow *createwin(int width, int height, const char *title)
+{
+ glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
+ glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
+
+ return glfwCreateWindow(width, height, title, NULL, NULL);
+}
+
+static void glfwexts(struct strlist *exts)
+{
+ const char **gexts;
+ uint32_t ngexts;
+
+ assert(exts != NULL);
+
+ gexts = glfwGetRequiredInstanceExtensions(&ngexts);
+ sl_append(exts, gexts, ngexts);
+}
+
+static VkInstance createinst(void)
+{
+ struct strlist layers = { 0 }, exts = { 0 };
+ VkInstanceCreateInfo info;
+ VkInstance inst;
+ VkResult res;
+
+ glfwexts(&exts);
+ validation_info(&layers, &exts);
+ info = (VkInstanceCreateInfo){
+ .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
+ .pApplicationInfo = &(VkApplicationInfo){
+ .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
+ .pApplicationName = NAME,
+ .applicationVersion = VK_MAKE_VERSION(1, 0, 0),
+ .pEngineName = NAME,
+ .engineVersion = VK_MAKE_VERSION(1, 0, 0),
+ .apiVersion = VK_API_VERSION_1_0,
+ },
+ .enabledExtensionCount = exts.count,
+ .ppEnabledExtensionNames = exts.list,
+ .enabledLayerCount = layers.count,
+ .ppEnabledLayerNames = layers.list,
+ };
+
+
+ res = vkCreateInstance(&info, NULL, &inst);
+ if (res != VK_SUCCESS)
+ eprintf("Could not create instance");
+
+ sl_free(&layers);
+ sl_free(&exts);
+
+ return inst;
+}
+
+static bool hasdevexts(VkPhysicalDevice phy)
+{
+ bool hasext[NELEM(devexts)] = { 0 };
+ VkExtensionProperties *extprops;
+ uint32_t nextprops;
+
+ vkEnumerateDeviceExtensionProperties(phy, NULL, &nextprops, NULL);
+ extprops = emalloc(nextprops * sizeof *extprops);
+ vkEnumerateDeviceExtensionProperties(phy, NULL, &nextprops, extprops);
+
+ for (uint32_t pi = 0; pi < nextprops; pi++) {
+ for (int xi = 0; xi < NELEM(devexts); xi++) {
+ if (strcmp(devexts[xi], extprops[pi].extensionName) == 0) {
+ hasext[xi] = true;
+ break;
+ }
+ }
+ }
+
+ free(extprops);
+
+ for (int i = 0; i < NELEM(hasext); i++)
+ if (!hasext[i])
+ return false;
+
+ return true;
+}
+
+static bool isdevsuitable(VkPhysicalDevice phy, const int *qf, const struct swpdtl *sdtl)
+{
+ assert(qf != NULL);
+
+ return qf[QF_GRAPHICS] >= 0 && qf[QF_PRESENT] >= 0 &&
+ hasdevexts(phy) && sdtl->fmts.count > 0 &&
+ sdtl->pmodes.count > 0;
+}
+
+static void getdevqfams(int *qf, VkPhysicalDevice phy, VkSurfaceKHR surf)
+{
+ VkQueueFamilyProperties *qfprops;
+ uint32_t nqfprops;
+
+ assert(qf != NULL);
+
+ vkGetPhysicalDeviceQueueFamilyProperties(phy, &nqfprops, NULL);
+ qfprops = emalloc(nqfprops * sizeof *qfprops);
+ vkGetPhysicalDeviceQueueFamilyProperties(phy, &nqfprops, qfprops);
+
+ for (int i = 0; i < QF_MAX; i++) qf[i] = -1;
+
+ for (uint32_t i = 0; i < nqfprops; i++) {
+ VkQueueFamilyProperties *qfp = &qfprops[i];
+ VkBool32 present = false;
+
+ if (qfp->queueCount <= 0) continue;
+ if (qf[QF_GRAPHICS] < 0 && qfp->queueFlags & VK_QUEUE_GRAPHICS_BIT)
+ qf[QF_GRAPHICS] = i;
+ vkGetPhysicalDeviceSurfaceSupportKHR(phy, i, surf, &present);
+ if (qf[QF_PRESENT] < 0 && present) qf[QF_PRESENT] = i;
+ }
+
+ free(qfprops);
+}
+
+static void getswpdtl(struct swpdtl *sdtl, VkPhysicalDevice phy, VkSurfaceKHR surf)
+{
+ assert(sdtl != NULL);
+
+ vkGetPhysicalDeviceSurfaceCapabilitiesKHR(phy, surf, &sdtl->caps);
+
+ vkGetPhysicalDeviceSurfaceFormatsKHR(phy, surf, &sdtl->fmts.count, NULL);
+ LIST_REALLOC(&sdtl->fmts);
+ vkGetPhysicalDeviceSurfaceFormatsKHR(phy, surf, &sdtl->fmts.count, sdtl->fmts.list);
+
+ vkGetPhysicalDeviceSurfacePresentModesKHR(phy, surf, &sdtl->pmodes.count, NULL);
+ LIST_REALLOC(&sdtl->pmodes);
+ vkGetPhysicalDeviceSurfacePresentModesKHR(phy, surf, &sdtl->pmodes.count, sdtl->pmodes.list);
+}
+static void freeswpdtl(struct swpdtl *sdtl)
+{
+ assert(sdtl != NULL);
+
+ free(sdtl->fmts.list);
+ free(sdtl->pmodes.list);
+}
+
+static VkPhysicalDevice pickphy(int *qf, struct swpdtl *sdtl, VkInstance inst, VkSurfaceKHR surf)
+{
+ VkPhysicalDevice *phys, phy;
+ uint32_t nphys;
+
+ assert(qf != NULL);
+
+ vkEnumeratePhysicalDevices(inst, &nphys, NULL);
+ phys = emalloc(nphys * sizeof *phys);
+ vkEnumeratePhysicalDevices(inst, &nphys, phys);
+
+ phy = VK_NULL_HANDLE;
+
+ for (uint32_t i = 0; i < nphys; i++) {
+ getdevqfams(qf, phys[i], surf);
+ getswpdtl(sdtl, phys[i], surf);
+ if (isdevsuitable(phys[i], qf, sdtl)) {
+ phy = phys[i];
+ break;
+ }
+ }
+
+ if (phy == VK_NULL_HANDLE)
+ eprintf("No suitable GPU found");
+
+ free(phys);
+
+ return phy;
+}
+
+static int loaddqcinfs(VkDeviceQueueCreateInfo *dqcinfs, const int *qf, const float *prio)
+{
+ int ndqcinfs = 0;
+
+ for (int fi = 0; fi < QF_MAX; fi++) {
+ for (int cii = 0; cii < fi; cii++)
+ if (dqcinfs[cii].queueFamilyIndex == qf[fi])
+ goto next;
+ dqcinfs[ndqcinfs++] = (VkDeviceQueueCreateInfo){
+ .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
+ .queueFamilyIndex = qf[fi],
+ .queueCount = 1,
+ .pQueuePriorities = prio,
+ };
+ next: ;
+ }
+
+ return ndqcinfs;
+}
+
+static VkDevice createdev(VkPhysicalDevice phy, const int *qf)
+{
+ VkDeviceQueueCreateInfo dqcinfs[QF_MAX];
+ struct strlist layers = { 0 };
+ VkDeviceCreateInfo info;
+ uint32_t ndqcinfs;
+ VkDevice dev;
+ VkResult res;
+
+ ndqcinfs = loaddqcinfs(dqcinfs, qf, &(float){ 1 });
+ validation_info(&layers, NULL);
+ info = (VkDeviceCreateInfo){
+ .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
+ .pQueueCreateInfos = dqcinfs,
+ .queueCreateInfoCount = ndqcinfs,
+ .pEnabledFeatures = &(VkPhysicalDeviceFeatures){ 0 },
+ .enabledLayerCount = layers.count,
+ .ppEnabledLayerNames = layers.list,
+ .enabledExtensionCount = 1,
+ .ppEnabledExtensionNames = devexts,
+ };
+ res = vkCreateDevice(phy, &info, NULL, &dev);
+ if (res != VK_SUCCESS)
+ eprintf("Could not create device");
+
+ return dev;
+}
+
+static VkSurfaceFormatKHR getswpfmt(struct swpfmts *scf)
+{
+ assert(scf != NULL);
+
+ if (scf->count == 1 && scf->list[0].format == VK_FORMAT_UNDEFINED)
+ return (VkSurfaceFormatKHR){ VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR };
+
+ for (uint32_t i = 0; i < scf->count; i++)
+ if (scf->list[i].format == VK_FORMAT_B8G8R8A8_UNORM
+ && scf->list[i].colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR)
+ return scf->list[i];
+
+ return scf->list[0];
+}
+
+static VkPresentModeKHR getswppmode(struct swppmodes *scp)
+{
+ VkPresentModeKHR mode = VK_PRESENT_MODE_FIFO_KHR;
+
+ assert(scp != NULL);
+
+ for (uint32_t i = 0; i < scp->count; i++) {
+ if (scp->list[i] == VK_PRESENT_MODE_MAILBOX_KHR)
+ return scp->list[i];
+ if (scp->list[i] == VK_PRESENT_MODE_IMMEDIATE_KHR)
+ mode = scp->list[i];
+ }
+
+ return mode;
+}
+
+static VkExtent2D getswpextent(const VkSurfaceCapabilitiesKHR *caps)
+{
+ VkExtent2D extent = { WINWIDTH, WINHEIGHT };
+
+ if (caps->currentExtent.width != UINT32_MAX)
+ return caps->currentExtent;
+
+ if (extent.width > caps->maxImageExtent.width) extent.width = caps->maxImageExtent.width;
+ if (extent.width < caps->minImageExtent.width) extent.width = caps->minImageExtent.width;
+
+ if (extent.height > caps->maxImageExtent.height) extent.height = caps->maxImageExtent.height;
+ if (extent.height < caps->minImageExtent.height) extent.height = caps->minImageExtent.height;
+
+ return extent;
+}
+
+static void createswp(struct swp *swp, VkSurfaceKHR surf, VkDevice dev, const int *qf, struct swpdtl *sdtl)
+{
+ VkSwapchainCreateInfoKHR info;
+ VkPresentModeKHR pmode;
+ VkSurfaceFormatKHR fmt;
+ uint32_t qfi[QF_MAX];
+ VkResult res;
+
+ assert(swp != NULL);
+ assert(qf != NULL);
+ assert(sdtl != NULL);
+
+ fmt = getswpfmt(&sdtl->fmts);
+ pmode = getswppmode(&sdtl->pmodes);
+ swp->extent = getswpextent(&sdtl->caps);
+ swp->imgs.count = sdtl->caps.minImageCount + 1;
+ if (sdtl->caps.maxImageCount != 0 && swp->imgs.count > sdtl->caps.maxImageCount)
+ swp->imgs.count = sdtl->caps.maxImageCount;
+
+ for (int i = 0; i < QF_MAX; i++) qfi[i] = qf[i];
+
+ info = (VkSwapchainCreateInfoKHR){
+ .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
+ .surface = surf,
+ .minImageCount = swp->imgs.count,
+ .imageFormat = fmt.format,
+ .imageColorSpace = fmt.colorSpace,
+ .imageArrayLayers = 1,
+ .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
+ .imageSharingMode = qf[QF_GRAPHICS] == qf[QF_PRESENT]
+ ? VK_SHARING_MODE_EXCLUSIVE
+ : VK_SHARING_MODE_CONCURRENT,
+ .queueFamilyIndexCount = QF_MAX,
+ .pQueueFamilyIndices = qfi,
+ .preTransform = sdtl->caps.currentTransform,
+ .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
+ .presentMode = pmode,
+ .clipped = VK_TRUE,
+ .oldSwapchain = VK_NULL_HANDLE,
+ };
+
+ res = vkCreateSwapchainKHR(dev, &info, NULL, &swp->swp);
+ if (res != VK_SUCCESS)
+ eprintf("Could not create swapchain");
+
+ swp->imgs = (struct swpimgs){ 0 };
+ vkGetSwapchainImagesKHR(dev, swp->swp, &swp->imgs.count, NULL);
+ LIST_REALLOC(&swp->imgs);
+ vkGetSwapchainImagesKHR(dev, swp->swp, &swp->imgs.count, swp->imgs.list);
+}
+
+static void createviews(struct views *views, VkDevice dev, const struct swpimgs *imgs, VkFormat fmt)
+{
+ VkImageViewCreateInfo info;
+ VkResult res;
+
+ assert(views != NULL);
+ assert(imgs != NULL);
+
+ *views = (struct views){ .count = imgs->count };
+ LIST_REALLOC(views);
+
+ LIST_FOREACH(views, i) {
+ info = (VkImageViewCreateInfo){
+ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
+ .image = imgs->list[i],
+ .viewType = VK_IMAGE_VIEW_TYPE_2D,
+ .format = fmt,
+ .components = {
+ .r = VK_COMPONENT_SWIZZLE_IDENTITY,
+ .g = VK_COMPONENT_SWIZZLE_IDENTITY,
+ .b = VK_COMPONENT_SWIZZLE_IDENTITY,
+ .a = VK_COMPONENT_SWIZZLE_IDENTITY,
+ },
+ .subresourceRange = {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .baseMipLevel = 0,
+ .levelCount = 1,
+ .baseArrayLayer = 0,
+ .layerCount = 1,
+ },
+ };
+ res = vkCreateImageView(dev, &info, NULL, &views->list[i]);
+ if (res != VK_SUCCESS)
+ eprintf("Could not create image view");
+ }
+}
+
+static void destroyviews(struct views *views, VkDevice dev)
+{
+ LIST_FOREACH(views, i) vkDestroyImageView(dev, views->list[i], NULL);
+}
+
+static VkShaderModule createsmod(VkDevice dev, const void *data, size_t size)
+{
+ VkShaderModuleCreateInfo smcinf;
+ VkShaderModule smod;
+ VkResult res;
+
+ assert(data != NULL);
+
+ smcinf = (VkShaderModuleCreateInfo){
+ .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
+ .codeSize = size,
+ .pCode = data,
+ };
+
+ res = vkCreateShaderModule(dev, &smcinf, NULL, &smod);
+ if (res != VK_SUCCESS)
+ eprintf("Could not create shader module");
+
+ return smod;
+}
+
+static VkRenderPass createpass(VkDevice dev, VkFormat fmt)
+{
+ VkRenderPassCreateInfo info;
+ VkRenderPass pass;
+ VkResult res;
+
+ info = (VkRenderPassCreateInfo){
+ .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
+ .attachmentCount = 1,
+ .pAttachments = &(VkAttachmentDescription){
+ .format = fmt,
+ .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,
+ },
+ .subpassCount = 1,
+ .pSubpasses = &(VkSubpassDescription){
+ .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
+ .colorAttachmentCount = 1,
+ .pColorAttachments = &(VkAttachmentReference){
+ .attachment = 0,
+ .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+ },
+ },
+ .dependencyCount = 1,
+ .pDependencies = &(VkSubpassDependency){
+ .srcSubpass = VK_SUBPASS_EXTERNAL,
+ .dstSubpass = 0,
+ .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
+ .srcAccessMask = 0,
+ .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
+ .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
+ },
+ };
+
+ res = vkCreateRenderPass(dev, &info, NULL, &pass);
+ if (res != VK_SUCCESS)
+ eprintf("Could not create render pass");
+
+ return pass;
+}
+
+static void createpipeline(struct ppln *ppln, VkDevice dev, const VkExtent2D *extent)
+{
+ VkGraphicsPipelineCreateInfo gpcinf;
+ VkPipelineLayoutCreateInfo plcinf;
+ VkShaderModule fsmod, vsmod;
+ VkResult res;
+
+ assert(ppln != NULL);
+ assert(extent != NULL);
+
+ plcinf = (VkPipelineLayoutCreateInfo){
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
+ /* .setLayoutCount = 0, */
+ /* .pSetLayouts = NULL, */
+ /* .pushConstantRangeCount = 0, */
+ /* .pPushConstantRanges = 0, */
+ };
+
+ res = vkCreatePipelineLayout(dev, &plcinf, NULL, &ppln->layout);
+ if (res != VK_SUCCESS)
+ eprintf("Could not create pipeline layout");
+
+ vsmod = createsmod(dev, _binary_vertex_spv_start, _binary_vertex_spv_size);
+ fsmod = createsmod(dev, _binary_fragment_spv_start, _binary_fragment_spv_size);
+
+ gpcinf = (VkGraphicsPipelineCreateInfo){
+ .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
+ .stageCount = 2,
+ .pStages = (VkPipelineShaderStageCreateInfo []){
+ {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
+ .stage = VK_SHADER_STAGE_VERTEX_BIT,
+ .module = vsmod,
+ .pName = "main",
+ }, {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
+ .stage = VK_SHADER_STAGE_FRAGMENT_BIT,
+ .module = fsmod,
+ .pName = "main",
+ },
+ },
+ .pVertexInputState = &(VkPipelineVertexInputStateCreateInfo){
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
+ .vertexBindingDescriptionCount = 0,
+ .pVertexBindingDescriptions = NULL,
+ .vertexAttributeDescriptionCount = 0,
+ .pVertexAttributeDescriptions = NULL,
+ },
+ .pInputAssemblyState = &(VkPipelineInputAssemblyStateCreateInfo){
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
+ .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
+ .primitiveRestartEnable = VK_FALSE,
+ },
+ .pViewportState = &(VkPipelineViewportStateCreateInfo){
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
+ .viewportCount = 1,
+ .pViewports = &(VkViewport){
+ .x = 0.0f,
+ .y = 0.0f,
+ .width = (float)extent->width,
+ .height = (float)extent->height,
+ .minDepth = 0.0f,
+ .maxDepth = 1.0f,
+ },
+ .scissorCount = 1,
+ .pScissors = &(VkRect2D){
+ .offset = { 0, 0 },
+ .extent = *extent,
+ },
+ },
+ .pRasterizationState = &(VkPipelineRasterizationStateCreateInfo){
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
+ .depthClampEnable = VK_FALSE,
+ .rasterizerDiscardEnable = VK_FALSE,
+ .polygonMode = VK_POLYGON_MODE_FILL,
+ .lineWidth = 1.0f,
+ .cullMode = VK_CULL_MODE_BACK_BIT,
+ .frontFace = VK_FRONT_FACE_CLOCKWISE,
+ .depthBiasEnable = VK_FALSE,
+ /* .depthBiasConstantFactor = 0, */
+ /* .depthBiasClamp = 0, */
+ /* .depthBiasSlopeFactor = 0, */
+ },
+ .pMultisampleState = &(VkPipelineMultisampleStateCreateInfo){
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
+ .sampleShadingEnable = VK_FALSE,
+ .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT,
+ /* .minSampleShading = 1, */
+ /* .pSampleMask = NULL, */
+ /* .alphaToCoverageEnable = VK_FALSE, */
+ /* .alphaToOneEnable = VK_FALSE, */
+ },
+ .pDepthStencilState = NULL,
+ .pColorBlendState = &(VkPipelineColorBlendStateCreateInfo){
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
+ .logicOpEnable = VK_FALSE,
+ /* .logicOp = VK_LOGIC_OP_COPY, */
+ .attachmentCount = 1,
+ .pAttachments = &(VkPipelineColorBlendAttachmentState){
+ .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT,
+ .blendEnable = VK_FALSE,
+ /* .srcColorBlendFactor = VK_BLEND_FACTOR_ONE, */
+ /* .dstColorBlendFactor = VK_BLEND_FACTOR_ZERO, */
+ /* .colorBlendOp = VK_BLEND_OP_ADD, */
+ /* .srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE, */
+ /* .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO, */
+ /* .alphaBlendOp = VK_BLEND_OP_ADD, */
+ },
+ /* .blendConstants[0] = 0, */
+ /* .blendConstants[1] = 0, */
+ /* .blendConstants[2] = 0, */
+ /* .blendConstants[3] = 0, */
+ },
+ .pDynamicState = NULL,
+ };
+
+ res = vkCreateGraphicsPipelines(dev, VK_NULL_HANDLE, 1, &gpcinf, NULL, &ppln->ppln);
+ if (res != VK_SUCCESS)
+ eprintf("Could not create graphics pipeline");
+
+ vkDestroyShaderModule(dev, fsmod, NULL);
+ vkDestroyShaderModule(dev, vsmod, NULL);
+}
+
+static void destroyppln(struct ppln *ppln, VkDevice dev)
+{
+ vkDestroyPipeline(dev, ppln->ppln, NULL);
+ vkDestroyPipelineLayout(dev, ppln->layout, NULL);
+}
+
+static void createfbs(struct fbs *fbs, VkDevice dev, const struct views *views, VkRenderPass pass, const VkExtent2D *extent)
+{
+ VkFramebufferCreateInfo info;
+ VkResult res;
+
+ assert(fbs != NULL);
+ assert(views != NULL);
+
+ *fbs = (struct fbs){ .count = views->count };
+ LIST_REALLOC(fbs);
+
+ LIST_FOREACH(fbs, i) {
+ info = (VkFramebufferCreateInfo){
+ .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
+ .renderPass = pass,
+ .attachmentCount = 1,
+ .pAttachments = &views->list[i],
+ .width = extent->width,
+ .height = extent->height,
+ .layers = 1,
+ };
+ res = vkCreateFramebuffer(dev, &info, NULL, &fbs->list[i]);
+ if (res != VK_SUCCESS)
+ eprintf("Could not create framebuffer");
+ }
+}
+
+static void destroyfbs(struct fbs *fbs, VkDevice dev)
+{
+ LIST_FOREACH(fbs, i) vkDestroyFramebuffer(dev, fbs->list[i], NULL);
+}
+
+static VkCommandPool createcpool(struct cmdbufs *cmdbufs, VkDevice dev, const int *qf,
+ const struct fbs *fbs, VkRenderPass pass,
+ VkExtent2D *extent, const struct ppln *ppln)
+{
+ VkCommandBufferAllocateInfo cbainf;
+ VkCommandPoolCreateInfo cpcinf;
+ VkCommandPool cpool;
+ VkResult res;
+
+ assert(cmdbufs != NULL);
+ assert(qf != NULL);
+
+ cpcinf = (VkCommandPoolCreateInfo){
+ .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
+ .queueFamilyIndex = qf[QF_GRAPHICS],
+ };
+
+ res = vkCreateCommandPool(dev, &cpcinf, NULL, &cpool);
+ if (res != VK_SUCCESS)
+ eprintf("Could not create command pool");
+
+ *cmdbufs = (struct cmdbufs){ .count = fbs->count };
+ LIST_REALLOC(cmdbufs);
+ cbainf = (VkCommandBufferAllocateInfo){
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
+ .commandPool = cpool,
+ .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
+ .commandBufferCount = fbs->count,
+ };
+ res = vkAllocateCommandBuffers(dev, &cbainf, cmdbufs->list);
+ if (res != VK_SUCCESS)
+ eprintf("Could not create command buffers");
+
+ LIST_FOREACH(cmdbufs, i) {
+ vkBeginCommandBuffer(cmdbufs->list[i], &(VkCommandBufferBeginInfo){
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+ .flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT,
+ /* .pInheritanceInfo = NULL, */
+ });
+ vkCmdBeginRenderPass(cmdbufs->list[i], &(VkRenderPassBeginInfo){
+ .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
+ .renderPass = pass,
+ .framebuffer = fbs->list[i],
+ .renderArea.offset = {0, 0},
+ .renderArea.extent = *extent,
+ .clearValueCount = 1,
+ .pClearValues = &(VkClearValue){
+ .color = {
+ .float32 = { 0, 0, 0, 1 }
+ }
+ },
+ }, VK_SUBPASS_CONTENTS_INLINE);
+ vkCmdBindPipeline(cmdbufs->list[i], VK_PIPELINE_BIND_POINT_GRAPHICS, ppln->ppln);
+ vkCmdDraw(cmdbufs->list[i], 3, 1, 0, 0);
+ vkCmdEndRenderPass(cmdbufs->list[i]);
+ };
+
+ return cpool;
+}
+
+static VkSemaphore createsem(VkDevice dev)
+{
+ VkSemaphore sem;
+ VkResult res;
+
+ res = vkCreateSemaphore(dev, &(VkSemaphoreCreateInfo){
+ .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
+ }, NULL, &sem);
+ if (res != VK_SUCCESS)
+ eprintf("Could not create semaphore");
+
+ return sem;
+}
+
+void drawframe(VkDevice dev, VkSwapchainKHR swp, VkQueue graphq,
+ VkQueue presq, VkSemaphore semavail,
+ VkSemaphore semfin, const struct cmdbufs *cmdbufs)
+{
+ VkPresentInfoKHR pinf;
+ VkSubmitInfo sinf;
+ uint32_t i;
+
+ vkAcquireNextImageKHR(dev, swp, UINT64_MAX, semavail, VK_NULL_HANDLE, &i);
+ sinf = (VkSubmitInfo){
+ .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
+ .waitSemaphoreCount = 1,
+ .pWaitSemaphores = &semavail,
+ .pWaitDstStageMask = &(VkPipelineStageFlags){
+ VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT
+ },
+ .commandBufferCount = 1,
+ .pCommandBuffers = &cmdbufs->list[i],
+ .signalSemaphoreCount = 1,
+ .pSignalSemaphores = &semfin,
+ };
+ vkQueueSubmit(graphq, 1, &sinf, VK_NULL_HANDLE);
+
+ pinf = (VkPresentInfoKHR){
+ .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
+ .waitSemaphoreCount = 1,
+ .pWaitSemaphores = &semfin,
+ .swapchainCount = 1,
+ .pSwapchains = &swp,
+ .pImageIndices = &i,
+ /* .pResults = NULL, */
+ };
+
+ vkQueuePresentKHR(presq, &pinf);
+}
+
+int main(void)
+{
+ struct cmdbufs cmdbufs = { 0 };
+ VkSemaphore semavail, semfin;
+ struct swpdtl sdtl = { 0 };
+ struct ppln ppln = { 0 };
+ struct fbs fbs = { 0 };
+ VkQueue graphq, presq;
+ VkPhysicalDevice phy;
+ VkCommandPool cpool;
+ struct views views;
+ VkRenderPass pass;
+ VkSurfaceKHR surf;
+ GLFWwindow *win;
+ VkInstance inst;
+ int qf[QF_MAX];
+ struct swp swp;
+ VkDevice dev;
+ VkResult res;
+ void *cb;
+
+ setprogname("vulkan");
+
+ glfwInit();
+ win = createwin(WINWIDTH, WINHEIGHT, NAME);
+ inst = createinst();
+ cb = validation_createcb(inst);
+ res = glfwCreateWindowSurface(inst, win, NULL, &surf);
+ if (res != VK_SUCCESS)
+ eprintf("Could not create window surface");
+ phy = pickphy(qf, &sdtl, inst, surf);
+ dev = createdev(phy, qf);
+ vkGetDeviceQueue(dev, qf[QF_GRAPHICS], 0, &graphq);
+ vkGetDeviceQueue(dev, qf[QF_PRESENT], 0, &presq);
+ createswp(&swp, surf, dev, qf, &sdtl);
+ createviews(&views, dev, &swp.imgs, swp.format);
+ pass = createpass(dev, swp.format);
+ createpipeline(&ppln, dev, &swp.extent);
+ createfbs(&fbs, dev, &views, pass, &swp.extent);
+ cpool = createcpool(&cmdbufs, dev, qf, &fbs, pass, &swp.extent, &ppln);
+ semavail = createsem(dev);
+ semfin = createsem(dev);
+
+ while (!glfwWindowShouldClose(win)) {
+ glfwPollEvents();
+ drawframe(dev, swp.swp, graphq, presq, semavail, semfin, &cmdbufs);
+ }
+
+ vkDeviceWaitIdle(dev);
+
+ vkDestroySemaphore(dev, semfin, NULL);
+ vkDestroySemaphore(dev, semavail, NULL);
+ vkDestroyCommandPool(dev, cpool, NULL);
+ destroyfbs(&fbs, dev);
+ destroyppln(&ppln, dev);
+ vkDestroyRenderPass(dev, pass, NULL);
+ destroyviews(&views, dev);
+ free(views.list);
+ vkDestroySwapchainKHR(dev, swp.swp, NULL);
+ vkDestroySurfaceKHR(inst, surf, NULL);
+ vkDestroyDevice(dev, NULL);
+ freeswpdtl(&sdtl);
+ validation_destroycb(inst, cb);
+ vkDestroyInstance(inst, NULL);
+ glfwDestroyWindow(win);
+ glfwTerminate();
+}