summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore13
-rw-r--r--Makefile36
-rw-r--r--fragment.frag10
-rw-r--r--glslv.mk8
-rw-r--r--nelem.h6
-rw-r--r--shaders.h14
-rw-r--r--strlist.c24
-rw-r--r--strlist.h16
-rw-r--r--validation.c103
-rw-r--r--validation.h18
-rw-r--r--vertex.vert26
-rw-r--r--vulkan.c839
12 files changed, 1113 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c76e195
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,13 @@
+*.o
+*.spv
+vulkan
+
+*.d
+
+tags
+TAGS
+
+eprintf.h
+eprintf.c
+
+config.mk
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..366e666
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,36 @@
+-include config.mk
+include glslv.mk
+
+PROG := vulkan
+
+EPRINTF_PATH ?= ../eprintf
+PKG_CONFIG ?= pkg-config
+LN ?= ln -sf
+
+LIBS := vulkan glfw3
+CPPFLAGS += -D_POSIX_C_SOURCE=200112L -DGLFW_INCLUDE_VULKAN
+CFLAGS += $(shell $(PKG_CONFIG) --cflags $(LIBS)) -std=c11 -MMD -MP
+LDFLAGS += -Wl,--as-needed
+LDLIBS += $(shell $(PKG_CONFIG) --libs $(LIBS))
+GLSLVFLAGS += -V
+
+OBJ := vulkan.o eprintf.o strlist.o fragment.o vertex.o
+ifeq ("$(WITH_VALIDATION)", "yes")
+ CPPFLAGS += -DWITH_VALIDATION
+ OBJ += validation.o
+endif
+
+DEP := $(OBJ:.o=.d)
+
+all: $(PROG)
+$(PROG): $(OBJ)
+clean:
+ $(RM) $(OBJ) $(DEP) $(PROG)
+
+include $(EPRINTF_PATH)/module.mk
+deplinks: $(EPRINTF_FILES)
+
+-include $(DEP)
+
+.PHONY: all clean
+
diff --git a/fragment.frag b/fragment.frag
new file mode 100644
index 0000000..91a094d
--- /dev/null
+++ b/fragment.frag
@@ -0,0 +1,10 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(location = 0) in vec3 color;
+layout(location = 0) out vec4 out_color;
+
+void main()
+{
+ out_color = vec4(color, 1.0);
+}
diff --git a/glslv.mk b/glslv.mk
new file mode 100644
index 0000000..0e402f9
--- /dev/null
+++ b/glslv.mk
@@ -0,0 +1,8 @@
+GLSLV ?= glslangValidator
+COMPILE.spv = $(GLSLV) $(GLSLVFLAGS)
+%.spv: %.vert
+ $(COMPILE.spv) $(OUTPUT_OPTION) $<
+%.spv: %.frag
+ $(COMPILE.spv) $(OUTPUT_OPTION) $<
+%.o: %.spv
+ $(LD) -r -b binary $(OUTPUT_OPTION) $<
diff --git a/nelem.h b/nelem.h
new file mode 100644
index 0000000..99b5f77
--- /dev/null
+++ b/nelem.h
@@ -0,0 +1,6 @@
+#ifndef VULKAN_NELEM_H
+#define VULKAN_NELEM_H
+
+#define NELEM(a) (sizeof (a) / sizeof (a)[0])
+
+#endif // VULKAN_NELEM_H
diff --git a/shaders.h b/shaders.h
new file mode 100644
index 0000000..3c58910
--- /dev/null
+++ b/shaders.h
@@ -0,0 +1,14 @@
+#ifndef VULKAN_SHADERS_H
+#define VULKAN_SHADERS_H
+
+#include <stddef.h>
+
+extern size_t _binary_vertex_spv_size;
+extern void *_binary_vertex_spv_start;
+extern void *_binary_vertex_spv_end;
+
+extern size_t _binary_fragment_spv_size;
+extern void *_binary_fragment_spv_start;
+extern void *_binary_fragment_spv_end;
+
+#endif // VULKAN_SHADERS_H
diff --git a/strlist.c b/strlist.c
new file mode 100644
index 0000000..58faa99
--- /dev/null
+++ b/strlist.c
@@ -0,0 +1,24 @@
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "eprintf.h"
+#include "strlist.h"
+
+void sl_append(struct strlist *sl, const char **list, int count)
+{
+ assert(sl != NULL);
+
+ sl->list = erealloc(sl->list, (sl->count + count) * sizeof *sl->list);
+ memcpy(sl->list + sl->count, list, count * sizeof *list);
+ sl->count += count;
+}
+
+void sl_free(struct strlist *sl)
+{
+ assert(sl != NULL);
+
+ free(sl->list);
+ sl->list = NULL;
+ sl->count = 0;
+}
diff --git a/strlist.h b/strlist.h
new file mode 100644
index 0000000..6c60a79
--- /dev/null
+++ b/strlist.h
@@ -0,0 +1,16 @@
+#ifndef VULKAN_STRLIST_H
+#define VULKAN_STRLIST_H
+
+struct strlist {
+ const char **list;
+ int count;
+};
+
+void sl_append(struct strlist *sl, const char **strs, int nstrs);
+static inline void sl_append1(struct strlist *sl, const char *str)
+{
+ sl_append(sl, &str, 1);
+}
+void sl_free(struct strlist *sl);
+
+#endif // VULKAN_STRLIST_H
diff --git a/validation.c b/validation.c
new file mode 100644
index 0000000..6f24177
--- /dev/null
+++ b/validation.c
@@ -0,0 +1,103 @@
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <vulkan/vulkan.h>
+
+
+#include "eprintf.h"
+#include "nelem.h"
+#define WITH_VALIDATION
+#include "validation.h"
+
+static const char *vlayers[] = {
+ "VK_LAYER_LUNARG_standard_validation",
+};
+static const char *vexts[] = {
+ VK_EXT_DEBUG_REPORT_EXTENSION_NAME,
+};
+
+// callback: Validation callback
+static VKAPI_ATTR VkBool32 VKAPI_CALL callback(
+ VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT type,
+ uint64_t obj, size_t location, int32_t code, const char
+ *prefix, const char *msg, void *data)
+{
+ (void)flags; (void)type; (void)obj; (void)location;
+ (void)code; (void)prefix; (void)data;
+ weprintf("Validation: %s", msg);
+
+ return VK_FALSE;
+}
+
+// havelayers: Check if all required validation layers are supported
+static bool havelayers(void)
+{
+ VkLayerProperties *instlrp;
+ uint32_t ninstlrp;
+ static enum { UNK, NO, YES } have = UNK;
+
+ if (have != UNK) return have == YES;
+ have = NO;
+
+ vkEnumerateInstanceLayerProperties(&ninstlrp, NULL);
+ instlrp = emalloc(ninstlrp * sizeof *instlrp);
+ vkEnumerateInstanceLayerProperties(&ninstlrp, instlrp);
+
+ for (int vl = 0; vl < NELEM(vlayers); vl++) {
+ for (uint32_t il = 0; il < ninstlrp; il++)
+ if (strcmp(vlayers[vl], instlrp[il].layerName) == 0)
+ goto next;
+ goto fail_layers;
+ next: ;
+ }
+ have = YES;
+fail_layers: free(instlrp);
+
+ return have == YES;
+}
+
+// validation_info: Append validation layers and extension
+void validation_info(struct strlist *layers, struct strlist *exts)
+{
+ if (havelayers() && layers != NULL)
+ sl_append(layers, vlayers, NELEM(vlayers));
+ if (exts != NULL) sl_append(exts, vexts, NELEM(vexts));
+}
+
+// validation_createcb: Create the validation callback
+void *validation_createcb(VkInstance inst)
+{
+ VkDebugReportCallbackCreateInfoEXT rccinf;
+ PFN_vkCreateDebugReportCallbackEXT func;
+ VkDebugReportCallbackEXT *cb;
+
+ func = (PFN_vkCreateDebugReportCallbackEXT)
+ vkGetInstanceProcAddr(inst, "vkCreateDebugReportCallbackEXT");
+ if (func == NULL)
+ eprintf("Validation: Could not create callback: Failed to get constructor");
+
+ rccinf = (VkDebugReportCallbackCreateInfoEXT){
+ .sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT,
+ .flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT,
+ .pfnCallback = callback,
+ };
+
+ cb = emalloc(sizeof *cb);
+ if (func(inst, &rccinf, NULL, cb) != VK_SUCCESS)
+ eprintf("Validation: Could not create callback: Constructor");
+
+ return cb;
+}
+
+// validation_destroycb: Destroy the validation callback
+void validation_destroycb(VkInstance inst, void *cb)
+{
+ PFN_vkDestroyDebugReportCallbackEXT func;
+
+ func = (PFN_vkDestroyDebugReportCallbackEXT)
+ vkGetInstanceProcAddr(inst, "vkDestroyDebugReportCallbackEXT");
+ if (func == NULL)
+ eprintf("Validation: Could not destroy callback: Failed to get destructor");
+ func(inst, cb, NULL);
+ free(cb);
+}
diff --git a/validation.h b/validation.h
new file mode 100644
index 0000000..44b1096
--- /dev/null
+++ b/validation.h
@@ -0,0 +1,18 @@
+#ifndef VULKAN_VALIDATION_H
+#define VULKAN_VALIDATION_H
+
+#include <stdint.h>
+
+#include "strlist.h"
+
+#ifdef WITH_VALIDATION
+void validation_info(struct strlist *layers, struct strlist *exts);
+void *validation_createcb(VkInstance inst);
+void validation_destroycb(VkInstance inst, void *cb);
+#else
+#define validation_info(a, b)
+#define validation_createcb(a) (NULL)
+#define validation_destroycb(a, b)
+#endif
+
+#endif // VULKAN_VALIDATION_H
diff --git a/vertex.vert b/vertex.vert
new file mode 100644
index 0000000..39c5faa
--- /dev/null
+++ b/vertex.vert
@@ -0,0 +1,26 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+out gl_PerVertex {
+ vec4 gl_Position;
+};
+
+layout(location = 0) out vec3 color;
+
+vec2 positions[3] = vec2[](
+ vec2( 0.0, -0.5),
+ vec2( 0.5, 0.5),
+ vec2(-0.5, 0.5)
+);
+
+vec3 colors[3] = vec3[](
+ vec3(1.0, 0.0, 0.0),
+ vec3(0.0, 1.0, 0.0),
+ vec3(0.0, 0.0, 1.0)
+);
+
+void main()
+{
+ gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
+ color = colors[gl_VertexIndex];
+}
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();
+}