aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorladevieq <ladevie.quentin@gmail.com>2022-10-27 23:22:55 +0200
committerladevieq <ladevie.quentin@gmail.com>2022-10-27 23:22:55 +0200
commitb47e177898657e435cb5bc913a4a783fddbe1f3d (patch)
treedf0a042b5ed9e98d7653462466dd3c6147127bc3
parentcee4f9c528a91691d94c52ec1849f3ad1245b081 (diff)
Api prototype
-rw-r--r--.clang-format9
-rw-r--r--CMakeLists.txt2
-rw-r--r--api-proto.txt264
-rw-r--r--api-test/CMakeLists.txt15
-rw-r--r--api-test/main.cpp77
-rw-r--r--api-test/vk-command-buffer.cpp25
-rw-r--r--api-test/vk-device.cpp519
-rw-r--r--include/api-test/buffer.hpp25
-rw-r--r--include/api-test/freelist.hpp32
-rw-r--r--include/api-test/handle.hpp8
-rw-r--r--include/api-test/resource-manager.hpp22
-rw-r--r--include/api-test/texture.hpp29
-rw-r--r--include/api-test/vk-command-buffer.hpp14
-rw-r--r--include/api-test/vk-device.hpp128
-rw-r--r--include/resource-manager.hpp6
-rw-r--r--include/window.hpp2
-rw-r--r--src/resource-manager.cpp2
-rw-r--r--src/window.cpp9
18 files changed, 1182 insertions, 6 deletions
diff --git a/.clang-format b/.clang-format
index b5daee6..ffb3010 100644
--- a/.clang-format
+++ b/.clang-format
@@ -1,3 +1,12 @@
+AccessModifierOffset: 0
+AlignConsecutiveAssignments:
+ Enabled: true
+ AcrossEmptyLines: true
+ AlignCompound: true
+AlignConsecutiveDeclarations:
+ Enabled: true
+ AcrossEmptyLines: true
+AlignOperands: Align
AlignEscapedNewlines: Right
AlignTrailingComments: true
ColumnLimit: 0
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6d62555..429fe21 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -13,7 +13,6 @@ project(path-tracer VERSION 1.0 LANGUAGES CXX)
find_package(imgui CONFIG REQUIRED)
find_package(nlohmann_json CONFIG REQUIRED)
find_package(Vulkan REQUIRED)
-find_package(Threads REQUIRED)
if(UNIX AND NOT APPLE)
find_package(X11 REQUIRED)
@@ -33,4 +32,5 @@ endif()
# endif()
add_subdirectory(src)
+add_subdirectory(api-test)
add_subdirectory(shaders)
diff --git a/api-proto.txt b/api-proto.txt
new file mode 100644
index 0000000..751f390
--- /dev/null
+++ b/api-proto.txt
@@ -0,0 +1,264 @@
+enum CMD_BUF_TYPE {
+ GRAPHICS,
+ COPY,
+ ASYNC_COMPUTE,
+}
+
+class command_buffer {
+ void start();
+ void stop();
+
+ VkCommandBuffer handle;
+ queue owner;
+}
+
+class graphics_command_buffer : command_buffer {
+ void image_barrier(handle tex, State new_state) {
+ texture = rm::get_texture(tex);
+
+ cmd.barrier(texture.img.device_image, texture.current_state, new_state);
+
+ texture.current_state = new_state;
+ }
+}
+
+class device_swapchain {
+ VkSurfaceKHR surface;
+ VkFormat surface_format;
+ VkExtent extent;
+ VkSwapchain swapchain;
+}
+
+class device_buffer {
+ VkBuffer handle;
+ VmaAllocation allocation;
+ VkDeviceAddr gpu_address;
+}
+
+class device_image {
+ VkImage handle;
+ VmaAllocation allocation;
+ array<uint, max_usage> bindless_id; // TODO: Handle this in a higher level ?
+
+ VkImageView whole_image_view;
+ vector<VkImageView> mips_view;
+
+ const uint invalid_id = -1;
+}
+
+class device_command_buffer {
+ CMD_BUF_TYPE buf_type = CMD_BUF_TYPE::MAX;
+ VkCommandBuffer handle;
+}
+
+class global_descriptor_set {
+ VkDescritptorSetLayout layout;
+ VkDescriptorSet set;
+};
+
+class device {
+ device_image create_image(TextureDesc desc) {
+ img {};
+ img.handle = vmaCreateImage(&img.alloc);
+ }
+
+ device_buffer create_buffer(BufferDesc desc);
+
+ device_command_buffer allocate_command_buffer(uint32_t count);
+
+ submit(span<device_command_buffer> cmd_buffers, semaphore)
+
+ wait(fence);
+ wait_idle();
+
+ private:
+
+ create_surface();
+ create_swapchain();
+
+ device_swapchain sc;
+
+ array<device_image> sc_images;
+ array<queue, CMD_BUF_TYPE::MAX> queues;
+
+ global_descriptor_set global_set;
+ // bindless
+ // set 0 global, textures, samplers
+ // set 1 materials buffer addr, camera, etc
+ // set 2 per mesh vertex attributes buffers addr
+ // set 2 per instance (transforms, etc)
+ pipeline_layout compute; // set 0 & 1
+ pipeline_layout graphics; // set 0, 1, 2
+}
+
+class renderer {
+ renderer() {
+ device_images = device::get_swapchain_images();
+
+ for (device_images) {
+ sc_images.push(ressourcemanager::create_texture(desc, images[i]))
+ }
+ }
+
+ void start_frame() {
+ vframe_index++;
+ device::wait(finish);
+
+ img_index = device::get_image(sc)
+ cur_image = images[img_index];
+ }
+ void finish_frame() {
+ device::submit(to_submit, wait_submit);
+ device::present(wait_submit, finish);
+ }
+
+ command_buffer start_record() {
+ auto buf = frames[current].command_buffers.pop();
+ buf.start();
+ device::submit(span(command_buffers[vframe_index][0...last_cmd_buf_index[vframe_index]]), wait_submit);
+ device::present();
+ }
+
+ void stop_record(command_buffer buf) {
+ buf.stop();
+ auto buf = frames[current][buf_type][++last_cmd_buf_index[vframe_index]];
+ }
+
+ private:
+
+ vector<handle> sc_images;
+
+ // Virtual frames stuff
+ array<semaphore, vframes_count> wait_image;
+ array<semaphore, vframes_count> wait_present;
+ array<fence, vframes_count> finish;
+ array<fence, vframes_count> command_pool;
+ // One per queue type
+ array<array<command_buffer, vframe_max_buf>, vframes_count> command_buffers;
+
+ array<uint, vframes_count> last_cmd_buf_index = { 0 };
+
+ uint vframe_index;
+ constexpr uint vframe_max_buf = 10;
+ constexpr uint vframes_count = 3;
+}
+
+class ressourcemanager {
+ handle create_texture(TextureDesc desc) {
+ new_image = device::create_image(desc);
+
+ return textures.push({
+ .desc = desc,
+ .img = new_image,
+ })
+ }
+
+ handle create_texture(TextureDesc desc, device_image img) {
+ return textures.push({
+ .desc = desc,
+ .img = img,
+ })
+ }
+
+ handle create_pipeline(PipelineDesc desc)
+
+ const Texture& get_texture(handle texture_handle);
+
+private:
+ FreeList textures;
+}
+
+class PipelineDesc {
+ // TODO: Where to store views ?
+ span<Texture> color_textures;
+ Texture depth_texture;
+}
+
+class TextureDesc {
+ Vector size;
+ UsageFlag usages;
+ Format format;
+}
+
+class BufferDesc {
+ uint size;
+ UsageFlag usages;
+}
+
+class Texture {
+ const TextureDesc desc;
+ const device_image img;
+ Layout current_layout = initial_layout;
+}
+
+class Buffer {
+ const BufferDesc desc;
+ const device_buffer buffer;
+}
+
+class mesh {
+ array<attribute_buffer> attributes;
+ buffer index;
+}
+
+class mesh_instance {
+ handle mesh;
+ mat4 transform;
+}
+
+class renderpass { // shadow example
+ renderpass() {
+ shadow_tex = rm::create_texture({
+ .size = { 1024, 1024 },
+ .usages = DEPTH_STENCIL_ATTACHMENT,
+ .format = VK_FORMAT_D24,
+ });
+
+ pso = rm::create_pipeline({
+ .color = null,
+ .depth = scene_depth, // contains format
+ })
+ }
+
+ render() {
+ handle meshes[] = scene.get_meshes(FILTER_NONE);
+ buf = renderer::start_record();
+ buf.start_renderpass({
+ .color_attachments
+ .depth_attachment = shadow_tex,
+ });
+
+ buf.bind_pipeline(pso);
+
+ foreach(mesh : meshes) {
+ buf.draw_index(mesh);
+ }
+
+ buf.stop_renderpass();
+ }
+
+ std::vector<Texture> color_attachments;
+
+ shadow_tex;
+ pipeline pso;
+}
+
+renderer::add_render_pass()
+
+TODOS :
+1. implement :
+ * image loading
+ * resizing
+ * mesh drawing
+
+IDEAS :
+1. Manage device resources with handles instead of having immutable ones in high level resources
+ PROS:
+ * Allows to manage (swap, create, delete, invalide) device resources easily
+ CONS:
+ * Can loose track of a resource easily
+
+RESOURCES :
+* SEED Halcyon presentation https://www.ea.com/seed/news/syysgraph-2018-modern-graphics-abstractions-real-time-ray-tracing
+* Cryengine master class https://www.youtube.com/watch?v=34S3onEr3r8
+* Nicebyte tweet api https://twitter.com/nice_byte/status/1579187394880090112
diff --git a/api-test/CMakeLists.txt b/api-test/CMakeLists.txt
new file mode 100644
index 0000000..3b39ea3
--- /dev/null
+++ b/api-test/CMakeLists.txt
@@ -0,0 +1,15 @@
+add_executable(
+ api-test
+ main.cpp
+ vk-device.cpp
+ ${CMAKE_SOURCE_DIR}/src/window.cpp
+ ${CMAKE_SOURCE_DIR}/src/utils.cpp
+ ${CMAKE_SOURCE_DIR}/src/vulkan-loader.cpp
+)
+
+target_compile_definitions(api-test PUBLIC WINDOWS VK_NO_PROTOTYPES VK_USE_PLATFORM_WIN32_KHR)
+
+target_include_directories(api-test SYSTEM PUBLIC ${Vulkan_INCLUDE_DIRS})
+target_include_directories(api-test SYSTEM PUBLIC ${CMAKE_SOURCE_DIR}/thirdparty)
+target_include_directories(api-test PUBLIC ${CMAKE_SOURCE_DIR}/include)
+target_include_directories(api-test PUBLIC ${CMAKE_SOURCE_DIR}/include/api-test)
diff --git a/api-test/main.cpp b/api-test/main.cpp
new file mode 100644
index 0000000..61db6be
--- /dev/null
+++ b/api-test/main.cpp
@@ -0,0 +1,77 @@
+#include <cassert>
+#include <cstdio>
+#include <vk_mem_alloc.h>
+
+#include "window.hpp"
+#include "vk-device.hpp"
+// #include "resource-manager.hpp"
+
+#define ENABLE_RENDERDOC
+
+#ifdef ENABLE_RENDERDOC
+#include <renderdoc.h>
+
+RENDERDOC_API_1_4_1 *rdoc_api = nullptr;
+#endif
+
+int main() {
+
+#ifdef ENABLE_RENDERDOC
+ if (HMODULE mod = GetModuleHandleA("renderdoc.dll")) {
+ auto RENDERDOC_GetAPI =
+ (pRENDERDOC_GetAPI)GetProcAddress(mod, "RENDERDOC_GetAPI");
+ int ret =
+ RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_1_2, (void **)&rdoc_api);
+ assert(ret == 1);
+ }
+#endif
+
+ window wnd { 1280U, 720U };
+
+#ifdef ENABLE_RENDERDOC
+ if(rdoc_api != nullptr) rdoc_api->StartFrameCapture(nullptr, nullptr);
+#endif
+
+ vkdevice device { wnd };
+
+ auto gpu_texture = device.create_texture({
+ .width = 1024U,
+ .height = 1024U,
+ .usages = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
+ .format = VK_FORMAT_R8G8B8A8_UNORM,
+ .type = VK_IMAGE_TYPE_2D,
+ });
+
+ auto gpu_buffer = device.get_buffer(device.create_buffer({
+ .size = 1024U * sizeof(uint32_t),
+ .usages = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
+ .memory_properties = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
+ .memory_usage = VMA_MEMORY_USAGE_GPU_ONLY,
+ }));
+
+ auto staging_buffer = device.get_buffer(device.create_buffer({
+ .size = 1024U * sizeof(uint32_t),
+ .usages = VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
+ .memory_properties = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT,
+ .memory_usage = VMA_MEMORY_USAGE_CPU_TO_GPU,
+ }));
+
+ uint64_t t = 7;
+ (*(uint64_t*)staging_buffer.mapped_ptr) = t;
+
+ auto command_buffer = device.get_command_buffer();
+
+ command_buffer.copy(staging_buffer, gpu_texture);
+
+ device.submit(command_buffer);
+
+#ifdef ENABLE_RENDERDOC
+ if(rdoc_api != nullptr) rdoc_api->EndFrameCapture(nullptr, nullptr);
+#endif
+
+ while(wnd.isOpen) {
+ wnd.poll_events();
+ }
+
+ return 0;
+}
diff --git a/api-test/vk-command-buffer.cpp b/api-test/vk-command-buffer.cpp
new file mode 100644
index 0000000..b9eed19
--- /dev/null
+++ b/api-test/vk-command-buffer.cpp
@@ -0,0 +1,25 @@
+#include "vk-command-buffer.hpp"
+
+#include <cassert>
+
+#include "vulkan-loader.hpp"
+
+#ifdef _DEBUG
+#define VKCHECK(result) assert(result == VK_SUCCESS);
+#else
+#define VKCHECK(result) result;
+#endif
+
+void command_buffer::start() {
+ VkCommandBufferBeginInfo begin_info = {};
+ begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
+ begin_info.pNext = nullptr;
+ begin_info.flags = 0U;
+ begin_info.pInheritanceInfo = nullptr;
+
+ VKCHECK(vkBeginCommandBuffer(handle, &begin_info))
+}
+
+void command_buffer::stop() {
+ VKCHECK(vkEndCommandBuffer(handle))
+}
diff --git a/api-test/vk-device.cpp b/api-test/vk-device.cpp
new file mode 100644
index 0000000..4b7c0d0
--- /dev/null
+++ b/api-test/vk-device.cpp
@@ -0,0 +1,519 @@
+#include "vk-device.hpp"
+#include "color.hpp"
+
+#include <cassert>
+#include <iostream>
+#include <thread>
+#include <vector>
+
+#define VMA_IMPLEMENTATION
+#include <vk_mem_alloc.h>
+
+#include "vulkan-loader.hpp"
+#include "window.hpp"
+
+#ifdef _DEBUG
+#define VKCHECK(result) assert(result == VK_SUCCESS);
+#else
+#define VKCHECK(result) result;
+#endif
+
+VKAPI_ATTR VkBool32 VKAPI_CALL debug_callback(VkDebugUtilsMessageSeverityFlagBitsEXT severity,
+ VkDebugUtilsMessageTypeFlagsEXT type,
+ const VkDebugUtilsMessengerCallbackDataEXT* data,
+ void*) {
+ const char* severity_cstr = nullptr;
+ switch (severity) {
+ case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT:
+ severity_cstr = "VERBOSE";
+ break;
+ case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT:
+ severity_cstr = "INFO";
+ break;
+ case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT:
+ severity_cstr = "WARNING";
+ break;
+ case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT:
+ severity_cstr = "ERROR";
+ break;
+ default:
+ break;
+ }
+
+ const char* type_cstr = nullptr;
+ switch (type) {
+ case VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT:
+ type_cstr = "GENERAL";
+ break;
+ case VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT:
+ type_cstr = "VALIDATION";
+ break;
+ case VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT:
+ type_cstr = "PERFORMANCE";
+ break;
+ default:
+ break;
+ }
+
+ std::cerr << "[" << severity_cstr << "_" << type_cstr << "]: " << data->pMessage << std::endl;
+ return VK_FALSE;
+}
+
+vkdevice::vkdevice(const window& window)
+ : queues{
+ {
+ .usages = VK_QUEUE_GRAPHICS_BIT,
+ },
+ // { .usages = VK_QUEUE_TRANSFER_BIT, },
+ } {
+ load_vulkan();
+ create_instance();
+
+#ifdef _DEBUG
+ create_debug_layer_callback();
+#endif // _DEBUG
+
+ create_device();
+
+ create_memory_allocator();
+
+ create_surface(window);
+
+ create_swapchain();
+}
+
+handle<device_texture> vkdevice::create_texture(const texture_desc& desc) {
+ device_texture device_texture = {};
+ device_texture.desc = desc;
+
+ assert(desc.mips <= texture_desc::max_mips);
+
+ VkImageCreateInfo create_info = {};
+ create_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
+ create_info.pNext = nullptr;
+ create_info.flags = 0U;
+ create_info.imageType = desc.type;
+ create_info.format = desc.format;
+ create_info.extent = {
+ .width = desc.width,
+ .height = desc.height,
+ .depth = desc.depth
+ };
+ create_info.mipLevels = desc.mips;
+ create_info.arrayLayers = 1;
+ create_info.samples = VK_SAMPLE_COUNT_1_BIT;
+ create_info.tiling = VK_IMAGE_TILING_OPTIMAL;
+ create_info.usage = desc.usages;
+ create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+ create_info.queueFamilyIndexCount = 0;
+ create_info.pQueueFamilyIndices = nullptr;
+ create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+
+ VmaAllocationCreateInfo alloc_create_info = {};
+ alloc_create_info.flags = 0U;
+ alloc_create_info.usage = VMA_MEMORY_USAGE_GPU_ONLY;
+ alloc_create_info.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
+ alloc_create_info.preferredFlags = 0U;
+ alloc_create_info.memoryTypeBits = 0U;
+ alloc_create_info.pool = nullptr;
+ alloc_create_info.pUserData = nullptr;
+
+ VKCHECK(vmaCreateImage(gpu_allocator, &create_info, &alloc_create_info, &device_texture.handle, &device_texture.alloc, nullptr))
+
+ if ((desc.usages & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) != 0U ||
+ (desc.usages & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) != 0U) {
+ create_views(device_texture);
+ }
+
+ return { textures.add(device_texture) };
+}
+
+handle<device_buffer> vkdevice::create_buffer(const buffer_desc& desc) {
+ device_buffer device_buffer = {};
+ device_buffer.desc = desc;
+
+ VkBufferCreateInfo create_info = {};
+ create_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+ create_info.pNext = nullptr;
+ create_info.flags = 0U;
+ create_info.size = desc.size;
+ create_info.usage = desc.usages;
+ create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+ create_info.queueFamilyIndexCount = 0;
+ create_info.pQueueFamilyIndices = nullptr;
+
+ VmaAllocatorCreateFlags alloc_create_flags =
+ desc.memory_usage != VMA_MEMORY_USAGE_GPU_ONLY ?
+ VMA_ALLOCATION_CREATE_MAPPED_BIT : 0U;
+
+ VmaAllocationCreateInfo alloc_create_info = {};
+ alloc_create_info.flags = alloc_create_flags;
+ alloc_create_info.usage = static_cast<VmaMemoryUsage>(desc.memory_usage);
+ alloc_create_info.requiredFlags = desc.memory_properties;
+ alloc_create_info.preferredFlags = 0U;
+ alloc_create_info.memoryTypeBits = 0U;
+ alloc_create_info.pool = nullptr;
+ alloc_create_info.pUserData = nullptr;
+
+ VmaAllocationInfo alloc_info = {};
+
+ VKCHECK(vmaCreateBuffer(gpu_allocator, &create_info, &alloc_create_info, &device_buffer.handle, &device_buffer.alloc, &alloc_info))
+
+ device_buffer.mapped_ptr = alloc_info.pMappedData;
+
+ return { buffers.add(device_buffer) };
+}
+
+void vkdevice::submit(std::span<graphics_command_buffer> command_buffers) {
+ VkSubmitInfo submit_info = {};
+ submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+ submit_info.pNext = nullptr;
+ submit_info.waitSemaphoreCount;
+ submit_info.pWaitSemaphores;
+ submit_info.pWaitDstStageMask;
+ submit_info.commandBufferCount;
+ submit_info.pCommandBuffers;
+ submit_info.signalSemaphoreCount;
+ submit_info.pSignalSemaphores;
+
+ vkQueueSubmit()
+}
+
+
+// private
+
+void vkdevice::create_views(device_texture& texture) {
+ VkImageAspectFlags aspect_mask = ((texture.desc.usages & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) != 0U)
+ ? VK_IMAGE_ASPECT_COLOR_BIT
+ : VK_IMAGE_ASPECT_DEPTH_BIT;
+ VkImageViewCreateInfo create_info = {};
+ create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+ create_info.pNext = nullptr;
+ create_info.flags = 0U;
+ create_info.image = texture.handle;
+ create_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
+ create_info.format = texture.desc.format;
+ create_info.components = {
+ .r = VK_COMPONENT_SWIZZLE_R,
+ .g = VK_COMPONENT_SWIZZLE_G,
+ .b = VK_COMPONENT_SWIZZLE_B,
+ .a = VK_COMPONENT_SWIZZLE_A,
+ };
+ create_info.subresourceRange = {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, // TODO: Choose either color or depth based on format ?
+ .baseMipLevel = 0,
+ .levelCount = 1,
+ .baseArrayLayer = 0,
+ .layerCount = 1,
+ };
+
+ for (auto mip_index{ 0 }; mip_index < texture.desc.mips; mip_index++) {
+ create_info.subresourceRange.baseMipLevel = mip_index;
+ VKCHECK(vkCreateImageView(device, &create_info, nullptr, &texture.mips_views[mip_index]))
+ }
+
+ create_info.subresourceRange.baseMipLevel = 0;
+ create_info.subresourceRange.levelCount = texture.desc.mips;
+ VKCHECK(vkCreateImageView(device, &create_info, nullptr, &texture.whole_view))
+}
+
+void vkdevice::create_surface(const window& window) {
+ VkWin32SurfaceCreateInfoKHR create_info = {};
+ create_info.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
+ create_info.pNext = nullptr;
+ create_info.flags = 0U;
+ create_info.hinstance = GetModuleHandle(nullptr);
+ create_info.hwnd = window.handle;
+
+ VKCHECK(vkCreateWin32SurfaceKHR(instance, &create_info, nullptr, &surface))
+}
+
+void vkdevice::create_swapchain() {
+ VkBool32 supported = VK_FALSE;
+ for (auto index{ 0U }; index < QueueTypes::MAX; index++) {
+ auto& queue = queues[index];
+ VKCHECK(vkGetPhysicalDeviceSurfaceSupportKHR(physical_device, queue.index, surface, &supported))
+
+ assert(supported);
+ }
+
+ VkSurfaceCapabilitiesKHR surface_capabilities = {};
+ VKCHECK(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical_device, surface, &surface_capabilities))
+
+ VkExtent2D extent = surface_capabilities.currentExtent;
+
+ if (extent.width < surface_capabilities.minImageExtent.width) {
+ extent.width = surface_capabilities.minImageExtent.width;
+ } else if (extent.width > surface_capabilities.maxImageExtent.width) {
+ extent.width = surface_capabilities.maxImageExtent.width;
+ }
+
+ if (extent.height < surface_capabilities.minImageExtent.height) {
+ extent.height = surface_capabilities.minImageExtent.height;
+ } else if (extent.height > surface_capabilities.maxImageExtent.height) {
+ extent.height = surface_capabilities.maxImageExtent.height;
+ }
+
+ auto supported_formats_count = 0U;
+ VKCHECK(vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &supported_formats_count, VK_NULL_HANDLE))
+
+ std::vector<VkSurfaceFormatKHR> supported_formats{ supported_formats_count };
+ VKCHECK(vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &supported_formats_count, supported_formats.data()))
+
+
+ auto supported_present_modes_count = 0U;
+ VKCHECK(vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, surface, &supported_present_modes_count, VK_NULL_HANDLE))
+
+ std::vector<VkPresentModeKHR> supported_present_modes{ supported_present_modes_count };
+
+ VKCHECK(vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, surface, &supported_present_modes_count, supported_present_modes.data()))
+
+ VkSwapchainCreateInfoKHR create_info = {};
+ create_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
+ create_info.pNext = nullptr;
+ create_info.flags = 0U;
+ create_info.surface = surface;
+ create_info.minImageCount = surface_capabilities.minImageCount;
+ create_info.imageFormat = supported_formats[0].format;
+ create_info.imageColorSpace = supported_formats[0].colorSpace;
+ create_info.imageExtent = extent;
+ create_info.imageArrayLayers = 1U;
+ create_info.imageUsage = VK_IMAGE_USAGE_TRANSFER_DST_BIT;
+ create_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
+ create_info.queueFamilyIndexCount = 0U;
+ create_info.pQueueFamilyIndices = nullptr;
+ create_info.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
+ create_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
+ create_info.presentMode = supported_present_modes[0];
+ create_info.clipped = VK_TRUE;
+ create_info.oldSwapchain = nullptr;
+
+ VKCHECK(vkCreateSwapchainKHR(device, &create_info, nullptr, &swapchain))
+}
+
+// ----------------- vkadapter -----------------
+
+void vkdevice::create_instance() {
+ VkApplicationInfo app_info = {};
+ app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
+ app_info.pApplicationName = "path-tracer";
+ app_info.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
+ app_info.pEngineName = "gpu-path-tracer";
+ app_info.engineVersion = VK_MAKE_VERSION(1, 0, 0);
+ app_info.apiVersion = VK_API_VERSION_1_3;
+
+ constexpr const char* instance_layers[] = {
+ "VK_LAYER_KHRONOS_validation",
+ };
+
+ constexpr const char* instance_extensions[] = {
+ VK_EXT_DEBUG_UTILS_EXTENSION_NAME,
+ "VK_KHR_surface",
+ "VK_KHR_win32_surface"
+ };
+
+ VkInstanceCreateInfo create_info = {};
+ create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
+ create_info.pNext = nullptr;
+ create_info.flags = 0U;
+ create_info.pApplicationInfo = &app_info;
+ create_info.enabledLayerCount = sizeof(instance_layers) / sizeof(instance_layers[0]);
+ create_info.ppEnabledLayerNames = instance_layers;
+ create_info.enabledExtensionCount = sizeof(instance_extensions) / sizeof(instance_extensions[0]);
+ create_info.ppEnabledExtensionNames = instance_extensions;
+
+ VKCHECK(vkCreateInstance(&create_info, nullptr, &instance))
+
+ load_instance_functions(instance);
+}
+
+void vkdevice::create_device() {
+ pick_physical_device();
+
+ VkDeviceQueueCreateInfo queue_create_infos[QueueTypes::MAX] = {};
+ float queue_priority = 1.f;
+
+ for (auto index{ 0U }; index < QueueTypes::MAX; index++) {
+ auto& queue = queues[index];
+ auto queue_index = pick_queue(queue.usages);
+
+ queue_create_infos[index].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
+ queue_create_infos[index].pNext = nullptr;
+ queue_create_infos[index].flags = 0U;
+ queue_create_infos[index].queueFamilyIndex = 0;
+ queue_create_infos[index].queueCount = 1;
+ queue_create_infos[index].pQueuePriorities = &queue_priority;
+
+ queue.index = queue_index;
+ }
+
+ constexpr const char* device_extensions[] = {
+ VK_KHR_SWAPCHAIN_EXTENSION_NAME,
+ };
+
+ VkPhysicalDeviceVulkan12Features physical_device_12_features = {};
+ physical_device_12_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES;
+ physical_device_12_features.pNext = VK_NULL_HANDLE;
+ physical_device_12_features.bufferDeviceAddress = VK_TRUE;
+ physical_device_12_features.runtimeDescriptorArray = VK_TRUE;
+ physical_device_12_features.shaderStorageImageArrayNonUniformIndexing = VK_TRUE;
+ physical_device_12_features.shaderSampledImageArrayNonUniformIndexing = VK_TRUE;
+ physical_device_12_features.descriptorBindingPartiallyBound = VK_TRUE;
+ physical_device_12_features.descriptorBindingPartiallyBound = VK_TRUE;
+ physical_device_12_features.descriptorBindingUpdateUnusedWhilePending = VK_TRUE;
+ physical_device_12_features.imagelessFramebuffer = VK_TRUE;
+
+ VkPhysicalDeviceVulkan13Features physical_device_13_features = {};
+ physical_device_13_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES;
+ physical_device_13_features.pNext = &physical_device_12_features;
+ physical_device_13_features.synchronization2 = VK_TRUE;
+ physical_device_13_features.dynamicRendering = VK_TRUE;
+
+ VkDeviceCreateInfo create_info = {};
+ create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
+ create_info.pNext = &physical_device_13_features;
+ create_info.flags = 0U;
+ create_info.queueCreateInfoCount = QueueTypes::MAX;
+ create_info.pQueueCreateInfos = queue_create_infos;
+ create_info.enabledLayerCount = 0;
+ create_info.ppEnabledLayerNames = nullptr;
+ create_info.enabledExtensionCount = sizeof(device_extensions) / sizeof(device_extensions[0]);
+ create_info.ppEnabledExtensionNames = device_extensions;
+ create_info.pEnabledFeatures = nullptr;
+
+ VKCHECK(vkCreateDevice(physical_device, &create_info, nullptr, &device))
+
+ load_device_functions(device);
+
+ for (auto& queue : queues) {
+ vkGetDeviceQueue(device, queue.index, 0, &queue.handle);
+ }
+
+ create_command_pools();
+}
+
+void vkdevice::create_memory_allocator() {
+ VmaAllocatorCreateInfo create_info = {};
+ create_info.flags = VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT;
+ create_info.physicalDevice = physical_device;
+ create_info.device = device;
+ create_info.instance = instance;
+ create_info.vulkanApiVersion = VK_API_VERSION_1_2;
+
+ VKCHECK(vmaCreateAllocator(&create_info, &gpu_allocator))
+}
+
+void vkdevice::create_command_pools() {
+ VkCommandPoolCreateInfo create_info = {};
+ create_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
+ create_info.pNext = nullptr;
+ create_info.flags = 0U;
+
+ for (auto& queue : queues) {
+ create_info.queueFamilyIndex = queue.index;
+
+ VKCHECK(vkCreateCommandPool(device, &create_info, nullptr, &queue.command_pool))
+ }
+}
+
+uint32_t vkdevice::pick_queue(VkQueueFlags queue_usages) const {
+ auto queue_properties_count = 0U;
+ vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_properties_count, nullptr);
+
+ std::vector<VkQueueFamilyProperties> queue_properties{ queue_properties_count };
+ vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_properties_count, queue_properties.data());
+
+ for (auto properties_index{ 0U }; properties_index < queue_properties_count; properties_index++) {
+ if ((queue_properties[properties_index].queueFlags & queue_usages) == queue_usages) {
+ return properties_index;
+ }
+ }
+
+ assert(true);
+ return UINT_MAX;
+}
+
+void vkdevice::pick_physical_device() {
+ auto physical_devices_count = 0U;
+ vkEnumeratePhysicalDevices(instance, &physical_devices_count, nullptr);
+
+ assert(physical_devices_count > 0);
+
+ std::vector<VkPhysicalDevice> physical_devices{ physical_devices_count };
+ vkEnumeratePhysicalDevices(instance, &physical_devices_count, physical_devices.data());
+
+ for (auto& physical_dev : physical_devices) {
+ if (device_support_features(physical_dev)) {
+ physical_device = physical_dev;
+ return;
+ }
+ }
+
+ assert(true);
+}
+
+bool vkdevice::device_support_features(VkPhysicalDevice physical_dev) {
+ VkPhysicalDeviceProperties physical_device_properties = {};
+
+ vkGetPhysicalDeviceProperties(physical_dev, &physical_device_properties);
+
+ // Vulkan 1.2 physical device features support
+ {
+ VkPhysicalDeviceVulkan12Features vulkan_12_features = {};
+ vulkan_12_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES;
+
+ VkPhysicalDeviceFeatures2 physical_device_features = {};
+ physical_device_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
+ physical_device_features.pNext = &vulkan_12_features;
+
+ vkGetPhysicalDeviceFeatures2(physical_dev, &physical_device_features);
+
+ // clang-format off
+ if (vulkan_12_features.bufferDeviceAddress == VK_FALSE ||
+ vulkan_12_features.runtimeDescriptorArray == VK_FALSE ||
+ vulkan_12_features.shaderStorageImageArrayNonUniformIndexing == VK_FALSE ||
+ vulkan_12_features.shaderSampledImageArrayNonUniformIndexing == VK_FALSE ||
+ vulkan_12_features.descriptorBindingPartiallyBound == VK_FALSE ||
+ vulkan_12_features.descriptorBindingPartiallyBound == VK_FALSE ||
+ vulkan_12_features.descriptorBindingUpdateUnusedWhilePending == VK_FALSE ||
+ vulkan_12_features.imagelessFramebuffer == VK_FALSE) {
+ return false;
+ }
+ // clang-format on
+ }
+
+ // Vulkan 1.3 physical device features support
+ {
+ VkPhysicalDeviceVulkan13Features vulkan_13_features = {};
+ vulkan_13_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES;
+
+ VkPhysicalDeviceFeatures2 physical_device_features = {};
+ physical_device_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
+ physical_device_features.pNext = &vulkan_13_features;
+
+ vkGetPhysicalDeviceFeatures2(physical_dev, &physical_device_features);
+
+ if (vulkan_13_features.synchronization2 == VK_FALSE ||
+ vulkan_13_features.dynamicRendering == VK_FALSE) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void vkdevice::create_debug_layer_callback() {
+ VkDebugUtilsMessengerCreateInfoEXT create_info = {};
+ create_info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
+ create_info.pNext = nullptr;
+ create_info.flags = 0U;
+ create_info.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT |
+ VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT;
+ create_info.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
+ VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT |
+ VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT;
+ create_info.pfnUserCallback = debug_callback;
+ create_info.pUserData = nullptr;
+
+ VKCHECK(vkCreateDebugUtilsMessengerEXT(instance, &create_info, nullptr, &debug_messenger))
+}
diff --git a/include/api-test/buffer.hpp b/include/api-test/buffer.hpp
new file mode 100644
index 0000000..500e15b
--- /dev/null
+++ b/include/api-test/buffer.hpp
@@ -0,0 +1,25 @@
+#pragma once
+
+#include <cstdint>
+
+#include <vulkan/vulkan_core.h>
+
+#include "handle.hpp"
+
+struct buffer_desc {
+ size_t size;
+ VkBufferUsageFlags usages;
+ VkMemoryPropertyFlags memory_properties;
+ uint32_t memory_usage;
+};
+
+struct device_buffer;
+
+class buffer {
+public:
+ buffer(handle<device_buffer> handle);
+
+private:
+ handle<device_buffer> handle;
+
+};
diff --git a/include/api-test/freelist.hpp b/include/api-test/freelist.hpp
new file mode 100644
index 0000000..e5af6f5
--- /dev/null
+++ b/include/api-test/freelist.hpp
@@ -0,0 +1,32 @@
+#include <numeric>
+#include <vector>
+
+template<typename T, const size_t initial_size = 1024U>
+class freelist {
+ public:
+ freelist()
+ : holes(initial_size),
+ items(initial_size) {
+ std::iota(holes.begin(), holes.end(), 0);
+ }
+
+ uint32_t add(const T& item) {
+ const auto id = holes.back();
+ holes.pop_back();
+
+ items[id] = std::move(item);
+
+ return id;
+ }
+
+ void remove(uint32_t item_id) {
+ holes.push_back(item_id);
+ }
+
+ const T& operator[](uint32_t index) {
+ return items[index];
+ }
+
+ std::vector<T> items;
+ std::vector<uint32_t> holes;
+};
diff --git a/include/api-test/handle.hpp b/include/api-test/handle.hpp
new file mode 100644
index 0000000..90c1ade
--- /dev/null
+++ b/include/api-test/handle.hpp
@@ -0,0 +1,8 @@
+#pragma once
+
+#include <cstdint>
+
+template<typename T>
+struct handle {
+ const uint32_t handle;
+};
diff --git a/include/api-test/resource-manager.hpp b/include/api-test/resource-manager.hpp
new file mode 100644
index 0000000..f414367
--- /dev/null
+++ b/include/api-test/resource-manager.hpp
@@ -0,0 +1,22 @@
+#pragma once
+
+#include "handle.hpp"
+#include "freelist.hpp"
+#include "texture.hpp"
+#include "buffer.hpp"
+
+class resourcemanager {
+public:
+
+ handle<texture> create_texture(const texture_desc& desc);
+
+ handle<buffer> create_buffer(const buffer_desc& desc);
+
+ const texture& get_texture(handle<texture> handle);
+
+ const buffer& get_buffer(handle<buffer> handle);
+
+private:
+ freelist<texture> textures;
+ freelist<buffer> buffers;
+};
diff --git a/include/api-test/texture.hpp b/include/api-test/texture.hpp
new file mode 100644
index 0000000..9632c5e
--- /dev/null
+++ b/include/api-test/texture.hpp
@@ -0,0 +1,29 @@
+#pragma once
+
+#include <cstdint>
+
+#include <vulkan/vulkan_core.h>
+
+#include "handle.hpp"
+
+struct texture_desc {
+ uint32_t width = 1U;
+ uint32_t height = 1U;
+ uint32_t depth = 1U;
+ uint32_t mips = 1U;
+ VkImageUsageFlags usages;
+ VkFormat format;
+ VkImageType type;
+
+ static constexpr auto max_mips = 16U;
+};
+
+struct device_texture;
+
+class texture {
+public:
+ texture(handle<device_texture> handle);
+
+private:
+ handle<device_texture> handle;
+};
diff --git a/include/api-test/vk-command-buffer.hpp b/include/api-test/vk-command-buffer.hpp
new file mode 100644
index 0000000..c1f1899
--- /dev/null
+++ b/include/api-test/vk-command-buffer.hpp
@@ -0,0 +1,14 @@
+#pragma once
+
+#include <vulkan/vulkan_core.h>
+
+class command_buffer {
+ void start();
+ void stop();
+
+ VkCommandBuffer handle;
+};
+
+class graphics_command_buffer : command_buffer {
+
+};
diff --git a/include/api-test/vk-device.hpp b/include/api-test/vk-device.hpp
new file mode 100644
index 0000000..f931c70
--- /dev/null
+++ b/include/api-test/vk-device.hpp
@@ -0,0 +1,128 @@
+#pragma once
+
+#include <cstdint>
+
+#include <vulkan/vulkan_core.h>
+
+#include "handle.hpp"
+#include "freelist.hpp"
+#include "texture.hpp"
+#include "buffer.hpp"
+
+using VmaAllocator = struct VmaAllocator_T*;
+using VmaAllocation = struct VmaAllocation_T*;
+
+// typedef struct VkInstance_T* VkInstance;
+// typedef struct VkDevice_T* VkDevice;
+// typedef struct VkPhysicalDevice_T* VkPhysicalDevice;
+// typedef struct VkQueue_T* VkQueue;
+// typedef uint32_t VkFlags;
+// typedef VkFlags VkQueueUsageFlags;
+// typedef struct VkImage_T* VkImage;
+// typedef struct VkImageView_T* VkImageView;
+// typedef struct VkBuffer_T* VkBuffer;
+// typedef enum VkImageType VkImageType;
+// typedef enum VkFormat VkFormat;
+// typedef VkFlags VkImageUsageFlags;
+//
+// #ifdef _DEBUG
+// typedef struct VkDebugUtilsMessengerEXT_T* VkDebugUtilsMessengerEXT;
+// #endif // _DEBUG
+
+
+struct device_texture {
+ VkImage handle;
+ VkImageView whole_view;
+ VkImageView mips_views[texture_desc::max_mips] = { VK_NULL_HANDLE };
+ VmaAllocation alloc;
+
+ texture_desc desc;
+};
+
+struct device_buffer {
+ VkBuffer handle;
+ VmaAllocation alloc;
+ VkDeviceAddress device_address;
+ void* mapped_ptr;
+
+ buffer_desc desc;
+};
+
+class window;
+
+class vkdevice {
+public:
+
+ vkdevice(const window& window);
+
+ handle<device_texture> create_texture(const texture_desc& desc);
+
+ handle<device_buffer> create_buffer(const buffer_desc& desc);
+
+ const device_texture& get_texture(handle<device_texture> handle) {
+ return textures[handle.handle];
+ }
+
+ const device_buffer& get_buffer(handle<device_buffer> handle) {
+ return buffers[handle.handle];
+ }
+
+ void submit(/*std::span<command_buffer>*/);
+
+private:
+
+ void create_views(device_texture& texture);
+
+ void create_surface(const window& window);
+
+ void create_swapchain();
+
+ VkSurfaceKHR surface;
+ VkSwapchainKHR swapchain;
+
+ // TODO: Wrap in a struct called adapter ?
+ void create_instance();
+
+ void create_device();
+
+ void create_memory_allocator();
+
+ void create_command_pools();
+
+ void pick_physical_device();
+
+ [[nodiscard]] uint32_t pick_queue(VkQueueFlags usages) const;
+
+ static bool device_support_features(VkPhysicalDevice physical_dev);
+
+ void create_debug_layer_callback();
+
+
+ enum QueueTypes : uint32_t {
+ GRAPHICS,
+ // COPY,
+ MAX,
+ };
+
+ struct queue {
+ VkQueue handle;
+ uint32_t index;
+ VkQueueFlags usages;
+ VkCommandPool command_pool;
+ };
+
+ queue queues[QueueTypes::MAX];
+
+ VkInstance instance;
+ VkPhysicalDevice physical_device;
+ VkDevice device;
+
+ VmaAllocator gpu_allocator;
+
+#ifdef _DEBUG
+ VkDebugUtilsMessengerEXT debug_messenger;
+#endif // _DEBUG
+
+ freelist<device_texture> textures;
+ freelist<device_buffer> buffers;
+};
diff --git a/include/resource-manager.hpp b/include/resource-manager.hpp
new file mode 100644
index 0000000..2a8a787
--- /dev/null
+++ b/include/resource-manager.hpp
@@ -0,0 +1,6 @@
+#pragma once
+
+class resourcemanager {
+ public:
+ static const resourcemanager resource_manager;
+};
diff --git a/include/window.hpp b/include/window.hpp
index aeea39a..33fce4f 100644
--- a/include/window.hpp
+++ b/include/window.hpp
@@ -146,7 +146,7 @@ class window {
static LRESULT CALLBACK window_procedure(HWND hwnd, UINT umsg, WPARAM wparam, LPARAM lparam);
- HWND window_handle {};
+ HWND handle {};
WNDCLASS window_class {};
LPVOID main_fiber;
LPVOID message_loop_fiber;
diff --git a/src/resource-manager.cpp b/src/resource-manager.cpp
new file mode 100644
index 0000000..e0b00aa
--- /dev/null
+++ b/src/resource-manager.cpp
@@ -0,0 +1,2 @@
+#include "resourcemanager.hpp"
+static const resour
diff --git a/src/window.cpp b/src/window.cpp
index d57d44a..d2cc5f4 100644
--- a/src/window.cpp
+++ b/src/window.cpp
@@ -214,13 +214,14 @@ window::window(uint32_t desired_width, uint32_t desired_height)
RECT rect { 0, 0, (int32_t)desired_width, (int32_t)desired_height };
DWORD style = WS_OVERLAPPEDWINDOW;
- if (FAILED(AdjustWindowRect(&rect, style, FALSE)))
+ if (FAILED(AdjustWindowRect(&rect, style, FALSE))) {
log_last_error();
+ }
main_fiber = ConvertThreadToFiber(nullptr);
message_loop_fiber = CreateFiber(0, &::message_loop, this);
- window_handle = CreateWindowA(
+ handle = CreateWindowA(
"default-window",
"path-tracer",
style,
@@ -234,13 +235,13 @@ window::window(uint32_t desired_width, uint32_t desired_height)
this
);
- ShowWindow(window_handle, SW_SHOW);
+ ShowWindow(handle, SW_SHOW);
isOpen = true;
}
window::~window() {
- DestroyWindow(window_handle);
+ DestroyWindow(handle);
UnregisterClassA(window_class.lpszClassName, GetModuleHandleA(nullptr));
}