summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitmodules3
-rw-r--r--CMakeLists.txt8
-rw-r--r--assets/Roboto-Regular.ttfbin0 -> 168260 bytes
-rw-r--r--assets/debug.pngbin0 -> 3062239 bytes
m---------ext/stb0
-rw-r--r--src/app.cpp84
-rw-r--r--src/vulkan_helper.cpp323
-rw-r--r--src/vulkan_helper.h76
-rw-r--r--src/vulkan_swapchain.cpp253
-rw-r--r--src/vulkan_swapchain.h11
10 files changed, 688 insertions, 70 deletions
diff --git a/.gitmodules b/.gitmodules
index 6ea0b14..0d0d572 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -13,3 +13,6 @@
[submodule "ext/VulkanMemoryAllocator"]
path = ext/VulkanMemoryAllocator
url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator
+[submodule "ext/stb"]
+ path = ext/stb
+ url = https://github.com/nothings/stb
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 41401be..10fb56f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -8,6 +8,7 @@ set(CMAKE_CXX_STANDARD 20)
set(PROJECT_ROOT ${CMAKE_SOURCE_DIR})
set(SRC_DIR ${PROJECT_ROOT}/src)
set(EXT_DIR ${PROJECT_ROOT}/ext)
+set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DUSE_VULKAN_VALIDATION_LAYERS")
# Add external libraries
include(${EXT_DIR}/imgui.cmake)
@@ -21,6 +22,9 @@ target_include_directories(tinygltf INTERFACE ${EXT_DIR}/tinygltf)
add_library(tinyobjloader INTERFACE)
target_include_directories(tinyobjloader INTERFACE ${EXT_DIR}/tinyobjloader)
+add_library(stb INTERFACE)
+target_include_directories(stb INTERFACE ${EXT_DIR}/stb)
+
file(GLOB_RECURSE SOURCES "${SRC_DIR}/*.cpp")
# Add executable from your source files
@@ -32,12 +36,12 @@ target_link_libraries(IrisRenderer PRIVATE glfw)
find_package(Vulkan REQUIRED)
target_link_libraries(IrisRenderer PRIVATE Vulkan::Vulkan)
-find_package(VulkanMemoryAllocator REQUIRED)
+find_package(VulkanMemoryAllocator CONFIG REQUIRED)
target_link_libraries(IrisRenderer PRIVATE GPUOpen::VulkanMemoryAllocator)
# Link external libraries to your project
target_link_libraries(IrisRenderer PRIVATE
- argparse imgui tinygltf tinyobjloader
+ argparse imgui tinygltf tinyobjloader stb
dl pthread X11 Xxf86vm Xrandr Xi)
# Optional: Include additional compiler options or flags
diff --git a/assets/Roboto-Regular.ttf b/assets/Roboto-Regular.ttf
new file mode 100644
index 0000000..2d116d9
--- /dev/null
+++ b/assets/Roboto-Regular.ttf
Binary files differ
diff --git a/assets/debug.png b/assets/debug.png
new file mode 100644
index 0000000..dca7c3a
--- /dev/null
+++ b/assets/debug.png
Binary files differ
diff --git a/ext/stb b/ext/stb
new file mode 160000
+Subproject f75e8d1cad7d90d72ef7a4661f1b994ef78b4e3
diff --git a/src/app.cpp b/src/app.cpp
index a5b3247..6148e04 100644
--- a/src/app.cpp
+++ b/src/app.cpp
@@ -1,37 +1,20 @@
+#include "imgui.h"
#include "vulkan_swapchain.h"
#include "imgui_impl_vulkan.h"
#include "argparse/argparse.hpp"
#include <cstdint>
#include <sys/types.h>
+#include <vk_mem_alloc.h>
+#include <vulkan/vulkan_core.h>
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>
#include <cstdlib>
#include <iostream>
-#include <memory>
#include <vector>
-std::unique_ptr<iris::Swapchain> start_up(int width, int height) {
- auto glfw_extensions = get_glfw_instance_extensions();
- iris::Device device({}, glfw_extensions);
-
- auto window = glfwCreateWindow(width, height, "IrisRenderer", nullptr, nullptr);
- if (window == nullptr) {
- std::cerr << "Failed to create GLFW window" << std::endl;
- abort();
- }
- return std::make_unique<iris::Swapchain>(window, device);
-}
-
-void shut_down(std::unique_ptr<iris::Swapchain>& swapchain) {
- ImGui_ImplVulkan_Shutdown();
-
- glfwDestroyWindow(swapchain->window);
- glfwTerminate();
-}
-
int main(int argc, char** argv) {
argparse::ArgumentParser program("IrisRenderer");
program.add_argument("width")
@@ -64,28 +47,69 @@ int main(int argc, char** argv) {
glfwTerminate();
return -1;
}
+ auto glfw_extensions = get_glfw_instance_extensions();
+ for (const auto& extension : glfw_extensions) {
+ std::cerr << "GLFW extension: " << extension << std::endl;
+ }
+
+ std::vector<std::string> layers;
+ iris::Device device({}, glfw_extensions);
+
+ glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
+ auto window = glfwCreateWindow(window_width, window_height, "IrisRenderer", nullptr, nullptr);
+ if (window == nullptr) {
+ std::cerr << "Failed to create GLFW window" << std::endl;
+ abort();
+ }
- auto swapchain = start_up(window_width, window_height);
- while (!glfwWindowShouldClose(swapchain->window)) {
+ // load debug image
+ iris::Texture2D debug_texture = device.create_texture_from_image(
+ "assets/debug.png",
+ VK_FORMAT_R8G8B8A8_UNORM,
+ VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT,
+ VmaAllocationCreateInfo {
+ .flags = VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT,
+ .usage = VMA_MEMORY_USAGE_AUTO,
+ });
+ // end load debug image
+
+ auto swapchain = iris::Swapchain(window, device, window_width, window_height);
+ std::cerr << "Swapchain created" << std::endl;
+ while (!glfwWindowShouldClose(swapchain.window)) {
glfwPollEvents();
int display_w, display_h;
- glfwGetFramebufferSize(swapchain->window, &display_w, &display_h);
+ glfwGetFramebufferSize(swapchain.window, &display_w, &display_h);
if (display_w == 0 || display_h == 0) {
break;
}
- swapchain->needs_recreate |=
- (uint32_t) display_w != swapchain->width ||
- (uint32_t) display_h != swapchain->height;
- if (swapchain->needs_recreate) {
- swapchain->resize(display_w, display_h);
- swapchain->needs_recreate = false;
+ swapchain.needs_recreate |=
+ (uint32_t) display_w != swapchain.width ||
+ (uint32_t) display_h != swapchain.height;
+ if (swapchain.needs_recreate) {
+ swapchain.resize(display_w, display_h);
+ swapchain.needs_recreate = false;
}
+ std::cerr << "Rendering frame" << std::endl;
ImGui_ImplVulkan_NewFrame();
+ ImGui_ImplGlfw_NewFrame();
+ swapchain.start_frame();
+ std::cerr << "Frame started" << std::endl;
+ ImGui::NewFrame();
+ ImGui::ShowDemoWindow();
+ ImGui::Render();
+ std::cerr << "ImGui rendered" << std::endl;
+ swapchain.display(debug_texture);
+ std::cerr << "Frame displayed" << std::endl;
}
- shut_down(swapchain);
+ ImGui_ImplVulkan_Shutdown();
+
+ glfwDestroyWindow(swapchain.window);
+ glfwTerminate();
+ device.destroy();
+
return 0;
} \ No newline at end of file
diff --git a/src/vulkan_helper.cpp b/src/vulkan_helper.cpp
index e533d2d..339b9c0 100644
--- a/src/vulkan_helper.cpp
+++ b/src/vulkan_helper.cpp
@@ -1,11 +1,25 @@
#include "vulkan_helper.h"
#include "vulkan/vulkan_core.h"
+#include <limits>
+#define STB_IMAGE_IMPLEMENTATION
+#include "stb_image.h"
#include <cstdint>
+#include <cstring>
#include <cstdlib>
#include <iostream>
#include <memory>
+#define VMA_IMPLEMENTATION
#include <vk_mem_alloc.h>
+#ifdef USE_VULKAN_VALIDATION_LAYERS
+static VKAPI_ATTR VkBool32 VKAPI_CALL debug_report(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objectType, uint64_t object, size_t location, int32_t messageCode, const char* pLayerPrefix, const char* pMessage, void* pUserData)
+{
+ (void)flags; (void)object; (void)location; (void)messageCode; (void)pUserData; (void)pLayerPrefix; // Unused arguments
+ fprintf(stderr, "[vulkan] Debug report from ObjectType: %i\nMessage: %s\n\n", objectType, pMessage);
+ return VK_FALSE;
+}
+#endif
+
namespace iris {
Device::Device(std::vector<std::string> layers, std::vector<std::string> instance_extensions) {
@@ -44,6 +58,19 @@ Device::Device(std::vector<std::string> layers, std::vector<std::string> instanc
VK_NULL_HANDLE,
&instance));
+#ifdef USE_VULKAN_VALIDATION_LAYERS
+ layers.push_back("VK_LAYER_KHRONOS_validation");
+ auto vkCreateDebugReportCallback = (PFN_vkCreateDebugReportCallbackEXT)vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT");
+ if (vkCreateDebugReportCallback) {
+ VkDebugReportCallbackCreateInfoEXT debug_report_create_info = {
+ .sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT,
+ .flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT | VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT,
+ .pfnCallback = debug_report,
+ };
+ CHECK_VULKAN(vkCreateDebugReportCallback(instance, &debug_report_create_info, nullptr, &debugReportCallback));
+ }
+#endif
+
// Enumerate and select the physical device
uint32_t physical_device_count = 0;
CHECK_VULKAN(vkEnumeratePhysicalDevices(
@@ -117,6 +144,7 @@ Device::Device(std::vector<std::string> layers, std::vector<std::string> instanc
};
constexpr char *device_extensions[] = {
+ VK_KHR_SWAPCHAIN_EXTENSION_NAME,
VK_EXT_SHADER_ATOMIC_FLOAT_EXTENSION_NAME,
VK_KHR_RAY_TRACING_PIPELINE_EXTENSION_NAME,
VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME,
@@ -154,28 +182,121 @@ Device::Device(std::vector<std::string> layers, std::vector<std::string> instanc
&allocator));
}
-Device::~Device() {
+// not handled by RAII, manually call at the end.
+void Device::destroy() {
vmaDestroyAllocator(allocator);
vkDestroyDevice(device, VK_NULL_HANDLE);
vkDestroyInstance(instance, VK_NULL_HANDLE);
}
+CommandBuffer::CommandBuffer(VkDevice device,
+ uint32_t queue_family_index,
+ VkQueue queue)
+ : device(device), queue(queue)
+{
+ VkCommandPoolCreateInfo pool_info = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
+ .queueFamilyIndex = queue_family_index, // TODO: query capabilities to find a proper queue index
+ };
+ CHECK_VULKAN(vkCreateCommandPool(
+ device,
+ &pool_info,
+ VK_NULL_HANDLE,
+ &this->pool));
+
+ VkCommandBufferAllocateInfo buffer_info = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
+ .commandPool = this->pool,
+ .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
+ .commandBufferCount = 1,
+ };
+ CHECK_VULKAN(vkAllocateCommandBuffers(
+ device,
+ &buffer_info,
+ &buffer));
+
+ VkFenceCreateInfo fence_info = {
+ .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
+ };
+ CHECK_VULKAN(vkCreateFence(
+ device,
+ &fence_info,
+ VK_NULL_HANDLE,
+ &fence));
+}
+
+CommandBuffer::~CommandBuffer() {
+ vkDestroyFence(device, fence, VK_NULL_HANDLE);
+ vkFreeCommandBuffers(device, pool, 1, &buffer);
+ vkDestroyCommandPool(device, pool, VK_NULL_HANDLE);
+}
+
+CommandBuffer Device::create_command_buffer() {
+ return CommandBuffer(device, main_queue_family_index, graphics_queue);
+}
+
+void CommandBuffer::begin(VkCommandBufferUsageFlags flags) {
+ CHECK_VULKAN(vkResetFences(device, 1, &fence));
+ CHECK_VULKAN(vkResetCommandPool(device, pool, 0u));
+
+ VkCommandBufferBeginInfo begin_info = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+ .flags = flags,
+ };
+ CHECK_VULKAN(vkBeginCommandBuffer(buffer, &begin_info));
+}
+
+void CommandBuffer::submit_sync() {
+ CHECK_VULKAN(vkEndCommandBuffer(buffer));
+ VkPipelineStageFlags wait_stage = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
+ VkSubmitInfo submit_info = {
+ .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
+ .waitSemaphoreCount = 0,
+ .pWaitSemaphores = VK_NULL_HANDLE,
+ .pWaitDstStageMask = &wait_stage,
+ .commandBufferCount = 1,
+ .pCommandBuffers = &buffer,
+ };
+ CHECK_VULKAN(vkQueueSubmit(
+ queue,
+ 1,
+ &submit_info,
+ fence));
+ CHECK_VULKAN(vkWaitForFences(
+ device,
+ 1,
+ &fence,
+ VK_TRUE,
+ std::numeric_limits<uint64_t>::max()));
+}
+
+void *Buffer_t::map() {
+ if (mapped_data == nullptr) {
+ CHECK_VULKAN(vmaMapMemory(allocator, allocation, &mapped_data));
+ }
+ return mapped_data;
+}
+
+void Buffer_t::unmap() {
+ if (mapped_data != nullptr) {
+ vmaUnmapMemory(allocator, allocation);
+ mapped_data = nullptr;
+ }
+}
Buffer_t::~Buffer_t() {
vmaDestroyBuffer(allocator, buffer, allocation);
}
-Buffer Device::create_buffer(VkDeviceSize size,
- VkBufferUsageFlags usage,
- VmaMemoryUsage memory_usage) {
+Buffer_t Device::create_buffer_raw(VkDeviceSize size,
+ VkBufferUsageFlags usage,
+ VmaAllocationCreateInfo create_info)
+{
VkBufferCreateInfo buffer_info = {
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.size = size,
.usage = usage,
};
- VmaAllocationCreateInfo allocation_info = {
- .usage = memory_usage,
- };
Buffer_t buffer = {
.allocator = this->allocator,
.flags = usage,
@@ -184,11 +305,18 @@ Buffer Device::create_buffer(VkDeviceSize size,
CHECK_VULKAN(vmaCreateBuffer(
allocator,
&buffer_info,
- &allocation_info,
+ &create_info,
&buffer.buffer,
&buffer.allocation,
VK_NULL_HANDLE));
- return std::make_shared<Buffer_t>(buffer);
+ return buffer;
+}
+
+Buffer Device::create_buffer(VkDeviceSize size,
+ VkBufferUsageFlags usage,
+ VmaAllocationCreateInfo create_info)
+{
+ return std::make_shared<Buffer_t>(create_buffer_raw(size, usage, create_info));
}
Texture2D_t::~Texture2D_t() {
@@ -196,10 +324,11 @@ Texture2D_t::~Texture2D_t() {
// TODO: optionally destroy image view, if created
}
-Texture2D Device::create_texture(VkExtent2D extent,
- VkFormat format,
- VkImageUsageFlags usage,
- VmaMemoryUsage memory_usage) {
+Texture2D_t Device::create_texture_raw(VkExtent2D extent,
+ VkFormat format,
+ VkImageUsageFlags usage,
+ VmaAllocationCreateInfo create_info)
+{
VkImageCreateInfo image_info = {
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.imageType = VK_IMAGE_TYPE_2D,
@@ -211,21 +340,181 @@ Texture2D Device::create_texture(VkExtent2D extent,
.tiling = VK_IMAGE_TILING_OPTIMAL,
.usage = usage,
};
- VmaAllocationCreateInfo allocation_info = {
- .usage = memory_usage,
- };
Texture2D_t texture = {
.allocator = this->allocator,
+ .layout = VK_IMAGE_LAYOUT_UNDEFINED,
.flags = usage,
.extent = extent,
};
CHECK_VULKAN(vmaCreateImage(
allocator,
&image_info,
- &allocation_info,
+ &create_info,
&texture.image,
&texture.allocation,
VK_NULL_HANDLE));
+ return texture;
+}
+
+Texture2D Device::create_texture(VkExtent2D extent,
+ VkFormat format,
+ VkImageUsageFlags usage,
+ VmaAllocationCreateInfo create_info)
+{
+ return std::make_shared<Texture2D_t>(create_texture_raw(extent, format, usage, create_info));
+}
+
+Texture2D Device::create_texture_from_image(const char* filename,
+ VkFormat format,
+ VkImageUsageFlags usage,
+ VmaAllocationCreateInfo create_info) {
+ int width, height, channels;
+ stbi_uc* pixels = stbi_load(filename, &width, &height, &channels, STBI_rgb_alpha);
+ if (pixels == nullptr) {
+ // TODO throw an exception
+ std::cerr << "Failed to load image: " << filename << std::endl;
+ abort();
+ }
+
+ // destroy after use, don't need to wrap with shared_ptr
+ auto staging_buf = create_buffer_raw(
+ width * height * 4,
+ VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
+ VmaAllocationCreateInfo {
+ .flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT
+ | VMA_ALLOCATION_CREATE_MAPPED_BIT,
+ .usage = VMA_MEMORY_USAGE_AUTO,
+ });
+ std::memcpy(
+ staging_buf.map(),
+ pixels,
+ width * height * 4);
+ staging_buf.unmap();
+ stbi_image_free(pixels);
+
+ VkExtent2D extent = {static_cast<uint32_t>(width), static_cast<uint32_t>(height)};
+ Texture2D_t texture = create_texture_raw(extent, format, usage, create_info);
+
+ // Transit image layout for copying
+ {
+ CommandBuffer cmd_buf = create_command_buffer();
+ VkCommandBufferBeginInfo begin_info = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+ .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
+ };
+ CHECK_VULKAN(vkBeginCommandBuffer(cmd_buf.buffer, &begin_info));
+ VkImageMemoryBarrier barrier = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .srcAccessMask = 0,
+ .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
+ .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
+ .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .image = texture.image,
+ .subresourceRange = {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .levelCount = 1,
+ .layerCount = 1,
+ },
+ };
+ vkCmdPipelineBarrier(
+ cmd_buf.buffer,
+ VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
+ VK_PIPELINE_STAGE_TRANSFER_BIT,
+ 0,
+ 0, nullptr,
+ 0, nullptr,
+ 1, &barrier);
+ CHECK_VULKAN(vkEndCommandBuffer(cmd_buf.buffer));
+ VkSubmitInfo submit_info = {
+ .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
+ .commandBufferCount = 1,
+ .pCommandBuffers = &cmd_buf.buffer,
+ };
+ CHECK_VULKAN(vkQueueSubmit(graphics_queue, 1, &submit_info, cmd_buf.fence));
+ CHECK_VULKAN(vkWaitForFences(cmd_buf.device, 1, &cmd_buf.fence, VK_TRUE, std::numeric_limits<uint64_t>::max()));
+ }
+
+ // copy staging buffer to texture
+ {
+ CommandBuffer cmd_buf = create_command_buffer();
+ VkCommandBufferBeginInfo begin_info = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+ .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
+ };
+ CHECK_VULKAN(vkBeginCommandBuffer(cmd_buf.buffer, &begin_info));
+ VkBufferImageCopy region = {
+ .bufferOffset = 0,
+ .bufferRowLength = 0,
+ .bufferImageHeight = 0,
+ .imageSubresource = {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .mipLevel = 0,
+ .baseArrayLayer = 0,
+ .layerCount = 1,
+ },
+ .imageOffset = {0, 0, 0},
+ .imageExtent = {(uint32_t) width, (uint32_t) height, 1},
+ };
+ vkCmdCopyBufferToImage(
+ cmd_buf.buffer,
+ staging_buf.buffer,
+ texture.image,
+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ 1,
+ &region);
+ CHECK_VULKAN(vkEndCommandBuffer(cmd_buf.buffer));
+ VkSubmitInfo submit_info = {
+ .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
+ .commandBufferCount = 1,
+ .pCommandBuffers = &cmd_buf.buffer,
+ };
+ CHECK_VULKAN(vkQueueSubmit(graphics_queue, 1, &submit_info, cmd_buf.fence));
+ CHECK_VULKAN(vkWaitForFences(cmd_buf.device, 1, &cmd_buf.fence, VK_TRUE, std::numeric_limits<uint64_t>::max()));
+ }
+
+ // Transit image layout back for use
+ {
+ CommandBuffer cmd_buf = create_command_buffer();
+ VkCommandBufferBeginInfo begin_info = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+ .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
+ };
+ CHECK_VULKAN(vkBeginCommandBuffer(cmd_buf.buffer, &begin_info));
+ VkImageMemoryBarrier barrier = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
+ .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
+ .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ .newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+ .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .image = texture.image,
+ .subresourceRange = {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .levelCount = 1,
+ .layerCount = 1,
+ },
+ };
+ vkCmdPipelineBarrier(
+ cmd_buf.buffer,
+ VK_PIPELINE_STAGE_TRANSFER_BIT,
+ VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
+ 0,
+ 0, nullptr,
+ 0, nullptr,
+ 1, &barrier);
+ CHECK_VULKAN(vkEndCommandBuffer(cmd_buf.buffer));
+ VkSubmitInfo submit_info = {
+ .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
+ .commandBufferCount = 1,
+ .pCommandBuffers = &cmd_buf.buffer,
+ };
+ CHECK_VULKAN(vkQueueSubmit(graphics_queue, 1, &submit_info, cmd_buf.fence));
+ CHECK_VULKAN(vkWaitForFences(cmd_buf.device, 1, &cmd_buf.fence, VK_TRUE, std::numeric_limits<uint64_t>::max()));
+ }
+ texture.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
return std::make_shared<Texture2D_t>(texture);
}
diff --git a/src/vulkan_helper.h b/src/vulkan_helper.h
index b6f270a..ebdd691 100644
--- a/src/vulkan_helper.h
+++ b/src/vulkan_helper.h
@@ -1,19 +1,20 @@
#include <memory>
#include <vulkan/vulkan_core.h>
+#include <vulkan/vk_enum_string_helper.h>
#include <vk_mem_alloc.h>
#include <cstdint>
#include <vector>
#include <string>
-#define CHECK_VULKAN(result) \
- do { \
- VkResult res = result; \
- if (res != VK_SUCCESS) { \
- /* TODO: throw error instead of returning */ \
- std::cerr << "Vulkan error: " << res << std::endl; \
- abort(); \
- } \
+#define CHECK_VULKAN(result) \
+ do { \
+ VkResult res = result; \
+ if (res != VK_SUCCESS) { \
+ /* TODO: throw error instead of returning */ \
+ std::cerr << "Vulkan error: " << string_VkResult(res) << std::endl; \
+ abort(); \
+ } \
} while (0)
namespace iris {
@@ -25,8 +26,11 @@ struct Buffer_t {
VkBufferUsageFlags flags;
VkDeviceSize size;
+ void *mapped_data = nullptr;
~Buffer_t();
+ void* map();
+ void unmap();
};
typedef std::shared_ptr<Buffer_t> Buffer;
@@ -36,6 +40,7 @@ struct Texture2D_t {
VmaAllocator allocator;
VmaAllocation allocation;
VkImageView image_view;
+ VkImageLayout layout;
VkImageUsageFlags flags;
VkExtent2D extent;
@@ -45,6 +50,22 @@ struct Texture2D_t {
typedef std::shared_ptr<Texture2D_t> Texture2D;
+// This is a really brute-force implementation,
+// every command pool contains only 1 command buffer
+struct CommandBuffer {
+ VkDevice device;
+ VkCommandPool pool;
+ VkCommandBuffer buffer;
+ VkFence fence;
+ VkQueue queue;
+
+ CommandBuffer(VkDevice device, uint32_t queue_family_index, VkQueue queue);
+ ~CommandBuffer();
+
+ void begin(VkCommandBufferUsageFlags flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT);
+ void submit_sync();
+};
+
struct Device {
VkInstance instance;
VkPhysicalDevice physical_device;
@@ -52,22 +73,55 @@ struct Device {
uint32_t main_queue_family_index;
VkQueue graphics_queue;
VmaAllocator allocator;
+#ifdef USE_VULKAN_VALIDATION_LAYERS
+ VkDebugReportCallbackEXT debugReportCallback;
+#endif
+ Device() = delete;
Device(
std::vector<std::string> layers,
std::vector<std::string> instance_extensions);
- ~Device();
+ void destroy();
+
+ Buffer_t create_buffer_raw(
+ VkDeviceSize size,
+ VkBufferUsageFlags usage,
+ VmaAllocationCreateInfo create_info = {
+ .usage = VMA_MEMORY_USAGE_AUTO,
+ });
+
+ Texture2D_t create_texture_raw(
+ VkExtent2D extent,
+ VkFormat format,
+ VkImageUsageFlags usage,
+ VmaAllocationCreateInfo create_info = {
+ .usage = VMA_MEMORY_USAGE_AUTO,
+ });
Buffer create_buffer(
VkDeviceSize size,
VkBufferUsageFlags usage,
- VmaMemoryUsage memory_usage = VMA_MEMORY_USAGE_AUTO);
+ VmaAllocationCreateInfo create_info = {
+ .usage = VMA_MEMORY_USAGE_AUTO,
+ });
Texture2D create_texture(
VkExtent2D extent,
VkFormat format,
VkImageUsageFlags usage,
- VmaMemoryUsage memory_usage = VMA_MEMORY_USAGE_AUTO);
+ VmaAllocationCreateInfo create_info = {
+ .usage = VMA_MEMORY_USAGE_AUTO,
+ });
+
+ Texture2D create_texture_from_image(
+ const char* filename,
+ VkFormat format,
+ VkImageUsageFlags usage,
+ VmaAllocationCreateInfo create_info = {
+ .usage = VMA_MEMORY_USAGE_AUTO,
+ });
+
+ CommandBuffer create_command_buffer();
};
} // namespace iris \ No newline at end of file
diff --git a/src/vulkan_swapchain.cpp b/src/vulkan_swapchain.cpp
index fde4388..41d27a5 100644
--- a/src/vulkan_swapchain.cpp
+++ b/src/vulkan_swapchain.cpp
@@ -1,6 +1,7 @@
#include "vulkan_swapchain.h"
#include "GLFW/glfw3.h"
+#include "imgui.h"
#include "imgui_impl_glfw.h"
#include "imgui_impl_vulkan.h"
#include "vulkan/vulkan_core.h"
@@ -42,13 +43,18 @@ void Swapchain::resize(uint32_t new_width, uint32_t new_height) {
vkDestroySwapchainKHR(device.device, swapchain, nullptr);
}
+ VkSurfaceCapabilitiesKHR surface_capabilities;
+ CHECK_VULKAN(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(
+ device.physical_device,
+ surface,
+ &surface_capabilities));
VkSwapchainCreateInfoKHR swapchain_create_info = {
.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
.surface = surface,
.minImageCount = SWAPCHAIN_IMAGE_COUNT,
.imageFormat = VK_FORMAT_B8G8R8A8_UNORM,
.imageColorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR,
- .imageExtent = {width, height},
+ .imageExtent = {new_width, new_height},
.imageArrayLayers = 1,
.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT,
.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
@@ -63,7 +69,6 @@ void Swapchain::resize(uint32_t new_width, uint32_t new_height) {
&swapchain_create_info,
nullptr,
&swapchain));
-
// images
uint32_t image_count = 0;
CHECK_VULKAN(vkGetSwapchainImagesKHR(
@@ -133,12 +138,21 @@ void Swapchain::resize(uint32_t new_width, uint32_t new_height) {
{width, height},
VK_FORMAT_B8G8R8A8_UNORM,
VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT,
- VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE);
+ VmaAllocationCreateInfo {
+ .flags = VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT,
+ .usage = VMA_MEMORY_USAGE_AUTO,
+ });
}
-Swapchain::Swapchain(
- GLFWwindow *window,
- iris::Device device) : device(device), window(window)
+Swapchain::Swapchain(GLFWwindow *window,
+ iris::Device device,
+ uint32_t width,
+ uint32_t height)
+ : device(device)
+ , window(window)
+ , width(width)
+ , height(height)
+ , cmd_buf(device.create_command_buffer())
{
if (!glfwVulkanSupported()) {
std::cerr << "GLFW failed to find Vulkan support" << std::endl;
@@ -146,6 +160,7 @@ Swapchain::Swapchain(
abort();
}
+ std::cerr << "Creating surface" << std::endl;
// Create the surface
CHECK_VULKAN(glfwCreateWindowSurface(
device.instance,
@@ -242,6 +257,23 @@ Swapchain::Swapchain(
// Create swapchain and image/imageview/framebuffers
this->resize(width, height);
+ // Create semaphores
+ for (uint32_t i = 0; i < SWAPCHAIN_IMAGE_COUNT; i++) {
+ VkSemaphoreCreateInfo semaphore_info = {
+ .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
+ };
+ CHECK_VULKAN(vkCreateSemaphore(
+ device.device,
+ &semaphore_info,
+ nullptr,
+ &image_available_semaphores[i]));
+ CHECK_VULKAN(vkCreateSemaphore(
+ device.device,
+ &semaphore_info,
+ nullptr,
+ &render_finished_semaphores[i]));
+ }
+
// Create descriptor pool
VkDescriptorPoolSize pool_size = {
.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
@@ -260,6 +292,15 @@ Swapchain::Swapchain(
&descriptor_pool));
// initialize ImGui
+ imgui_context = ImGui::CreateContext();
+ ImGui::SetCurrentContext(imgui_context);
+
+ ImGuiIO &io = ImGui::GetIO();
+ io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
+ io.FontDefault = io.Fonts->AddFontFromFileTTF(
+ "assets/Roboto-Regular.ttf",
+ 16.0f);
+
ImGui_ImplGlfw_InitForVulkan(window, true);
ImGui_ImplVulkan_InitInfo init_info = {
.Instance = device.instance,
@@ -268,28 +309,222 @@ Swapchain::Swapchain(
.QueueFamily = device.main_queue_family_index,
.Queue = device.graphics_queue,
.DescriptorPool = descriptor_pool,
+ .RenderPass = render_pass,
.MinImageCount = SWAPCHAIN_IMAGE_COUNT,
.ImageCount = SWAPCHAIN_IMAGE_COUNT,
.MSAASamples = VK_SAMPLE_COUNT_1_BIT,
-
- // optional
- .CheckVkResultFn = [](const VkResult err) { CHECK_VULKAN(err); },
};
ImGui_ImplVulkan_Init(&init_info);
}
+void Swapchain::start_frame() {
+ VkCommandBufferBeginInfo begin_info = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+ .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
+ };
+ CHECK_VULKAN(vkBeginCommandBuffer(cmd_buf.buffer, &begin_info));
+}
+
+void Swapchain::display(Texture2D& texture) {
+ std::cerr << "Displaying" << std::endl;
+ VkResult present_result = vkAcquireNextImageKHR(
+ device.device,
+ swapchain,
+ UINT64_MAX,
+ image_available_semaphores[semaphore_index],
+ VK_NULL_HANDLE,
+ &frame_index);
+ switch (present_result) {
+ case VK_ERROR_OUT_OF_DATE_KHR:
+ case VK_SUBOPTIMAL_KHR:
+ needs_recreate = true;
+ return;
+ case VK_SUCCESS:
+ break;
+ default:
+ CHECK_VULKAN(present_result);
+ }
+
+ std::cerr << "starting image layout transition" << std::endl;
+ // Command buffer begins at the very start of the frame
+ // Blit the texture to the swapchain image
+ {
+ // swapchain image layout, undefined -> transfer dst
+ VkImageMemoryBarrier swapchain_image_barrier = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .srcAccessMask = 0,
+ .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
+ .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
+ .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .image = swapchain_images[frame_index],
+ .subresourceRange = {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .levelCount = 1,
+ .layerCount = 1,
+ },
+ };
+ // src image layout, whatever -> transfer src
+ VkImageMemoryBarrier src_image_barrier = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .srcAccessMask = 0,
+ .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
+ .oldLayout = texture->layout,
+ .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+ .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .image = texture->image,
+ .subresourceRange = {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .levelCount = 1,
+ .layerCount = 1,
+ },
+ };
+ VkImageMemoryBarrier barriers[2] = {swapchain_image_barrier, src_image_barrier};
+ std::cerr << "starting image layout transition of 2" << std::endl;
+ vkCmdPipelineBarrier(
+ cmd_buf.buffer,
+ VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
+ VK_PIPELINE_STAGE_TRANSFER_BIT,
+ 0,
+ 0, nullptr,
+ 0, nullptr,
+ 2, barriers);
+
+ // Blit the texture to the swapchain image
+ VkImageBlit blit = {
+ .srcSubresource = {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .mipLevel = 0,
+ .baseArrayLayer = 0,
+ .layerCount = 1,
+ },
+ .srcOffsets = {
+ {0, 0, 0},
+ {static_cast<int32_t>(texture->extent.width), static_cast<int32_t>(texture->extent.height), 1},
+ },
+ .dstSubresource = {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .mipLevel = 0,
+ .baseArrayLayer = 0,
+ .layerCount = 1,
+ },
+ .dstOffsets = {
+ {0, 0, 0},
+ {static_cast<int32_t>(width), static_cast<int32_t>(height), 1},
+ },
+ };
+ std::cerr << "Starting blit" << std::endl;
+ vkCmdBlitImage(
+ cmd_buf.buffer,
+ texture->image,
+ VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+ swapchain_images[frame_index],
+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ 1,
+ &blit,
+ VK_FILTER_NEAREST);
+
+ // swapchain image layout, transfer dst -> color attachment
+ // re-use the swapchain_image_barrier
+ swapchain_image_barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+ swapchain_image_barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+ swapchain_image_barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+ swapchain_image_barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT |
+ VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+ vkCmdPipelineBarrier(
+ cmd_buf.buffer,
+ VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
+ VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
+ 0,
+ 0, nullptr,
+ 0, nullptr,
+ 1, &swapchain_image_barrier);
+ }
+
+ std::cerr << "starting render pass" << std::endl;
+ // Render
+ VkRenderPassBeginInfo render_pass_begin_info = {
+ .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
+ .renderPass = render_pass,
+ .framebuffer = framebuffers[frame_index],
+ .renderArea = {
+ .offset = {0, 0},
+ .extent = {width, height},
+ },
+ .clearValueCount = 0,
+ };
+ vkCmdBeginRenderPass(
+ cmd_buf.buffer,
+ &render_pass_begin_info,
+ VK_SUBPASS_CONTENTS_INLINE);
+ std::cerr << "rendering imgui" << std::endl;
+ ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), cmd_buf.buffer);
+ vkCmdEndRenderPass(cmd_buf.buffer);
+
+ // End command buffer and submit
+ CHECK_VULKAN(vkEndCommandBuffer(cmd_buf.buffer));
+ {
+ VkPipelineStageFlags wait_stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+ VkSubmitInfo submit_info = {
+ .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
+ .waitSemaphoreCount = 1,
+ .pWaitSemaphores = &image_available_semaphores[semaphore_index],
+ .pWaitDstStageMask = &wait_stage,
+ .commandBufferCount = 1,
+ .pCommandBuffers = &cmd_buf.buffer,
+ .signalSemaphoreCount = 1,
+ .pSignalSemaphores = &render_finished_semaphores[semaphore_index],
+ };
+ CHECK_VULKAN(vkQueueSubmit(
+ cmd_buf.queue,
+ 1,
+ &submit_info,
+ VK_NULL_HANDLE));
+ }
+
+ // Present the image to the swapchain
+ VkPresentInfoKHR present_info = {
+ .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
+ .waitSemaphoreCount = 1,
+ .pWaitSemaphores = &render_finished_semaphores[semaphore_index],
+ .swapchainCount = 1,
+ .pSwapchains = &swapchain,
+ .pImageIndices = &frame_index,
+ };
+ present_result = vkQueuePresentKHR(device.graphics_queue, &present_info);
+
+ switch (present_result) {
+ case VK_ERROR_OUT_OF_DATE_KHR:
+ case VK_SUBOPTIMAL_KHR:
+ needs_recreate = true;
+ case VK_SUCCESS:
+ break;
+ default:
+ CHECK_VULKAN(present_result);
+ }
+
+ // Rotate the semaphore index
+ semaphore_index = (semaphore_index + 1) % SWAPCHAIN_IMAGE_COUNT;
+}
+
Swapchain::~Swapchain() {
ImGui_ImplVulkan_Shutdown();
for (uint32_t i = 0; i < SWAPCHAIN_IMAGE_COUNT; i++) {
vkDestroyFramebuffer(device.device, framebuffers[i], nullptr);
vkDestroyImageView(device.device, swapchain_image_views[i], nullptr);
+ vkDestroySemaphore(device.device, image_available_semaphores[i], nullptr);
+ vkDestroySemaphore(device.device, render_finished_semaphores[i], nullptr);
}
vkDestroyDescriptorPool(device.device, descriptor_pool, nullptr);
vkDestroySwapchainKHR(device.device, swapchain, nullptr);
vkDestroyRenderPass(device.device, render_pass, nullptr);
vkDestroySurfaceKHR(device.instance, surface, nullptr);
+
+ upload_texture.reset();
}
} // namespace iris \ No newline at end of file
diff --git a/src/vulkan_swapchain.h b/src/vulkan_swapchain.h
index 509753f..b15028f 100644
--- a/src/vulkan_swapchain.h
+++ b/src/vulkan_swapchain.h
@@ -26,11 +26,20 @@ struct Swapchain {
VkImage swapchain_images[SWAPCHAIN_IMAGE_COUNT];
VkImageView swapchain_image_views[SWAPCHAIN_IMAGE_COUNT];
VkFramebuffer framebuffers[SWAPCHAIN_IMAGE_COUNT];
+ VkSemaphore image_available_semaphores[SWAPCHAIN_IMAGE_COUNT];
+ VkSemaphore render_finished_semaphores[SWAPCHAIN_IMAGE_COUNT];
+ CommandBuffer cmd_buf;
Texture2D upload_texture;
+ ImGuiContext *imgui_context = nullptr;
+ uint32_t semaphore_index = 0;
+ uint32_t frame_index = 0;
+
void resize(uint32_t new_width, uint32_t new_height);
- Swapchain(GLFWwindow *window, iris::Device device);
+ void start_frame();
+ void display(Texture2D &texture);
+ Swapchain(GLFWwindow *window, iris::Device device, uint32_t width, uint32_t height);
~Swapchain();
};