Big changes

This commit is contained in:
2026-08-26 03:18:05 -07:00
parent d09f01402d
commit 99e06bbbb1
22 changed files with 1127 additions and 135 deletions
@@ -0,0 +1,14 @@
#pragma once
#include <string>
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
+1 -1
View File
@@ -7,7 +7,7 @@ namespace sophia {
class Application::Impl { class Application::Impl {
public: public:
Impl(const Application::Config& config) 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(); } void run() { engine.run(); }
-72
View File
@@ -1,72 +0,0 @@
#include "compute_pipeline.hpp"
#include <sophia/util/logger.hpp>
#include <stdexcept>
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<u32>& 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
-29
View File
@@ -1,29 +0,0 @@
#pragma once
#include <vulkan/vulkan.hpp>
#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<u32>& spirv);
Device& device;
vk::UniquePipeline computePipeline;
};
} // namespace sophia
+112
View File
@@ -0,0 +1,112 @@
#include <cmath>
#include <cstring>
#include <sophia/compute_smoke_test.hpp>
#include <sophia/util/logger.hpp>
#include <stdexcept>
#include <vector>
#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<f32> input(kElementCount);
for (u32 i = 0; i < kElementCount; i++) {
input[i] = static_cast<f32>(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<sophia::ComputePipeline> 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<f32> 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
+70 -11
View File
@@ -21,6 +21,10 @@ debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT m_severity,
return VK_SUCCESS; return VK_SUCCESS;
} else if (m_severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { } else if (m_severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) {
log::warning(pCallback_data->pMessage); 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 { } else {
log::info(pCallback_data->pMessage); 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<const char*>{}
: std::vector<const char*>{
VK_KHR_SWAPCHAIN_EXTENSION_NAME}} {
createVulkanInstance(); createVulkanInstance();
setupDebugMessenger(); setupDebugMessenger();
this->window.createSurface(*instance, surface); if (!this->headless) {
window->createSurface(*instance, surface);
}
pickPhysicalDevice(); pickPhysicalDevice();
createLogicalDevice(); createLogicalDevice();
createCommandPool(); createCommandPool();
@@ -70,8 +80,42 @@ Device::~Device() {
this->device->destroyCommandPool(commandPool); this->device->destroyCommandPool(commandPool);
log::trace("destroyed vk::CommandPool"); log::trace("destroyed vk::CommandPool");
if (!this->headless) {
this->instance->destroySurfaceKHR(surface); this->instance->destroySurfaceKHR(surface);
log::trace("destroyed vk::SurfaceKHR"); 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> 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<Device>(new Device(nullptr));
} else {
log::verbose("building windowed Device");
return std::unique_ptr<Device>(new Device(this->window));
}
} }
u32 Device::findMemoryType(u32 typeFilter, vk::MemoryPropertyFlags properties) { u32 Device::findMemoryType(u32 typeFilter, vk::MemoryPropertyFlags properties) {
@@ -299,12 +343,14 @@ bool Device::checkValidationLayerSupport() {
} }
std::vector<const char*> Device::getRequiredExtensions() { std::vector<const char*> Device::getRequiredExtensions() {
u32 glfw_extension_count = 0; std::vector<const char*> extensions;
const char** glfw_extensions;
glfw_extensions = glfwGetRequiredInstanceExtensions(&glfw_extension_count);
std::vector<const char*> extensions(glfw_extensions, if (!this->headless) {
glfw_extensions + glfw_extension_count); u32 glfw_extension_count = 0;
const char** glfw_extensions =
glfwGetRequiredInstanceExtensions(&glfw_extension_count);
extensions.assign(glfw_extensions, glfw_extensions + glfw_extension_count);
}
if (enableValidationLayers) { if (enableValidationLayers) {
extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
@@ -381,7 +427,8 @@ QueueFamilyIndices Device::findQueueFamilies(vk::PhysicalDevice device) {
indices.graphicsFamily = i; indices.graphicsFamily = i;
} }
if (queueFamily.queueCount > 0 && device.getSurfaceSupportKHR(i, surface)) { if (!this->headless && queueFamily.queueCount > 0 &&
device.getSurfaceSupportKHR(i, surface)) {
indices.presentFamily = i; indices.presentFamily = i;
} }
@@ -410,6 +457,10 @@ bool Device::isPhysicalDeviceSuitable(const vk::PhysicalDevice& device) {
bool extensionsSupported = checkDeviceExtensionSupport(device); bool extensionsSupported = checkDeviceExtensionSupport(device);
if (this->headless) {
return indices.graphicsFamily.has_value() && extensionsSupported;
}
bool swapchainAdequate = false; bool swapchainAdequate = false;
if (extensionsSupported) { if (extensionsSupported) {
SwapchainSupportDetails swapchainSupport = querySwapchainSupport(device); SwapchainSupportDetails swapchainSupport = querySwapchainSupport(device);
@@ -450,10 +501,12 @@ void Device::pickPhysicalDevice() {
void Device::createLogicalDevice() { void Device::createLogicalDevice() {
QueueFamilyIndices indices = findQueueFamilies(this->physicalDevice); QueueFamilyIndices indices = findQueueFamilies(this->physicalDevice);
std::vector<vk::DeviceQueueCreateInfo> queueCreateInfos; std::set<uint32_t> uniqueQueueFamilies = {indices.graphicsFamily.value()};
std::set<uint32_t> uniqueQueueFamilies = {indices.graphicsFamily.value(), if (!this->headless) {
indices.presentFamily.value()}; uniqueQueueFamilies.insert(indices.presentFamily.value());
}
std::vector<vk::DeviceQueueCreateInfo> queueCreateInfos;
float queuePriority = 1.0f; float queuePriority = 1.0f;
for (uint32_t queueFamily : uniqueQueueFamilies) { for (uint32_t queueFamily : uniqueQueueFamilies) {
@@ -461,11 +514,15 @@ void Device::createLogicalDevice() {
{vk::DeviceQueueCreateFlags(), queueFamily, 1, &queuePriority}); {vk::DeviceQueueCreateFlags(), queueFamily, 1, &queuePriority});
} }
vk::PhysicalDeviceVulkan13Features vulkan13Features{};
vulkan13Features.dynamicRendering = vk::True;
auto deviceFeatures = vk::PhysicalDeviceFeatures(); auto deviceFeatures = vk::PhysicalDeviceFeatures();
auto createInfo = vk::DeviceCreateInfo( auto createInfo = vk::DeviceCreateInfo(
vk::DeviceCreateFlags(), static_cast<uint32_t>(queueCreateInfos.size()), vk::DeviceCreateFlags(), static_cast<uint32_t>(queueCreateInfos.size()),
queueCreateInfos.data()); queueCreateInfos.data());
createInfo.pEnabledFeatures = &deviceFeatures; createInfo.pEnabledFeatures = &deviceFeatures;
createInfo.pNext = &vulkan13Features;
createInfo.enabledExtensionCount = createInfo.enabledExtensionCount =
static_cast<uint32_t>(this->enabledExtensions.size()); static_cast<uint32_t>(this->enabledExtensions.size());
@@ -480,7 +537,9 @@ void Device::createLogicalDevice() {
} }
this->graphicsQueue = device->getQueue(indices.graphicsFamily.value(), 0); this->graphicsQueue = device->getQueue(indices.graphicsFamily.value(), 0);
if (!this->headless) {
this->presentQueue = device->getQueue(indices.presentFamily.value(), 0); this->presentQueue = device->getQueue(indices.presentFamily.value(), 0);
}
} }
SwapchainSupportDetails Device::querySwapchainSupport( SwapchainSupportDetails Device::querySwapchainSupport(
+22 -4
View File
@@ -3,6 +3,7 @@
#include <imgui_impl_vulkan.h> #include <imgui_impl_vulkan.h>
#include <cassert> #include <cassert>
#include <memory>
#include <optional> #include <optional>
#include <sophia/types.hpp> #include <sophia/types.hpp>
#include <vector> #include <vector>
@@ -31,12 +32,13 @@ struct SwapchainSupportDetails {
class Device { class Device {
public: public:
class Builder;
Device(const Device&) = delete; Device(const Device&) = delete;
Device& operator=(const Device&) = delete; Device& operator=(const Device&) = delete;
Device(Device&&) = delete; Device(Device&&) = delete;
Device& operator=(Device&&) = delete; Device& operator=(Device&&) = delete;
Device(Window& window);
~Device(); ~Device();
const vk::Device* get() const { const vk::Device* get() const {
@@ -85,6 +87,8 @@ class Device {
vk::PhysicalDeviceProperties properties; vk::PhysicalDeviceProperties properties;
private: private:
Device(Window* window);
void setupDebugMessenger(); void setupDebugMessenger();
bool checkValidationLayerSupport(); bool checkValidationLayerSupport();
@@ -104,7 +108,7 @@ class Device {
vk::UniqueInstance instance; vk::UniqueInstance instance;
VkDebugUtilsMessengerEXT debugMessenger; VkDebugUtilsMessengerEXT debugMessenger;
Window& window; const bool headless;
vk::SurfaceKHR surface; vk::SurfaceKHR surface;
vk::PhysicalDevice physicalDevice = VK_NULL_HANDLE; vk::PhysicalDevice physicalDevice = VK_NULL_HANDLE;
@@ -123,8 +127,22 @@ class Device {
const std::vector<const char*> enabledLayers = { const std::vector<const char*> enabledLayers = {
"VK_LAYER_KHRONOS_validation"}; "VK_LAYER_KHRONOS_validation"};
#endif #endif
const std::vector<const char*> enabledExtensions = { const std::vector<const char*> enabledExtensions;
VK_KHR_SWAPCHAIN_EXTENSION_NAME}; };
class Device::Builder {
public:
Builder() = default;
Builder& withWindow(Window& window);
Builder& headless();
std::unique_ptr<Device> build() const;
private:
Window* window = nullptr;
bool headlessRequested = false;
bool modeSet = false;
}; };
} // namespace sophia } // namespace sophia
+13 -7
View File
@@ -7,11 +7,15 @@
namespace sophia { namespace sophia {
Engine::Engine(const Config& config) Engine::Engine(const Config& config) : config{config} {
: config{config}, if (config.headless) {
window{config.width, config.height, config.name}, this->device = Device::Builder().headless().build();
device{this->window} // renderer{this->window, this->device} } else {
{ this->window =
std::make_unique<Window>(config.width, config.height, config.name);
this->device = Device::Builder().withWindow(*this->window).build();
}
// this->imguiDescriptorPool = // this->imguiDescriptorPool =
// DescriptorPool::Builder(this->device) // DescriptorPool::Builder(this->device)
// .addPoolSize(vk::DescriptorType::eCombinedImageSampler, // .addPoolSize(vk::DescriptorType::eCombinedImageSampler,
@@ -31,7 +35,7 @@ Engine::Engine(const Config& config)
// .build(); // .build();
}; };
Engine::~Engine() { this->device.waitIdle(); }; Engine::~Engine() { this->device->waitIdle(); };
void Engine::run() { void Engine::run() {
auto startTime = std::chrono::high_resolution_clock::now(); auto startTime = std::chrono::high_resolution_clock::now();
@@ -41,7 +45,9 @@ void Engine::run() {
// std::bind(&Application::onEvent, this, std::placeholders::_1)); // std::bind(&Application::onEvent, this, std::placeholders::_1));
while (this->isRunning) { while (this->isRunning) {
if (this->window) {
glfwPollEvents(); glfwPollEvents();
}
auto newTime = std::chrono::high_resolution_clock::now(); auto newTime = std::chrono::high_resolution_clock::now();
double deltaTime = double deltaTime =
@@ -65,7 +71,7 @@ void Engine::run() {
this->isRunning = false; this->isRunning = false;
} }
this->device.waitIdle(); this->device->waitIdle();
auto endTime = std::chrono::high_resolution_clock::now(); auto endTime = std::chrono::high_resolution_clock::now();
double totalRuntime = double totalRuntime =
+4 -2
View File
@@ -1,5 +1,6 @@
#pragma once #pragma once
#include <memory>
#include <string> #include <string>
#include "device.hpp" #include "device.hpp"
@@ -13,6 +14,7 @@ class Engine {
int width; int width;
int height; int height;
std::string name; std::string name;
bool headless = false;
}; };
Engine(const Engine&) = delete; Engine(const Engine&) = delete;
@@ -26,8 +28,8 @@ class Engine {
private: private:
Config config; Config config;
Window window; std::unique_ptr<Window> window;
Device device; std::unique_ptr<Device> device;
bool isRunning = true; bool isRunning = true;
}; };
+83
View File
@@ -0,0 +1,83 @@
#include "compute_pipeline.hpp"
#include <sophia/util/logger.hpp>
#include <stdexcept>
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> 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<ComputePipeline>(
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
+39
View File
@@ -0,0 +1,39 @@
#pragma once
#include <memory>
#include <vulkan/vulkan.hpp>
#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<ComputePipeline> build() const;
private:
Device& device;
Shader* shader = nullptr;
vk::PipelineLayout pipelineLayout = VK_NULL_HANDLE;
};
} // namespace sophia
+282
View File
@@ -0,0 +1,282 @@
#include "graphics_pipeline.hpp"
#include <sophia/util/logger.hpp>
#include <stdexcept>
#include <utility>
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<vk::Format> 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<vk::VertexInputBindingDescription> bindingDescriptions,
std::vector<vk::VertexInputAttributeDescription> attributeDescriptions) {
this->bindingDescriptions = std::move(bindingDescriptions);
this->attributeDescriptions = std::move(attributeDescriptions);
return *this;
}
std::unique_ptr<GraphicsPipeline> 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<GraphicsPipeline>(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<vk::Format> colorAttachmentFormats,
vk::Format depthAttachmentFormat, vk::Format stencilAttachmentFormat,
std::vector<vk::VertexInputBindingDescription> bindingDescriptions,
std::vector<vk::VertexInputAttributeDescription> 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<u32>(this->config.bindingDescriptions.size());
vertexInputInfo.pVertexBindingDescriptions =
this->config.bindingDescriptions.data();
vertexInputInfo.vertexAttributeDescriptionCount =
static_cast<u32>(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<u32>(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<u32>(this->config.dynamicStateEnables.size());
}
} // namespace sophia
@@ -0,0 +1,88 @@
#pragma once
#include <memory>
#include <sophia/types.hpp>
#include <string>
#include <vector>
#include <vulkan/vulkan.hpp>
#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<vk::VertexInputBindingDescription> bindingDescriptions{};
std::vector<vk::VertexInputAttributeDescription> attributeDescriptions{};
vk::PipelineInputAssemblyStateCreateInfo inputAssemblyInfo;
vk::PipelineRasterizationStateCreateInfo rasterizationInfo;
vk::PipelineMultisampleStateCreateInfo multisampleInfo;
vk::PipelineColorBlendAttachmentState colorBlendAttachment;
vk::PipelineColorBlendStateCreateInfo colorBlendInfo;
vk::PipelineDepthStencilStateCreateInfo depthStencilInfo;
std::vector<vk::DynamicState> dynamicStateEnables;
vk::PipelineDynamicStateCreateInfo dynamicStateInfo;
vk::PipelineLayout pipelineLayout = VK_NULL_HANDLE;
std::vector<vk::Format> 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<vk::Format> colorAttachmentFormats,
vk::Format depthAttachmentFormat, vk::Format stencilAttachmentFormat,
std::vector<vk::VertexInputBindingDescription> bindingDescriptions,
std::vector<vk::VertexInputAttributeDescription> 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<vk::Format> colorAttachmentFormats);
Builder& setDepthAttachmentFormat(vk::Format depthAttachmentFormat);
Builder& setStencilAttachmentFormat(vk::Format stencilAttachmentFormat);
Builder& setVertexInput(
std::vector<vk::VertexInputBindingDescription> bindingDescriptions,
std::vector<vk::VertexInputAttributeDescription> attributeDescriptions);
std::unique_ptr<GraphicsPipeline> build() const;
private:
Device& device;
Shader* vertexShader = nullptr;
Shader* fragmentShader = nullptr;
vk::PipelineLayout pipelineLayout = VK_NULL_HANDLE;
std::vector<vk::Format> colorAttachmentFormats{};
vk::Format depthAttachmentFormat = vk::Format::eUndefined;
vk::Format stencilAttachmentFormat = vk::Format::eUndefined;
std::vector<vk::VertexInputBindingDescription> bindingDescriptions{};
std::vector<vk::VertexInputAttributeDescription> attributeDescriptions{};
};
} // namespace sophia
+27
View File
@@ -0,0 +1,27 @@
#include "pipeline.hpp"
#include <sophia/util/logger.hpp>
#include <stdexcept>
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<u32>& 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
+32
View File
@@ -0,0 +1,32 @@
#pragma once
#include <sophia/types.hpp>
#include <vector>
#include <vulkan/vulkan.hpp>
#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<u32>& spirv);
Device& device;
vk::UniquePipeline pipeline;
private:
vk::PipelineBindPoint bindPoint;
};
} // namespace sophia
View File
+3
View File
@@ -0,0 +1,3 @@
#pragma once
namespace sophia {} // namespace sophia
+220
View File
@@ -0,0 +1,220 @@
#include "swapchain.hpp"
#include <algorithm>
#include <limits>
#include <sophia/util/logger.hpp>
namespace sophia {
Swapchain::Swapchain(Device& device, vk::Extent2D extent)
: device{device}, extent{extent} {
initialize();
}
Swapchain::Swapchain(Device& device, vk::Extent2D extent,
std::shared_ptr<Swapchain> 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<u64>::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<vk::Image> 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<vk::SurfaceFormatKHR>& 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<vk::PresentModeKHR> 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<u32>::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
+83
View File
@@ -0,0 +1,83 @@
#pragma once
#include <memory>
#include <sophia/types.hpp>
#include <vector>
#include <vulkan/vulkan.hpp>
#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<Swapchain> 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<float>(this->extent.width) /
static_cast<float>(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<vk::SurfaceFormatKHR>& availableFormats);
vk::PresentModeKHR choosePresentMode(
const std::vector<vk::PresentModeKHR> 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<Swapchain> oldSwapchain;
vk::Format imageFormat;
std::vector<std::unique_ptr<Image>> images;
vk::Format depthFormat;
std::vector<std::unique_ptr<Image>> depthImages;
};
} // namespace sophia
+4
View File
@@ -25,3 +25,7 @@ target_include_directories(testbed PRIVATE
"${CMAKE_SOURCE_DIR}/engine/include" "${CMAKE_SOURCE_DIR}/engine/include"
"${CMAKE_SOURCE_DIR}/external/imgui" "${CMAKE_SOURCE_DIR}/external/imgui"
) )
target_compile_definitions(testbed PRIVATE
TESTBED_SHADER_DIR="${CMAKE_CURRENT_SOURCE_DIR}/shaders"
)
+12 -3
View File
@@ -1,5 +1,6 @@
#include <iostream> #include <iostream>
#include <sophia/application.hpp> // #include <sophia/application.hpp>
#include <sophia/compute_smoke_test.hpp>
#include <stdexcept> #include <stdexcept>
int main(int argc, const char** argv) { int main(int argc, const char** argv) {
@@ -7,9 +8,17 @@ int main(int argc, const char** argv) {
(void)argv; (void)argv;
std::cout << __FILE__ << "::" << __LINE__ << '\n'; 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; return EXIT_SUCCESS;
} }
+12
View File
@@ -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;
}