From f2d87fdd00259684f984d16b1188186723ed8378 Mon Sep 17 00:00:00 2001 From: jacekpoz Date: Thu, 8 Aug 2024 14:38:43 +0200 Subject: [PATCH] init --- .clangd | 2 + .envrc | 1 + .gitignore | 5 + Makefile | 73 ++ doc/resources/c.md | 3 + doc/resources/vulkan.md | 6 + flake.lock | 26 + flake.nix | 52 ++ include/ptk.h | 69 ++ include/ptk_log.h | 35 + nix/default.nix | 46 ++ shaders/shader.frag.glsl | 12 + shaders/shader.vert.glsl | 17 + src/ptk.c | 199 +++++ src/ptk_log.c | 53 ++ src/ptk_option.h | 26 + src/ptk_vec.c | 77 ++ src/ptk_vec.h | 61 ++ src/ptk_vk/components.c | 73 ++ src/ptk_vk/components.h | 24 + src/ptk_vk/draw.c | 92 +++ src/ptk_vk/draw.h | 12 + src/ptk_vk/init.c | 1553 ++++++++++++++++++++++++++++++++++++++ src/ptk_vk/init.h | 41 + src/ptk_vk/utils.c | 58 ++ src/ptk_vk/utils.h | 17 + test/Makefile | 25 + test/init.c | 16 + test/test.h | 50 ++ test/vec.c | 106 +++ 30 files changed, 2830 insertions(+) create mode 100644 .clangd create mode 100644 .envrc create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 doc/resources/c.md create mode 100644 doc/resources/vulkan.md create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 include/ptk.h create mode 100644 include/ptk_log.h create mode 100644 nix/default.nix create mode 100644 shaders/shader.frag.glsl create mode 100644 shaders/shader.vert.glsl create mode 100644 src/ptk.c create mode 100644 src/ptk_log.c create mode 100644 src/ptk_option.h create mode 100644 src/ptk_vec.c create mode 100644 src/ptk_vec.h create mode 100644 src/ptk_vk/components.c create mode 100644 src/ptk_vk/components.h create mode 100644 src/ptk_vk/draw.c create mode 100644 src/ptk_vk/draw.h create mode 100644 src/ptk_vk/init.c create mode 100644 src/ptk_vk/init.h create mode 100644 src/ptk_vk/utils.c create mode 100644 src/ptk_vk/utils.h create mode 100644 test/Makefile create mode 100644 test/init.c create mode 100644 test/test.h create mode 100644 test/vec.c diff --git a/.clangd b/.clangd new file mode 100644 index 0000000..8dfec3b --- /dev/null +++ b/.clangd @@ -0,0 +1,2 @@ +CompileFlags: + Add: [-xc, -DGLFW_INCLUDE_VULKAN] diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..350d208 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.direnv +result +target +compile_commands.json +.cache diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..47e5d17 --- /dev/null +++ b/Makefile @@ -0,0 +1,73 @@ +CC = clang +CFLAGS = -std=c99 -Wall -Wextra -Wpedantic +CFLAGS += -fstrict-aliasing +CFLAGS += $(shell pkg-config --cflags glfw3 vulkan) +LDFLAGS = $(shell pkg-config --libs glfw3 vulkan) + +ifdef DEBUG +CFLAGS += -DDEBUG -g +else +CFLAGS += -O3 +endif + +GLSLC = glslc +GLSLFLAGS = + +NAME = ptk + +CFLAGS += -DPTK_ENGINE_NAME=\"ptk\" +CFLAGS += -DPTK_VERSION_MAJOR=0 +CFLAGS += -DPTK_VERSION_MINOR=1 +CFLAGS += -DPTK_VERSION_PATCH=0 + +INCLUDE = include +SRC = src +CFLAGS += -I$(INCLUDE) -I$(SRC) + +BIN = target + +H = $(shell find $(SRC) -type f -name "*.h") +H += $(shell find $(INCLUDE) -type f -name "*.h") + +PROG = $(shell find $(SRC) -type f -name "*.c") +OBJ = $(addprefix $(BIN)/, $(PROG:.c=.o)) + +SHADER = shaders + +SHADER_SRC = $(shell find $(SHADER) -type f -name "*.glsl") +SHADER_OBJ = $(addprefix $(BIN)/, $(SHADER_SRC:.glsl=.spv)) + +.PHONY: all test clean + +all: dirs shaders + $(MAKE) $(NAME) + +dirs: + mkdir -p $(BIN) + +$(NAME): $(OBJ) + $(CC) $^ $(CFLAGS) $(LDFLAGS) -shared -o $(BIN)/lib$@.so + +$(BIN)/%.o: %.c $(H) + @mkdir -p $(@D) + $(CC) $(CFLAGS) -c $< -o $@ + +shaders: $(SHADER_OBJ) + +$(BIN)/%.vert.spv: %.vert.glsl + @mkdir -p $(@D) + $(GLSLC) $(GLSLFLAGS) -fshader-stage=vert $< -o $@ + +$(BIN)/%.frag.spv: %.frag.glsl + @mkdir -p $(@D) + $(GLSLC) $(GLSLFLAGS) -fshader-stage=frag $< -o $@ + +clean: + rm -rf $(BIN) + +test: + @CC="$(CC)" \ + CFLAGS="$(CFLAGS)" \ + BIN="$(BIN)" \ + INCLUDE="$(INCLUDE)" \ + $(MAKE) -f test/Makefile diff --git a/doc/resources/c.md b/doc/resources/c.md new file mode 100644 index 0000000..92f92ac --- /dev/null +++ b/doc/resources/c.md @@ -0,0 +1,3 @@ +# Vulkan related resources used in writing this project + + diff --git a/doc/resources/vulkan.md b/doc/resources/vulkan.md new file mode 100644 index 0000000..25b1e83 --- /dev/null +++ b/doc/resources/vulkan.md @@ -0,0 +1,6 @@ +# Vulkan related resources used in writing this project + + + + + diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..2742eba --- /dev/null +++ b/flake.lock @@ -0,0 +1,26 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1722421184, + "narHash": "sha256-/DJBI6trCeVnasdjUo9pbnodCLZcFqnVZiLUfqLH4jA=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "9f918d616c5321ad374ae6cb5ea89c9e04bf3e58", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "ref": "nixos-unstable", + "type": "indirect" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..d59e674 --- /dev/null +++ b/flake.nix @@ -0,0 +1,52 @@ +{ + description = "poz toolkit"; + + inputs = { + nixpkgs.url = "nixpkgs/nixos-unstable"; + }; + + outputs = {self, nixpkgs, ...}: let + name = "ptk"; + + systems = ["x86_64-linux" "aarch64-linux"]; + forEachSystem = nixpkgs.lib.genAttrs systems; + pkgsForEach = nixpkgs.legacyPackages; + in { + devShells = forEachSystem ( + system: let + pkgs = pkgsForEach.${system}; + shell = pkgs.mkShell { + name = "ptk"; + + packages = with pkgs; [ + bear + gdb + vulkan-tools + shaderc + spirv-tools + mesa-demos + ]; + + inputsFrom = [ self.packages.${system}.default ]; + + # https://discourse.nixos.org/t/setting-up-vulkan-for-development/11715/3 + LD_LIBRARY_PATH = with pkgs; "${glfw}/lib:${vulkan-loader}/lib:${vulkan-validation-layers}/lib:./target"; + VULKAN_SDK = with pkgs; "${vulkan-headers}"; + VK_LAYER_PATH = with pkgs; "${vulkan-validation-layers}/share/vulkan/explicit_layer.d"; + }; + in { + "${name}" = shell; + default = shell; + } + ); + packages = forEachSystem ( + system: let + pkgs = pkgsForEach.${system}; + package = pkgs.callPackage ./nix/default.nix {}; + in { + "${name}" = package; + default = package; + } + ); + }; +} diff --git a/include/ptk.h b/include/ptk.h new file mode 100644 index 0000000..990500a --- /dev/null +++ b/include/ptk.h @@ -0,0 +1,69 @@ +#ifndef _PTK_PTK_H +#define _PTK_PTK_H + +#include +#include +#include + +#include + +typedef struct { + uint32_t major; + uint32_t minor; + uint32_t patch; +} PtkVersion; + +bool ptk_init(size_t width, size_t height, const char *title, PtkVersion application_version); + +typedef struct PtkComponent *PtkHandle; +#define PTK_NULL_HANDLE (void *)0 + +typedef enum { + PTK_COMPONENT_TYPE_BOX = 0, + PTK_COMPONENT_TYPE_TRIANGLE = 1, + PTK_COMPONENT_TYPE_RECT = 2, + PTK_COMPONENT_TYPE_ELLIPSE = 3, +} PtkComponentType; + +typedef struct PtkComponent { + PtkComponentType type; +} PtkComponent; + +typedef struct PtkBox { + PtkComponentType type; + size_t child_count; + PtkHandle *children; +} PtkBox; + +typedef struct PtkTriangle { + PtkComponentType type; + vec2 vertices[3]; + vec3 color; +} PtkTriangle; + +typedef struct PtkRect { + PtkComponentType type; + vec2 top_left; + vec2 size; + vec3 color; +} PtkRect; + +typedef struct PtkEllipse { + PtkComponentType type; + vec2 center; + vec2 radii; + vec3 color; +} PtkEllipse; + +PtkHandle ptk_box(size_t child_count, PtkHandle *children); +PtkHandle ptk_triangle(vec2 vertices[3], vec3 color); +PtkHandle ptk_rect(vec2 top_left, vec2 size, vec3 color); +PtkHandle ptk_square(vec2 top_left, float size, vec3 color); +PtkHandle ptk_ellipse(vec2 center, vec2 radii, vec3 color); +PtkHandle ptk_circle(vec2 center, float radius, vec3 color); + +#define PTK_BOX(...) ptk_box(sizeof((PtkHandle []){ __VA_ARGS__ }) / sizeof(PtkHandle), (PtkHandle []) { __VA_ARGS__ }) + +int ptk_run(PtkHandle root); + +#endif // _PTK_PTK_H diff --git a/include/ptk_log.h b/include/ptk_log.h new file mode 100644 index 0000000..807a1cf --- /dev/null +++ b/include/ptk_log.h @@ -0,0 +1,35 @@ +#ifndef _PTK_PTK_LOG_H +#define _PTK_PTK_LOG_H + +#include +#include + +typedef enum { + PTK_LOG_LEVEL_OFF = 0, + PTK_LOG_LEVEL_ERR = 1, + PTK_LOG_LEVEL_WARN = 2, + PTK_LOG_LEVEL_INFO = 3, + PTK_LOG_LEVEL_DEBUG = 4, + PTK_LOG_LEVEL_TRACE = 5, + PTK_LOG_LEVEL_ALL = INT_MAX, +} PtkLogLevel; + +void ptk_log_init(PtkLogLevel level); + +void ptk_log(const char *file, int line, PtkLogLevel level, const char *fmt, ...); + +void ptk_err (const char *file, int line, const char *fmt, ...); +void ptk_warn (const char *file, int line, const char *fmt, ...); +void ptk_info (const char *file, int line, const char *fmt, ...); +void ptk_debug(const char *file, int line, const char *fmt, ...); +void ptk_trace(const char *file, int line, const char *fmt, ...); + +#define PTK_LOG(level, ...) ptk_log(__FILE__, __LINE__, level, __VA_ARGS__) + +#define PTK_ERR(...) ptk_err(__FILE__, __LINE__, __VA_ARGS__) +#define PTK_WARN(...) ptk_warn(__FILE__, __LINE__, __VA_ARGS__) +#define PTK_INFO(...) ptk_info(__FILE__, __LINE__, __VA_ARGS__) +#define PTK_DEBUG(...) ptk_debug(__FILE__, __LINE__, __VA_ARGS__) +#define PTK_TRACE(...) ptk_trace(__FILE__, __LINE__, __VA_ARGS__) + +#endif // _PTK_PTK_LOG_H diff --git a/nix/default.nix b/nix/default.nix new file mode 100644 index 0000000..96a5fc6 --- /dev/null +++ b/nix/default.nix @@ -0,0 +1,46 @@ +{ + lib, + stdenv, + cglm, + clang, + glfw, + pkg-config, + vulkan-headers, + vulkan-loader, + vulkan-validation-layers, + ... +}: let + pname = "ptk"; +in stdenv.mkDerivation { + inherit pname; + version = "0.1.0"; + + src = ../.; + + buildInputs = [ + cglm + glfw + vulkan-headers + vulkan-loader + vulkan-validation-layers + ]; + + nativeBuildInputs = [ + clang + pkg-config + ]; + + installPhase = '' + runHook preInstall + + install -Dm755 target/lib${pname}.so -t $out/lib + + runHook postInstall + ''; + + meta = with lib; { + homepage = "https://git.jacekpoz.pl/jacekpoz/${pname}"; + description = "poz toolkit"; + license = licenses.eupl12; + }; +} diff --git a/shaders/shader.frag.glsl b/shaders/shader.frag.glsl new file mode 100644 index 0000000..a7e6afe --- /dev/null +++ b/shaders/shader.frag.glsl @@ -0,0 +1,12 @@ +#version 450 + +layout(location = 0) out vec4 outColor; +layout(location = 0) in vec3 fragColor; +layout(location = 1) in vec2 position; + +void main() { + if (length(position) > 0.5) { + discard; + } + outColor = vec4(fragColor, 1.0); +} diff --git a/shaders/shader.vert.glsl b/shaders/shader.vert.glsl new file mode 100644 index 0000000..dcbbcc3 --- /dev/null +++ b/shaders/shader.vert.glsl @@ -0,0 +1,17 @@ +#version 450 + +layout(binding = 0) uniform UniformBufferObject { + vec2 windowSize; +} ubo; + +layout(location = 0) in vec2 inPosition; +layout(location = 1) in vec3 inColor; + +layout(location = 0) out vec3 fragColor; +layout(location = 1) out vec2 outPosition; + +void main() { + gl_Position = vec4(((inPosition / ubo.windowSize) - 0.5) * 2.0, 0.0, 1.0); + fragColor = inColor; + outPosition = inPosition; +} diff --git a/src/ptk.c b/src/ptk.c new file mode 100644 index 0000000..0bdc206 --- /dev/null +++ b/src/ptk.c @@ -0,0 +1,199 @@ +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#ifndef GLFW_INCLUDE_VULKAN +#define GLFW_INCLUDE_VULKAN +#endif +#include + +#include + +#include +#include +#include +#include + +static GLFWwindow *g_window = NULL; + +void framebuffer_resize_callback(GLFWwindow *window, int width, int height) { + (void)window; (void)width; (void)height; + g_framebuffer_resized = true; +} + +PTK_OPTION_DEFINE(PtkLogLevel); + +PTK_OPTION(PtkLogLevel) get_log_level(void) { + const char *log_level = getenv("PTK_LOG_LEVEL"); + if (log_level == NULL) { + PTK_INFO("$PTK_LOG_LEVEL not set"); + PTK_INFO("using default log level"); + return PTK_OPTION_NONE(PtkLogLevel); + } + + size_t log_level_len = strlen(log_level); + + char *lowercase = malloc(log_level_len * sizeof(char)); + + for (size_t i = 0; i < log_level_len; ++i) { + lowercase[i] = tolower(log_level[i]); + } + + PTK_OPTION(PtkLogLevel) ret = PTK_OPTION_NONE(PtkLogLevel); + + if (strncmp(lowercase, "off", sizeof("off")) == 0) { + ret = PTK_OPTION_SOME(PtkLogLevel, PTK_LOG_LEVEL_OFF); + } else if (strncmp(lowercase, "err", sizeof("err")) == 0) { + ret = PTK_OPTION_SOME(PtkLogLevel, PTK_LOG_LEVEL_ERR); + } else if (strncmp(lowercase, "warn", sizeof("warn")) == 0) { + ret = PTK_OPTION_SOME(PtkLogLevel, PTK_LOG_LEVEL_WARN); + } else if (strncmp(lowercase, "info", sizeof("info")) == 0) { + ret = PTK_OPTION_SOME(PtkLogLevel, PTK_LOG_LEVEL_INFO); + } else if (strncmp(lowercase, "debug", sizeof("debug")) == 0) { + ret = PTK_OPTION_SOME(PtkLogLevel, PTK_LOG_LEVEL_DEBUG); + } else if (strncmp(lowercase, "trace", sizeof("trace")) == 0) { + ret = PTK_OPTION_SOME(PtkLogLevel, PTK_LOG_LEVEL_TRACE); + } else if (strncmp(lowercase, "all", sizeof("all")) == 0) { + ret = PTK_OPTION_SOME(PtkLogLevel, PTK_LOG_LEVEL_ALL); + } else { + PTK_WARN("unknown log level from $PTK_LOG_LEVEL"); + PTK_WARN("using default log level"); + } + + free(lowercase); + + return ret; +} + +bool ptk_init(size_t width, size_t height, const char *title, PtkVersion application_version) { + PTK_OPTION(PtkLogLevel) log_level = get_log_level(); + if (log_level.exists) { + ptk_log_init(log_level.value); + } + +#ifdef DEBUG + PTK_WARN("running in debug mode!"); + PTK_WARN("expect decreased performance"); +#endif + + if (!glfwInit()) { + PTK_ERR("failed initializing GLFW"); + return false; + } + + if (!glfwVulkanSupported()) { + PTK_ERR("vulkan loader or ICD haven't been found!"); + return false; + } + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + g_window = glfwCreateWindow(width, height, title, NULL, NULL); + + if (g_window == NULL) { + PTK_ERR("failed creating GLFW window"); + return false; + } + + glfwSetFramebufferSizeCallback(g_window, framebuffer_resize_callback); + + vk_components_init(); + + if (!vk_init(g_window, title, application_version)) { + PTK_ERR("failed initializing vulkan"); + return false; + } + + return true; +} + +PtkHandle ptk_box(size_t child_count, PtkHandle *children) { + PtkBox *ret = malloc(sizeof(PtkBox)); + ret->type = PTK_COMPONENT_TYPE_BOX; + ret->child_count = child_count; + ret->children = children; + + return (PtkHandle)ret; +} + +PtkHandle ptk_triangle(vec2 vertices[3], vec3 color) { + PtkTriangle *ret = malloc(sizeof(PtkTriangle)); + ret->type = PTK_COMPONENT_TYPE_TRIANGLE; + memcpy(ret->vertices, vertices, sizeof(vec2) * 3); + memcpy(ret->color, color, sizeof(vec3)); + + return (PtkHandle)ret; +} + +PtkHandle ptk_rect(vec2 top_left, vec2 size, vec3 color) { + PtkRect *ret = malloc(sizeof(PtkRect)); + ret->type = PTK_COMPONENT_TYPE_RECT; + memcpy(ret->top_left, top_left, sizeof(vec2)); + memcpy(ret->size, size, sizeof(vec2)); + memcpy(ret->color, color, sizeof(vec3)); + + return (PtkHandle)ret; +} + +PtkHandle ptk_square(vec2 top_left, float size, vec3 color) { + return ptk_rect(top_left, (vec2){size, size}, color); +} + +PtkHandle ptk_ellipse(vec2 center, vec2 radii, vec3 color) { + PtkEllipse *ret = malloc(sizeof(PtkEllipse)); + ret->type = PTK_COMPONENT_TYPE_ELLIPSE; + memcpy(ret->center, center, sizeof(vec2)); + memcpy(ret->radii, radii, sizeof(vec2)); + memcpy(ret->color, color, sizeof(vec3)); + + return (PtkHandle)ret; +} + +PtkHandle ptk_circle(vec2 center, float radius, vec3 color) { + return ptk_ellipse(center, (vec2){radius, radius}, color); +} + +void init_components(PtkHandle root) { + switch (root->type) { + case PTK_COMPONENT_TYPE_TRIANGLE: { + vk_triangle((PtkTriangle *)root); + } break; + case PTK_COMPONENT_TYPE_RECT: { + vk_rect((PtkRect *)root); + } break; + case PTK_COMPONENT_TYPE_ELLIPSE: { + vk_ellipse((PtkEllipse *)root); + } break; + case PTK_COMPONENT_TYPE_BOX: { + PtkBox *box = (PtkBox *)root; + for (size_t i = 0; i < box->child_count; ++i) { + init_components(box->children[i]); + } + } break; + } +} + +int ptk_run(PtkHandle root) { + init_components(root); + + while (!glfwWindowShouldClose(g_window)) { + glfwPollEvents(); + if (!vk_draw_frame()) { + break; + } + } + + vkDeviceWaitIdle(g_dev); + + vk_cleanup(); + vk_components_cleanup(); + + return EXIT_SUCCESS; +} diff --git a/src/ptk_log.c b/src/ptk_log.c new file mode 100644 index 0000000..23565de --- /dev/null +++ b/src/ptk_log.c @@ -0,0 +1,53 @@ +#include + +#include + +#define FG(r, g, b) "\033[38;2;" #r ";" #g ";" #b "m" +#define BG(r, g, b) "\033[48;2;" #r ";" #g ";" #b "m" +#define RESET "\033[0m" + +static const char *log_level_to_str(PtkLogLevel level) { + const char *ret; + switch (level) { + case PTK_LOG_LEVEL_ERR: ret = FG(235, 28, 35) " ERR" RESET; break; + case PTK_LOG_LEVEL_WARN: ret = FG(255, 191, 23) " WARN" RESET; break; + case PTK_LOG_LEVEL_INFO: ret = FG( 23, 155, 98) " INFO" RESET; break; + case PTK_LOG_LEVEL_DEBUG: ret = FG( 51, 102, 204) "DEBUG" RESET; break; + case PTK_LOG_LEVEL_TRACE: ret = FG(108, 112, 134) "TRACE" RESET; break; + default: ret = "?????"; + } + return ret; +} + +#ifdef DEBUG +static PtkLogLevel log_level = PTK_LOG_LEVEL_DEBUG; +#else +static PtkLogLevel log_level = PTK_LOG_LEVEL_WARN; +#endif + +void ptk_log_init(PtkLogLevel level) { + log_level = level; +} + +void _ptk_log(const char *file, int line, PtkLogLevel level, const char *fmt, va_list args) { + if (log_level < level) { return; } + + fprintf(stderr, "[%s | %s:%d] ", log_level_to_str(level), file, line); + + vfprintf(stderr, fmt, args); + + fprintf(stderr, "\n"); +} + +void ptk_log(const char *file, int line, PtkLogLevel level, const char *fmt, ...) { + va_list args; + va_start(args, fmt); + _ptk_log(file, line, level, fmt, args); + va_end(args); +} + +void ptk_err (const char *file, int line, const char *fmt, ...) { va_list args; va_start(args, fmt); _ptk_log(file, line, PTK_LOG_LEVEL_ERR, fmt, args); va_end(args); } +void ptk_warn (const char *file, int line, const char *fmt, ...) { va_list args; va_start(args, fmt); _ptk_log(file, line, PTK_LOG_LEVEL_WARN, fmt, args); va_end(args); } +void ptk_info (const char *file, int line, const char *fmt, ...) { va_list args; va_start(args, fmt); _ptk_log(file, line, PTK_LOG_LEVEL_INFO, fmt, args); va_end(args); } +void ptk_debug(const char *file, int line, const char *fmt, ...) { va_list args; va_start(args, fmt); _ptk_log(file, line, PTK_LOG_LEVEL_DEBUG, fmt, args); va_end(args); } +void ptk_trace(const char *file, int line, const char *fmt, ...) { va_list args; va_start(args, fmt); _ptk_log(file, line, PTK_LOG_LEVEL_TRACE, fmt, args); va_end(args); } diff --git a/src/ptk_option.h b/src/ptk_option.h new file mode 100644 index 0000000..01b6b70 --- /dev/null +++ b/src/ptk_option.h @@ -0,0 +1,26 @@ +#ifndef _PTK_PTK_OPTION_H +#define _PTK_PTK_OPTION_H + +#include + +#define PTK_OPTION(T) struct PtkOption_##T + +#define PTK_OPTION_DEFINE(T) \ + PTK_OPTION(T) {\ + T value;\ + bool exists;\ + } + +#define PTK_OPTION_SOME(T, _value) \ + (PTK_OPTION(T)){\ + .value = _value,\ + .exists = true,\ + } + +#define PTK_OPTION_NONE(T) \ + (PTK_OPTION(T)){\ + .value = (T){0},\ + .exists = false,\ + } + +#endif // _PTK_PTK_OPTION_H diff --git a/src/ptk_vec.c b/src/ptk_vec.c new file mode 100644 index 0000000..a9f4f42 --- /dev/null +++ b/src/ptk_vec.c @@ -0,0 +1,77 @@ +#include + +#include +#include +#include + +bool _grow_PtkVec(void **data, uint32_t *allocated, size_t element_size) { + errno = 0; + void *tmp = realloc(*data, (*allocated * 2) * element_size); + + if (errno == ENOMEM || tmp == NULL) { + free(*data); + return false; + } + *data = tmp; + + *allocated *= 2; + + return true; +} + +bool _add_PtkVec(void **data, uint32_t *size, uint32_t *allocated, void *elements, size_t element_count, size_t element_size) { + size_t elements_added = 0; + + for (size_t i = 0; i < element_count; ++i) { + if (*size == *allocated) { + if (!_grow_PtkVec(data, allocated, element_size)) { + break; + } + } + + uint8_t *element = ((uint8_t *)elements) + (i * element_size); + + uint8_t *target = ((uint8_t *)*data) + (*size * element_size); + memcpy(target, element, element_size); + elements_added += 1; + *size += 1; + } + + return elements_added == element_count; +} + +bool _remove_PtkVec(void *data, uint32_t *size, void *elements, size_t element_count, size_t element_size) { + size_t elements_found = 0; + + for (size_t i = 0; i < element_count; ++i) { + uint8_t *element = ((uint8_t *)elements) + (i * element_size); + + for (size_t j = 0; j < *size; ++j) { + uint8_t *item = ((uint8_t *)data) + (j * element_size); + + if (memcmp(element, item, element_size) != 0) { + continue; + } + + elements_found += 1; + // from the loop we know that j is always smaller than size + // we can safely ignore the return value of this function + _remove_at_PtkVec(data, size, j, element_size); + break; + } + } + + return elements_found == element_count; +} + +bool _remove_at_PtkVec(void *data, uint32_t *size, size_t index, size_t element_size) { + if (index >= *size) { + return false; + } + + uint8_t *item = ((uint8_t *)data) + (index * element_size); + memmove(item, item + element_size, (*size - 1 - index) * element_size); + *size -= 1; + + return true; +} diff --git a/src/ptk_vec.h b/src/ptk_vec.h new file mode 100644 index 0000000..6261b18 --- /dev/null +++ b/src/ptk_vec.h @@ -0,0 +1,61 @@ +#ifndef _PTK_PTK_VEC_H +#define _PTK_PTK_VEC_H + +#include +#include +#include +#include + +#define PTK_VEC(T) struct PtkVec_##T + +#define PTK_VEC_ADD(T, vec, elem) _add_PtkVec((void **)&vec.data, &vec.size, &vec.allocated, &elem, 1, sizeof(T)) + +#define PTK_VEC_ADD_ALL(T, vec, ...) _add_PtkVec((void **)&vec.data, &vec.size, &vec.allocated, (T []) __VA_ARGS__, sizeof((T []) __VA_ARGS__) / sizeof(T), sizeof(T)) + +#define PTK_VEC_REMOVE(T, vec, elem) _remove_PtkVec((void *)vec.data, &vec.size, &elem, 1, sizeof(T)) + +#define PTK_VEC_REMOVE_ALL(T, vec, ...) _remove_PtkVec((void *)vec.data, &vec.size, (T []) __VA_ARGS__, sizeof((T []) __VA_ARGS__) / sizeof(T), sizeof(T)) + +#define PTK_VEC_REMOVE_AT(T, vec, index) _remove_at_PtkVec((void *)vec.data, &vec.size, index, sizeof(T)) + +bool _grow_PtkVec(void **data, uint32_t *allocated, size_t element_size); +bool _add_PtkVec(void **data, uint32_t *size, uint32_t *allocated, void *elements, size_t element_count, size_t element_size); +bool _remove_PtkVec(void *data, uint32_t *size, void *elements, size_t element_count, size_t element_size); +bool _remove_at_PtkVec(void *data, uint32_t *size, size_t index, size_t element_size); + +#define PTK_VEC_DEFINE(T) \ + PTK_VEC(T) {\ + T *data;\ + uint32_t allocated;\ + uint32_t size;\ + }\ + +#define PTK_VEC_NEW(T, _size) \ + (PTK_VEC(T)){\ + .data = (T *)malloc(_size * sizeof(T)),\ + .size = 0,\ + .allocated = _size,\ + } + +#define PTK_VEC_STATIC_INIT(T, ...) \ + (PTK_VEC(T)){\ + .data = (T []) __VA_ARGS__ ,\ + .size = sizeof((T []) __VA_ARGS__) / sizeof(T),\ + .allocated = sizeof((T []) __VA_ARGS__) / sizeof(T),\ + } + +#define PTK_VEC_FREE(vec) \ + free(vec.data) + +#define PTK_VEC_FILLED(vec) \ + vec.size = vec.allocated; + +PTK_VEC_DEFINE(char); + +#define PTK_STRING PTK_VEC(char) + +#define PTK_STRING_NEW(size) PTK_VEC_NEW(char, size) + +#define PTK_STRING_FREE(str) PTK_VEC_FREE(str) + +#endif // _PTK_PTK_VEC_H diff --git a/src/ptk_vk/components.c b/src/ptk_vk/components.c new file mode 100644 index 0000000..46d4871 --- /dev/null +++ b/src/ptk_vk/components.c @@ -0,0 +1,73 @@ +#include +#include +#include + +#include + +PTK_VEC_DEFINE(PtkHandle); + +PTK_VEC(PtkHandle) m_components; + +PTK_VEC(Vertex) g_vertices; + +void vk_components_init(void) { + m_components = PTK_VEC_NEW(PtkHandle, 1); + + g_vertices = PTK_VEC_NEW(Vertex, 1); +} + +void _vk_triangle(PtkTriangle *triangle) { + for (size_t i = 0; i < 3; ++i) { + Vertex v; + memcpy(v.pos, triangle->vertices[i], sizeof(vec2)); + memcpy(v.color, triangle->color, sizeof(vec3)); + + PTK_VEC_ADD(Vertex, g_vertices, v); + } +} + +void vk_triangle(PtkTriangle *triangle) { + PTK_VEC_ADD(PtkHandle, m_components, triangle); + _vk_triangle(triangle); + vk_transfer_vertex_data(); +} + +void vk_rect(PtkRect *rect) { + PTK_VEC_ADD(PtkHandle, m_components, rect); + vec2 t1_positions[3]; + // top left + memcpy(t1_positions[0], &(vec2){ rect->top_left[0], rect->top_left[1] }, sizeof(vec2)); + // bottom left + memcpy(t1_positions[1], &(vec2){ rect->top_left[0] + rect->size[0], rect->top_left[1] }, sizeof(vec2)); + // top right + memcpy(t1_positions[2], &(vec2){ rect->top_left[0], rect->top_left[1] + rect->size[1] }, sizeof(vec2)); + PtkTriangle *t1 = (PtkTriangle *)ptk_triangle((vec2 *)t1_positions, rect->color); + + _vk_triangle(t1); + + vec2 t2_positions[3]; + // bottom left + memcpy(t2_positions[0], &(vec2){ rect->top_left[0] + rect->size[0], rect->top_left[1] }, sizeof(vec2)); + // top right + memcpy(t2_positions[1], &(vec2){ rect->top_left[0], rect->top_left[1] + rect->size[1] }, sizeof(vec2)); + // bottom right + memcpy(t2_positions[2], &(vec2){ rect->top_left[0] + rect->size[0], rect->top_left[1] + rect->size[1] }, sizeof(vec2)); + PtkTriangle *t2 = (PtkTriangle *)ptk_triangle((vec2 *)t2_positions, rect->color); + + _vk_triangle(t2); + vk_transfer_vertex_data(); +} + +void vk_ellipse(PtkEllipse *ellipse) { + PTK_VEC_ADD(PtkHandle, m_components, ellipse); + +} + +void vk_components_cleanup(void) { + for (size_t i = 0; i < m_components.size; ++i) { + free(m_components.data[i]); + } + + PTK_VEC_FREE(m_components); + PTK_VEC_FREE(g_vertices); +} diff --git a/src/ptk_vk/components.h b/src/ptk_vk/components.h new file mode 100644 index 0000000..55950e0 --- /dev/null +++ b/src/ptk_vk/components.h @@ -0,0 +1,24 @@ +#ifndef _PTK_PTK_VK_COMPONENTS_H +#define _PTK_PTK_VK_COMPONENTS_H + +#include +#include + +typedef struct { + vec2 pos; + vec3 color; +} Vertex; + +PTK_VEC_DEFINE(Vertex); + +extern PTK_VEC(Vertex) g_vertices; + +void vk_components_init(void); + +void vk_triangle(PtkTriangle *triangle); +void vk_rect(PtkRect *rect); +void vk_ellipse(PtkEllipse *ellipse); + +void vk_components_cleanup(void); + +#endif // _PTK_PTK_VK_COMPONENTS_H diff --git a/src/ptk_vk/draw.c b/src/ptk_vk/draw.c new file mode 100644 index 0000000..ca8c4fc --- /dev/null +++ b/src/ptk_vk/draw.c @@ -0,0 +1,92 @@ +#include + +#include +#include + +#include + +#include + +bool g_framebuffer_resized = false; +uint32_t g_current_frame = 0; + +bool vk_draw_frame(void) { + vkWaitForFences(g_dev, 1, &g_in_flight_fences.data[g_current_frame], VK_TRUE, UINT64_MAX); + + uint32_t image_index; + VkResult res = vkAcquireNextImageKHR( + g_dev, + g_swapchain, + UINT64_MAX, + g_image_available_semaphores.data[g_current_frame], + VK_NULL_HANDLE, + &image_index + ); + + if (res == VK_ERROR_OUT_OF_DATE_KHR) { + if (!vk_recreate_swapchain()) { + return false; + } + return true; + } else if (res != VK_SUCCESS && res != VK_SUBOPTIMAL_KHR) { + PTK_ERR("%s", vk_result_string(res)); + return false; + } + + vk_update_uniform_buffer(g_current_frame); + + vkResetFences(g_dev, 1, &g_in_flight_fences.data[g_current_frame]); + + vkResetCommandBuffer(g_command_buffers.data[g_current_frame], 0); + if (!vk_record_command_buffer(g_command_buffers.data[g_current_frame], image_index)) { + PTK_ERR("failed recording command buffer"); + return false; + } + + VkSemaphore signal_semaphores[] = {g_render_finished_semaphores.data[g_current_frame]}; + + VK_TRY(false, + vkQueueSubmit( + g_graphics_queue, + 1, + &(VkSubmitInfo){ + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .pNext = NULL, + .waitSemaphoreCount = 1, + .pWaitSemaphores = &g_image_available_semaphores.data[g_current_frame], + .pWaitDstStageMask = &(VkPipelineStageFlags){VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}, + .commandBufferCount = 1, + .pCommandBuffers = &g_command_buffers.data[g_current_frame], + .signalSemaphoreCount = 1, + .pSignalSemaphores = signal_semaphores, + }, + g_in_flight_fences.data[g_current_frame] + ) + ); + + res = vkQueuePresentKHR(g_present_queue, &(VkPresentInfoKHR){ + .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, + .pNext = NULL, + .waitSemaphoreCount = 1, + .pWaitSemaphores = signal_semaphores, + .swapchainCount = 1, + .pSwapchains = &g_swapchain, + .pImageIndices = &image_index, + .pResults = NULL, + }); + + if (res == VK_ERROR_OUT_OF_DATE_KHR || res == VK_SUBOPTIMAL_KHR || g_framebuffer_resized) { + g_framebuffer_resized = false; + if (!vk_recreate_swapchain()) { + return false; + } + PTK_TRACE("recreated swapchain"); + } else if (res != VK_SUCCESS) { + PTK_ERR("%s", vk_result_string(res)); + return false; + } + + g_current_frame = (g_current_frame + 1) % g_max_frames_in_flight; + + return true; +} diff --git a/src/ptk_vk/draw.h b/src/ptk_vk/draw.h new file mode 100644 index 0000000..7edb524 --- /dev/null +++ b/src/ptk_vk/draw.h @@ -0,0 +1,12 @@ +#ifndef _PTK_PTK_VK_DRAW_H +#define _PTK_PTK_VK_DRAW_H + +#include +#include + +extern bool g_framebuffer_resized; +extern uint32_t g_current_frame; + +bool vk_draw_frame(void); + +#endif // _PTK_PTK_VK_DRAW_H diff --git a/src/ptk_vk/init.c b/src/ptk_vk/init.c new file mode 100644 index 0000000..7adc8a9 --- /dev/null +++ b/src/ptk_vk/init.c @@ -0,0 +1,1553 @@ +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include + +PTK_VEC_DEFINE(VkImage); +PTK_VEC_DEFINE(VkImageView); +PTK_OPTION_DEFINE(uint32_t); +PTK_VEC_DEFINE(VkSurfaceFormatKHR); +PTK_VEC_DEFINE(VkPresentModeKHR); +PTK_VEC_DEFINE(VkFramebuffer); +PTK_VEC_DEFINE(VkVertexInputAttributeDescription); +PTK_VEC_DEFINE(VkExtensionProperties); +PTK_VEC_DEFINE(VkQueueFamilyProperties); +PTK_OPTION_DEFINE(VkShaderModule); + +#ifdef DEBUG +PTK_VEC_DEFINE(VkLayerProperties); +#endif + +typedef struct { + VkSurfaceCapabilitiesKHR capabilities; + PTK_VEC(VkSurfaceFormatKHR) formats; + PTK_VEC(VkPresentModeKHR) present_modes; +} SwapchainSupportInfo; + +static GLFWwindow *m_window = NULL; + +static VkInstance m_instance = VK_NULL_HANDLE; +static VkPhysicalDevice m_physical_dev = VK_NULL_HANDLE; +VkDevice g_dev = VK_NULL_HANDLE; + +static VkSurfaceKHR m_surface = VK_NULL_HANDLE; + +VkSwapchainKHR g_swapchain = VK_NULL_HANDLE; +static VkFormat m_swapchain_image_format; +static VkExtent2D m_swapchain_extent; + +static PTK_VEC(VkImage) m_swapchain_images; + +static PTK_VEC(VkImageView) m_swapchain_image_views; + +static struct { + PTK_OPTION(uint32_t) graphics; + PTK_OPTION(uint32_t) present; +} m_queue_family_indices = {0}; +static const size_t m_queue_family_count = sizeof(m_queue_family_indices) / sizeof(PTK_OPTION(uint32_t)); + +VkQueue g_graphics_queue = VK_NULL_HANDLE; +VkQueue g_present_queue = VK_NULL_HANDLE; + +static const size_t m_device_extension_count = 1; +static const char *m_device_extensions[] = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME +}; + +static VkRenderPass m_render_pass; +static VkDescriptorSetLayout m_descriptor_set_layout; +static VkPipelineLayout m_pipeline_layout; +static VkPipeline m_pipeline; + +static PTK_VEC(VkFramebuffer) m_swapchain_framebuffers; + +const size_t g_max_frames_in_flight = 2; + +static VkCommandPool m_command_pool; +PTK_VEC(VkCommandBuffer) g_command_buffers; + +PTK_VEC(VkSemaphore) g_image_available_semaphores; +PTK_VEC(VkSemaphore) g_render_finished_semaphores; +PTK_VEC(VkFence) g_in_flight_fences; + +static const VkVertexInputBindingDescription m_vertex_binding_description = { + .binding = 0, + .stride = sizeof(Vertex), + .inputRate = VK_VERTEX_INPUT_RATE_VERTEX, +}; + +PTK_VEC(VkVertexInputAttributeDescription) m_vertex_attribute_descriptions = PTK_VEC_STATIC_INIT(VkVertexInputAttributeDescription, { + (VkVertexInputAttributeDescription){ + .location = 0, + .binding = 0, + .format = VK_FORMAT_R32G32_SFLOAT, + .offset = offsetof(Vertex, pos), + }, + (VkVertexInputAttributeDescription){ + .location = 1, + .binding = 0, + .format = VK_FORMAT_R32G32B32_SFLOAT, + .offset = offsetof(Vertex, color), + }, +}); + +static VkBuffer m_vertex_buffer; +static VkDeviceMemory m_vertex_buffer_memory; + +PTK_VEC_DEFINE(VkBuffer); +PTK_VEC_DEFINE(VkDeviceMemory); +typedef void *voidptr; +PTK_VEC_DEFINE(voidptr); + +typedef struct { + vec2 window_size; +} UniformBufferObject; + +static UniformBufferObject m_uniform_buffer_object; + +static PTK_VEC(VkBuffer) m_uniform_buffers; +static PTK_VEC(VkDeviceMemory) m_uniform_buffers_memory; +static PTK_VEC(voidptr) m_uniform_buffers_mapped; + +PTK_VEC_DEFINE(VkDescriptorSet); + +static VkDescriptorPool m_descriptor_pool; +static PTK_VEC(VkDescriptorSet) m_descriptor_sets; + +#ifdef DEBUG +static const uint32_t g_validation_layer_count = 1; +static const char * const g_validation_layers[] = { + "VK_LAYER_KHRONOS_validation" +}; + +bool check_validation_layers(const char * const *validation_layers, uint32_t validation_layer_count) { + PTK_VEC(VkLayerProperties) available_layers; + + vkEnumerateInstanceLayerProperties(&available_layers.allocated, NULL); + + available_layers = PTK_VEC_NEW(VkLayerProperties, available_layers.allocated); + vkEnumerateInstanceLayerProperties(&available_layers.allocated, available_layers.data); + PTK_VEC_FILLED(available_layers); + + for (size_t i = 0; i < validation_layer_count; ++i) { + const char *layer_name = validation_layers[i]; + + bool layer_found = false; + + for (size_t j = 0; j < available_layers.size; ++j) { + VkLayerProperties layer_properties = available_layers.data[j]; + if (strcmp(layer_name, layer_properties.layerName) == 0) { + layer_found = true; + break; + } + } + + if (!layer_found) { + return false; + } + } + + return true; +} +#endif + +bool create_vk_instance(const char *title, const PtkVersion version) { +#ifdef DEBUG + if (!check_validation_layers(g_validation_layers, g_validation_layer_count)) { + PTK_ERR("couldn't find requested validation layer"); + return false; + } + uint32_t enabledLayerCount = g_validation_layer_count; + const char * const *ppEnabledLayerNames = g_validation_layers; +#else + uint32_t enabledLayerCount = 0; + const char * const *ppEnabledLayerNames = NULL; +#endif + + uint32_t extension_count = 0; + const char * const *extension_names = glfwGetRequiredInstanceExtensions(&extension_count); + + VK_TRY(false, + vkCreateInstance( + &(VkInstanceCreateInfo){ + .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + .pNext = NULL, + .flags = 0, + .pApplicationInfo = &(VkApplicationInfo){ + .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, + .pNext = NULL, + .pApplicationName = title, + .applicationVersion = VK_MAKE_API_VERSION(0, version.major, version.minor, version.patch), + .pEngineName = PTK_ENGINE_NAME, + .engineVersion = VK_MAKE_API_VERSION(0, PTK_VERSION_MAJOR, PTK_VERSION_MINOR, PTK_VERSION_PATCH), + .apiVersion = VK_API_VERSION_1_3, + }, + .enabledLayerCount = enabledLayerCount, + .ppEnabledLayerNames = ppEnabledLayerNames, + .enabledExtensionCount = extension_count, + .ppEnabledExtensionNames = extension_names, + }, + NULL, + &m_instance + ) + ); + + return true; +} + +bool select_physical_dev(void) { + uint32_t dev_count = 0; + vkEnumeratePhysicalDevices(m_instance, &dev_count, NULL); + if (dev_count == 0) { + PTK_ERR("failed to find GPU with vulkan support"); + return false; + } + VkPhysicalDevice devs[dev_count]; + vkEnumeratePhysicalDevices(m_instance, &dev_count, devs); + + VkPhysicalDeviceProperties dev_props[dev_count]; + uint32_t dgpus[dev_count]; + uint32_t dgpu_count = 0; + uint32_t igpus[dev_count]; + uint32_t igpu_count = 0; + + VkPhysicalDeviceMemoryProperties dev_mem_props[dev_count]; + uint32_t dev_mem_counts[dev_count]; + VkDeviceSize dev_mem_totals[dev_count]; + + for (size_t i = 0; i < dev_count; ++i) { + vkGetPhysicalDeviceProperties(devs[i], &dev_props[i]); + + if (dev_props[i].deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) { + dgpus[dgpu_count] = i; + dgpu_count += 1; + } else if (dev_props[i].deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU) { + igpus[igpu_count] = i; + igpu_count += 1; + } + + vkGetPhysicalDeviceMemoryProperties(devs[i], &dev_mem_props[i]); + + dev_mem_counts[i] = dev_mem_props[i].memoryHeapCount; + dev_mem_totals[i] = 0; + + for (size_t j = 0; j < dev_mem_counts[i]; ++j) { + dev_mem_totals[i] += dev_mem_props[i].memoryHeaps[j].size; + } + } + + VkDeviceSize max_mem_size = 0; + size_t dev_best_index = 0; + + if (dgpu_count != 0) { + for (size_t i = 0; i < dgpu_count; ++i) { + if (dev_mem_totals[i] > max_mem_size) { + dev_best_index = dgpus[i]; + max_mem_size = dev_mem_totals[i]; + } + } + } else if (igpu_count != 0) { + for (size_t i = 0; i < igpu_count; ++i) { + if (dev_mem_totals[i] > max_mem_size) { + dev_best_index = igpus[i]; + max_mem_size = dev_mem_totals[i]; + } + } + } + + PTK_DEBUG("best device index: %ld", dev_best_index); + PTK_DEBUG("device name: %s", dev_props[dev_best_index].deviceName); + + PTK_DEBUG("device type: %s", (dgpu_count != 0 ? "dgpu" : (igpu_count != 0 ? "igpu" : "probably cpu"))); + + PTK_DEBUG("total memory: %lu", dev_mem_totals[dev_best_index]); + + m_physical_dev = devs[dev_best_index]; + + return true; +} + +VkSurfaceFormatKHR select_swap_surface_format(const PTK_VEC(VkSurfaceFormatKHR) available_formats) { + for (size_t i = 0; i < available_formats.size; ++i) { + VkSurfaceFormatKHR current_format = available_formats.data[i]; + if (current_format.format == VK_FORMAT_B8G8R8A8_SRGB && current_format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return current_format; + } + } + + return available_formats.data[0]; +} + +VkPresentModeKHR select_swap_present_mode(const PTK_VEC(VkPresentModeKHR) available_present_modes) { + for (size_t i = 0; i < available_present_modes.size; ++i) { + VkPresentModeKHR current_present_mode = available_present_modes.data[i]; + if (current_present_mode == VK_PRESENT_MODE_MAILBOX_KHR) { + return current_present_mode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; +} + +VkExtent2D select_swap_extent(VkSurfaceCapabilitiesKHR capabilities) { + if (capabilities.currentExtent.width != UINT32_MAX) { + return capabilities.currentExtent; + } + + int width, height; + + glfwGetFramebufferSize(m_window, &width, &height); + + VkExtent2D actual_extent = { + .width = (uint32_t)width, + .height = (uint32_t)height, + }; + + if (actual_extent.width < capabilities.minImageExtent.width) { + actual_extent.width = capabilities.minImageExtent.width; + } else if (actual_extent.width > capabilities.maxImageExtent.width) { + actual_extent.width = capabilities.maxImageExtent.width; + } + + if (actual_extent.height < capabilities.minImageExtent.height) { + actual_extent.height = capabilities.minImageExtent.height; + } else if (actual_extent.height > capabilities.maxImageExtent.height) { + actual_extent.height = capabilities.maxImageExtent.height; + } + + return actual_extent; +} + +SwapchainSupportInfo query_swapchain_support(VkPhysicalDevice dev) { + SwapchainSupportInfo info; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(dev, m_surface, &info.capabilities); + + const uint32_t width = info.capabilities.currentExtent.width; + const uint32_t height = info.capabilities.currentExtent.height; + + if ( + info.capabilities.currentTransform & VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR + || info.capabilities.currentTransform & VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR + ) { + info.capabilities.currentExtent.height = width; + info.capabilities.currentExtent.width = height; + } + + vkGetPhysicalDeviceSurfaceFormatsKHR(dev, m_surface, &info.formats.allocated, NULL); + + if (info.formats.allocated != 0) { + info.formats = PTK_VEC_NEW(VkSurfaceFormatKHR, info.formats.allocated); + vkGetPhysicalDeviceSurfaceFormatsKHR(dev, m_surface, &info.formats.allocated, info.formats.data); + info.formats.size = info.formats.allocated; + } + + vkGetPhysicalDeviceSurfacePresentModesKHR(dev, m_surface, &info.present_modes.allocated, NULL); + + if (info.present_modes.allocated != 0) { + info.present_modes = PTK_VEC_NEW(VkPresentModeKHR, info.present_modes.allocated); + vkGetPhysicalDeviceSurfacePresentModesKHR(dev, m_surface, &info.present_modes.allocated, info.present_modes.data); + info.present_modes.size = info.present_modes.allocated; + } + + return info; +} + +bool are_extensions_supported(VkPhysicalDevice dev) { + PTK_VEC(VkExtensionProperties) available_extensions; + vkEnumerateDeviceExtensionProperties(dev, NULL, &available_extensions.allocated, NULL); + + available_extensions = PTK_VEC_NEW(VkExtensionProperties, available_extensions.allocated); + vkEnumerateDeviceExtensionProperties(dev, NULL, &available_extensions.allocated, available_extensions.data); + PTK_VEC_FILLED(available_extensions); + + size_t supported_extensions = 0; + + for (size_t i = 0; i < m_device_extension_count; ++i) { + for (size_t j = 0; j < available_extensions.size; ++j) { + if (strcmp(m_device_extensions[i], available_extensions.data[j].extensionName) == 0) { + supported_extensions += 1; + break; + } + } + } + + return supported_extensions == m_device_extension_count; +} + +bool is_device_suitable(VkPhysicalDevice dev) { + PTK_VEC(VkQueueFamilyProperties) queue_families; + vkGetPhysicalDeviceQueueFamilyProperties(dev, &queue_families.allocated, NULL); + + queue_families = PTK_VEC_NEW(VkQueueFamilyProperties, queue_families.allocated); + vkGetPhysicalDeviceQueueFamilyProperties(dev, &queue_families.allocated, queue_families.data); + PTK_VEC_FILLED(queue_families); + + m_queue_family_indices.graphics = PTK_OPTION_NONE(uint32_t); + m_queue_family_indices.present = PTK_OPTION_NONE(uint32_t); + for (size_t i = 0; i < queue_families.size; ++i) { + if (queue_families.data[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) { + m_queue_family_indices.graphics = PTK_OPTION_SOME(uint32_t, i); + } + + VkBool32 present_support = VK_FALSE; + vkGetPhysicalDeviceSurfaceSupportKHR(dev, i, m_surface, &present_support); + if (present_support) { + m_queue_family_indices.present = PTK_OPTION_SOME(uint32_t, i); + } + } + + bool indices_found = m_queue_family_indices.graphics.exists + && m_queue_family_indices.present.exists; + bool extensions_supported = are_extensions_supported(dev); + bool swapchain_adequate = false; + + if (extensions_supported) { + SwapchainSupportInfo swapchain_support = query_swapchain_support(dev); + swapchain_adequate = swapchain_support.formats.size != 0 + && swapchain_support.present_modes.size != 0; + PTK_VEC_FREE(swapchain_support.formats); + PTK_VEC_FREE(swapchain_support.present_modes); + } + + PTK_VEC_FREE(queue_families); + + return indices_found && extensions_supported && swapchain_adequate; +} + +bool create_logical_dev(void) { + if (!is_device_suitable(m_physical_dev)) { + PTK_ERR("physical device isn't suitable"); + return false; + } + + VkDeviceQueueCreateInfo queue_create_infos[m_queue_family_count]; + + for (size_t i = 0; i < m_queue_family_count; ++i) { + PTK_OPTION(uint32_t) index = *(((PTK_OPTION(uint32_t) *)&m_queue_family_indices) + i); + + queue_create_infos[i] = (VkDeviceQueueCreateInfo){ + .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, + .pNext = NULL, + .flags = 0, + .queueFamilyIndex = index.value, + .queueCount = 1, + .pQueuePriorities = &(float){1.0f}, + }; + } + +#ifdef DEBUG + uint32_t enabledLayerCount = g_validation_layer_count; + const char * const *ppEnabledLayerNames = g_validation_layers; +#else + uint32_t enabledLayerCount = 0; + const char * const *ppEnabledLayerNames = NULL; +#endif + + VK_TRY(false, + vkCreateDevice( + m_physical_dev, + &(VkDeviceCreateInfo){ + .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, + .pNext = NULL, + .flags = 0, + .queueCreateInfoCount = m_queue_family_count, + .pQueueCreateInfos = queue_create_infos, + .enabledLayerCount = enabledLayerCount, + .ppEnabledLayerNames = ppEnabledLayerNames, + .enabledExtensionCount = m_device_extension_count, + .ppEnabledExtensionNames = m_device_extensions, + .pEnabledFeatures = &(VkPhysicalDeviceFeatures){0}, + }, + NULL, + &g_dev + ) + ); + + vkGetDeviceQueue(g_dev, m_queue_family_indices.graphics.value, 0, &g_graphics_queue); + vkGetDeviceQueue(g_dev, m_queue_family_indices.present.value, 0, &g_present_queue); + + return true; +} + +bool create_swapchain(void) { + SwapchainSupportInfo swapchain_support = query_swapchain_support(m_physical_dev); + + VkSurfaceFormatKHR surface_format = select_swap_surface_format(swapchain_support.formats); + VkPresentModeKHR present_mode = select_swap_present_mode(swapchain_support.present_modes); + VkExtent2D extent = select_swap_extent(swapchain_support.capabilities); + + uint32_t image_count = swapchain_support.capabilities.minImageCount + 1; + + if (swapchain_support.capabilities.maxImageCount > 0 && image_count > swapchain_support.capabilities.maxImageCount) { + image_count = swapchain_support.capabilities.maxImageCount; + } + + bool queue_families_differ = m_queue_family_indices.graphics.value != m_queue_family_indices.present.value; + + VK_TRY(false, + vkCreateSwapchainKHR( + g_dev, + &(VkSwapchainCreateInfoKHR){ + .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, + .pNext = NULL, + .flags = 0, + .surface = m_surface, + .minImageCount = image_count, + .imageFormat = surface_format.format, + .imageColorSpace = surface_format.colorSpace, + .imageExtent = extent, + .imageArrayLayers = 1, + .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + .imageSharingMode = + queue_families_differ + ? VK_SHARING_MODE_CONCURRENT + : VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = + queue_families_differ + ? 2 + : 0, + .pQueueFamilyIndices = + queue_families_differ + ? (const uint32_t []){ + m_queue_family_indices.graphics.value, + m_queue_family_indices.present.value + } + : NULL, + .preTransform = swapchain_support.capabilities.currentTransform, + .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, + .presentMode = present_mode, + .clipped = VK_TRUE, + .oldSwapchain = VK_NULL_HANDLE, + }, + NULL, + &g_swapchain + ) + ); + + vkGetSwapchainImagesKHR(g_dev, g_swapchain, &m_swapchain_images.allocated, NULL); + m_swapchain_images = PTK_VEC_NEW(VkImage, m_swapchain_images.allocated); + vkGetSwapchainImagesKHR(g_dev, g_swapchain, &m_swapchain_images.allocated, m_swapchain_images.data); + PTK_VEC_FILLED(m_swapchain_images); + + m_swapchain_image_format = surface_format.format; + m_swapchain_extent = extent; + + PTK_VEC_FREE(swapchain_support.formats); + PTK_VEC_FREE(swapchain_support.present_modes); + + return true; +} + +bool create_image_views(void) { + m_swapchain_image_views = PTK_VEC_NEW(VkImageView, m_swapchain_images.size); + + for (size_t i = 0; i < m_swapchain_images.size; ++i) { + VK_TRY(false, + vkCreateImageView( + g_dev, + &(VkImageViewCreateInfo){ + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .pNext = NULL, + .flags = 0, + .image = m_swapchain_images.data[i], + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = m_swapchain_image_format, + .components = (VkComponentMapping){ + .r = VK_COMPONENT_SWIZZLE_IDENTITY, + .g = VK_COMPONENT_SWIZZLE_IDENTITY, + .b = VK_COMPONENT_SWIZZLE_IDENTITY, + .a = VK_COMPONENT_SWIZZLE_IDENTITY, + }, + .subresourceRange = (VkImageSubresourceRange){ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }, + }, + NULL, + &m_swapchain_image_views.data[i] + ) + ); + m_swapchain_image_views.size += 1; + } + + return true; +} + +PTK_STRING read_spv(const char *filename) { + errno = 0; + FILE *file = fopen(filename, "r"); + + if (errno != 0) { + switch (errno) { + case EACCES: PTK_ERR("insufficient permissions to open %s", filename); break; + default: PTK_ERR("unknown error while reading %s", filename); break; + } + return PTK_VEC_NEW(char, 0); + } + + PTK_STRING buffer; + + fseek(file, 0L, SEEK_END); + buffer.allocated = ftell(file); + rewind(file); + + buffer = PTK_STRING_NEW(buffer.allocated); + + size_t items_read = fread(buffer.data, sizeof(char), buffer.allocated, file); + + if (items_read != buffer.allocated) { + if (feof(file)) { + PTK_ERR("unexpected end of file while reading %s", filename); + } else if (ferror(file)) { + PTK_ERR("error reading %s", filename); + } + return PTK_STRING_NEW(0); + } + + PTK_VEC_FILLED(buffer); + + fclose(file); + + return buffer; +} + +PTK_OPTION(VkShaderModule) create_shader_module(PTK_STRING code) { + VkShaderModule shader_module; + + VK_TRY(PTK_OPTION_NONE(VkShaderModule), + vkCreateShaderModule( + g_dev, + &(VkShaderModuleCreateInfo){ + .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, + .pNext = NULL, + .flags = 0, + .codeSize = code.size, + .pCode = (const uint32_t *)code.data, + }, + NULL, + &shader_module + ) + ); + + return PTK_OPTION_SOME(VkShaderModule, shader_module); +} + +bool create_render_pass(void) { + VK_TRY(false, + vkCreateRenderPass( + g_dev, + &(VkRenderPassCreateInfo){ + .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, + .pNext = NULL, + .flags = 0, + .attachmentCount = 1, + .pAttachments = &(VkAttachmentDescription){ + .flags = 0, + .format = m_swapchain_image_format, + .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){ + .flags = 0, + .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, + .inputAttachmentCount = 0, + .pInputAttachments = NULL, + .colorAttachmentCount = 1, + .pColorAttachments = &(VkAttachmentReference){ + .attachment = 0, + .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + }, + .pResolveAttachments = NULL, + .pDepthStencilAttachment = NULL, + .preserveAttachmentCount = 0, + .pPreserveAttachments = NULL, + }, + .dependencyCount = 1, + .pDependencies = &(VkSubpassDependency){ + .srcSubpass = VK_SUBPASS_EXTERNAL, + .dstSubpass = 0, + .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .srcAccessMask = 0, + .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .dependencyFlags = 0, + }, + }, + NULL, + &m_render_pass + ) + ); + + return true; +} + +bool create_descriptor_set_layout(void) { + VK_TRY(false, + vkCreateDescriptorSetLayout( + g_dev, + &(VkDescriptorSetLayoutCreateInfo){ + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, + .pNext = NULL, + .flags = 0, + .bindingCount = 1, + .pBindings = &(VkDescriptorSetLayoutBinding){ + .binding = 0, + .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + .descriptorCount = 1, + .stageFlags = VK_SHADER_STAGE_VERTEX_BIT, + .pImmutableSamplers = NULL, + }, + }, + NULL, + &m_descriptor_set_layout + ) + ); + + return true; +} + +bool create_graphics_pipeline(void) { + PTK_STRING vert_shader_code = read_spv("target/shaders/shader.vert.spv"); + PTK_STRING frag_shader_code = read_spv("target/shaders/shader.frag.spv"); + + PTK_OPTION(VkShaderModule) vert_shader_module = create_shader_module(vert_shader_code); + + if (!vert_shader_module.exists) { + PTK_ERR("failed creating vert shader module"); + return false; + } + + PTK_OPTION(VkShaderModule) frag_shader_module = create_shader_module(frag_shader_code); + + if (!frag_shader_module.exists) { + PTK_ERR("failed creating frag shader module"); + return false; + } + + VkPipelineShaderStageCreateInfo shader_stages[] = { + (VkPipelineShaderStageCreateInfo){ + .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + .pNext = NULL, + .flags = 0, + .stage = VK_SHADER_STAGE_VERTEX_BIT, + .module = vert_shader_module.value, + .pName = "main", + .pSpecializationInfo = NULL, + }, + (VkPipelineShaderStageCreateInfo){ + .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + .pNext = NULL, + .flags = 0, + .stage = VK_SHADER_STAGE_FRAGMENT_BIT, + .module = frag_shader_module.value, + .pName = "main", + .pSpecializationInfo = NULL, + }, + }; + + VkPipelineVertexInputStateCreateInfo vertex_input_info = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, + .pNext = NULL, + .flags = 0, + .vertexBindingDescriptionCount = 1, + .pVertexBindingDescriptions = &m_vertex_binding_description, + .vertexAttributeDescriptionCount = m_vertex_attribute_descriptions.size, + .pVertexAttributeDescriptions = m_vertex_attribute_descriptions.data, + }; + + VkPipelineDynamicStateCreateInfo dynamic_state = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, + .pNext = NULL, + .flags = 0, + .dynamicStateCount = 0, + .pDynamicStates = NULL, + }; + + VkPipelineInputAssemblyStateCreateInfo input_assembly = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, + .pNext = NULL, + .flags = 0, + .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, + .primitiveRestartEnable = VK_FALSE, + }; + + VkPipelineViewportStateCreateInfo viewport_state = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, + .pNext = NULL, + .flags = 0, + .viewportCount = 1, + .pViewports = &(VkViewport){ + .x = 0.0f, + .y = 0.0f, + .width = (float) m_swapchain_extent.width, + .height = (float) m_swapchain_extent.height, + .minDepth = 0.0f, + .maxDepth = 1.0f, + }, + .scissorCount = 1, + .pScissors = &(VkRect2D){ + .offset = (VkOffset2D){ + .x = 0, + .y = 0, + }, + .extent = m_swapchain_extent, + }, + }; + + VkPipelineRasterizationStateCreateInfo rasterizer = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, + .pNext = NULL, + .flags = 0, + .depthClampEnable = VK_FALSE, + .rasterizerDiscardEnable = VK_FALSE, + .polygonMode = VK_POLYGON_MODE_FILL, + .cullMode = VK_CULL_MODE_NONE, + .frontFace = VK_FRONT_FACE_CLOCKWISE, + .depthBiasEnable = VK_FALSE, + .depthBiasConstantFactor = 0.0f, + .depthBiasClamp = 0.0f, + .depthBiasSlopeFactor = 0.0f, + .lineWidth = 1.0f, + }; + + VkPipelineMultisampleStateCreateInfo multisampling = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, + .pNext = NULL, + .flags = 0, + .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT, + .sampleShadingEnable = VK_FALSE, + .minSampleShading = 1.0f, + .pSampleMask = NULL, + .alphaToCoverageEnable = VK_FALSE, + .alphaToOneEnable = VK_FALSE, + }; + + VkPipelineColorBlendAttachmentState color_blend_attachment = { + .blendEnable = VK_TRUE, + .srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA, + .dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, + .colorBlendOp = VK_BLEND_OP_ADD, + .srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE, + .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO, + .alphaBlendOp = VK_BLEND_OP_ADD, + .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT, + }; + + VkPipelineColorBlendStateCreateInfo color_blending = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, + .pNext = NULL, + .flags = 0, + .logicOpEnable = VK_FALSE, + .logicOp = VK_LOGIC_OP_COPY, + .attachmentCount = 1, + .pAttachments = &color_blend_attachment, + .blendConstants[0] = 0.0f, + .blendConstants[1] = 0.0f, + .blendConstants[2] = 0.0f, + .blendConstants[3] = 0.0f, + }; + + VK_TRY(false, + vkCreatePipelineLayout( + g_dev, + &(VkPipelineLayoutCreateInfo){ + .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, + .pNext = NULL, + .flags = 0, + .setLayoutCount = 1, + .pSetLayouts = &m_descriptor_set_layout, + .pushConstantRangeCount = 0, + .pPushConstantRanges = NULL, + }, + NULL, + &m_pipeline_layout + ) + ); + + VK_TRY(false, + vkCreateGraphicsPipelines( + g_dev, + VK_NULL_HANDLE, + 1, + &(VkGraphicsPipelineCreateInfo){ + .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, + .pNext = NULL, + .flags = 0, + .stageCount = 2, + .pStages = shader_stages, + .pVertexInputState = &vertex_input_info, + .pInputAssemblyState = &input_assembly, + .pTessellationState = NULL, + .pViewportState = &viewport_state, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pDepthStencilState = NULL, + .pColorBlendState = &color_blending, + .pDynamicState = &dynamic_state, + .layout = m_pipeline_layout, + .renderPass = m_render_pass, + .subpass = 0, + .basePipelineHandle = VK_NULL_HANDLE, + .basePipelineIndex = -1, + }, + NULL, + &m_pipeline + ) + ); + + vkDestroyShaderModule(g_dev, vert_shader_module.value, NULL); + vkDestroyShaderModule(g_dev, frag_shader_module.value, NULL); + + return true; +} + +bool create_framebuffers(void) { + m_swapchain_framebuffers = PTK_VEC_NEW(VkFramebuffer, m_swapchain_image_views.size); + + for (size_t i = 0; i < m_swapchain_image_views.size; ++i) { + VK_TRY(false, + vkCreateFramebuffer( + g_dev, + &(VkFramebufferCreateInfo){ + .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, + .pNext = NULL, + .flags = 0, + .renderPass = m_render_pass, + .attachmentCount = 1, + .pAttachments = &(VkImageView){m_swapchain_image_views.data[i]}, + .width = m_swapchain_extent.width, + .height = m_swapchain_extent.height, + .layers = 1, + }, + NULL, + &m_swapchain_framebuffers.data[i] + ) + ); + m_swapchain_framebuffers.size += 1; + } + + return true; +} + +bool create_command_pool(void) { + VK_TRY(false, + vkCreateCommandPool( + g_dev, + &(VkCommandPoolCreateInfo){ + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .pNext = NULL, + .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, + .queueFamilyIndex = m_queue_family_indices.graphics.value, + }, + NULL, + &m_command_pool + ) + ); + + return true; +} + +PTK_OPTION(uint32_t) find_memory_type(uint32_t type_filter, VkMemoryPropertyFlags props) { + VkPhysicalDeviceMemoryProperties mem_props; + vkGetPhysicalDeviceMemoryProperties(m_physical_dev, &mem_props); + + for (uint32_t i = 0; i < mem_props.memoryTypeCount; ++i) { + if (type_filter & (1 << i) && (mem_props.memoryTypes[i].propertyFlags & props) == props) { + return PTK_OPTION_SOME(uint32_t, i); + } + } + + return PTK_OPTION_NONE(uint32_t); +} + +bool create_buffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags props, VkBuffer *buffer, VkDeviceMemory *buffer_memory) { + VK_TRY(false, + vkCreateBuffer( + g_dev, + &(VkBufferCreateInfo){ + .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + .pNext = NULL, + .flags = 0, + .size = size, + .usage = usage, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = NULL, + }, + NULL, + buffer + ) + ); + + VkMemoryRequirements mem_reqs; + vkGetBufferMemoryRequirements(g_dev, *buffer, &mem_reqs); + + PTK_OPTION(uint32_t) memory_type = find_memory_type(mem_reqs.memoryTypeBits, props); + + if (!memory_type.exists) { + PTK_ERR("failed to find suitable memory type"); + return false; + } + + VK_TRY(false, + vkAllocateMemory( + g_dev, + &(VkMemoryAllocateInfo){ + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .pNext = NULL, + .allocationSize = mem_reqs.size, + .memoryTypeIndex = memory_type.value, + }, + NULL, + buffer_memory + ) + ); + + vkBindBufferMemory(g_dev, *buffer, *buffer_memory, 0); + + return true; +} + +bool copy_buffer(VkBuffer src, VkBuffer dst, VkDeviceSize size) { + VkCommandBuffer command_buffer; + + VK_TRY(false, + vkAllocateCommandBuffers( + g_dev, + &(VkCommandBufferAllocateInfo){ + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .pNext = NULL, + .commandPool = m_command_pool, + .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, + .commandBufferCount = 1, + }, + &command_buffer + ) + ); + + VK_TRY(false, + vkBeginCommandBuffer(command_buffer, &(VkCommandBufferBeginInfo){ + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .pNext = NULL, + .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, + .pInheritanceInfo = NULL, + }) + ); + + vkCmdCopyBuffer(command_buffer, src, dst, 1, &(VkBufferCopy){ + .srcOffset = 0, + .dstOffset = 0, + .size = size, + }); + + VK_TRY(false, + vkEndCommandBuffer(command_buffer) + ); + + vkQueueSubmit( + g_graphics_queue, + 1, + &(VkSubmitInfo){ + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .pNext = NULL, + .waitSemaphoreCount = 0, + .pWaitSemaphores = NULL, + .pWaitDstStageMask = NULL, + .commandBufferCount = 1, + .pCommandBuffers = &command_buffer, + .signalSemaphoreCount = 0, + .pSignalSemaphores = NULL, + }, + VK_NULL_HANDLE + ); + + vkQueueWaitIdle(g_graphics_queue); + + vkFreeCommandBuffers(g_dev, m_command_pool, 1, &command_buffer); + + return true; +} + +bool vk_transfer_vertex_data(void) { + PTK_DEBUG("vertices updated!"); + for (size_t i = 0; i < g_vertices.size; ++i) { + PTK_DEBUG("v[%d]:", i); + Vertex current = g_vertices.data[i]; + PTK_DEBUG(" pos: (%f, %f)", current.pos[0], current.pos[1]); + PTK_DEBUG(" color: (%f, %f, %f)", current.color[0], current.color[1], current.color[2]); + } + PTK_DEBUG("transferring vertices to gpu…"); + VkDeviceSize buffer_size = sizeof(g_vertices.data[0]) * g_vertices.size; + + VkBuffer staging_buffer; + VkDeviceMemory staging_buffer_memory; + if ( + !create_buffer( + buffer_size, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + &staging_buffer, + &staging_buffer_memory + ) + ) { + PTK_ERR("failed creating staging buffer"); + return false; + } + + void *data; + vkMapMemory(g_dev, staging_buffer_memory, 0, buffer_size, 0, &data); + memcpy(data, g_vertices.data, (size_t)buffer_size); + vkUnmapMemory(g_dev, staging_buffer_memory); + + if (!copy_buffer(staging_buffer, m_vertex_buffer, buffer_size)) { + PTK_ERR("failed copying staging buffer to vertex buffer"); + return false; + } + + vkDestroyBuffer(g_dev, staging_buffer, NULL); + vkFreeMemory(g_dev, staging_buffer_memory, NULL); + + return true; +} + +bool create_vertex_buffer(void) { + // VkDeviceSize buffer_size = sizeof(g_vertices.data[0]) * g_vertices.size; + VkDeviceSize buffer_size = 65536; + if ( + !create_buffer( + buffer_size, + VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + &m_vertex_buffer, + &m_vertex_buffer_memory + ) + ) { + PTK_ERR("failed creating vertex buffer"); + return false; + } + + return true; +} + +bool create_uniform_buffers(void) { + VkDeviceSize buffer_size = sizeof(UniformBufferObject); + + m_uniform_buffers = PTK_VEC_NEW(VkBuffer, g_max_frames_in_flight); + m_uniform_buffers_memory = PTK_VEC_NEW(VkDeviceMemory, g_max_frames_in_flight); + m_uniform_buffers_mapped = PTK_VEC_NEW(voidptr, g_max_frames_in_flight); + + for (size_t i = 0; i < g_max_frames_in_flight; ++i) { + if ( + !create_buffer( + buffer_size, + VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + &m_uniform_buffers.data[i], + &m_uniform_buffers_memory.data[i] + ) + ) { + PTK_ERR("failed creating vertex buffer"); + return false; + } + + vkMapMemory(g_dev, m_uniform_buffers_memory.data[i], 0, buffer_size, 0, &m_uniform_buffers_mapped.data[i]); + } + + return true; +} + +bool create_descriptor_pool(void) { + VK_TRY(false, + vkCreateDescriptorPool( + g_dev, + &(VkDescriptorPoolCreateInfo){ + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, + .pNext = NULL, + .flags = 0, + .maxSets = g_max_frames_in_flight, + .poolSizeCount = 1, + .pPoolSizes = &(VkDescriptorPoolSize){ + .type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + .descriptorCount = g_max_frames_in_flight, + }, + }, + NULL, + &m_descriptor_pool + ) + ); + + return true; +} + +PTK_VEC_DEFINE(VkDescriptorSetLayout); + +bool create_descriptor_sets(void) { + PTK_VEC(VkDescriptorSetLayout) layouts = PTK_VEC_NEW(VkDescriptorSetLayout, g_max_frames_in_flight); + for (size_t i = 0; i < g_max_frames_in_flight; ++i) { + PTK_VEC_ADD(VkDescriptorSetLayout, layouts, m_descriptor_set_layout); + } + + m_descriptor_sets = PTK_VEC_NEW(VkDescriptorSet, g_max_frames_in_flight); + + VK_TRY(false, + vkAllocateDescriptorSets( + g_dev, + &(VkDescriptorSetAllocateInfo){ + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, + .pNext = NULL, + .descriptorPool = m_descriptor_pool, + .descriptorSetCount = layouts.size, + .pSetLayouts = layouts.data, + }, + m_descriptor_sets.data + ) + ); + + for (size_t i = 0; i < g_max_frames_in_flight; ++i) { + vkUpdateDescriptorSets( + g_dev, + 1, + &(VkWriteDescriptorSet){ + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .pNext = NULL, + .dstSet = m_descriptor_sets.data[i], + .dstBinding = 0, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + .pImageInfo = NULL, + .pBufferInfo = &(VkDescriptorBufferInfo){ + .buffer = m_uniform_buffers.data[i], + .offset = 0, + .range = sizeof(UniformBufferObject), + }, + .pTexelBufferView = NULL, + }, + 0, + NULL + ); + } + + return true; +} + +bool allocate_command_buffers(void) { + g_command_buffers = PTK_VEC_NEW(VkCommandBuffer, g_max_frames_in_flight); + + VK_TRY(false, + vkAllocateCommandBuffers( + g_dev, + &(VkCommandBufferAllocateInfo){ + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .pNext = NULL, + .commandPool = m_command_pool, + .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, + .commandBufferCount = g_command_buffers.allocated, + }, + g_command_buffers.data + ) + ); + PTK_VEC_FILLED(g_command_buffers); + + return true; +} + +bool vk_record_command_buffer(VkCommandBuffer command_buffer, uint32_t image_index) { + VK_TRY(false, + vkBeginCommandBuffer( + command_buffer, + &(VkCommandBufferBeginInfo){ + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .pNext = NULL, + .flags = 0, + .pInheritanceInfo = NULL, + } + ) + ); + + vkCmdBeginRenderPass( + command_buffer, + &(VkRenderPassBeginInfo){ + .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, + .pNext = NULL, + .renderPass = m_render_pass, + .framebuffer = m_swapchain_framebuffers.data[image_index], + .renderArea = (VkRect2D){ + .offset = (VkOffset2D){ + .x = 0, + .y = 0, + }, + .extent = m_swapchain_extent, + }, + .clearValueCount = 1, + .pClearValues = &(VkClearValue){ + .color = (VkClearColorValue){ + .float32[0] = 0.0f, + .float32[1] = 0.0f, + .float32[2] = 0.0f, + .float32[3] = 1.0f, + }, + }, + }, + VK_SUBPASS_CONTENTS_INLINE + ); + + vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipeline); + + vkCmdBindVertexBuffers(command_buffer, 0, 1, (VkBuffer []){m_vertex_buffer}, (VkDeviceSize []){0}); + + vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipeline_layout, 0, 1, &m_descriptor_sets.data[g_current_frame], 0, NULL); + + vkCmdDraw(command_buffer, g_vertices.size, 1, 0, 0); + + vkCmdEndRenderPass(command_buffer); + + VK_TRY(false, + vkEndCommandBuffer(command_buffer) + ); + + return true; +} + +bool create_sync_objects(void) { + g_image_available_semaphores = PTK_VEC_NEW(VkSemaphore, g_max_frames_in_flight); + g_render_finished_semaphores = PTK_VEC_NEW(VkSemaphore, g_max_frames_in_flight); + g_in_flight_fences = PTK_VEC_NEW(VkFence, g_max_frames_in_flight); + + VkSemaphoreCreateInfo semaphore_info = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, + .pNext = NULL, + .flags = 0, + }; + + for (size_t i = 0; i < g_max_frames_in_flight; ++i) { + VK_TRY(false, + vkCreateSemaphore(g_dev, &semaphore_info, NULL, &g_image_available_semaphores.data[i]) + ); + + VK_TRY(false, + vkCreateSemaphore(g_dev, &semaphore_info, NULL, &g_render_finished_semaphores.data[i]) + ); + + VK_TRY(false, + vkCreateFence( + g_dev, + &(VkFenceCreateInfo){ + .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, + .pNext = NULL, + .flags = VK_FENCE_CREATE_SIGNALED_BIT, + }, + NULL, + &g_in_flight_fences.data[i] + ) + ); + } + + return true; +} + +void cleanup_swapchain(void) { + for (size_t i = 0; i < m_swapchain_framebuffers.size; ++i) { + vkDestroyFramebuffer(g_dev, m_swapchain_framebuffers.data[i], NULL); + } + PTK_VEC_FREE(m_swapchain_framebuffers); + + PTK_VEC_FREE(m_swapchain_images); + for (size_t i = 0; i < m_swapchain_image_views.size; ++i) { + vkDestroyImageView(g_dev, m_swapchain_image_views.data[i], NULL); + } + PTK_VEC_FREE(m_swapchain_image_views); + + vkDestroySwapchainKHR(g_dev, g_swapchain, NULL); +} + +bool vk_recreate_swapchain(void) { + int width = 0, height = 0; + glfwGetFramebufferSize(m_window, &width, &height); + + while (width == 0 || height == 0) { + glfwGetFramebufferSize(m_window, &width, &height); + glfwWaitEvents(); + } + + vkDeviceWaitIdle(g_dev); + + cleanup_swapchain(); + + if (!create_swapchain()) { + PTK_ERR("failed creating new swapchain"); + return false; + } + + if (!create_image_views()) { + PTK_ERR("failed creating new image views"); + return false; + } + + if (!create_framebuffers()) { + PTK_ERR("failed creating new framebuffers"); + return false; + } + + return true; +} + +bool vk_update_uniform_buffer(size_t current_frame) { + m_uniform_buffer_object.window_size[0] = m_swapchain_extent.width; + m_uniform_buffer_object.window_size[1] = m_swapchain_extent.height; + PTK_DEBUG("m_swapchain_extent.width: %d", m_swapchain_extent.width); + PTK_DEBUG("m_swapchain_extent.height: %d", m_swapchain_extent.height); + + memcpy(m_uniform_buffers_mapped.data[current_frame], &m_uniform_buffer_object, sizeof(m_uniform_buffer_object)); + + return true; +} + +bool vk_init(GLFWwindow *window, const char *title, const PtkVersion version) { + m_window = window; + + if (!create_vk_instance(title, version)) { + PTK_ERR("failed creating VkInstance"); + return false; + } + + if (!select_physical_dev()) { + PTK_ERR("failed selecting physical device"); + return false; + } + + VK_TRY(false, glfwCreateWindowSurface(m_instance, m_window, NULL, &m_surface)); + + if (!create_logical_dev()) { + PTK_ERR("failed creating logical device"); + return false; + } + + if (!create_swapchain()) { + PTK_ERR("failed creating swapchain"); + return false; + } + + if (!create_image_views()) { + PTK_ERR("failed creating image views"); + return false; + } + + if (!create_render_pass()) { + PTK_ERR("failed creating render pass"); + return false; + } + + if (!create_descriptor_set_layout()) { + PTK_ERR("failed creating descriptor set layout"); + return false; + } + + if (!create_graphics_pipeline()) { + PTK_ERR("failed creating graphics pipeline"); + return false; + } + + if (!create_framebuffers()) { + PTK_ERR("failed creating framebuffers"); + return false; + } + + if (!create_command_pool()) { + PTK_ERR("failed creating command pool"); + return false; + } + + if (!create_vertex_buffer()) { + PTK_ERR("failed creating vertex buffer"); + return false; + } + + if (!create_uniform_buffers()) { + PTK_ERR("failed creating uniform buffers"); + return false; + } + + if (!create_descriptor_pool()) { + PTK_ERR("failed creating descriptor pool"); + return false; + } + + if (!create_descriptor_sets()) { + PTK_ERR("failed creating descriptor sets"); + return false; + } + + if (!allocate_command_buffers()) { + PTK_ERR("failed allocating command buffers"); + return false; + } + + if (!create_sync_objects()) { + PTK_ERR("failed creating sync objects"); + return false; + } + + return true; +} + +void vk_cleanup(void) { + cleanup_swapchain(); + + vkDestroyPipeline(g_dev, m_pipeline, NULL); + vkDestroyPipelineLayout(g_dev, m_pipeline_layout, NULL); + + vkDestroyRenderPass(g_dev, m_render_pass, NULL); + + for (size_t i = 0; i < g_max_frames_in_flight; ++i) { + vkDestroyBuffer(g_dev, m_uniform_buffers.data[i], NULL); + vkFreeMemory(g_dev, m_uniform_buffers_memory.data[i], NULL); + } + PTK_VEC_FREE(m_uniform_buffers); + PTK_VEC_FREE(m_uniform_buffers_memory); + PTK_VEC_FREE(m_uniform_buffers_mapped); + + vkDestroyDescriptorPool(g_dev, m_descriptor_pool, NULL); + + vkDestroyDescriptorSetLayout(g_dev, m_descriptor_set_layout, NULL); + + vkDestroyBuffer(g_dev, m_vertex_buffer, NULL); + vkFreeMemory(g_dev, m_vertex_buffer_memory, NULL); + + for (size_t i = 0; i < g_max_frames_in_flight; ++i) { + vkDestroySemaphore(g_dev, g_image_available_semaphores.data[i], NULL); + vkDestroySemaphore(g_dev, g_render_finished_semaphores.data[i], NULL); + vkDestroyFence(g_dev, g_in_flight_fences.data[i], NULL); + } + + vkDestroyCommandPool(g_dev, m_command_pool, NULL); + + vkDestroyDevice(g_dev, NULL); + + vkDestroySurfaceKHR(m_instance, m_surface, NULL); + vkDestroyInstance(m_instance, NULL); + + glfwDestroyWindow(m_window); + + glfwTerminate(); +} diff --git a/src/ptk_vk/init.h b/src/ptk_vk/init.h new file mode 100644 index 0000000..b741c35 --- /dev/null +++ b/src/ptk_vk/init.h @@ -0,0 +1,41 @@ +#ifndef _PTK_PTK_VK_INIT_H +#define _PTK_PTK_VK_INIT_H + +#ifndef GLFW_INCLUDE_VULKAN +#define GLFW_INCLUDE_VULKAN +#endif +#include +#include +#include + +PTK_VEC_DEFINE(VkCommandBuffer); +PTK_VEC_DEFINE(VkSemaphore); +PTK_VEC_DEFINE(VkFence); + +extern VkDevice g_dev; +extern VkSwapchainKHR g_swapchain; + +extern VkQueue g_graphics_queue; +extern VkQueue g_present_queue; + +extern const size_t g_max_frames_in_flight; + +extern PTK_VEC(VkCommandBuffer) g_command_buffers; + +extern PTK_VEC(VkSemaphore) g_image_available_semaphores; +extern PTK_VEC(VkSemaphore) g_render_finished_semaphores; +extern PTK_VEC(VkFence) g_in_flight_fences; + +bool vk_init(GLFWwindow *window, const char *title, const PtkVersion version); + +bool vk_transfer_vertex_data(void); + +bool vk_record_command_buffer(VkCommandBuffer command_buffer, uint32_t image_index); + +bool vk_recreate_swapchain(void); + +bool vk_update_uniform_buffer(size_t current_frame); + +void vk_cleanup(void); + +#endif // _PTK_PTK_VK_INIT_H diff --git a/src/ptk_vk/utils.c b/src/ptk_vk/utils.c new file mode 100644 index 0000000..1a2c442 --- /dev/null +++ b/src/ptk_vk/utils.c @@ -0,0 +1,58 @@ +#include + +const char *vk_result_string(VkResult res) { + const char *str; + + switch (res) { + case VK_SUCCESS: str = "VK_SUCCESS"; break; + case VK_NOT_READY: str = "VK_NOT_READY"; break; + case VK_TIMEOUT: str = "VK_TIMEOUT"; break; + case VK_EVENT_SET: str = "VK_EVENT_SET"; break; + case VK_EVENT_RESET: str = "VK_EVENT_RESET"; break; + case VK_INCOMPLETE: str = "VK_INCOMPLETE"; break; + case VK_ERROR_OUT_OF_HOST_MEMORY: str = "VK_ERROR_OUT_OF_HOST_MEMORY"; break; + case VK_ERROR_OUT_OF_DEVICE_MEMORY: str = "VK_ERROR_OUT_OF_DEVICE_MEMORY"; break; + case VK_ERROR_INITIALIZATION_FAILED: str = "VK_ERROR_INITIALIZATION_FAILED"; break; + case VK_ERROR_DEVICE_LOST: str = "VK_ERROR_DEVICE_LOST"; break; + case VK_ERROR_MEMORY_MAP_FAILED: str = "VK_ERROR_MEMORY_MAP_FAILED"; break; + case VK_ERROR_LAYER_NOT_PRESENT: str = "VK_ERROR_LAYER_NOT_PRESENT"; break; + case VK_ERROR_EXTENSION_NOT_PRESENT: str = "VK_ERROR_EXTENSION_NOT_PRESENT"; break; + case VK_ERROR_FEATURE_NOT_PRESENT: str = "VK_ERROR_FEATURE_NOT_PRESENT"; break; + case VK_ERROR_INCOMPATIBLE_DRIVER: str = "VK_ERROR_INCOMPATIBLE_DRIVER"; break; + case VK_ERROR_TOO_MANY_OBJECTS: str = "VK_ERROR_TOO_MANY_OBJECTS"; break; + case VK_ERROR_FORMAT_NOT_SUPPORTED: str = "VK_ERROR_FORMAT_NOT_SUPPORTED"; break; + case VK_ERROR_FRAGMENTED_POOL: str = "VK_ERROR_FRAGMENTED_POOL"; break; + case VK_ERROR_UNKNOWN: str = "VK_ERROR_UNKNOWN"; break; + case VK_ERROR_OUT_OF_POOL_MEMORY: str = "VK_ERROR_OUT_OF_POOL_MEMORY"; break; + case VK_ERROR_INVALID_EXTERNAL_HANDLE: str = "VK_ERROR_INVALID_EXTERNAL_HANDLE"; break; + case VK_ERROR_FRAGMENTATION: str = "VK_ERROR_FRAGMENTATION"; break; + case VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS: str = "VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS"; break; + case VK_PIPELINE_COMPILE_REQUIRED: str = "VK_PIPELINE_COMPILE_REQUIRED"; break; + case VK_ERROR_SURFACE_LOST_KHR: str = "VK_ERROR_SURFACE_LOST_KHR"; break; + case VK_ERROR_NATIVE_WINDOW_IN_USE_KHR: str = "VK_ERROR_NATIVE_WINDOW_IN_USE_KHR"; break; + case VK_SUBOPTIMAL_KHR: str = "VK_SUBOPTIMAL_KHR"; break; + case VK_ERROR_OUT_OF_DATE_KHR: str = "VK_ERROR_OUT_OF_DATE_KHR"; break; + case VK_ERROR_INCOMPATIBLE_DISPLAY_KHR: str = "VK_ERROR_INCOMPATIBLE_DISPLAY_KHR"; break; + case VK_ERROR_VALIDATION_FAILED_EXT: str = "VK_ERROR_VALIDATION_FAILED_EXT"; break; + case VK_ERROR_INVALID_SHADER_NV: str = "VK_ERROR_INVALID_SHADER_NV"; break; + case VK_ERROR_IMAGE_USAGE_NOT_SUPPORTED_KHR: str = "VK_ERROR_IMAGE_USAGE_NOT_SUPPORTED_KHR"; break; + case VK_ERROR_VIDEO_PICTURE_LAYOUT_NOT_SUPPORTED_KHR: str = "VK_ERROR_VIDEO_PICTURE_LAYOUT_NOT_SUPPORTED_KHR"; break; + case VK_ERROR_VIDEO_PROFILE_OPERATION_NOT_SUPPORTED_KHR: str = "VK_ERROR_VIDEO_PROFILE_OPERATION_NOT_SUPPORTED_KHR"; break; + case VK_ERROR_VIDEO_PROFILE_FORMAT_NOT_SUPPORTED_KHR: str = "VK_ERROR_VIDEO_PROFILE_FORMAT_NOT_SUPPORTED_KHR"; break; + case VK_ERROR_VIDEO_PROFILE_CODEC_NOT_SUPPORTED_KHR: str = "VK_ERROR_VIDEO_PROFILE_CODEC_NOT_SUPPORTED_KHR"; break; + case VK_ERROR_VIDEO_STD_VERSION_NOT_SUPPORTED_KHR: str = "VK_ERROR_VIDEO_STD_VERSION_NOT_SUPPORTED_KHR"; break; + case VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT: str = "VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT"; break; + case VK_ERROR_NOT_PERMITTED_KHR: str = "VK_ERROR_NOT_PERMITTED_KHR"; break; + case VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT: str = "VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT"; break; + case VK_THREAD_IDLE_KHR: str = "VK_THREAD_IDLE_KHR"; break; + case VK_THREAD_DONE_KHR: str = "VK_THREAD_DONE_KHR"; break; + case VK_OPERATION_DEFERRED_KHR: str = "VK_OPERATION_DEFERRED_KHR"; break; + case VK_OPERATION_NOT_DEFERRED_KHR: str = "VK_OPERATION_NOT_DEFERRED_KHR"; break; + case VK_ERROR_INVALID_VIDEO_STD_PARAMETERS_KHR: str = "VK_ERROR_INVALID_VIDEO_STD_PARAMETERS_KHR"; break; + case VK_ERROR_COMPRESSION_EXHAUSTED_EXT: str = "VK_ERROR_COMPRESSION_EXHAUSTED_EXT"; break; + case VK_INCOMPATIBLE_SHADER_BINARY_EXT: str = "VK_INCOMPATIBLE_SHADER_BINARY_EXT"; break; + case VK_RESULT_MAX_ENUM: str = "VK_RESULT_MAX_ENUM"; break; + } + + return str; +} diff --git a/src/ptk_vk/utils.h b/src/ptk_vk/utils.h new file mode 100644 index 0000000..baf79c0 --- /dev/null +++ b/src/ptk_vk/utils.h @@ -0,0 +1,17 @@ +#ifndef _PTK_PTK_VK_UTILS_H +#define _PTK_PTK_VK_UTILS_H + +#include +#include + +const char *vk_result_string(VkResult res); + +#define VK_TRY(return_value, ...) do {\ + VkResult res = __VA_ARGS__;\ + if (res != VK_SUCCESS) {\ + PTK_ERR("%s", vk_result_string(res));\ + return return_value;\ + }\ +} while (0) + +#endif // _PTK_PTK_VK_UTILS_H diff --git a/test/Makefile b/test/Makefile new file mode 100644 index 0000000..2d1b5fd --- /dev/null +++ b/test/Makefile @@ -0,0 +1,25 @@ +INCLUDE += test +CFLAGS += $(addprefix -I, $(INCLUDE)) +LDFLAGS = -L$(BIN) -lptk + +TEST = test + +TESTS = $(shell find $(TEST) -type f -name "*.c") + +OBJ = $(addprefix $(BIN)/, $(TESTS:.c=)) + +.PHONY: all + +all: dirs_test $(OBJ) + @echo + @echo "---------- STARTING TESTS ----------" + @echo + @$(foreach test,$(OBJ),./$(test);) + @echo "---------- FINISHED TESTS ----------" + @echo + +dirs_test: + mkdir -p $(BIN)/test + +$(BIN)/test/%: $(TEST)/%.c + $(CC) $< $(LDFLAGS) $(CFLAGS) -o $@ diff --git a/test/init.c b/test/init.c new file mode 100644 index 0000000..173b013 --- /dev/null +++ b/test/init.c @@ -0,0 +1,16 @@ +#include +#include + +#include + +TEST_START() + TEST("link to the library", { + bool init_result = ptk_init(800, 600, "test", (PtkVersion){ .major = 0, .minor = 1, .patch = 0 }); + TEST_ASSERT(init_result, "ptk_init() failed"); + int run_result = ptk_run(PTK_BOX( + ptk_triangle((vec2 []){{400.0f, 50.f}, {700.0f, 500.0f}, {100.0f, 500.0f}}, (vec3){0.0f, 1.0f, 0.0f}), + ptk_rect((vec2){200.0f, 200.0f}, (vec2){300.0f, 300.0f}, (vec3){1.0f, 0.0f, 0.0f}), + )); + TEST_ASSERT(run_result == 0, "ptk_run() failed"); + }); +TEST_FINISH() diff --git a/test/test.h b/test/test.h new file mode 100644 index 0000000..39465e6 --- /dev/null +++ b/test/test.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include +#include + +#define FG(r, g, b) "\033[38;2;" #r ";" #g ";" #b "m" +#define BG(r, g, b) "\033[48;2;" #r ";" #g ";" #b "m" +#define RESET "\033[0m" + +#define TEST_START() \ + int main(void) {\ + uint64_t tests_failed = 0;\ + uint64_t tests_total = 0;\ + printf("----- STARTING TEST FILE " __FILE__ " -----\n"); + +#define TEST_FINISH() \ + printf("----- FINISHED TEST FILE " __FILE__ " -----\n");\ + printf("PASSED TESTS: %s" FG(0, 0, 0) "%ld/%ld" RESET "\n",\ + (tests_failed != 0 ? BG(255, 0, 0) : BG(0, 255, 0)),\ + (tests_total - tests_failed), tests_total);\ + printf("\n");\ + return tests_failed;\ + } + +#define TEST_ASSERT(condition, message) \ + do {\ + asserts_total += 1;\ + if (!(condition)) {\ + asserts_failed += 1;\ + printf(" " BG(255, 0, 0) FG(0, 0, 0) #condition ": " message " (" __FILE__ ":%d)" RESET "\n", __LINE__);\ + }\ + } while (0) + +#define TEST(name, ...) \ + do {\ + uint64_t asserts_failed = 0;\ + uint64_t asserts_total = 0;\ + tests_total += 1;\ + printf(" RUNNING TEST `" name "`\n");\ + do {\ + __VA_ARGS__;\ + } while(0);\ + printf(" PASSED ASSERTS: %s" FG(0, 0, 0) "%ld/%ld" RESET "\n",\ + (asserts_failed != 0 ? BG(255, 0, 0) : BG(0, 255, 0)),\ + (asserts_total - asserts_failed), asserts_total);\ + if (asserts_failed > 0) {\ + tests_failed += 1;\ + }\ + } while (0) diff --git a/test/vec.c b/test/vec.c new file mode 100644 index 0000000..dc4795a --- /dev/null +++ b/test/vec.c @@ -0,0 +1,106 @@ +#include +#include + +PTK_VEC_DEFINE(uint32_t); + +TEST_START() + TEST("create vec", { + size_t size = 5; + PTK_VEC(uint32_t) vec = PTK_VEC_NEW(uint32_t, size); + + TEST_ASSERT(vec.allocated == size, "incorrect vec allocation"); + TEST_ASSERT(vec.size == 0, "vec isn't empty after allocation"); + }); + + TEST("add elements without growing", { + size_t size = 5; + PTK_VEC(uint32_t) vec = PTK_VEC_NEW(uint32_t, size); + + PTK_VEC_ADD(uint32_t, vec, (uint32_t){21}); + PTK_VEC_ADD(uint32_t, vec, (uint32_t){37}); + + TEST_ASSERT(vec.size == 2, "size doesn't match number of elements added"); + TEST_ASSERT(vec.allocated == size, "needlessly grew vec"); + + TEST_ASSERT(vec.data[0] == 21, "first element doesn't match"); + TEST_ASSERT(vec.data[1] == 37, "second element doesn't match"); + }); + + TEST("add elements and grow", { + size_t size = 1; + PTK_VEC(uint32_t) vec = PTK_VEC_NEW(uint32_t, size); + + PTK_VEC_ADD(uint32_t, vec, (uint32_t){21}); + PTK_VEC_ADD(uint32_t, vec, (uint32_t){37}); + + TEST_ASSERT(vec.allocated == size * 2, "(1st grow) didn't grow size by a factor of 2"); + TEST_ASSERT(vec.data[1] == 37, "(1st grow) element added in grown space doesn't match"); + + PTK_VEC_ADD(uint32_t, vec, (uint32_t){2137}); + + TEST_ASSERT(vec.allocated == size * 4, "(2nd grow) didn't grow size by a factor of 2"); + TEST_ASSERT(vec.data[2] == 2137, "(2nd grow) element added in grown space doesn't match"); + }); + + TEST("add multiple elements", { + size_t size = 1; + PTK_VEC(uint32_t) vec = PTK_VEC_NEW(uint32_t, size); + + PTK_VEC_ADD_ALL(uint32_t, vec, { 21, 37, 2137, 31, 27, 7312 }); + TEST_ASSERT(vec.size == 6, "size doesn't match number of elements added"); + + TEST_ASSERT(vec.data[0] == 21, "first element doesn't match"); + TEST_ASSERT(vec.data[5] == 7312, "last element doesn't match"); + }); + + TEST("remove elements", { + size_t size = 1; + PTK_VEC(uint32_t) vec = PTK_VEC_NEW(uint32_t, size); + + PTK_VEC_ADD(uint32_t, vec, (uint32_t){21}); + PTK_VEC_ADD(uint32_t, vec, (uint32_t){37}); + PTK_VEC_ADD(uint32_t, vec, (uint32_t){2137}); + PTK_VEC_ADD(uint32_t, vec, (uint32_t){31}); + PTK_VEC_ADD(uint32_t, vec, (uint32_t){27}); + PTK_VEC_ADD(uint32_t, vec, (uint32_t){7312}); + + PTK_VEC_REMOVE(uint32_t, vec, (uint32_t){2137}); + TEST_ASSERT(vec.size == 5, "size doesn't match after removing"); + TEST_ASSERT(vec.data[2] == 31, "remaining elements not moved to the left"); + TEST_ASSERT(vec.data[4] == 7312, "last element moved improperly (check for off-by-ones)"); + }); + + TEST("remove multiple elements", { + size_t size = 1; + PTK_VEC(uint32_t) vec = PTK_VEC_NEW(uint32_t, size); + + PTK_VEC_ADD(uint32_t, vec, (uint32_t){21}); + PTK_VEC_ADD(uint32_t, vec, (uint32_t){37}); + PTK_VEC_ADD(uint32_t, vec, (uint32_t){2137}); + PTK_VEC_ADD(uint32_t, vec, (uint32_t){31}); + PTK_VEC_ADD(uint32_t, vec, (uint32_t){27}); + PTK_VEC_ADD(uint32_t, vec, (uint32_t){7312}); + + PTK_VEC_REMOVE_ALL(uint32_t, vec, { 2137, 37, 27 }); + TEST_ASSERT(vec.size == 3, "size doesn't match after removing"); + TEST_ASSERT(vec.data[1] == 31, "remaining elements not moved to the left"); + TEST_ASSERT(vec.data[2] == 7312, "last element moved improperly (check for off-by-ones)"); + }); + + TEST("remove elements at index", { + size_t size = 1; + PTK_VEC(uint32_t) vec = PTK_VEC_NEW(uint32_t, size); + + PTK_VEC_ADD(uint32_t, vec, (uint32_t){21}); + PTK_VEC_ADD(uint32_t, vec, (uint32_t){37}); + PTK_VEC_ADD(uint32_t, vec, (uint32_t){2137}); + PTK_VEC_ADD(uint32_t, vec, (uint32_t){31}); + PTK_VEC_ADD(uint32_t, vec, (uint32_t){27}); + PTK_VEC_ADD(uint32_t, vec, (uint32_t){7312}); + + PTK_VEC_REMOVE_AT(uint32_t, vec, 2); + TEST_ASSERT(vec.size == 5, "size doesn't match after removing"); + TEST_ASSERT(vec.data[2] == 31, "remaining elements not moved to the left"); + TEST_ASSERT(vec.data[4] == 7312, "last element moved improperly (check for off-by-ones)"); + }); +TEST_FINISH()