This commit is contained in:
jacekpoz 2024-08-08 14:38:43 +02:00
commit f2d87fdd00
Signed by: poz
SSH key fingerprint: SHA256:JyLeVWE4bF3tDnFeUpUaJsPsNlJyBldDGV/dIKSLyN8
30 changed files with 2830 additions and 0 deletions

2
.clangd Normal file
View file

@ -0,0 +1,2 @@
CompileFlags:
Add: [-xc, -DGLFW_INCLUDE_VULKAN]

1
.envrc Normal file
View file

@ -0,0 +1 @@
use flake

5
.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
.direnv
result
target
compile_commands.json
.cache

73
Makefile Normal file
View file

@ -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

3
doc/resources/c.md Normal file
View file

@ -0,0 +1,3 @@
# Vulkan related resources used in writing this project
<https://stackoverflow.com/questions/2565039/how-are-multi-dimensional-arrays-formatted-in-memory>

6
doc/resources/vulkan.md Normal file
View file

@ -0,0 +1,6 @@
# Vulkan related resources used in writing this project
<https://docs.vulkan.org/guide/latest/index.html>
<https://vulkan-tutorial.com/Vertex_buffers/Vertex_buffer_creation>
<https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html>
<https://www.youtube.com/watch?v=rXSdDE7NWmA>

26
flake.lock Normal file
View file

@ -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
}

52
flake.nix Normal file
View file

@ -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;
}
);
};
}

69
include/ptk.h Normal file
View file

@ -0,0 +1,69 @@
#ifndef _PTK_PTK_H
#define _PTK_PTK_H
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
#include <cglm/types.h>
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

35
include/ptk_log.h Normal file
View file

@ -0,0 +1,35 @@
#ifndef _PTK_PTK_LOG_H
#define _PTK_PTK_LOG_H
#include <limits.h>
#include <stdarg.h>
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

46
nix/default.nix Normal file
View file

@ -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;
};
}

12
shaders/shader.frag.glsl Normal file
View file

@ -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);
}

17
shaders/shader.vert.glsl Normal file
View file

@ -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;
}

199
src/ptk.c Normal file
View file

@ -0,0 +1,199 @@
#include <ptk.h>
#include <ptk_log.h>
#include <ptk_vec.h>
#include <ptk_option.h>
#include <ptk_vk/components.h>
#include <ptk_vk/draw.h>
#include <ptk_vk/init.h>
#include <ptk_vk/utils.h>
#include <vulkan/vulkan_core.h>
#ifndef GLFW_INCLUDE_VULKAN
#define GLFW_INCLUDE_VULKAN
#endif
#include <GLFW/glfw3.h>
#include <cglm/cglm.h>
#include <ctype.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
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;
}

53
src/ptk_log.c Normal file
View file

@ -0,0 +1,53 @@
#include <ptk_log.h>
#include <stdio.h>
#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); }

26
src/ptk_option.h Normal file
View file

@ -0,0 +1,26 @@
#ifndef _PTK_PTK_OPTION_H
#define _PTK_PTK_OPTION_H
#include <stdbool.h>
#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

77
src/ptk_vec.c Normal file
View file

@ -0,0 +1,77 @@
#include <ptk_vec.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
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;
}

61
src/ptk_vec.h Normal file
View file

@ -0,0 +1,61 @@
#ifndef _PTK_PTK_VEC_H
#define _PTK_PTK_VEC_H
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdint.h>
#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

73
src/ptk_vk/components.c Normal file
View file

@ -0,0 +1,73 @@
#include <ptk.h>
#include <ptk_vk/components.h>
#include <ptk_vk/init.h>
#include <string.h>
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);
}

24
src/ptk_vk/components.h Normal file
View file

@ -0,0 +1,24 @@
#ifndef _PTK_PTK_VK_COMPONENTS_H
#define _PTK_PTK_VK_COMPONENTS_H
#include <ptk.h>
#include <ptk_vec.h>
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

92
src/ptk_vk/draw.c Normal file
View file

@ -0,0 +1,92 @@
#include <ptk_vk/draw.h>
#include <ptk_vk/init.h>
#include <ptk_vk/utils.h>
#include <ptk_log.h>
#include <stdint.h>
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;
}

12
src/ptk_vk/draw.h Normal file
View file

@ -0,0 +1,12 @@
#ifndef _PTK_PTK_VK_DRAW_H
#define _PTK_PTK_VK_DRAW_H
#include <stdbool.h>
#include <stdint.h>
extern bool g_framebuffer_resized;
extern uint32_t g_current_frame;
bool vk_draw_frame(void);
#endif // _PTK_PTK_VK_DRAW_H

1553
src/ptk_vk/init.c Normal file

File diff suppressed because it is too large Load diff

41
src/ptk_vk/init.h Normal file
View file

@ -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 <GLFW/glfw3.h>
#include <ptk.h>
#include <ptk_vec.h>
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

58
src/ptk_vk/utils.c Normal file
View file

@ -0,0 +1,58 @@
#include <ptk_vk/utils.h>
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;
}

17
src/ptk_vk/utils.h Normal file
View file

@ -0,0 +1,17 @@
#ifndef _PTK_PTK_VK_UTILS_H
#define _PTK_PTK_VK_UTILS_H
#include <vulkan/vulkan_core.h>
#include <ptk_log.h>
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

25
test/Makefile Normal file
View file

@ -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 $@

16
test/init.c Normal file
View file

@ -0,0 +1,16 @@
#include <test.h>
#include <ptk.h>
#include <stdbool.h>
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()

50
test/test.h Normal file
View file

@ -0,0 +1,50 @@
#pragma once
#include <stdbool.h>
#include <stdio.h>
#include <stdint.h>
#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)

106
test/vec.c Normal file
View file

@ -0,0 +1,106 @@
#include <test.h>
#include <ptk_vec.h>
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()