mirror of
https://github.com/nicbarker/clay.git
synced 2025-12-24 01:51:05 +00:00
1141 lines
47 KiB
C++
1141 lines
47 KiB
C++
// Vulkan + Clay demo
|
|
// Vulkan SDK documentation: https://vulkan.lunarg.com/doc/sdk
|
|
// ImGui integration ideas for font atlases and descriptor reuse: https://github.com/ocornut/imgui/wiki
|
|
|
|
#define GLFW_INCLUDE_VULKAN
|
|
#include <GLFW/glfw3.h>
|
|
|
|
#include <array>
|
|
#include <cstdint>
|
|
#include <cstdlib>
|
|
#include <iostream>
|
|
#include <limits>
|
|
#include <optional>
|
|
#include <set>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#define CLAY_IMPLEMENTATION
|
|
extern "C"
|
|
{
|
|
#include "../../clay.h"
|
|
#include "../../renderers/vulkan/clay_renderer_vulkan.c"
|
|
}
|
|
|
|
namespace
|
|
{
|
|
const uint32_t s_DefaultWidth = 1280;
|
|
const uint32_t s_DefaultHeight = 720;
|
|
const std::vector<const char*> s_DeviceExtensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME };
|
|
|
|
struct QueueFamilyIndices
|
|
{
|
|
std::optional<uint32_t> m_GraphicsFamily;
|
|
std::optional<uint32_t> m_PresentFamily;
|
|
|
|
bool IsComplete() const
|
|
{
|
|
return m_GraphicsFamily.has_value() && m_PresentFamily.has_value();
|
|
}
|
|
};
|
|
|
|
struct SwapchainSupportDetails
|
|
{
|
|
VkSurfaceCapabilitiesKHR m_Capabilities{};
|
|
std::vector<VkSurfaceFormatKHR> m_Formats{};
|
|
std::vector<VkPresentModeKHR> m_PresentModes{};
|
|
};
|
|
} // namespace
|
|
|
|
class VulkanDemoApp
|
|
{
|
|
public:
|
|
void Run()
|
|
{
|
|
try
|
|
{
|
|
InitializeWindow();
|
|
InitializeVulkan();
|
|
InitializeClay();
|
|
MainLoop();
|
|
Cleanup();
|
|
}
|
|
catch (const std::exception& l_Error)
|
|
{
|
|
std::cerr << "Demo failed: " << l_Error.what() << std::endl;
|
|
throw;
|
|
}
|
|
}
|
|
|
|
private:
|
|
GLFWwindow* m_Window = nullptr;
|
|
VkInstance m_Instance = VK_NULL_HANDLE;
|
|
VkSurfaceKHR m_Surface = VK_NULL_HANDLE;
|
|
VkPhysicalDevice m_PhysicalDevice = VK_NULL_HANDLE;
|
|
VkDevice m_Device = VK_NULL_HANDLE;
|
|
VkQueue m_GraphicsQueue = VK_NULL_HANDLE;
|
|
VkQueue m_PresentQueue = VK_NULL_HANDLE;
|
|
VkSwapchainKHR m_Swapchain = VK_NULL_HANDLE;
|
|
std::vector<VkImage> m_SwapchainImages{};
|
|
std::vector<VkImageView> m_SwapchainImageViews{};
|
|
VkFormat m_SwapchainImageFormat = VK_FORMAT_B8G8R8A8_SRGB;
|
|
VkExtent2D m_SwapchainExtent{};
|
|
VkRenderPass m_RenderPass = VK_NULL_HANDLE;
|
|
std::vector<VkFramebuffer> m_SwapchainFramebuffers{};
|
|
VkCommandPool m_CommandPool = VK_NULL_HANDLE;
|
|
std::vector<VkCommandBuffer> m_CommandBuffers{};
|
|
VkSemaphore m_ImageAvailableSemaphore = VK_NULL_HANDLE;
|
|
VkSemaphore m_RenderFinishedSemaphore = VK_NULL_HANDLE;
|
|
VkFence m_InFlightFence = VK_NULL_HANDLE;
|
|
VkDescriptorSetLayout m_DescriptorSetLayout = VK_NULL_HANDLE;
|
|
VkPipelineLayout m_PipelineLayout = VK_NULL_HANDLE;
|
|
VkPipeline m_RectPipeline = VK_NULL_HANDLE;
|
|
VkPipeline m_TextPipeline = VK_NULL_HANDLE;
|
|
VkPipeline m_ImagePipeline = VK_NULL_HANDLE;
|
|
VkDescriptorPool m_DescriptorPool = VK_NULL_HANDLE;
|
|
|
|
// Clay renderer bindings
|
|
ClayVulkanRenderer m_ClayRenderer{};
|
|
Clay_Arena m_ClayArena{};
|
|
|
|
void InitializeWindow()
|
|
{
|
|
if (!glfwInit())
|
|
{
|
|
throw std::runtime_error("Failed to initialize GLFW");
|
|
}
|
|
|
|
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
|
|
m_Window = glfwCreateWindow(static_cast<int>(s_DefaultWidth), static_cast<int>(s_DefaultHeight), "Clay Vulkan Demo", nullptr, nullptr);
|
|
if (m_Window == nullptr)
|
|
{
|
|
throw std::runtime_error("Failed to create GLFW window");
|
|
}
|
|
}
|
|
|
|
void InitializeVulkan()
|
|
{
|
|
CreateInstance();
|
|
CreateSurface();
|
|
PickPhysicalDevice();
|
|
CreateLogicalDevice();
|
|
CreateSwapchain();
|
|
CreateImageViews();
|
|
CreateRenderPass();
|
|
CreateDescriptorSetLayout();
|
|
CreatePipelineLayout();
|
|
CreatePipelines();
|
|
CreateFramebuffers();
|
|
CreateCommandPool();
|
|
AllocateCommandBuffers();
|
|
CreateSyncObjects();
|
|
}
|
|
|
|
void InitializeClay()
|
|
{
|
|
uint64_t l_TotalMemorySize = Clay_MinMemorySize();
|
|
m_ClayArena = Clay_CreateArenaWithCapacityAndMemory(l_TotalMemorySize, malloc(l_TotalMemorySize));
|
|
|
|
// Build the layout dimensions explicitly to keep MSVC happy with aggregate initialization rules.
|
|
Clay_Dimensions l_LayoutDimensions{};
|
|
l_LayoutDimensions.width = static_cast<float>(m_SwapchainExtent.width);
|
|
l_LayoutDimensions.height = static_cast<float>(m_SwapchainExtent.height);
|
|
|
|
Clay_Initialize(m_ClayArena, l_LayoutDimensions, Clay_ErrorHandler{ 0 });
|
|
|
|
Clay_VulkanRenderer_Init(&m_ClayRenderer, m_Device, m_RenderPass, m_PipelineLayout);
|
|
Clay_VulkanRenderer_SetPipelines(&m_ClayRenderer, m_RectPipeline, m_TextPipeline, m_ImagePipeline);
|
|
// Descriptor pool and layouts have been created; future improvements can allocate texture/font descriptors per frame.
|
|
Clay_VulkanRenderer_RegisterTextMeasure(&m_ClayRenderer);
|
|
}
|
|
|
|
void MainLoop()
|
|
{
|
|
while (!glfwWindowShouldClose(m_Window))
|
|
{
|
|
glfwPollEvents();
|
|
|
|
int l_Width = 0;
|
|
int l_Height = 0;
|
|
glfwGetFramebufferSize(m_Window, &l_Width, &l_Height);
|
|
m_SwapchainExtent.width = static_cast<uint32_t>(l_Width);
|
|
m_SwapchainExtent.height = static_cast<uint32_t>(l_Height);
|
|
|
|
Clay_Dimensions l_LayoutDimensions{};
|
|
l_LayoutDimensions.width = static_cast<float>(l_Width);
|
|
l_LayoutDimensions.height = static_cast<float>(l_Height);
|
|
Clay_SetLayoutDimensions(l_LayoutDimensions);
|
|
|
|
Clay_RenderCommandArray l_Commands = CreateLayout();
|
|
DrawFrame(l_Commands);
|
|
}
|
|
|
|
vkDeviceWaitIdle(m_Device);
|
|
}
|
|
|
|
void Cleanup()
|
|
{
|
|
vkDeviceWaitIdle(m_Device);
|
|
|
|
vkDestroyFence(m_Device, m_InFlightFence, nullptr);
|
|
vkDestroySemaphore(m_Device, m_RenderFinishedSemaphore, nullptr);
|
|
vkDestroySemaphore(m_Device, m_ImageAvailableSemaphore, nullptr);
|
|
|
|
if (m_RectPipeline != VK_NULL_HANDLE)
|
|
{
|
|
vkDestroyPipeline(m_Device, m_RectPipeline, nullptr);
|
|
}
|
|
if (m_TextPipeline != VK_NULL_HANDLE)
|
|
{
|
|
vkDestroyPipeline(m_Device, m_TextPipeline, nullptr);
|
|
}
|
|
if (m_ImagePipeline != VK_NULL_HANDLE)
|
|
{
|
|
vkDestroyPipeline(m_Device, m_ImagePipeline, nullptr);
|
|
}
|
|
if (m_PipelineLayout != VK_NULL_HANDLE)
|
|
{
|
|
vkDestroyPipelineLayout(m_Device, m_PipelineLayout, nullptr);
|
|
}
|
|
if (m_DescriptorSetLayout != VK_NULL_HANDLE)
|
|
{
|
|
vkDestroyDescriptorSetLayout(m_Device, m_DescriptorSetLayout, nullptr);
|
|
}
|
|
if (m_DescriptorPool != VK_NULL_HANDLE)
|
|
{
|
|
vkDestroyDescriptorPool(m_Device, m_DescriptorPool, nullptr);
|
|
}
|
|
|
|
for (VkFramebuffer l_Framebuffer : m_SwapchainFramebuffers)
|
|
{
|
|
vkDestroyFramebuffer(m_Device, l_Framebuffer, nullptr);
|
|
}
|
|
for (VkImageView l_ImageView : m_SwapchainImageViews)
|
|
{
|
|
vkDestroyImageView(m_Device, l_ImageView, nullptr);
|
|
}
|
|
|
|
vkDestroyRenderPass(m_Device, m_RenderPass, nullptr);
|
|
vkDestroyCommandPool(m_Device, m_CommandPool, nullptr);
|
|
vkDestroySwapchainKHR(m_Device, m_Swapchain, nullptr);
|
|
vkDestroyDevice(m_Device, nullptr);
|
|
vkDestroySurfaceKHR(m_Instance, m_Surface, nullptr);
|
|
vkDestroyInstance(m_Instance, nullptr);
|
|
|
|
glfwDestroyWindow(m_Window);
|
|
glfwTerminate();
|
|
|
|
// Clay_Arena owns memory allocated with malloc above; release the backing allocation explicitly.
|
|
free(m_ClayArena.memory);
|
|
}
|
|
|
|
void CreateInstance()
|
|
{
|
|
VkApplicationInfo l_AppInfo{};
|
|
l_AppInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
|
|
l_AppInfo.pApplicationName = "Clay Vulkan Demo";
|
|
l_AppInfo.applicationVersion = VK_MAKE_API_VERSION(0, 1, 0, 0);
|
|
l_AppInfo.pEngineName = "Clay";
|
|
l_AppInfo.engineVersion = VK_MAKE_API_VERSION(0, 1, 0, 0);
|
|
l_AppInfo.apiVersion = VK_API_VERSION_1_3;
|
|
|
|
VkInstanceCreateInfo l_CreateInfo{};
|
|
l_CreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
|
|
l_CreateInfo.pApplicationInfo = &l_AppInfo;
|
|
|
|
uint32_t l_GlfwExtensionCount = 0;
|
|
const char** l_Extensions = glfwGetRequiredInstanceExtensions(&l_GlfwExtensionCount);
|
|
l_CreateInfo.enabledExtensionCount = l_GlfwExtensionCount;
|
|
l_CreateInfo.ppEnabledExtensionNames = l_Extensions;
|
|
|
|
if (vkCreateInstance(&l_CreateInfo, nullptr, &m_Instance) != VK_SUCCESS)
|
|
{
|
|
throw std::runtime_error("Failed to create Vulkan instance; see Vulkan SDK setup guidance.");
|
|
}
|
|
}
|
|
|
|
VkShaderModule CreateShaderModule(const std::vector<uint32_t>& code)
|
|
{
|
|
VkShaderModuleCreateInfo l_CreateInfo{};
|
|
l_CreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
|
|
l_CreateInfo.codeSize = code.size() * sizeof(uint32_t);
|
|
l_CreateInfo.pCode = code.data();
|
|
|
|
VkShaderModule l_ShaderModule = VK_NULL_HANDLE;
|
|
if (vkCreateShaderModule(m_Device, &l_CreateInfo, nullptr, &l_ShaderModule) != VK_SUCCESS)
|
|
{
|
|
throw std::runtime_error("Failed to create shader module; see Vulkan SDK validation output.");
|
|
}
|
|
|
|
return l_ShaderModule;
|
|
}
|
|
|
|
void CreateDescriptorSetLayout()
|
|
{
|
|
// Texture sampling uses a single combined image sampler bound at set 0, binding 0.
|
|
VkDescriptorSetLayoutBinding l_SamplerBinding{};
|
|
l_SamplerBinding.binding = 0;
|
|
l_SamplerBinding.descriptorCount = 1;
|
|
l_SamplerBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
|
l_SamplerBinding.pImmutableSamplers = nullptr;
|
|
l_SamplerBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
|
|
|
|
VkDescriptorSetLayoutCreateInfo l_LayoutInfo{};
|
|
l_LayoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
|
|
l_LayoutInfo.bindingCount = 1;
|
|
l_LayoutInfo.pBindings = &l_SamplerBinding;
|
|
|
|
if (vkCreateDescriptorSetLayout(m_Device, &l_LayoutInfo, nullptr, &m_DescriptorSetLayout) != VK_SUCCESS)
|
|
{
|
|
throw std::runtime_error("Failed to create descriptor set layout for images");
|
|
}
|
|
|
|
// Keep a small descriptor pool ready for sample textures; real integrations should grow this pool.
|
|
VkDescriptorPoolSize l_PoolSize{};
|
|
l_PoolSize.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
|
l_PoolSize.descriptorCount = 8;
|
|
|
|
VkDescriptorPoolCreateInfo l_PoolInfo{};
|
|
l_PoolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
|
|
l_PoolInfo.poolSizeCount = 1;
|
|
l_PoolInfo.pPoolSizes = &l_PoolSize;
|
|
l_PoolInfo.maxSets = 8;
|
|
|
|
if (vkCreateDescriptorPool(m_Device, &l_PoolInfo, nullptr, &m_DescriptorPool) != VK_SUCCESS)
|
|
{
|
|
throw std::runtime_error("Failed to create descriptor pool for textures");
|
|
}
|
|
}
|
|
|
|
void CreatePipelineLayout()
|
|
{
|
|
VkPipelineLayoutCreateInfo l_PipelineLayoutInfo{};
|
|
l_PipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
|
|
l_PipelineLayoutInfo.setLayoutCount = 1;
|
|
l_PipelineLayoutInfo.pSetLayouts = &m_DescriptorSetLayout;
|
|
|
|
if (vkCreatePipelineLayout(m_Device, &l_PipelineLayoutInfo, nullptr, &m_PipelineLayout) != VK_SUCCESS)
|
|
{
|
|
throw std::runtime_error("Failed to create pipeline layout");
|
|
}
|
|
}
|
|
|
|
void CreatePipelines()
|
|
{
|
|
// Embedded SPIR-V shaders keep the demo self contained; see Vulkan SDK docs for shader compilation guidance.
|
|
static const std::array<uint32_t, 346> s_RectVertSpv = {
|
|
0x7230203,0x10000,0x8000b,0x32,0x0,0x20011,0x1,0x6000b,
|
|
0x1,0x4c534c47,0x6474732e,0x3035342e,0x0,0x3000e,0x0,0x1,
|
|
0x8000f,0x0,0x4,0x6e69616d,0x0,0xd,0x1b,0x29,
|
|
0x30003,0x2,0x1c2,0x40005,0x4,0x6e69616d,0x0,0x60005,
|
|
0xb,0x505f6c67,0x65567265,0x78657472,0x0,0x60006,0xb,0x0,
|
|
0x505f6c67,0x7469736f,0x6e6f69,0x70006,0xb,0x1,0x505f6c67,0x746e696f,
|
|
0x657a6953,0x0,0x70006,0xb,0x2,0x435f6c67,0x4470696c,0x61747369,
|
|
0x65636e,0x70006,0xb,0x3,0x435f6c67,0x446c6c75,0x61747369,0x65636e,
|
|
0x30005,0xd,0x0,0x60005,0x1b,0x565f6c67,0x65747265,0x646e4978,
|
|
0x7865,0x50005,0x1e,0x65646e69,0x6c626178,0x65,0x40005,0x29,
|
|
0x5574756f,0x56,0x50005,0x2f,0x65646e69,0x6c626178,0x65,0x30047,
|
|
0xb,0x2,0x50048,0xb,0x0,0xb,0x0,0x50048,
|
|
0xb,0x1,0xb,0x1,0x50048,0xb,0x2,0xb,
|
|
0x3,0x50048,0xb,0x3,0xb,0x4,0x40047,0x1b,
|
|
0xb,0x2a,0x40047,0x29,0x1e,0x0,0x20013,0x2,
|
|
0x30021,0x3,0x2,0x30016,0x6,0x20,0x40017,0x7,
|
|
0x6,0x4,0x40015,0x8,0x20,0x0,0x4002b,0x8,
|
|
0x9,0x1,0x4001c,0xa,0x6,0x9,0x6001e,0xb,
|
|
0x7,0x6,0xa,0xa,0x40020,0xc,0x3,0xb,
|
|
0x4003b,0xc,0xd,0x3,0x40015,0xe,0x20,0x1,
|
|
0x4002b,0xe,0xf,0x0,0x40017,0x10,0x6,0x2,
|
|
0x4002b,0x8,0x11,0x6,0x4001c,0x12,0x10,0x11,
|
|
0x4002b,0x6,0x13,0xbf800000,0x5002c,0x10,0x14,0x13,
|
|
0x13,0x4002b,0x6,0x15,0x3f800000,0x5002c,0x10,0x16,
|
|
0x15,0x13,0x5002c,0x10,0x17,0x15,0x15,0x5002c,
|
|
0x10,0x18,0x13,0x15,0x9002c,0x12,0x19,0x14,
|
|
0x16,0x17,0x14,0x17,0x18,0x40020,0x1a,0x1,
|
|
0xe,0x4003b,0x1a,0x1b,0x1,0x40020,0x1d,0x7,
|
|
0x12,0x40020,0x1f,0x7,0x10,0x4002b,0x6,0x22,
|
|
0x0,0x40020,0x26,0x3,0x7,0x40020,0x28,0x3,
|
|
0x10,0x4003b,0x28,0x29,0x3,0x5002c,0x10,0x2a,
|
|
0x22,0x22,0x5002c,0x10,0x2b,0x15,0x22,0x5002c,
|
|
0x10,0x2c,0x22,0x15,0x9002c,0x12,0x2d,0x2a,
|
|
0x2b,0x17,0x2a,0x17,0x2c,0x50036,0x2,0x4,
|
|
0x0,0x3,0x200f8,0x5,0x4003b,0x1d,0x1e,0x7,
|
|
0x4003b,0x1d,0x2f,0x7,0x4003d,0xe,0x1c,0x1b,
|
|
0x3003e,0x1e,0x19,0x50041,0x1f,0x20,0x1e,0x1c,
|
|
0x4003d,0x10,0x21,0x20,0x50051,0x6,0x23,0x21,
|
|
0x0,0x50051,0x6,0x24,0x21,0x1,0x70050,0x7,
|
|
0x25,0x23,0x24,0x22,0x15,0x50041,0x26,0x27,
|
|
0xd,0xf,0x3003e,0x27,0x25,0x4003d,0xe,0x2e,
|
|
0x1b,0x3003e,0x2f,0x2d,0x50041,0x1f,0x30,0x2f,
|
|
0x2e,0x4003d,0x10,0x31,0x30,0x3003e,0x29,0x31,
|
|
0x100fd,0x10038,
|
|
};
|
|
static const std::array<uint32_t, 123> s_ColorFragSpv = {
|
|
0x7230203,0x10000,0x8000b,0x13,0x0,0x20011,0x1,0x6000b,
|
|
0x1,0x4c534c47,0x6474732e,0x3035342e,0x0,0x3000e,0x0,0x1,
|
|
0x7000f,0x4,0x4,0x6e69616d,0x0,0x9,0xc,0x30010,
|
|
0x4,0x7,0x30003,0x2,0x1c2,0x40005,0x4,0x6e69616d,
|
|
0x0,0x50005,0x9,0x4374756f,0x726f6c6f,0x0,0x40005,0xc,
|
|
0x56556e69,0x0,0x40047,0x9,0x1e,0x0,0x40047,0xc,
|
|
0x1e,0x0,0x20013,0x2,0x30021,0x3,0x2,0x30016,
|
|
0x6,0x20,0x40017,0x7,0x6,0x4,0x40020,0x8,
|
|
0x3,0x7,0x4003b,0x8,0x9,0x3,0x40017,0xa,
|
|
0x6,0x2,0x40020,0xb,0x1,0xa,0x4003b,0xb,
|
|
0xc,0x1,0x4002b,0x6,0xe,0x3f000000,0x4002b,0x6,
|
|
0xf,0x3f800000,0x50036,0x2,0x4,0x0,0x3,0x200f8,
|
|
0x5,0x4003d,0xa,0xd,0xc,0x50051,0x6,0x10,
|
|
0xd,0x0,0x50051,0x6,0x11,0xd,0x1,0x70050,
|
|
0x7,0x12,0x10,0x11,0xe,0xf,0x3003e,0x9,
|
|
0x12,0x100fd,0x10038,
|
|
};
|
|
static const std::array<uint32_t, 140> s_ImageFragSpv = {
|
|
0x7230203,0x10000,0x8000b,0x14,0x0,0x20011,0x1,0x6000b,
|
|
0x1,0x4c534c47,0x6474732e,0x3035342e,0x0,0x3000e,0x0,0x1,
|
|
0x7000f,0x4,0x4,0x6e69616d,0x0,0x9,0x11,0x30010,
|
|
0x4,0x7,0x30003,0x2,0x1c2,0x40005,0x4,0x6e69616d,
|
|
0x0,0x50005,0x9,0x4374756f,0x726f6c6f,0x0,0x50005,0xd,
|
|
0x78655475,0x65727574,0x0,0x40005,0x11,0x56556e69,0x0,0x40047,
|
|
0x9,0x1e,0x0,0x40047,0xd,0x21,0x0,0x40047,
|
|
0xd,0x22,0x0,0x40047,0x11,0x1e,0x0,0x20013,
|
|
0x2,0x30021,0x3,0x2,0x30016,0x6,0x20,0x40017,
|
|
0x7,0x6,0x4,0x40020,0x8,0x3,0x7,0x4003b,
|
|
0x8,0x9,0x3,0x90019,0xa,0x6,0x1,0x0,
|
|
0x0,0x0,0x1,0x0,0x3001b,0xb,0xa,0x40020,
|
|
0xc,0x0,0xb,0x4003b,0xc,0xd,0x0,0x40017,
|
|
0xf,0x6,0x2,0x40020,0x10,0x1,0xf,0x4003b,
|
|
0x10,0x11,0x1,0x50036,0x2,0x4,0x0,0x3,
|
|
0x200f8,0x5,0x4003d,0xb,0xe,0xd,0x4003d,0xf,
|
|
0x12,0x11,0x50057,0x7,0x13,0xe,0x12,0x3003e,
|
|
0x9,0x13,0x100fd,0x10038,
|
|
};
|
|
|
|
const std::vector<uint32_t> l_VertCode(s_RectVertSpv.begin(), s_RectVertSpv.end());
|
|
const std::vector<uint32_t> l_ColorFragCode(s_ColorFragSpv.begin(), s_ColorFragSpv.end());
|
|
const std::vector<uint32_t> l_ImageFragCode(s_ImageFragSpv.begin(), s_ImageFragSpv.end());
|
|
|
|
VkShaderModule l_VertShaderModule = CreateShaderModule(l_VertCode);
|
|
VkShaderModule l_ColorFragShaderModule = CreateShaderModule(l_ColorFragCode);
|
|
VkShaderModule l_ImageFragShaderModule = CreateShaderModule(l_ImageFragCode);
|
|
|
|
VkPipelineShaderStageCreateInfo l_VertStageInfo{};
|
|
l_VertStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
|
l_VertStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;
|
|
l_VertStageInfo.module = l_VertShaderModule;
|
|
l_VertStageInfo.pName = "main";
|
|
|
|
VkPipelineShaderStageCreateInfo l_ColorFragStage{};
|
|
l_ColorFragStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
|
l_ColorFragStage.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
|
|
l_ColorFragStage.module = l_ColorFragShaderModule;
|
|
l_ColorFragStage.pName = "main";
|
|
|
|
VkPipelineShaderStageCreateInfo l_ImageFragStage{};
|
|
l_ImageFragStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
|
l_ImageFragStage.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
|
|
l_ImageFragStage.module = l_ImageFragShaderModule;
|
|
l_ImageFragStage.pName = "main";
|
|
|
|
VkPipelineShaderStageCreateInfo l_ColorStages[] = { l_VertStageInfo, l_ColorFragStage };
|
|
VkPipelineShaderStageCreateInfo l_ImageStages[] = { l_VertStageInfo, l_ImageFragStage };
|
|
|
|
VkPipelineVertexInputStateCreateInfo l_VertexInputInfo{};
|
|
l_VertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
|
|
|
|
VkPipelineInputAssemblyStateCreateInfo l_InputAssembly{};
|
|
l_InputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
|
|
l_InputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
|
l_InputAssembly.primitiveRestartEnable = VK_FALSE;
|
|
|
|
VkViewport l_Viewport{};
|
|
l_Viewport.x = 0.0f;
|
|
l_Viewport.y = 0.0f;
|
|
l_Viewport.width = static_cast<float>(m_SwapchainExtent.width);
|
|
l_Viewport.height = static_cast<float>(m_SwapchainExtent.height);
|
|
l_Viewport.minDepth = 0.0f;
|
|
l_Viewport.maxDepth = 1.0f;
|
|
|
|
VkRect2D l_Scissor{};
|
|
l_Scissor.offset = { 0, 0 };
|
|
l_Scissor.extent = m_SwapchainExtent;
|
|
|
|
VkPipelineViewportStateCreateInfo l_ViewportState{};
|
|
l_ViewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
|
|
l_ViewportState.viewportCount = 1;
|
|
l_ViewportState.pViewports = &l_Viewport;
|
|
l_ViewportState.scissorCount = 1;
|
|
l_ViewportState.pScissors = &l_Scissor;
|
|
|
|
VkPipelineRasterizationStateCreateInfo l_Rasterizer{};
|
|
l_Rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
|
|
l_Rasterizer.depthClampEnable = VK_FALSE;
|
|
l_Rasterizer.rasterizerDiscardEnable = VK_FALSE;
|
|
l_Rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
|
|
l_Rasterizer.lineWidth = 1.0f;
|
|
l_Rasterizer.cullMode = VK_CULL_MODE_BACK_BIT;
|
|
l_Rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
|
|
l_Rasterizer.depthBiasEnable = VK_FALSE;
|
|
|
|
VkPipelineMultisampleStateCreateInfo l_Multisampling{};
|
|
l_Multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
|
|
l_Multisampling.sampleShadingEnable = VK_FALSE;
|
|
l_Multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
|
|
|
|
VkPipelineColorBlendAttachmentState l_ColorBlendAttachment{};
|
|
l_ColorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
|
|
l_ColorBlendAttachment.blendEnable = VK_TRUE;
|
|
l_ColorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
|
|
l_ColorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
|
|
l_ColorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
|
|
l_ColorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
|
|
l_ColorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
|
|
l_ColorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
|
|
|
|
VkPipelineColorBlendStateCreateInfo l_ColorBlending{};
|
|
l_ColorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
|
|
l_ColorBlending.logicOpEnable = VK_FALSE;
|
|
l_ColorBlending.attachmentCount = 1;
|
|
l_ColorBlending.pAttachments = &l_ColorBlendAttachment;
|
|
|
|
std::vector<VkDynamicState> l_DynamicStates = { VK_DYNAMIC_STATE_SCISSOR, VK_DYNAMIC_STATE_VIEWPORT };
|
|
VkPipelineDynamicStateCreateInfo l_DynamicState{};
|
|
l_DynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
|
|
l_DynamicState.dynamicStateCount = static_cast<uint32_t>(l_DynamicStates.size());
|
|
l_DynamicState.pDynamicStates = l_DynamicStates.data();
|
|
|
|
VkGraphicsPipelineCreateInfo l_PipelineInfo{};
|
|
l_PipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
|
|
l_PipelineInfo.pVertexInputState = &l_VertexInputInfo;
|
|
l_PipelineInfo.pInputAssemblyState = &l_InputAssembly;
|
|
l_PipelineInfo.pViewportState = &l_ViewportState;
|
|
l_PipelineInfo.pRasterizationState = &l_Rasterizer;
|
|
l_PipelineInfo.pMultisampleState = &l_Multisampling;
|
|
l_PipelineInfo.pColorBlendState = &l_ColorBlending;
|
|
l_PipelineInfo.pDynamicState = &l_DynamicState;
|
|
l_PipelineInfo.layout = m_PipelineLayout;
|
|
l_PipelineInfo.renderPass = m_RenderPass;
|
|
l_PipelineInfo.subpass = 0;
|
|
|
|
l_PipelineInfo.stageCount = 2;
|
|
l_PipelineInfo.pStages = l_ColorStages;
|
|
if (vkCreateGraphicsPipelines(m_Device, VK_NULL_HANDLE, 1, &l_PipelineInfo, nullptr, &m_RectPipeline) != VK_SUCCESS)
|
|
{
|
|
throw std::runtime_error("Failed to create rectangle pipeline");
|
|
}
|
|
|
|
l_PipelineInfo.pStages = l_ImageStages;
|
|
if (vkCreateGraphicsPipelines(m_Device, VK_NULL_HANDLE, 1, &l_PipelineInfo, nullptr, &m_ImagePipeline) != VK_SUCCESS)
|
|
{
|
|
throw std::runtime_error("Failed to create image pipeline");
|
|
}
|
|
|
|
// Text rendering can reuse the color fragment shader for now; future work can swap in a signed-distance-field shader.
|
|
l_PipelineInfo.pStages = l_ColorStages;
|
|
if (vkCreateGraphicsPipelines(m_Device, VK_NULL_HANDLE, 1, &l_PipelineInfo, nullptr, &m_TextPipeline) != VK_SUCCESS)
|
|
{
|
|
throw std::runtime_error("Failed to create text pipeline");
|
|
}
|
|
|
|
vkDestroyShaderModule(m_Device, l_VertShaderModule, nullptr);
|
|
vkDestroyShaderModule(m_Device, l_ColorFragShaderModule, nullptr);
|
|
vkDestroyShaderModule(m_Device, l_ImageFragShaderModule, nullptr);
|
|
}
|
|
|
|
void CreateSurface()
|
|
{
|
|
if (glfwCreateWindowSurface(m_Instance, m_Window, nullptr, &m_Surface) != VK_SUCCESS)
|
|
{
|
|
throw std::runtime_error("Failed to create window surface");
|
|
}
|
|
}
|
|
|
|
void PickPhysicalDevice()
|
|
{
|
|
uint32_t l_DeviceCount = 0;
|
|
vkEnumeratePhysicalDevices(m_Instance, &l_DeviceCount, nullptr);
|
|
if (l_DeviceCount == 0)
|
|
{
|
|
throw std::runtime_error("No Vulkan-capable GPU found");
|
|
}
|
|
|
|
std::vector<VkPhysicalDevice> l_Devices(l_DeviceCount);
|
|
vkEnumeratePhysicalDevices(m_Instance, &l_DeviceCount, l_Devices.data());
|
|
|
|
for (const VkPhysicalDevice& it_Device : l_Devices)
|
|
{
|
|
if (IsDeviceSuitable(it_Device))
|
|
{
|
|
m_PhysicalDevice = it_Device;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (m_PhysicalDevice == VK_NULL_HANDLE)
|
|
{
|
|
throw std::runtime_error("No suitable GPU found");
|
|
}
|
|
}
|
|
|
|
void CreateLogicalDevice()
|
|
{
|
|
QueueFamilyIndices l_Indices = FindQueueFamilies(m_PhysicalDevice);
|
|
|
|
std::vector<VkDeviceQueueCreateInfo> l_QueueCreateInfos{};
|
|
std::set<uint32_t> l_UniqueQueueFamilies = { l_Indices.m_GraphicsFamily.value(), l_Indices.m_PresentFamily.value() };
|
|
|
|
float l_QueuePriority = 1.0f;
|
|
for (uint32_t it_FamilyIndex : l_UniqueQueueFamilies)
|
|
{
|
|
VkDeviceQueueCreateInfo l_QueueCreateInfo{};
|
|
l_QueueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
|
|
l_QueueCreateInfo.queueFamilyIndex = it_FamilyIndex;
|
|
l_QueueCreateInfo.queueCount = 1;
|
|
l_QueueCreateInfo.pQueuePriorities = &l_QueuePriority;
|
|
l_QueueCreateInfos.push_back(l_QueueCreateInfo);
|
|
}
|
|
|
|
VkPhysicalDeviceFeatures l_DeviceFeatures{};
|
|
VkDeviceCreateInfo l_CreateInfo{};
|
|
l_CreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
|
|
l_CreateInfo.queueCreateInfoCount = static_cast<uint32_t>(l_QueueCreateInfos.size());
|
|
l_CreateInfo.pQueueCreateInfos = l_QueueCreateInfos.data();
|
|
l_CreateInfo.pEnabledFeatures = &l_DeviceFeatures;
|
|
l_CreateInfo.enabledExtensionCount = static_cast<uint32_t>(s_DeviceExtensions.size());
|
|
l_CreateInfo.ppEnabledExtensionNames = s_DeviceExtensions.data();
|
|
|
|
if (vkCreateDevice(m_PhysicalDevice, &l_CreateInfo, nullptr, &m_Device) != VK_SUCCESS)
|
|
{
|
|
throw std::runtime_error("Failed to create logical device");
|
|
}
|
|
|
|
vkGetDeviceQueue(m_Device, l_Indices.m_GraphicsFamily.value(), 0, &m_GraphicsQueue);
|
|
vkGetDeviceQueue(m_Device, l_Indices.m_PresentFamily.value(), 0, &m_PresentQueue);
|
|
}
|
|
|
|
void CreateSwapchain()
|
|
{
|
|
SwapchainSupportDetails l_Support = QuerySwapchainSupport(m_PhysicalDevice);
|
|
|
|
VkSurfaceFormatKHR l_SurfaceFormat = ChooseSwapSurfaceFormat(l_Support.m_Formats);
|
|
VkPresentModeKHR l_PresentMode = ChoosePresentMode(l_Support.m_PresentModes);
|
|
VkExtent2D l_Extent = ChooseSwapExtent(l_Support.m_Capabilities);
|
|
|
|
uint32_t l_ImageCount = l_Support.m_Capabilities.minImageCount + 1;
|
|
if (l_Support.m_Capabilities.maxImageCount > 0 && l_ImageCount > l_Support.m_Capabilities.maxImageCount)
|
|
{
|
|
l_ImageCount = l_Support.m_Capabilities.maxImageCount;
|
|
}
|
|
|
|
VkSwapchainCreateInfoKHR l_CreateInfo{};
|
|
l_CreateInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
|
|
l_CreateInfo.surface = m_Surface;
|
|
l_CreateInfo.minImageCount = l_ImageCount;
|
|
l_CreateInfo.imageFormat = l_SurfaceFormat.format;
|
|
l_CreateInfo.imageColorSpace = l_SurfaceFormat.colorSpace;
|
|
l_CreateInfo.imageExtent = l_Extent;
|
|
l_CreateInfo.imageArrayLayers = 1;
|
|
l_CreateInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
|
|
|
|
QueueFamilyIndices l_Indices = FindQueueFamilies(m_PhysicalDevice);
|
|
uint32_t l_QueueFamilyIndices[] = { l_Indices.m_GraphicsFamily.value(), l_Indices.m_PresentFamily.value() };
|
|
|
|
if (l_Indices.m_GraphicsFamily != l_Indices.m_PresentFamily)
|
|
{
|
|
l_CreateInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
|
|
l_CreateInfo.queueFamilyIndexCount = 2;
|
|
l_CreateInfo.pQueueFamilyIndices = l_QueueFamilyIndices;
|
|
}
|
|
else
|
|
{
|
|
l_CreateInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
|
l_CreateInfo.queueFamilyIndexCount = 0;
|
|
l_CreateInfo.pQueueFamilyIndices = nullptr;
|
|
}
|
|
|
|
l_CreateInfo.preTransform = l_Support.m_Capabilities.currentTransform;
|
|
l_CreateInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
|
|
l_CreateInfo.presentMode = l_PresentMode;
|
|
l_CreateInfo.clipped = VK_TRUE;
|
|
|
|
if (vkCreateSwapchainKHR(m_Device, &l_CreateInfo, nullptr, &m_Swapchain) != VK_SUCCESS)
|
|
{
|
|
throw std::runtime_error("Failed to create swapchain");
|
|
}
|
|
|
|
vkGetSwapchainImagesKHR(m_Device, m_Swapchain, &l_ImageCount, nullptr);
|
|
m_SwapchainImages.resize(l_ImageCount);
|
|
vkGetSwapchainImagesKHR(m_Device, m_Swapchain, &l_ImageCount, m_SwapchainImages.data());
|
|
|
|
m_SwapchainImageFormat = l_SurfaceFormat.format;
|
|
m_SwapchainExtent = l_Extent;
|
|
}
|
|
|
|
void CreateImageViews()
|
|
{
|
|
m_SwapchainImageViews.resize(m_SwapchainImages.size());
|
|
|
|
for (size_t it_Index = 0; it_Index < m_SwapchainImages.size(); ++it_Index)
|
|
{
|
|
VkImageViewCreateInfo l_CreateInfo{};
|
|
l_CreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
|
l_CreateInfo.image = m_SwapchainImages[it_Index];
|
|
l_CreateInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
|
l_CreateInfo.format = m_SwapchainImageFormat;
|
|
l_CreateInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
|
|
l_CreateInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
|
|
l_CreateInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
|
|
l_CreateInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
|
|
l_CreateInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
l_CreateInfo.subresourceRange.baseMipLevel = 0;
|
|
l_CreateInfo.subresourceRange.levelCount = 1;
|
|
l_CreateInfo.subresourceRange.baseArrayLayer = 0;
|
|
l_CreateInfo.subresourceRange.layerCount = 1;
|
|
|
|
if (vkCreateImageView(m_Device, &l_CreateInfo, nullptr, &m_SwapchainImageViews[it_Index]) != VK_SUCCESS)
|
|
{
|
|
throw std::runtime_error("Failed to create image views");
|
|
}
|
|
}
|
|
}
|
|
|
|
void CreateRenderPass()
|
|
{
|
|
VkAttachmentDescription l_ColorAttachment{};
|
|
l_ColorAttachment.format = m_SwapchainImageFormat;
|
|
l_ColorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
|
|
l_ColorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
|
l_ColorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
|
l_ColorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
|
l_ColorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
|
l_ColorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
l_ColorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
|
|
|
VkAttachmentReference l_ColorAttachmentRef{};
|
|
l_ColorAttachmentRef.attachment = 0;
|
|
l_ColorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
|
|
|
VkSubpassDescription l_Subpass{};
|
|
l_Subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
|
l_Subpass.colorAttachmentCount = 1;
|
|
l_Subpass.pColorAttachments = &l_ColorAttachmentRef;
|
|
|
|
VkRenderPassCreateInfo l_RenderPassInfo{};
|
|
l_RenderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
|
|
l_RenderPassInfo.attachmentCount = 1;
|
|
l_RenderPassInfo.pAttachments = &l_ColorAttachment;
|
|
l_RenderPassInfo.subpassCount = 1;
|
|
l_RenderPassInfo.pSubpasses = &l_Subpass;
|
|
|
|
if (vkCreateRenderPass(m_Device, &l_RenderPassInfo, nullptr, &m_RenderPass) != VK_SUCCESS)
|
|
{
|
|
throw std::runtime_error("Failed to create render pass");
|
|
}
|
|
}
|
|
|
|
void CreateFramebuffers()
|
|
{
|
|
m_SwapchainFramebuffers.resize(m_SwapchainImageViews.size());
|
|
|
|
for (size_t it_Index = 0; it_Index < m_SwapchainImageViews.size(); ++it_Index)
|
|
{
|
|
VkImageView l_Attachments[] = { m_SwapchainImageViews[it_Index] };
|
|
|
|
VkFramebufferCreateInfo l_FramebufferInfo{};
|
|
l_FramebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
|
|
l_FramebufferInfo.renderPass = m_RenderPass;
|
|
l_FramebufferInfo.attachmentCount = 1;
|
|
l_FramebufferInfo.pAttachments = l_Attachments;
|
|
l_FramebufferInfo.width = m_SwapchainExtent.width;
|
|
l_FramebufferInfo.height = m_SwapchainExtent.height;
|
|
l_FramebufferInfo.layers = 1;
|
|
|
|
if (vkCreateFramebuffer(m_Device, &l_FramebufferInfo, nullptr, &m_SwapchainFramebuffers[it_Index]) != VK_SUCCESS)
|
|
{
|
|
throw std::runtime_error("Failed to create framebuffer");
|
|
}
|
|
}
|
|
}
|
|
|
|
void CreateCommandPool()
|
|
{
|
|
QueueFamilyIndices l_QueueFamilyIndices = FindQueueFamilies(m_PhysicalDevice);
|
|
|
|
VkCommandPoolCreateInfo l_PoolInfo{};
|
|
l_PoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
|
|
l_PoolInfo.queueFamilyIndex = l_QueueFamilyIndices.m_GraphicsFamily.value();
|
|
l_PoolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
|
|
|
|
if (vkCreateCommandPool(m_Device, &l_PoolInfo, nullptr, &m_CommandPool) != VK_SUCCESS)
|
|
{
|
|
throw std::runtime_error("Failed to create command pool");
|
|
}
|
|
}
|
|
|
|
void AllocateCommandBuffers()
|
|
{
|
|
m_CommandBuffers.resize(m_SwapchainFramebuffers.size());
|
|
|
|
VkCommandBufferAllocateInfo l_AllocInfo{};
|
|
l_AllocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
|
l_AllocInfo.commandPool = m_CommandPool;
|
|
l_AllocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
|
l_AllocInfo.commandBufferCount = static_cast<uint32_t>(m_CommandBuffers.size());
|
|
|
|
if (vkAllocateCommandBuffers(m_Device, &l_AllocInfo, m_CommandBuffers.data()) != VK_SUCCESS)
|
|
{
|
|
throw std::runtime_error("Failed to allocate command buffers");
|
|
}
|
|
}
|
|
|
|
void CreateSyncObjects()
|
|
{
|
|
VkSemaphoreCreateInfo l_SemaphoreInfo{};
|
|
l_SemaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
|
|
|
|
VkFenceCreateInfo l_FenceInfo{};
|
|
l_FenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
|
|
l_FenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
|
|
|
|
if (vkCreateSemaphore(m_Device, &l_SemaphoreInfo, nullptr, &m_ImageAvailableSemaphore) != VK_SUCCESS ||
|
|
vkCreateSemaphore(m_Device, &l_SemaphoreInfo, nullptr, &m_RenderFinishedSemaphore) != VK_SUCCESS ||
|
|
vkCreateFence(m_Device, &l_FenceInfo, nullptr, &m_InFlightFence) != VK_SUCCESS)
|
|
{
|
|
throw std::runtime_error("Failed to create synchronization objects");
|
|
}
|
|
}
|
|
|
|
void DrawFrame(const Clay_RenderCommandArray& renderCommands)
|
|
{
|
|
vkWaitForFences(m_Device, 1, &m_InFlightFence, VK_TRUE, UINT64_MAX);
|
|
vkResetFences(m_Device, 1, &m_InFlightFence);
|
|
|
|
uint32_t l_ImageIndex = 0;
|
|
vkAcquireNextImageKHR(m_Device, m_Swapchain, UINT64_MAX, m_ImageAvailableSemaphore, VK_NULL_HANDLE, &l_ImageIndex);
|
|
|
|
vkResetCommandBuffer(m_CommandBuffers[l_ImageIndex], 0);
|
|
RecordCommandBuffer(m_CommandBuffers[l_ImageIndex], l_ImageIndex, renderCommands);
|
|
|
|
VkSemaphore l_WaitSemaphores[] = { m_ImageAvailableSemaphore };
|
|
VkPipelineStageFlags l_WaitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
|
|
VkSemaphore l_SignalSemaphores[] = { m_RenderFinishedSemaphore };
|
|
|
|
VkSubmitInfo l_SubmitInfo{};
|
|
l_SubmitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
|
l_SubmitInfo.waitSemaphoreCount = 1;
|
|
l_SubmitInfo.pWaitSemaphores = l_WaitSemaphores;
|
|
l_SubmitInfo.pWaitDstStageMask = l_WaitStages;
|
|
l_SubmitInfo.commandBufferCount = 1;
|
|
l_SubmitInfo.pCommandBuffers = &m_CommandBuffers[l_ImageIndex];
|
|
l_SubmitInfo.signalSemaphoreCount = 1;
|
|
l_SubmitInfo.pSignalSemaphores = l_SignalSemaphores;
|
|
|
|
if (vkQueueSubmit(m_GraphicsQueue, 1, &l_SubmitInfo, m_InFlightFence) != VK_SUCCESS)
|
|
{
|
|
throw std::runtime_error("Failed to submit draw command buffer");
|
|
}
|
|
|
|
VkPresentInfoKHR l_PresentInfo{};
|
|
l_PresentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
|
|
l_PresentInfo.waitSemaphoreCount = 1;
|
|
l_PresentInfo.pWaitSemaphores = l_SignalSemaphores;
|
|
l_PresentInfo.swapchainCount = 1;
|
|
l_PresentInfo.pSwapchains = &m_Swapchain;
|
|
l_PresentInfo.pImageIndices = &l_ImageIndex;
|
|
|
|
vkQueuePresentKHR(m_PresentQueue, &l_PresentInfo);
|
|
}
|
|
|
|
void RecordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex, const Clay_RenderCommandArray& renderCommands)
|
|
{
|
|
VkCommandBufferBeginInfo l_BeginInfo{};
|
|
l_BeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
|
|
|
if (vkBeginCommandBuffer(commandBuffer, &l_BeginInfo) != VK_SUCCESS)
|
|
{
|
|
throw std::runtime_error("Failed to begin recording command buffer");
|
|
}
|
|
|
|
VkClearValue l_ClearColor{};
|
|
l_ClearColor.color = { { 0.1f, 0.1f, 0.12f, 1.0f } };
|
|
|
|
VkRenderPassBeginInfo l_RenderPassInfo{};
|
|
l_RenderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
|
|
l_RenderPassInfo.renderPass = m_RenderPass;
|
|
l_RenderPassInfo.framebuffer = m_SwapchainFramebuffers[imageIndex];
|
|
l_RenderPassInfo.renderArea.offset = { 0, 0 };
|
|
l_RenderPassInfo.renderArea.extent = m_SwapchainExtent;
|
|
l_RenderPassInfo.clearValueCount = 1;
|
|
l_RenderPassInfo.pClearValues = &l_ClearColor;
|
|
|
|
VkViewport l_Viewport{};
|
|
l_Viewport.x = 0.0f;
|
|
l_Viewport.y = 0.0f;
|
|
l_Viewport.width = static_cast<float>(m_SwapchainExtent.width);
|
|
l_Viewport.height = static_cast<float>(m_SwapchainExtent.height);
|
|
l_Viewport.minDepth = 0.0f;
|
|
l_Viewport.maxDepth = 1.0f;
|
|
|
|
VkRect2D l_Scissor{};
|
|
l_Scissor.offset = { 0, 0 };
|
|
l_Scissor.extent = m_SwapchainExtent;
|
|
|
|
vkCmdBeginRenderPass(commandBuffer, &l_RenderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
|
|
|
|
// Set dynamic state expected by the pipelines before Clay emits draw calls.
|
|
vkCmdSetViewport(commandBuffer, 0, 1, &l_Viewport);
|
|
vkCmdSetScissor(commandBuffer, 0, 1, &l_Scissor);
|
|
|
|
// Clay renderer translates render commands into Vulkan draw calls using the pipelines prepared during initialization.
|
|
Clay_Vulkan_Render(&m_ClayRenderer, renderCommands, commandBuffer);
|
|
|
|
vkCmdEndRenderPass(commandBuffer);
|
|
|
|
if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS)
|
|
{
|
|
throw std::runtime_error("Failed to record command buffer");
|
|
}
|
|
}
|
|
|
|
Clay_RenderCommandArray CreateLayout()
|
|
{
|
|
Clay_BeginLayout();
|
|
|
|
// Shared sizing config to expand containers along both axes.
|
|
Clay_Sizing l_Expand{};
|
|
l_Expand.width = CLAY_SIZING_GROW(0);
|
|
l_Expand.height = CLAY_SIZING_GROW(0);
|
|
|
|
// Root container stacks children vertically and adds generous padding.
|
|
CLAY(CLAY_ID("Root"), {
|
|
.layout = {
|
|
.sizing = l_Expand,
|
|
.padding = CLAY_PADDING_ALL(12),
|
|
.childGap = 12,
|
|
.childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_TOP },
|
|
.layoutDirection = CLAY_TOP_TO_BOTTOM
|
|
},
|
|
.backgroundColor = { 18, 18, 20, 255 }
|
|
})
|
|
{
|
|
// Header keeps children centered and reserves a fixed height.
|
|
CLAY(CLAY_ID("Header"), {
|
|
.layout = {
|
|
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(64) },
|
|
.padding = CLAY_PADDING_ALL(0),
|
|
.childGap = 0,
|
|
.childAlignment = {.x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER },
|
|
.layoutDirection = CLAY_LEFT_TO_RIGHT
|
|
},
|
|
.backgroundColor = { 32, 64, 96, 255 }
|
|
})
|
|
{
|
|
// Keep designated fields in declaration order for MSVC aggregate compatibility.
|
|
Clay_TextElementConfig* l_HeaderTextConfig = CLAY_TEXT_CONFIG({
|
|
.userData = nullptr,
|
|
.textColor = { 245, 245, 245, 255 },
|
|
.fontId = 0,
|
|
.fontSize = 28
|
|
});
|
|
CLAY_TEXT(CLAY_STRING("Clay Vulkan Demo"), l_HeaderTextConfig);
|
|
}
|
|
|
|
// Body splits the space horizontally into a sidebar and viewport.
|
|
CLAY(CLAY_ID("Body"), {
|
|
.layout = {
|
|
.sizing = l_Expand,
|
|
.padding = CLAY_PADDING_ALL(0),
|
|
.childGap = 10,
|
|
.childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_TOP },
|
|
.layoutDirection = CLAY_LEFT_TO_RIGHT
|
|
}
|
|
})
|
|
{
|
|
// Sidebar lists current wiring status.
|
|
CLAY(CLAY_ID("Sidebar"), {
|
|
.layout = {
|
|
.sizing = {.width = CLAY_SIZING_FIXED(240), .height = CLAY_SIZING_GROW(0) },
|
|
.padding = CLAY_PADDING_ALL(10),
|
|
.childGap = 8,
|
|
.childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_TOP },
|
|
.layoutDirection = CLAY_TOP_TO_BOTTOM
|
|
},
|
|
.backgroundColor = { 40, 40, 44, 255 }
|
|
})
|
|
{
|
|
// Keep designated fields in declaration order for MSVC aggregate compatibility.
|
|
Clay_TextElementConfig* l_StatusTextConfig = CLAY_TEXT_CONFIG({
|
|
.userData = nullptr,
|
|
.textColor = { 200, 220, 255, 255 },
|
|
.fontId = 0,
|
|
.fontSize = 20
|
|
});
|
|
CLAY_TEXT(CLAY_STRING("Swapchain + Clay wiring ready.\nTODO: upload fonts and textures."), l_StatusTextConfig);
|
|
}
|
|
|
|
// Viewport stands in for rendered content.
|
|
CLAY(CLAY_ID("Viewport"), {
|
|
.layout = {
|
|
.sizing = l_Expand,
|
|
.padding = CLAY_PADDING_ALL(0),
|
|
.childGap = 0,
|
|
.childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_TOP },
|
|
.layoutDirection = CLAY_TOP_TO_BOTTOM
|
|
},
|
|
.backgroundColor = { 28, 28, 32, 255 }
|
|
})
|
|
{
|
|
// Keep designated fields in declaration order for MSVC aggregate compatibility.
|
|
Clay_TextElementConfig* l_ContentTextConfig = CLAY_TEXT_CONFIG({
|
|
.userData = nullptr,
|
|
.textColor = { 240, 240, 240, 255 },
|
|
.fontId = 0,
|
|
.fontSize = 22
|
|
});
|
|
CLAY_TEXT(CLAY_STRING("Render scene would appear here."), l_ContentTextConfig);
|
|
}
|
|
}
|
|
}
|
|
|
|
return Clay_EndLayout();
|
|
}
|
|
|
|
QueueFamilyIndices FindQueueFamilies(VkPhysicalDevice device)
|
|
{
|
|
QueueFamilyIndices l_Indices{};
|
|
|
|
uint32_t l_QueueFamilyCount = 0;
|
|
vkGetPhysicalDeviceQueueFamilyProperties(device, &l_QueueFamilyCount, nullptr);
|
|
|
|
std::vector<VkQueueFamilyProperties> l_QueueFamilies(l_QueueFamilyCount);
|
|
vkGetPhysicalDeviceQueueFamilyProperties(device, &l_QueueFamilyCount, l_QueueFamilies.data());
|
|
|
|
uint32_t it_Index = 0;
|
|
for (const VkQueueFamilyProperties& it_QueueFamily : l_QueueFamilies)
|
|
{
|
|
if (it_QueueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT)
|
|
{
|
|
l_Indices.m_GraphicsFamily = it_Index;
|
|
}
|
|
|
|
VkBool32 l_PresentSupport = VK_FALSE;
|
|
vkGetPhysicalDeviceSurfaceSupportKHR(device, it_Index, m_Surface, &l_PresentSupport);
|
|
if (l_PresentSupport)
|
|
{
|
|
l_Indices.m_PresentFamily = it_Index;
|
|
}
|
|
|
|
if (l_Indices.IsComplete())
|
|
{
|
|
break;
|
|
}
|
|
++it_Index;
|
|
}
|
|
|
|
return l_Indices;
|
|
}
|
|
|
|
bool IsDeviceSuitable(VkPhysicalDevice device)
|
|
{
|
|
QueueFamilyIndices l_Indices = FindQueueFamilies(device);
|
|
|
|
bool l_ExtensionsSupported = CheckDeviceExtensionSupport(device);
|
|
bool l_SwapchainAdequate = false;
|
|
if (l_ExtensionsSupported)
|
|
{
|
|
SwapchainSupportDetails l_SwapchainSupport = QuerySwapchainSupport(device);
|
|
l_SwapchainAdequate = !l_SwapchainSupport.m_Formats.empty() && !l_SwapchainSupport.m_PresentModes.empty();
|
|
}
|
|
|
|
return l_Indices.IsComplete() && l_ExtensionsSupported && l_SwapchainAdequate;
|
|
}
|
|
|
|
bool CheckDeviceExtensionSupport(VkPhysicalDevice device)
|
|
{
|
|
uint32_t l_ExtensionCount = 0;
|
|
vkEnumerateDeviceExtensionProperties(device, nullptr, &l_ExtensionCount, nullptr);
|
|
|
|
std::vector<VkExtensionProperties> l_AvailableExtensions(l_ExtensionCount);
|
|
vkEnumerateDeviceExtensionProperties(device, nullptr, &l_ExtensionCount, l_AvailableExtensions.data());
|
|
|
|
std::set<std::string> l_RequiredExtensions(s_DeviceExtensions.begin(), s_DeviceExtensions.end());
|
|
|
|
for (const VkExtensionProperties& it_Extension : l_AvailableExtensions)
|
|
{
|
|
l_RequiredExtensions.erase(it_Extension.extensionName);
|
|
}
|
|
|
|
return l_RequiredExtensions.empty();
|
|
}
|
|
|
|
SwapchainSupportDetails QuerySwapchainSupport(VkPhysicalDevice device)
|
|
{
|
|
SwapchainSupportDetails l_Details{};
|
|
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, m_Surface, &l_Details.m_Capabilities);
|
|
|
|
uint32_t l_FormatCount = 0;
|
|
vkGetPhysicalDeviceSurfaceFormatsKHR(device, m_Surface, &l_FormatCount, nullptr);
|
|
if (l_FormatCount != 0)
|
|
{
|
|
l_Details.m_Formats.resize(l_FormatCount);
|
|
vkGetPhysicalDeviceSurfaceFormatsKHR(device, m_Surface, &l_FormatCount, l_Details.m_Formats.data());
|
|
}
|
|
|
|
uint32_t l_PresentModeCount = 0;
|
|
vkGetPhysicalDeviceSurfacePresentModesKHR(device, m_Surface, &l_PresentModeCount, nullptr);
|
|
if (l_PresentModeCount != 0)
|
|
{
|
|
l_Details.m_PresentModes.resize(l_PresentModeCount);
|
|
vkGetPhysicalDeviceSurfacePresentModesKHR(device, m_Surface, &l_PresentModeCount, l_Details.m_PresentModes.data());
|
|
}
|
|
|
|
return l_Details;
|
|
}
|
|
|
|
VkSurfaceFormatKHR ChooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& availableFormats)
|
|
{
|
|
for (const VkSurfaceFormatKHR& it_AvailableFormat : availableFormats)
|
|
{
|
|
if (it_AvailableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && it_AvailableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR)
|
|
{
|
|
return it_AvailableFormat;
|
|
}
|
|
}
|
|
|
|
return availableFormats.front();
|
|
}
|
|
|
|
VkPresentModeKHR ChoosePresentMode(const std::vector<VkPresentModeKHR>& availablePresentModes)
|
|
{
|
|
for (VkPresentModeKHR it_PresentMode : availablePresentModes)
|
|
{
|
|
if (it_PresentMode == VK_PRESENT_MODE_MAILBOX_KHR)
|
|
{
|
|
return it_PresentMode;
|
|
}
|
|
}
|
|
|
|
return VK_PRESENT_MODE_FIFO_KHR;
|
|
}
|
|
|
|
VkExtent2D ChooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities)
|
|
{
|
|
if (capabilities.currentExtent.width != std::numeric_limits<uint32_t>::max())
|
|
{
|
|
return capabilities.currentExtent;
|
|
}
|
|
|
|
VkExtent2D l_ActualExtent = { s_DefaultWidth, s_DefaultHeight };
|
|
l_ActualExtent.width = std::max(capabilities.minImageExtent.width, std::min(capabilities.maxImageExtent.width, l_ActualExtent.width));
|
|
l_ActualExtent.height = std::max(capabilities.minImageExtent.height, std::min(capabilities.maxImageExtent.height, l_ActualExtent.height));
|
|
|
|
return l_ActualExtent;
|
|
}
|
|
};
|
|
|
|
int main()
|
|
{
|
|
VulkanDemoApp l_App{};
|
|
|
|
l_App.Run();
|
|
|
|
return 0;
|
|
} |