diff --git a/engine/include/sophia/compute_smoke_test.hpp b/engine/include/sophia/compute_smoke_test.hpp new file mode 100644 index 0000000..2267190 --- /dev/null +++ b/engine/include/sophia/compute_smoke_test.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include + +namespace sophia { + +// Minimal end-to-end compute dispatch: compiles the GLSL compute shader at +// `computeShaderPath`, builds the descriptor/pipeline layer around a small +// storage buffer, dispatches it, reads the buffer back, and verifies the +// result against what the shader is expected to compute. Logs the outcome +// and throws std::runtime_error if the GPU result doesn't match. +void runComputeSmokeTest(const std::string& computeShaderPath); + +} // namespace sophia diff --git a/engine/src/application.cpp b/engine/src/application.cpp index 675813a..52e1170 100644 --- a/engine/src/application.cpp +++ b/engine/src/application.cpp @@ -7,7 +7,7 @@ namespace sophia { class Application::Impl { public: Impl(const Application::Config& config) - : engine{{config.width, config.height, config.name}} {} + : engine{{config.width, config.height, config.name, config.headless}} {} void run() { engine.run(); } diff --git a/engine/src/compute_pipeline.cpp b/engine/src/compute_pipeline.cpp deleted file mode 100644 index 0b2143c..0000000 --- a/engine/src/compute_pipeline.cpp +++ /dev/null @@ -1,72 +0,0 @@ -#include "compute_pipeline.hpp" - -#include -#include - -namespace sophia { - -ComputePipeline::ComputePipeline(Device& device) : device{device} {} - -ComputePipeline::~ComputePipeline() { - log::trace("destoryed vk::Pipeline (compute)"); -} - -void ComputePipeline::create(Shader& shader, - vk::PipelineLayout pipelineLayout) { - if (shader.getStage() != ShaderStage::COMPUTE) { - log::fatal( - "failed to create compute pipeline: shader is not a compute shader"); - throw std::runtime_error( - "failed to create compute pipeline: shader is not a compute shader"); - } - - if (pipelineLayout == VK_NULL_HANDLE) { - log::fatal( - "failed to create compute pipeline: no vk::PipelineLayout provided"); - throw std::runtime_error( - "failed to create compute pipeline: no vk::PipelineLayout provided"); - } - - vk::UniqueShaderModule computeShaderModule = - createShaderModule(shader.getSpirv()); - log::trace("created compute shader module"); - - vk::PipelineShaderStageCreateInfo shaderStage{ - vk::PipelineShaderStageCreateFlags(), vk::ShaderStageFlagBits::eCompute, - computeShaderModule.get(), "main"}; - - vk::ComputePipelineCreateInfo pipelineInfo{}; - pipelineInfo.stage = shaderStage; - pipelineInfo.layout = pipelineLayout; - pipelineInfo.basePipelineHandle = nullptr; - - try { - this->computePipeline = - this->device.get() - ->createComputePipelineUnique(nullptr, pipelineInfo) - .value; - log::trace("created vk::Pipeline (compute)"); - } catch (const vk::SystemError& err) { - log::fatal("failed to create vk::Pipeline (compute)"); - throw std::runtime_error("failed to create vk::Pipeline (compute)"); - } -} - -void ComputePipeline::bind(vk::CommandBuffer commandBuffer) { - commandBuffer.bindPipeline(vk::PipelineBindPoint::eCompute, - this->computePipeline.get()); -} - -vk::UniqueShaderModule ComputePipeline::createShaderModule( - const std::vector& spirv) { - try { - return device.get()->createShaderModuleUnique( - {vk::ShaderModuleCreateFlags(), spirv.size() * sizeof(u32), - spirv.data()}); - } catch (const vk::SystemError& err) { - log::fatal("failed to create shader module"); - throw std::runtime_error("failed to create shader module"); - } -} - -} // namespace sophia diff --git a/engine/src/compute_pipeline.hpp b/engine/src/compute_pipeline.hpp deleted file mode 100644 index 3a9f2bb..0000000 --- a/engine/src/compute_pipeline.hpp +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include - -#include "device.hpp" -#include "shader.hpp" - -namespace sophia { - -class ComputePipeline { - public: - ComputePipeline(const ComputePipeline&) = delete; - ComputePipeline& operator=(const ComputePipeline&) = delete; - - ComputePipeline(Device& device); - ~ComputePipeline(); - - void create(Shader& shader, vk::PipelineLayout pipelineLayout); - - void bind(vk::CommandBuffer commandBuffer); - - private: - vk::UniqueShaderModule createShaderModule(const std::vector& spirv); - - Device& device; - vk::UniquePipeline computePipeline; -}; - -} // namespace sophia diff --git a/engine/src/compute_smoke_test.cpp b/engine/src/compute_smoke_test.cpp new file mode 100644 index 0000000..393d32a --- /dev/null +++ b/engine/src/compute_smoke_test.cpp @@ -0,0 +1,112 @@ +#include +#include +#include +#include +#include +#include + +#include "buffer.hpp" +#include "descriptors/descriptor_pool.hpp" +#include "descriptors/descriptor_set_layout.hpp" +#include "descriptors/descriptor_writer.hpp" +#include "device.hpp" +#include "pipelines/compute_pipeline.hpp" +#include "shader.hpp" + +namespace sophia { + +namespace { + +constexpr u32 kElementCount = 256; +constexpr u32 kWorkgroupSize = 64; + +} // namespace + +void runComputeSmokeTest(const std::string& computeShaderPath) { + auto device = Device::Builder().headless().build(); + + Buffer buffer{*device, sizeof(f32), kElementCount, + vk::BufferUsageFlagBits::eStorageBuffer, + vk::MemoryPropertyFlagBits::eHostVisible | + vk::MemoryPropertyFlagBits::eHostCoherent}; + + std::vector input(kElementCount); + for (u32 i = 0; i < kElementCount; i++) { + input[i] = static_cast(i); + } + + buffer.map(); + buffer.writeToBuffer(input.data(), sizeof(f32) * kElementCount); + buffer.unmap(); + + auto shader = Shader::Builder() + .fromGLSL(computeShaderPath) + .setStage(ShaderStage::COMPUTE) + .build(); + + auto setLayout = DescriptorSetLayout::Builder(*device) + .addBinding(0, vk::DescriptorType::eStorageBuffer, + vk::ShaderStageFlagBits::eCompute) + .build(); + + auto pool = DescriptorPool::Builder(*device) + .addPoolSize(vk::DescriptorType::eStorageBuffer, 1) + .setMaxSets(1) + .build(); + + vk::DescriptorBufferInfo bufferInfo = buffer.descriptorInfo(); + vk::DescriptorSet descriptorSet; + if (!DescriptorWriter(*setLayout, *pool) + .writeBuffer(0, &bufferInfo) + .build(descriptorSet)) { + log::fatal("compute smoke test: failed to allocate descriptor set"); + throw std::runtime_error( + "compute smoke test: failed to allocate descriptor set"); + } + + vk::DescriptorSetLayout rawSetLayout = setLayout->getDescriptorSetLayout(); + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{}; + pipelineLayoutInfo.setLayoutCount = 1; + pipelineLayoutInfo.pSetLayouts = &rawSetLayout; + + vk::UniquePipelineLayout pipelineLayout = + device->get()->createPipelineLayoutUnique(pipelineLayoutInfo); + + std::unique_ptr computePipeline = + ComputePipeline::Builder(*device) + .setShader(*shader) + .setPipelineLayout(pipelineLayout.get()) + .build(); + + vk::CommandBuffer commandBuffer = device->beginSingleTimeCommands(); + computePipeline->bind(commandBuffer); + commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eCompute, + pipelineLayout.get(), 0, descriptorSet, {}); + commandBuffer.dispatch((kElementCount + kWorkgroupSize - 1) / kWorkgroupSize, + 1, 1); + device->endSingleTimeCommands(commandBuffer); + + std::vector output(kElementCount); + buffer.map(); + std::memcpy(output.data(), buffer.getMappedMemory(), + sizeof(f32) * kElementCount); + buffer.unmap(); + + u32 mismatches = 0; + for (u32 i = 0; i < kElementCount; i++) { + f32 expected = input[i] * 2.0f; + if (std::fabs(output[i] - expected) > 1e-4f) { + mismatches++; + } + } + + if (mismatches > 0) { + log::fatal("compute smoke test FAILED:", mismatches, "/", kElementCount, + "elements mismatched"); + throw std::runtime_error("compute smoke test: GPU result did not match"); + } + + log::info("compute smoke test PASSED:", kElementCount, "elements verified"); +} + +} // namespace sophia diff --git a/engine/src/device.cpp b/engine/src/device.cpp index 7d93870..61f1257 100644 --- a/engine/src/device.cpp +++ b/engine/src/device.cpp @@ -21,6 +21,10 @@ debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT m_severity, return VK_SUCCESS; } else if (m_severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { log::warning(pCallback_data->pMessage); + } else if (m_severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eInfo) { + log::info(pCallback_data->pMessage); + } else if (m_severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose) { + log::verbose(pCallback_data->pMessage); } else { log::info(pCallback_data->pMessage); } @@ -51,10 +55,16 @@ void destroyDebugUtilsMessengerEXT(VkInstance instance, } } -Device::Device(Window& window) : window{window} { +Device::Device(Window* window) + : headless{window == nullptr}, + enabledExtensions{headless ? std::vector{} + : std::vector{ + VK_KHR_SWAPCHAIN_EXTENSION_NAME}} { createVulkanInstance(); setupDebugMessenger(); - this->window.createSurface(*instance, surface); + if (!this->headless) { + window->createSurface(*instance, surface); + } pickPhysicalDevice(); createLogicalDevice(); createCommandPool(); @@ -70,8 +80,42 @@ Device::~Device() { this->device->destroyCommandPool(commandPool); log::trace("destroyed vk::CommandPool"); - this->instance->destroySurfaceKHR(surface); - log::trace("destroyed vk::SurfaceKHR"); + if (!this->headless) { + this->instance->destroySurfaceKHR(surface); + log::trace("destroyed vk::SurfaceKHR"); + } +} + +Device::Builder& Device::Builder::withWindow(Window& window) { + this->window = &window; + this->headlessRequested = false; + this->modeSet = true; + return *this; +} + +Device::Builder& Device::Builder::headless() { + this->window = nullptr; + this->headlessRequested = true; + this->modeSet = true; + return *this; +} + +std::unique_ptr Device::Builder::build() const { + if (!this->modeSet) { + log::fatal( + "failed to build device: neither withWindow() nor headless() was " + "called"); + throw std::runtime_error( + "failed to build device: no window/headless mode specified"); + } + + if (this->headlessRequested) { + log::verbose("building headless Device"); + return std::unique_ptr(new Device(nullptr)); + } else { + log::verbose("building windowed Device"); + return std::unique_ptr(new Device(this->window)); + } } u32 Device::findMemoryType(u32 typeFilter, vk::MemoryPropertyFlags properties) { @@ -299,12 +343,14 @@ bool Device::checkValidationLayerSupport() { } std::vector Device::getRequiredExtensions() { - u32 glfw_extension_count = 0; - const char** glfw_extensions; - glfw_extensions = glfwGetRequiredInstanceExtensions(&glfw_extension_count); + std::vector extensions; - std::vector extensions(glfw_extensions, - glfw_extensions + glfw_extension_count); + if (!this->headless) { + u32 glfw_extension_count = 0; + const char** glfw_extensions = + glfwGetRequiredInstanceExtensions(&glfw_extension_count); + extensions.assign(glfw_extensions, glfw_extensions + glfw_extension_count); + } if (enableValidationLayers) { extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); @@ -381,7 +427,8 @@ QueueFamilyIndices Device::findQueueFamilies(vk::PhysicalDevice device) { indices.graphicsFamily = i; } - if (queueFamily.queueCount > 0 && device.getSurfaceSupportKHR(i, surface)) { + if (!this->headless && queueFamily.queueCount > 0 && + device.getSurfaceSupportKHR(i, surface)) { indices.presentFamily = i; } @@ -410,6 +457,10 @@ bool Device::isPhysicalDeviceSuitable(const vk::PhysicalDevice& device) { bool extensionsSupported = checkDeviceExtensionSupport(device); + if (this->headless) { + return indices.graphicsFamily.has_value() && extensionsSupported; + } + bool swapchainAdequate = false; if (extensionsSupported) { SwapchainSupportDetails swapchainSupport = querySwapchainSupport(device); @@ -450,10 +501,12 @@ void Device::pickPhysicalDevice() { void Device::createLogicalDevice() { QueueFamilyIndices indices = findQueueFamilies(this->physicalDevice); - std::vector queueCreateInfos; - std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), - indices.presentFamily.value()}; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value()}; + if (!this->headless) { + uniqueQueueFamilies.insert(indices.presentFamily.value()); + } + std::vector queueCreateInfos; float queuePriority = 1.0f; for (uint32_t queueFamily : uniqueQueueFamilies) { @@ -461,11 +514,15 @@ void Device::createLogicalDevice() { {vk::DeviceQueueCreateFlags(), queueFamily, 1, &queuePriority}); } + vk::PhysicalDeviceVulkan13Features vulkan13Features{}; + vulkan13Features.dynamicRendering = vk::True; + auto deviceFeatures = vk::PhysicalDeviceFeatures(); auto createInfo = vk::DeviceCreateInfo( vk::DeviceCreateFlags(), static_cast(queueCreateInfos.size()), queueCreateInfos.data()); createInfo.pEnabledFeatures = &deviceFeatures; + createInfo.pNext = &vulkan13Features; createInfo.enabledExtensionCount = static_cast(this->enabledExtensions.size()); @@ -480,7 +537,9 @@ void Device::createLogicalDevice() { } this->graphicsQueue = device->getQueue(indices.graphicsFamily.value(), 0); - this->presentQueue = device->getQueue(indices.presentFamily.value(), 0); + if (!this->headless) { + this->presentQueue = device->getQueue(indices.presentFamily.value(), 0); + } } SwapchainSupportDetails Device::querySwapchainSupport( diff --git a/engine/src/device.hpp b/engine/src/device.hpp index dd6ad4e..5eeeede 100644 --- a/engine/src/device.hpp +++ b/engine/src/device.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -31,12 +32,13 @@ struct SwapchainSupportDetails { class Device { public: + class Builder; + Device(const Device&) = delete; Device& operator=(const Device&) = delete; Device(Device&&) = delete; Device& operator=(Device&&) = delete; - Device(Window& window); ~Device(); const vk::Device* get() const { @@ -85,6 +87,8 @@ class Device { vk::PhysicalDeviceProperties properties; private: + Device(Window* window); + void setupDebugMessenger(); bool checkValidationLayerSupport(); @@ -104,7 +108,7 @@ class Device { vk::UniqueInstance instance; VkDebugUtilsMessengerEXT debugMessenger; - Window& window; + const bool headless; vk::SurfaceKHR surface; vk::PhysicalDevice physicalDevice = VK_NULL_HANDLE; @@ -123,8 +127,22 @@ class Device { const std::vector enabledLayers = { "VK_LAYER_KHRONOS_validation"}; #endif - const std::vector enabledExtensions = { - VK_KHR_SWAPCHAIN_EXTENSION_NAME}; + const std::vector enabledExtensions; +}; + +class Device::Builder { + public: + Builder() = default; + + Builder& withWindow(Window& window); + Builder& headless(); + + std::unique_ptr build() const; + + private: + Window* window = nullptr; + bool headlessRequested = false; + bool modeSet = false; }; } // namespace sophia diff --git a/engine/src/engine.cpp b/engine/src/engine.cpp index 846582c..753c467 100644 --- a/engine/src/engine.cpp +++ b/engine/src/engine.cpp @@ -7,11 +7,15 @@ namespace sophia { -Engine::Engine(const Config& config) - : config{config}, - window{config.width, config.height, config.name}, - device{this->window} // renderer{this->window, this->device} -{ +Engine::Engine(const Config& config) : config{config} { + if (config.headless) { + this->device = Device::Builder().headless().build(); + } else { + this->window = + std::make_unique(config.width, config.height, config.name); + this->device = Device::Builder().withWindow(*this->window).build(); + } + // this->imguiDescriptorPool = // DescriptorPool::Builder(this->device) // .addPoolSize(vk::DescriptorType::eCombinedImageSampler, @@ -31,7 +35,7 @@ Engine::Engine(const Config& config) // .build(); }; -Engine::~Engine() { this->device.waitIdle(); }; +Engine::~Engine() { this->device->waitIdle(); }; void Engine::run() { auto startTime = std::chrono::high_resolution_clock::now(); @@ -41,7 +45,9 @@ void Engine::run() { // std::bind(&Application::onEvent, this, std::placeholders::_1)); while (this->isRunning) { - glfwPollEvents(); + if (this->window) { + glfwPollEvents(); + } auto newTime = std::chrono::high_resolution_clock::now(); double deltaTime = @@ -65,7 +71,7 @@ void Engine::run() { this->isRunning = false; } - this->device.waitIdle(); + this->device->waitIdle(); auto endTime = std::chrono::high_resolution_clock::now(); double totalRuntime = diff --git a/engine/src/engine.hpp b/engine/src/engine.hpp index 64c6d0b..da893fc 100644 --- a/engine/src/engine.hpp +++ b/engine/src/engine.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include "device.hpp" @@ -13,6 +14,7 @@ class Engine { int width; int height; std::string name; + bool headless = false; }; Engine(const Engine&) = delete; @@ -26,8 +28,8 @@ class Engine { private: Config config; - Window window; - Device device; + std::unique_ptr window; + std::unique_ptr device; bool isRunning = true; }; diff --git a/engine/src/pipelines/compute_pipeline.cpp b/engine/src/pipelines/compute_pipeline.cpp new file mode 100644 index 0000000..22fd277 --- /dev/null +++ b/engine/src/pipelines/compute_pipeline.cpp @@ -0,0 +1,83 @@ +#include "compute_pipeline.hpp" + +#include +#include + +namespace sophia { + +ComputePipeline::Builder& ComputePipeline::Builder::setShader(Shader& shader) { + if (shader.getStage() != ShaderStage::COMPUTE) { + log::fatal( + "failed to configure compute pipeline: shader is not a compute " + "shader"); + throw std::runtime_error( + "failed to configure compute pipeline: shader is not a compute " + "shader"); + } + + if (this->shader != nullptr) { + log::warning("compute pipeline builder: shader already set, overwriting"); + } + + this->shader = &shader; + return *this; +} + +ComputePipeline::Builder& ComputePipeline::Builder::setPipelineLayout( + vk::PipelineLayout pipelineLayout) { + this->pipelineLayout = pipelineLayout; + return *this; +} + +std::unique_ptr ComputePipeline::Builder::build() const { + if (this->shader == nullptr) { + log::fatal("failed to build compute pipeline: setShader() was not called"); + throw std::runtime_error( + "failed to build compute pipeline: setShader() was not called"); + } + + if (this->pipelineLayout == VK_NULL_HANDLE) { + log::fatal( + "failed to build compute pipeline: setPipelineLayout() was not " + "called"); + throw std::runtime_error( + "failed to build compute pipeline: setPipelineLayout() was not " + "called"); + } + + return std::unique_ptr( + new ComputePipeline(this->device, *this->shader, this->pipelineLayout)); +} + +ComputePipeline::ComputePipeline(Device& device, Shader& shader, + vk::PipelineLayout pipelineLayout) + : Pipeline(device, vk::PipelineBindPoint::eCompute) { + vk::UniqueShaderModule computeShaderModule = + createShaderModule(shader.getSpirv()); + log::trace("created compute shader module"); + + vk::PipelineShaderStageCreateInfo shaderStage{ + vk::PipelineShaderStageCreateFlags(), vk::ShaderStageFlagBits::eCompute, + computeShaderModule.get(), "main"}; + + vk::ComputePipelineCreateInfo pipelineInfo{}; + pipelineInfo.stage = shaderStage; + pipelineInfo.layout = pipelineLayout; + pipelineInfo.basePipelineHandle = nullptr; + + try { + this->pipeline = this->device.get() + ->createComputePipelineUnique(nullptr, pipelineInfo) + .value; + log::trace("created vk::Pipeline (compute)"); + } catch (const vk::SystemError& err) { + log::fatal("failed to create vk::Pipeline (compute)"); + throw std::runtime_error("failed to create vk::Pipeline (compute)"); + } +} + +ComputePipeline::~ComputePipeline() { + log::trace("destoryed vk::Pipeline (compute)"); +} + +} // namespace sophia diff --git a/engine/src/pipelines/compute_pipeline.hpp b/engine/src/pipelines/compute_pipeline.hpp new file mode 100644 index 0000000..74e5e90 --- /dev/null +++ b/engine/src/pipelines/compute_pipeline.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include +#include + +#include "device.hpp" +#include "pipeline.hpp" +#include "shader.hpp" + +namespace sophia { + +class ComputePipeline : public Pipeline { + public: + class Builder; + + ~ComputePipeline() override; + + private: + ComputePipeline(Device& device, Shader& shader, + vk::PipelineLayout pipelineLayout); +}; + +class ComputePipeline::Builder { + public: + Builder(Device& device) : device{device} {} + + Builder& setShader(Shader& shader); + Builder& setPipelineLayout(vk::PipelineLayout pipelineLayout); + + std::unique_ptr build() const; + + private: + Device& device; + + Shader* shader = nullptr; + vk::PipelineLayout pipelineLayout = VK_NULL_HANDLE; +}; + +} // namespace sophia diff --git a/engine/src/pipelines/graphics_pipeline.cpp b/engine/src/pipelines/graphics_pipeline.cpp new file mode 100644 index 0000000..5c737c8 --- /dev/null +++ b/engine/src/pipelines/graphics_pipeline.cpp @@ -0,0 +1,282 @@ +#include "graphics_pipeline.hpp" + +#include +#include +#include + +namespace sophia { + +GraphicsPipeline::Builder& GraphicsPipeline::Builder::setVertexShader( + Shader& vertexShader) { + if (vertexShader.getStage() != ShaderStage::VERTEX) { + log::fatal( + "failed to configure graphics pipeline: shader is not a vertex " + "shader"); + throw std::runtime_error( + "failed to configure graphics pipeline: shader is not a vertex " + "shader"); + } + + if (this->vertexShader != nullptr) { + log::warning( + "graphics pipeline builder: vertex shader already set, overwriting"); + } + + this->vertexShader = &vertexShader; + return *this; +} + +GraphicsPipeline::Builder& GraphicsPipeline::Builder::setFragmentShader( + Shader& fragmentShader) { + if (fragmentShader.getStage() != ShaderStage::FRAGMENT) { + log::fatal( + "failed to configure graphics pipeline: shader is not a fragment " + "shader"); + throw std::runtime_error( + "failed to configure graphics pipeline: shader is not a fragment " + "shader"); + } + + if (this->fragmentShader != nullptr) { + log::warning( + "graphics pipeline builder: fragment shader already set, " + "overwriting"); + } + + this->fragmentShader = &fragmentShader; + return *this; +} + +GraphicsPipeline::Builder& GraphicsPipeline::Builder::addShader( + Shader& shader) { + switch (shader.getStage()) { + case ShaderStage::VERTEX: + return setVertexShader(shader); + case ShaderStage::FRAGMENT: + return setFragmentShader(shader); + default: + log::fatal( + "failed to configure graphics pipeline: unsupported shader stage"); + throw std::runtime_error( + "failed to configure graphics pipeline: unsupported shader stage"); + } +} + +GraphicsPipeline::Builder& GraphicsPipeline::Builder::setPipelineLayout( + vk::PipelineLayout pipelineLayout) { + this->pipelineLayout = pipelineLayout; + return *this; +} + +GraphicsPipeline::Builder& GraphicsPipeline::Builder::setColorAttachmentFormats( + std::vector colorAttachmentFormats) { + this->colorAttachmentFormats = std::move(colorAttachmentFormats); + return *this; +} + +GraphicsPipeline::Builder& GraphicsPipeline::Builder::setDepthAttachmentFormat( + vk::Format depthAttachmentFormat) { + this->depthAttachmentFormat = depthAttachmentFormat; + return *this; +} + +GraphicsPipeline::Builder& +GraphicsPipeline::Builder::setStencilAttachmentFormat( + vk::Format stencilAttachmentFormat) { + this->stencilAttachmentFormat = stencilAttachmentFormat; + return *this; +} + +GraphicsPipeline::Builder& GraphicsPipeline::Builder::setVertexInput( + std::vector bindingDescriptions, + std::vector attributeDescriptions) { + this->bindingDescriptions = std::move(bindingDescriptions); + this->attributeDescriptions = std::move(attributeDescriptions); + return *this; +} + +std::unique_ptr GraphicsPipeline::Builder::build() const { + if (this->vertexShader == nullptr || this->fragmentShader == nullptr) { + log::fatal( + "failed to build graphics pipeline: setVertexShader()/" + "setFragmentShader() was not called"); + throw std::runtime_error( + "failed to build graphics pipeline: setVertexShader()/" + "setFragmentShader() was not called"); + } + + if (this->pipelineLayout == VK_NULL_HANDLE) { + log::fatal( + "failed to build graphics pipeline: setPipelineLayout() was not " + "called"); + throw std::runtime_error( + "failed to build graphics pipeline: setPipelineLayout() was not " + "called"); + } + + if (this->colorAttachmentFormats.empty() && + this->depthAttachmentFormat == vk::Format::eUndefined) { + log::fatal( + "failed to build graphics pipeline: no color or depth attachment " + "format provided"); + throw std::runtime_error( + "failed to build graphics pipeline: setColorAttachmentFormats() or " + "setDepthAttachmentFormat() was not called"); + } + + return std::unique_ptr(new GraphicsPipeline( + this->device, *this->vertexShader, *this->fragmentShader, + this->pipelineLayout, this->colorAttachmentFormats, + this->depthAttachmentFormat, this->stencilAttachmentFormat, + this->bindingDescriptions, this->attributeDescriptions)); +} + +GraphicsPipeline::GraphicsPipeline( + Device& device, Shader& vertexShader, Shader& fragmentShader, + vk::PipelineLayout pipelineLayout, + std::vector colorAttachmentFormats, + vk::Format depthAttachmentFormat, vk::Format stencilAttachmentFormat, + std::vector bindingDescriptions, + std::vector attributeDescriptions) + : Pipeline(device, vk::PipelineBindPoint::eGraphics) { + setDefaultGraphicsPipelineConfig(); + + this->config.bindingDescriptions = std::move(bindingDescriptions); + this->config.attributeDescriptions = std::move(attributeDescriptions); + this->config.pipelineLayout = pipelineLayout; + this->config.colorAttachmentFormats = std::move(colorAttachmentFormats); + this->config.depthAttachmentFormat = depthAttachmentFormat; + this->config.stencilAttachmentFormat = stencilAttachmentFormat; + + vk::UniqueShaderModule vertexShaderModule = + createShaderModule(vertexShader.getSpirv()); + vk::UniqueShaderModule fragmentShaderModule = + createShaderModule(fragmentShader.getSpirv()); + log::trace("created graphics shader modules"); + + vk::PipelineShaderStageCreateInfo shaderStages[] = { + {vk::PipelineShaderStageCreateFlags(), vk::ShaderStageFlagBits::eVertex, + vertexShaderModule.get(), "main"}, + {vk::PipelineShaderStageCreateFlags(), vk::ShaderStageFlagBits::eFragment, + fragmentShaderModule.get(), "main"}}; + + vk::PipelineVertexInputStateCreateInfo vertexInputInfo{}; + vertexInputInfo.vertexBindingDescriptionCount = + static_cast(this->config.bindingDescriptions.size()); + vertexInputInfo.pVertexBindingDescriptions = + this->config.bindingDescriptions.data(); + vertexInputInfo.vertexAttributeDescriptionCount = + static_cast(this->config.attributeDescriptions.size()); + vertexInputInfo.pVertexAttributeDescriptions = + this->config.attributeDescriptions.data(); + + vk::PipelineViewportStateCreateInfo viewportInfo{}; + viewportInfo.viewportCount = 1; + viewportInfo.pViewports = nullptr; + viewportInfo.scissorCount = 1; + viewportInfo.pScissors = nullptr; + + vk::PipelineRenderingCreateInfo pipelineRenderingInfo{}; + pipelineRenderingInfo.colorAttachmentCount = + static_cast(this->config.colorAttachmentFormats.size()); + pipelineRenderingInfo.pColorAttachmentFormats = + this->config.colorAttachmentFormats.data(); + pipelineRenderingInfo.depthAttachmentFormat = + this->config.depthAttachmentFormat; + pipelineRenderingInfo.stencilAttachmentFormat = + this->config.stencilAttachmentFormat; + + vk::GraphicsPipelineCreateInfo pipelineInfo{}; + pipelineInfo.pNext = &pipelineRenderingInfo; + pipelineInfo.stageCount = 2; + pipelineInfo.pStages = shaderStages; + pipelineInfo.pVertexInputState = &vertexInputInfo; + pipelineInfo.pInputAssemblyState = &this->config.inputAssemblyInfo; + pipelineInfo.pViewportState = &viewportInfo; + pipelineInfo.pRasterizationState = &this->config.rasterizationInfo; + pipelineInfo.pMultisampleState = &this->config.multisampleInfo; + pipelineInfo.pDepthStencilState = &this->config.depthStencilInfo; + pipelineInfo.pColorBlendState = &this->config.colorBlendInfo; + pipelineInfo.pDynamicState = &this->config.dynamicStateInfo; + + pipelineInfo.layout = this->config.pipelineLayout; + pipelineInfo.basePipelineHandle = nullptr; + + try { + this->pipeline = this->device.get() + ->createGraphicsPipelineUnique(nullptr, pipelineInfo) + .value; + log::trace("created vk::Pipeline (graphics)"); + } catch (const vk::SystemError& err) { + log::fatal("failed to create vk::Pipeline (graphics)"); + throw std::runtime_error("failed to create vk::Pipeline (graphics)"); + } +} + +GraphicsPipeline::~GraphicsPipeline() { + log::trace("destroyed vk::Pipeline (graphics)"); +} + +void GraphicsPipeline::setDefaultGraphicsPipelineConfig() { + this->config.inputAssemblyInfo.topology = + vk::PrimitiveTopology::eTriangleList; + this->config.inputAssemblyInfo.primitiveRestartEnable = vk::False; + + this->config.rasterizationInfo.depthClampEnable = vk::False; + this->config.rasterizationInfo.rasterizerDiscardEnable = vk::False; + this->config.rasterizationInfo.polygonMode = vk::PolygonMode::eFill; + this->config.rasterizationInfo.lineWidth = 1.0f; + this->config.rasterizationInfo.cullMode = vk::CullModeFlagBits::eNone; + this->config.rasterizationInfo.frontFace = vk::FrontFace::eClockwise; + this->config.rasterizationInfo.depthBiasEnable = vk::False; + this->config.rasterizationInfo.depthBiasConstantFactor = 0.0f; + this->config.rasterizationInfo.depthBiasClamp = 0.0f; + this->config.rasterizationInfo.depthBiasSlopeFactor = 0.0f; + + this->config.multisampleInfo.sampleShadingEnable = vk::False; + this->config.multisampleInfo.rasterizationSamples = + vk::SampleCountFlagBits::e1; + this->config.multisampleInfo.minSampleShading = 1.0f; + this->config.multisampleInfo.pSampleMask = nullptr; + this->config.multisampleInfo.alphaToCoverageEnable = vk::False; + this->config.multisampleInfo.alphaToOneEnable = vk::False; + + this->config.colorBlendAttachment.colorWriteMask = + vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | + vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA; + this->config.colorBlendAttachment.blendEnable = vk::False; + this->config.colorBlendAttachment.srcColorBlendFactor = vk::BlendFactor::eOne; + this->config.colorBlendAttachment.dstColorBlendFactor = + vk::BlendFactor::eZero; + this->config.colorBlendAttachment.colorBlendOp = vk::BlendOp::eAdd; + this->config.colorBlendAttachment.srcAlphaBlendFactor = vk::BlendFactor::eOne; + this->config.colorBlendAttachment.dstAlphaBlendFactor = + vk::BlendFactor::eZero; + this->config.colorBlendAttachment.alphaBlendOp = vk::BlendOp::eAdd; + + this->config.colorBlendInfo.logicOpEnable = vk::False; + this->config.colorBlendInfo.logicOp = vk::LogicOp::eCopy; + this->config.colorBlendInfo.attachmentCount = 1; + this->config.colorBlendInfo.pAttachments = &this->config.colorBlendAttachment; + this->config.colorBlendInfo.blendConstants[0] = 0.0f; + this->config.colorBlendInfo.blendConstants[1] = 0.0f; + this->config.colorBlendInfo.blendConstants[2] = 0.0f; + this->config.colorBlendInfo.blendConstants[3] = 0.0f; + + this->config.depthStencilInfo.depthTestEnable = vk::True; + this->config.depthStencilInfo.depthWriteEnable = vk::True; + this->config.depthStencilInfo.depthCompareOp = vk::CompareOp::eLess; + this->config.depthStencilInfo.depthBoundsTestEnable = vk::False; + this->config.depthStencilInfo.minDepthBounds = 0.0f; + this->config.depthStencilInfo.maxDepthBounds = 1.0f; + this->config.depthStencilInfo.stencilTestEnable = vk::False; + + this->config.dynamicStateEnables = {vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + this->config.dynamicStateInfo.pDynamicStates = + this->config.dynamicStateEnables.data(); + this->config.dynamicStateInfo.dynamicStateCount = + static_cast(this->config.dynamicStateEnables.size()); +} + +} // namespace sophia diff --git a/engine/src/pipelines/graphics_pipeline.hpp b/engine/src/pipelines/graphics_pipeline.hpp new file mode 100644 index 0000000..600253c --- /dev/null +++ b/engine/src/pipelines/graphics_pipeline.hpp @@ -0,0 +1,88 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "device.hpp" +#include "pipeline.hpp" +#include "shader.hpp" + +namespace sophia { + +struct GraphicsPipelineConfig { + GraphicsPipelineConfig() = default; + GraphicsPipelineConfig(const GraphicsPipelineConfig&) = delete; + GraphicsPipelineConfig& operator=(const GraphicsPipelineConfig&) = delete; + + std::vector bindingDescriptions{}; + std::vector attributeDescriptions{}; + vk::PipelineInputAssemblyStateCreateInfo inputAssemblyInfo; + vk::PipelineRasterizationStateCreateInfo rasterizationInfo; + vk::PipelineMultisampleStateCreateInfo multisampleInfo; + vk::PipelineColorBlendAttachmentState colorBlendAttachment; + vk::PipelineColorBlendStateCreateInfo colorBlendInfo; + vk::PipelineDepthStencilStateCreateInfo depthStencilInfo; + std::vector dynamicStateEnables; + vk::PipelineDynamicStateCreateInfo dynamicStateInfo; + + vk::PipelineLayout pipelineLayout = VK_NULL_HANDLE; + std::vector colorAttachmentFormats{}; + vk::Format depthAttachmentFormat = vk::Format::eUndefined; + vk::Format stencilAttachmentFormat = vk::Format::eUndefined; +}; + +class GraphicsPipeline : public Pipeline { + public: + class Builder; + + ~GraphicsPipeline() override; + + private: + GraphicsPipeline( + Device& device, Shader& vertexShader, Shader& fragmentShader, + vk::PipelineLayout pipelineLayout, + std::vector colorAttachmentFormats, + vk::Format depthAttachmentFormat, vk::Format stencilAttachmentFormat, + std::vector bindingDescriptions, + std::vector attributeDescriptions); + + void setDefaultGraphicsPipelineConfig(); + + GraphicsPipelineConfig config; +}; + +class GraphicsPipeline::Builder { + public: + Builder(Device& device) : device{device} {} + + Builder& setVertexShader(Shader& vertexShader); + Builder& setFragmentShader(Shader& fragmentShader); + Builder& addShader(Shader& shader); + Builder& setPipelineLayout(vk::PipelineLayout pipelineLayout); + Builder& setColorAttachmentFormats( + std::vector colorAttachmentFormats); + Builder& setDepthAttachmentFormat(vk::Format depthAttachmentFormat); + Builder& setStencilAttachmentFormat(vk::Format stencilAttachmentFormat); + Builder& setVertexInput( + std::vector bindingDescriptions, + std::vector attributeDescriptions); + + std::unique_ptr build() const; + + private: + Device& device; + + Shader* vertexShader = nullptr; + Shader* fragmentShader = nullptr; + vk::PipelineLayout pipelineLayout = VK_NULL_HANDLE; + std::vector colorAttachmentFormats{}; + vk::Format depthAttachmentFormat = vk::Format::eUndefined; + vk::Format stencilAttachmentFormat = vk::Format::eUndefined; + std::vector bindingDescriptions{}; + std::vector attributeDescriptions{}; +}; + +} // namespace sophia diff --git a/engine/src/pipelines/pipeline.cpp b/engine/src/pipelines/pipeline.cpp new file mode 100644 index 0000000..a2e2e83 --- /dev/null +++ b/engine/src/pipelines/pipeline.cpp @@ -0,0 +1,27 @@ +#include "pipeline.hpp" + +#include +#include + +namespace sophia { + +Pipeline::Pipeline(Device& device, vk::PipelineBindPoint bindPoint) + : device{device}, bindPoint{bindPoint} {} + +void Pipeline::bind(vk::CommandBuffer commandBuffer) { + commandBuffer.bindPipeline(this->bindPoint, this->pipeline.get()); +} + +vk::UniqueShaderModule Pipeline::createShaderModule( + const std::vector& spirv) { + try { + return device.get()->createShaderModuleUnique( + {vk::ShaderModuleCreateFlags(), spirv.size() * sizeof(u32), + spirv.data()}); + } catch (const vk::SystemError& err) { + log::fatal("failed to create shader module"); + throw std::runtime_error("failed to create shader module"); + } +} + +} // namespace sophia diff --git a/engine/src/pipelines/pipeline.hpp b/engine/src/pipelines/pipeline.hpp new file mode 100644 index 0000000..92b44fb --- /dev/null +++ b/engine/src/pipelines/pipeline.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include + +#include "device.hpp" + +namespace sophia { + +class Pipeline { + public: + Pipeline(const Pipeline&) = delete; + Pipeline& operator=(const Pipeline&) = delete; + + virtual ~Pipeline() = default; + + void bind(vk::CommandBuffer commandBuffer); + + protected: + Pipeline(Device& device, vk::PipelineBindPoint bindPoint); + + vk::UniqueShaderModule createShaderModule(const std::vector& spirv); + + Device& device; + vk::UniquePipeline pipeline; + + private: + vk::PipelineBindPoint bindPoint; +}; + +} // namespace sophia diff --git a/engine/src/renderer.cpp b/engine/src/renderer.cpp new file mode 100644 index 0000000..e69de29 diff --git a/engine/src/renderer.hpp b/engine/src/renderer.hpp new file mode 100644 index 0000000..2360362 --- /dev/null +++ b/engine/src/renderer.hpp @@ -0,0 +1,3 @@ +#pragma once + +namespace sophia {} // namespace sophia diff --git a/engine/src/swapchain.cpp b/engine/src/swapchain.cpp new file mode 100644 index 0000000..82786b2 --- /dev/null +++ b/engine/src/swapchain.cpp @@ -0,0 +1,220 @@ +#include "swapchain.hpp" + +#include +#include +#include + +namespace sophia { + +Swapchain::Swapchain(Device& device, vk::Extent2D extent) + : device{device}, extent{extent} { + initialize(); +} + +Swapchain::Swapchain(Device& device, vk::Extent2D extent, + std::shared_ptr previous) + : device{device}, extent{extent}, oldSwapchain{previous} { + initialize(); + this->oldSwapchain = nullptr; +} + +Swapchain::~Swapchain() { + this->images.clear(); + this->depthImages.clear(); + + this->device.get()->destroySwapchainKHR(this->swapchain); + // log::trace("destroyed vk::SwapchainKHR"); +} + +vk::Result Swapchain::acquireNextImage(vk::Semaphore signalSemaphore, + u32& imageIndex) { + return this->device.get()->acquireNextImageKHR( + this->swapchain, std::numeric_limits::max(), signalSemaphore, + VK_NULL_HANDLE, &imageIndex); +} + +vk::Result Swapchain::present(vk::Queue presentQueue, u32 imageIndex, + vk::Semaphore waitSemaphore) { + vk::PresentInfoKHR presentInfo = {}; + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = &waitSemaphore; + + vk::SwapchainKHR swapChains[] = {this->swapchain}; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = swapChains; + presentInfo.pImageIndices = &imageIndex; + + return presentQueue.presentKHR(&presentInfo); +} + +void Swapchain::initialize() { + setDefaultCreateInfo(); + createSwapchain(); + createImages(); + createDepthImages(); +} + +void Swapchain::setDefaultCreateInfo() { + SwapchainSupportDetails swapchainSupport = this->device.getSwapchainSupport(); + + vk::SurfaceFormatKHR surfaceFormat = + chooseSurfaceFormat(swapchainSupport.formats); + vk::PresentModeKHR presentMode = + choosePresentMode(swapchainSupport.presentModes); + this->extent = chooseExtent(swapchainSupport.capabilities); + + u32 imageCount = swapchainSupport.capabilities.minImageCount + 1; + if (swapchainSupport.capabilities.maxImageCount > 0 && + imageCount > swapchainSupport.capabilities.maxImageCount) { + imageCount = swapchainSupport.capabilities.maxImageCount; + } + + this->swapchainCreateInfo = {vk::SwapchainCreateFlagsKHR(), + this->device.getSurface(), + imageCount, + surfaceFormat.format, + surfaceFormat.colorSpace, + this->extent, + 1, + vk::ImageUsageFlagBits::eColorAttachment}; + + QueueFamilyIndices indices = this->device.getQueueIndices(); + u32 queueFamilyIndices[] = {indices.graphicsFamily.value(), + indices.presentFamily.value()}; + + if (indices.graphicsFamily != indices.presentFamily) { + swapchainCreateInfo.imageSharingMode = vk::SharingMode::eConcurrent; + swapchainCreateInfo.queueFamilyIndexCount = 2; + swapchainCreateInfo.pQueueFamilyIndices = queueFamilyIndices; + } else { + swapchainCreateInfo.imageSharingMode = vk::SharingMode::eExclusive; + } + + swapchainCreateInfo.preTransform = + swapchainSupport.capabilities.currentTransform; + swapchainCreateInfo.compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque; + swapchainCreateInfo.presentMode = presentMode; + swapchainCreateInfo.clipped = VK_TRUE; + + swapchainCreateInfo.oldSwapchain = vk::SwapchainKHR(nullptr); + swapchainCreateInfo.oldSwapchain = this->oldSwapchain == nullptr + ? VK_NULL_HANDLE + : this->oldSwapchain->swapchain; + + this->imageFormat = surfaceFormat.format; +} + +void Swapchain::createSwapchain() { + try { + this->swapchain = + this->device.get()->createSwapchainKHR(this->swapchainCreateInfo); + // log::trace("created vk::SwapchainKHR"); + } catch (const vk::SystemError& err) { + log::fatal("failed to create swapchain. Error: ", err.what()); + throw std::runtime_error("failed to create swapchain"); + } +} + +void Swapchain::createImages() { + std::vector swapchainImages = + this->device.get()->getSwapchainImagesKHR(this->swapchain); + + this->images.clear(); + this->images.reserve(swapchainImages.size()); + + for (vk::Image image : swapchainImages) { + this->images.push_back(Image::Builder(this->device) + .wrapExisting(image) + .setFormat(this->imageFormat) + .setExtent(width(), height()) + .withView() + .build()); + } + + // log::trace("created all swapchain vk::ImageView"); +} + +void Swapchain::createDepthImages() { + this->depthFormat = findDepthFormat(); + + this->depthImages.clear(); + this->depthImages.reserve(imageCount()); + + for (size_t i = 0; i < imageCount(); i++) { + this->depthImages.push_back(Image::Builder(this->device) + .asDepthAttachment() + .setFormat(this->depthFormat) + .setExtent(width(), height()) + .withView() + .build()); + } + + // log::trace("created all depth resources"); +} + +vk::SurfaceFormatKHR Swapchain::chooseSurfaceFormat( + const std::vector& availableFormats) { + if (availableFormats.size() == 1 && + availableFormats[0].format == vk::Format::eUndefined) { + return {vk::Format::eB8G8R8A8Unorm, vk::ColorSpaceKHR::eSrgbNonlinear}; + } + + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == vk::Format::eB8G8R8A8Unorm && + availableFormat.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear) { + return availableFormat; + } + } + + log::info("No available surface formats, choosing default."); + return availableFormats[0]; +} + +vk::PresentModeKHR Swapchain::choosePresentMode( + const std::vector availablePresentModes) { + vk::PresentModeKHR bestMode = vk::PresentModeKHR::eFifo; + + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == vk::PresentModeKHR::eMailbox) { + log::info("Selected present mode: MAILBOX (triple buffering)"); + return availablePresentMode; + } else if (availablePresentMode == vk::PresentModeKHR::eImmediate) { + bestMode = availablePresentMode; + } + } + log::warning("Present mode MAILBOX not available."); + + if (bestMode == vk::PresentModeKHR::eImmediate) { + log::info("Selected present mode: IMMEDIATE (no vsync)"); + } else { + log::info("Selected present mode: FIFO (vsync)"); + } + + return bestMode; +} + +vk::Extent2D Swapchain::chooseExtent( + const vk::SurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } else { + vk::Extent2D choosenExtent = {}; + choosenExtent.width = + std::clamp(this->extent.width, capabilities.minImageExtent.width, + capabilities.maxImageExtent.width); + choosenExtent.height = + std::clamp(this->extent.height, capabilities.minImageExtent.height, + capabilities.maxImageExtent.height); + return choosenExtent; + } +} + +vk::Format Swapchain::findDepthFormat() { + return this->device.findSupportedFormat( + {vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint, + vk::Format::eD24UnormS8Uint}, + vk::ImageTiling::eOptimal, + vk::FormatFeatureFlagBits::eDepthStencilAttachment); +} + +} // namespace sophia diff --git a/engine/src/swapchain.hpp b/engine/src/swapchain.hpp new file mode 100644 index 0000000..8168700 --- /dev/null +++ b/engine/src/swapchain.hpp @@ -0,0 +1,83 @@ +#pragma once + +#include +#include +#include +#include + +#include "device.hpp" +#include "image.hpp" + +namespace sophia { + +class Swapchain { + public: + Swapchain(const Swapchain&) = delete; + Swapchain& operator=(const Swapchain&) = delete; + + Swapchain(Device& device, vk::Extent2D extent); + Swapchain(Device& device, vk::Extent2D extent, + std::shared_ptr previous); + ~Swapchain(); + + Image& getImage(int index) const { return *this->images.at(index); } + Image& getDepthImage(int index) const { return *this->depthImages.at(index); } + + vk::ImageView getImageView(int index) const { + return this->images.at(index)->getView(); + } + vk::ImageView getDepthImageView(int index) const { + return this->depthImages.at(index)->getView(); + } + + size_t imageCount() { return this->images.size(); } + vk::Format getImageFormat() { return this->imageFormat; } + vk::Extent2D getExtent() { return this->extent; } + u32 width() { return this->extent.width; } + u32 height() { return this->extent.height; } + + float extentAspectRatio() { + return static_cast(this->extent.width) / + static_cast(this->extent.height); + } + + vk::Result acquireNextImage(vk::Semaphore signalSemaphore, u32& imageIndex); + + vk::Result present(vk::Queue presentQueue, u32 imageIndex, + vk::Semaphore waitSemaphore); + + bool compareSwapchainFormats(const Swapchain& swapchain) const { + return swapchain.depthFormat == this->depthFormat && + swapchain.imageFormat == this->imageFormat; + } + + private: + void initialize(); + void setDefaultCreateInfo(); + void createSwapchain(); + void createImages(); + void createDepthImages(); + + vk::SurfaceFormatKHR chooseSurfaceFormat( + const std::vector& availableFormats); + vk::PresentModeKHR choosePresentMode( + const std::vector availablePresentModes); + vk::Extent2D chooseExtent(const vk::SurfaceCapabilitiesKHR& capabilities); + + vk::Format findDepthFormat(); + + Device& device; + vk::Extent2D extent; + + vk::SwapchainKHR swapchain; + vk::SwapchainCreateInfoKHR swapchainCreateInfo; + std::shared_ptr oldSwapchain; + + vk::Format imageFormat; + std::vector> images; + + vk::Format depthFormat; + std::vector> depthImages; +}; + +} // namespace sophia diff --git a/testbed/CMakeLists.txt b/testbed/CMakeLists.txt index 6853d95..f03f0a6 100644 --- a/testbed/CMakeLists.txt +++ b/testbed/CMakeLists.txt @@ -21,7 +21,11 @@ add_executable(testbed main.cpp) target_link_libraries(testbed PRIVATE deps) -target_include_directories(testbed PRIVATE - "${CMAKE_SOURCE_DIR}/engine/include" +target_include_directories(testbed PRIVATE + "${CMAKE_SOURCE_DIR}/engine/include" "${CMAKE_SOURCE_DIR}/external/imgui" ) + +target_compile_definitions(testbed PRIVATE + TESTBED_SHADER_DIR="${CMAKE_CURRENT_SOURCE_DIR}/shaders" +) diff --git a/testbed/main.cpp b/testbed/main.cpp index d544a4d..25628e0 100644 --- a/testbed/main.cpp +++ b/testbed/main.cpp @@ -1,5 +1,6 @@ #include -#include +// #include +#include #include int main(int argc, const char** argv) { @@ -7,9 +8,17 @@ int main(int argc, const char** argv) { (void)argv; std::cout << __FILE__ << "::" << __LINE__ << '\n'; - sophia::Application app{{.width = 750, .height = 1000, .name = "Testbed"}}; + try { + sophia::runComputeSmokeTest(TESTBED_SHADER_DIR "/smoke_test.comp"); + } catch (const std::exception& e) { + std::cerr << "Compute smoke test failed: " << e.what() << '\n'; + return EXIT_FAILURE; + } - app.run(); + // sophia::Application app{ + // {.width = 750, .height = 1000, .name = "Testbed", .headless = true}}; + + // app.run(); return EXIT_SUCCESS; } diff --git a/testbed/shaders/smoke_test.comp b/testbed/shaders/smoke_test.comp new file mode 100644 index 0000000..c2863f1 --- /dev/null +++ b/testbed/shaders/smoke_test.comp @@ -0,0 +1,12 @@ +#version 450 + +layout(local_size_x = 64) in; + +layout(std430, binding = 0) buffer Data { + float values[]; +}; + +void main() { + uint idx = gl_GlobalInvocationID.x; + values[idx] = values[idx] * 2.0; +}