Compare commits
5 Commits
0cb0628101
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 99e06bbbb1 | |||
| d09f01402d | |||
| ce075403d5 | |||
| 518c664741 | |||
| c9fa1b8299 |
+2
-1
@@ -39,4 +39,5 @@ endif()
|
||||
|
||||
add_subdirectory(third_party)
|
||||
add_subdirectory(engine)
|
||||
add_subdirectory(testbed)
|
||||
add_subdirectory(testbed)
|
||||
add_subdirectory(examples)
|
||||
+15
-8
@@ -2,17 +2,24 @@ set(ENGINE_NAME ${PROJECT_NAME})
|
||||
|
||||
add_library(${ENGINE_NAME} STATIC)
|
||||
|
||||
file(GLOB_RECURSE SOURCES src/*.cpp)
|
||||
file(GLOB_RECURSE HEADERS include/*.hpp)
|
||||
file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS src/*.cpp)
|
||||
file(GLOB_RECURSE HEADERS CONFIGURE_DEPENDS include/*.hpp)
|
||||
|
||||
target_sources(${ENGINE_NAME} PRIVATE
|
||||
${SOURCES}
|
||||
# ${HEADERS}
|
||||
target_sources(${ENGINE_NAME} PRIVATE
|
||||
${SOURCES}
|
||||
)
|
||||
|
||||
target_include_directories(${ENGINE_NAME} PRIVATE
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src"
|
||||
)
|
||||
|
||||
target_include_directories(${ENGINE_NAME} PUBLIC
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/include"
|
||||
"${CMAKE_SOURCE_DIR}/third_party/imgui"
|
||||
"${CMAKE_SOURCE_DIR}/third_party/imgui/backends"
|
||||
)
|
||||
|
||||
target_link_libraries(${ENGINE_NAME} PUBLIC
|
||||
vulkan
|
||||
glfw
|
||||
glm
|
||||
imgui
|
||||
)
|
||||
|
||||
@@ -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
|
||||
@@ -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(); }
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,89 @@
|
||||
#include "descriptor_pool.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace sophia {
|
||||
|
||||
DescriptorPool::Builder& DescriptorPool::Builder::addPoolSize(
|
||||
vk::DescriptorType descriptorType, u32 count) {
|
||||
this->poolSizes.push_back({descriptorType, count});
|
||||
return *this;
|
||||
}
|
||||
|
||||
DescriptorPool::Builder& DescriptorPool::Builder::setPoolFlags(
|
||||
vk::DescriptorPoolCreateFlags flags) {
|
||||
this->poolFlags = flags;
|
||||
return *this;
|
||||
}
|
||||
|
||||
DescriptorPool::Builder& DescriptorPool::Builder::setMaxSets(u32 count) {
|
||||
this->maxSets = count;
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::unique_ptr<DescriptorPool> DescriptorPool::Builder::build() const {
|
||||
return std::make_unique<DescriptorPool>(this->device, this->maxSets,
|
||||
this->poolFlags, this->poolSizes);
|
||||
}
|
||||
|
||||
DescriptorPool::DescriptorPool(
|
||||
Device& device, u32 maxSets, vk::DescriptorPoolCreateFlags poolFlags,
|
||||
const std::vector<vk::DescriptorPoolSize>& poolSizes)
|
||||
: device{device} {
|
||||
vk::DescriptorPoolCreateInfo descriptorPoolInfo{};
|
||||
descriptorPoolInfo.poolSizeCount = static_cast<uint32_t>(poolSizes.size());
|
||||
descriptorPoolInfo.pPoolSizes = poolSizes.data();
|
||||
descriptorPoolInfo.maxSets = maxSets;
|
||||
descriptorPoolInfo.flags = poolFlags;
|
||||
|
||||
vk::Result result = this->device.get()->createDescriptorPool(
|
||||
&descriptorPoolInfo, nullptr, &this->descriptorPool);
|
||||
|
||||
if (result != vk::Result::eSuccess) {
|
||||
log::fatal("failed to create descriptor pool");
|
||||
throw std::runtime_error("failed to create descriptor pool");
|
||||
}
|
||||
}
|
||||
|
||||
DescriptorPool::~DescriptorPool() {
|
||||
this->device.get()->destroyDescriptorPool(this->descriptorPool);
|
||||
}
|
||||
|
||||
bool DescriptorPool::allocateDescriptorSet(
|
||||
const vk::DescriptorSetLayout descriptorSetLayout,
|
||||
vk::DescriptorSet& descriptor) const {
|
||||
vk::DescriptorSetAllocateInfo allocInfo{};
|
||||
allocInfo.descriptorPool = descriptorPool;
|
||||
allocInfo.pSetLayouts = &descriptorSetLayout;
|
||||
allocInfo.descriptorSetCount = 1;
|
||||
|
||||
// Might want to create a "DescriptorPoolManager" class that handles this
|
||||
// case, and builds a new pool whenever an old pool fills up. But this is
|
||||
// beyond our current scope
|
||||
vk::Result result =
|
||||
this->device.get()->allocateDescriptorSets(&allocInfo, &descriptor);
|
||||
|
||||
if (result == vk::Result::eSuccess) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void DescriptorPool::freeDescriptors(
|
||||
std::vector<vk::DescriptorSet>& descriptors) const {
|
||||
vk::Result result = this->device.get()->freeDescriptorSets(
|
||||
this->descriptorPool, static_cast<u32>(descriptors.size()),
|
||||
descriptors.data());
|
||||
|
||||
if (result != vk::Result::eSuccess) {
|
||||
log::fatal("failed to free descriptor sets");
|
||||
throw std::runtime_error("failed to free descriptor sets");
|
||||
}
|
||||
}
|
||||
|
||||
void DescriptorPool::resetPool() {
|
||||
this->device.get()->resetDescriptorPool(this->descriptorPool);
|
||||
}
|
||||
|
||||
} // namespace sophia
|
||||
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "device.hpp"
|
||||
|
||||
namespace sophia {
|
||||
|
||||
class DescriptorPool {
|
||||
public:
|
||||
DescriptorPool(const DescriptorPool&) = delete;
|
||||
DescriptorPool& operator=(const DescriptorPool&) = delete;
|
||||
|
||||
class Builder {
|
||||
public:
|
||||
Builder(Device& device) : device{device} {}
|
||||
|
||||
Builder& addPoolSize(vk::DescriptorType descriptorType, u32 count);
|
||||
Builder& setPoolFlags(vk::DescriptorPoolCreateFlags flags);
|
||||
Builder& setMaxSets(u32 count);
|
||||
std::unique_ptr<DescriptorPool> build() const;
|
||||
|
||||
private:
|
||||
Device& device;
|
||||
std::vector<vk::DescriptorPoolSize> poolSizes{};
|
||||
u32 maxSets = 1000;
|
||||
vk::DescriptorPoolCreateFlags poolFlags{};
|
||||
};
|
||||
|
||||
DescriptorPool(Device& device, u32 maxSets,
|
||||
vk::DescriptorPoolCreateFlags poolFlags,
|
||||
const std::vector<vk::DescriptorPoolSize>& poolSizes);
|
||||
~DescriptorPool();
|
||||
|
||||
bool allocateDescriptorSet(const vk::DescriptorSetLayout descriptorSetLayout,
|
||||
vk::DescriptorSet& descriptor) const;
|
||||
|
||||
void freeDescriptors(std::vector<vk::DescriptorSet>& descriptors) const;
|
||||
|
||||
void resetPool();
|
||||
|
||||
const vk::DescriptorPool& get() { return this->descriptorPool; }
|
||||
|
||||
private:
|
||||
Device& device;
|
||||
vk::DescriptorPool descriptorPool;
|
||||
|
||||
friend class DescriptorWriter;
|
||||
};
|
||||
|
||||
} // namespace sophia
|
||||
@@ -0,0 +1,53 @@
|
||||
#include "descriptor_set_layout.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace sophia {
|
||||
|
||||
DescriptorSetLayout::Builder& DescriptorSetLayout::Builder::addBinding(
|
||||
u32 binding, vk::DescriptorType descriptorType,
|
||||
vk::ShaderStageFlags stageFlags, u32 count) {
|
||||
assert(this->bindings.count(binding) == 0 && "binding already in use");
|
||||
|
||||
vk::DescriptorSetLayoutBinding layoutBinding{binding, descriptorType, count,
|
||||
stageFlags};
|
||||
this->bindings[binding] = layoutBinding;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::unique_ptr<DescriptorSetLayout> DescriptorSetLayout::Builder::build()
|
||||
const {
|
||||
return std::make_unique<DescriptorSetLayout>(this->device, this->bindings);
|
||||
}
|
||||
|
||||
DescriptorSetLayout::DescriptorSetLayout(
|
||||
Device& device,
|
||||
std::unordered_map<u32, vk::DescriptorSetLayoutBinding> bindings)
|
||||
: device{device}, bindings{bindings} {
|
||||
std::vector<vk::DescriptorSetLayoutBinding> setLayoutBindings{};
|
||||
|
||||
for (auto binding : bindings) {
|
||||
setLayoutBindings.push_back(binding.second);
|
||||
}
|
||||
|
||||
vk::DescriptorSetLayoutCreateInfo layoutCreateInfo{};
|
||||
layoutCreateInfo.bindingCount =
|
||||
static_cast<uint32_t>(setLayoutBindings.size());
|
||||
layoutCreateInfo.pBindings = setLayoutBindings.data();
|
||||
|
||||
vk::Result result = this->device.get()->createDescriptorSetLayout(
|
||||
&layoutCreateInfo, nullptr, &this->layout);
|
||||
|
||||
if (result != vk::Result::eSuccess) {
|
||||
log::fatal("failed to create descriptor pool");
|
||||
throw std::runtime_error("failed to create descriptor set layout");
|
||||
}
|
||||
}
|
||||
|
||||
DescriptorSetLayout::~DescriptorSetLayout() {
|
||||
this->device.get()->destroyDescriptorSetLayout(this->layout);
|
||||
}
|
||||
|
||||
} // namespace sophia
|
||||
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "device.hpp"
|
||||
|
||||
namespace sophia {
|
||||
|
||||
class DescriptorSetLayout {
|
||||
public:
|
||||
DescriptorSetLayout(const DescriptorSetLayout&) = delete;
|
||||
DescriptorSetLayout& operator=(const DescriptorSetLayout&) = delete;
|
||||
|
||||
class Builder {
|
||||
public:
|
||||
Builder(Device& device) : device{device} {}
|
||||
|
||||
Builder& addBinding(u32 binding, vk::DescriptorType descriptorType,
|
||||
vk::ShaderStageFlags stageFlags, u32 count = 1);
|
||||
|
||||
std::unique_ptr<DescriptorSetLayout> build() const;
|
||||
|
||||
private:
|
||||
Device& device;
|
||||
std::unordered_map<u32, vk::DescriptorSetLayoutBinding> bindings{};
|
||||
};
|
||||
|
||||
DescriptorSetLayout(
|
||||
Device& device,
|
||||
std::unordered_map<u32, vk::DescriptorSetLayoutBinding> bindings);
|
||||
~DescriptorSetLayout();
|
||||
|
||||
vk::DescriptorSetLayout getDescriptorSetLayout() const {
|
||||
return this->layout;
|
||||
}
|
||||
|
||||
private:
|
||||
Device& device;
|
||||
vk::DescriptorSetLayout layout;
|
||||
std::unordered_map<u32, vk::DescriptorSetLayoutBinding> bindings;
|
||||
|
||||
friend class DescriptorWriter;
|
||||
};
|
||||
|
||||
} // namespace sophia
|
||||
@@ -0,0 +1,72 @@
|
||||
#include "descriptor_writer.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace sophia {
|
||||
|
||||
DescriptorWriter::DescriptorWriter(DescriptorSetLayout& setLayout,
|
||||
DescriptorPool& pool)
|
||||
: setLayout{setLayout}, pool{pool} {}
|
||||
|
||||
DescriptorWriter& DescriptorWriter::writeBuffer(
|
||||
u32 binding, vk::DescriptorBufferInfo* bufferInfo) {
|
||||
assert(this->setLayout.bindings.count(binding) == 1 &&
|
||||
"setLayout does not contain specified binding");
|
||||
|
||||
auto& bindingDescription = this->setLayout.bindings[binding];
|
||||
|
||||
assert(bindingDescription.descriptorCount == 1 &&
|
||||
"binding single descriptor info, but binding expects multiple");
|
||||
|
||||
vk::WriteDescriptorSet write{};
|
||||
write.descriptorType = bindingDescription.descriptorType;
|
||||
write.dstBinding = binding;
|
||||
write.pBufferInfo = bufferInfo;
|
||||
write.descriptorCount = 1;
|
||||
|
||||
writes.push_back(write);
|
||||
return *this;
|
||||
}
|
||||
|
||||
DescriptorWriter& DescriptorWriter::writeImage(
|
||||
u32 binding, vk::DescriptorImageInfo* imageInfo) {
|
||||
assert(this->setLayout.bindings.count(binding) == 1 &&
|
||||
"setLayout does not contain specified binding");
|
||||
|
||||
auto& bindingDescription = this->setLayout.bindings[binding];
|
||||
|
||||
assert(bindingDescription.descriptorCount == 1 &&
|
||||
"binding single descriptor info, but binding expects multiple");
|
||||
|
||||
vk::WriteDescriptorSet write{};
|
||||
write.descriptorType = bindingDescription.descriptorType;
|
||||
write.dstBinding = binding;
|
||||
write.pImageInfo = imageInfo;
|
||||
write.descriptorCount = 1;
|
||||
|
||||
writes.push_back(write);
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool DescriptorWriter::build(vk::DescriptorSet& set) {
|
||||
bool success =
|
||||
this->pool.allocateDescriptorSet(setLayout.getDescriptorSetLayout(), set);
|
||||
|
||||
if (!success) {
|
||||
return false;
|
||||
}
|
||||
overwrite(set);
|
||||
return true;
|
||||
}
|
||||
|
||||
void DescriptorWriter::overwrite(vk::DescriptorSet& set) {
|
||||
for (auto& write : writes) {
|
||||
write.dstSet = set;
|
||||
}
|
||||
|
||||
this->pool.device.get()->updateDescriptorSets(writes.size(), writes.data(), 0,
|
||||
nullptr);
|
||||
}
|
||||
|
||||
} // namespace sophia
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "descriptor_pool.hpp"
|
||||
#include "descriptor_set_layout.hpp"
|
||||
#include "device.hpp"
|
||||
|
||||
namespace sophia {
|
||||
|
||||
class DescriptorWriter {
|
||||
public:
|
||||
DescriptorWriter(DescriptorSetLayout& setLayout, DescriptorPool& pool);
|
||||
|
||||
DescriptorWriter& writeBuffer(u32 binding,
|
||||
vk::DescriptorBufferInfo* bufferInfo);
|
||||
DescriptorWriter& writeImage(u32 binding, vk::DescriptorImageInfo* imageInfo);
|
||||
|
||||
bool build(vk::DescriptorSet& set);
|
||||
void overwrite(vk::DescriptorSet& set);
|
||||
|
||||
private:
|
||||
DescriptorSetLayout& setLayout;
|
||||
DescriptorPool& pool;
|
||||
std::vector<vk::WriteDescriptorSet> writes;
|
||||
};
|
||||
|
||||
} // namespace sophia
|
||||
+74
-15
@@ -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<const char*>{}
|
||||
: std::vector<const char*>{
|
||||
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> 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) {
|
||||
@@ -299,12 +343,14 @@ bool Device::checkValidationLayerSupport() {
|
||||
}
|
||||
|
||||
std::vector<const char*> Device::getRequiredExtensions() {
|
||||
u32 glfw_extension_count = 0;
|
||||
const char** glfw_extensions;
|
||||
glfw_extensions = glfwGetRequiredInstanceExtensions(&glfw_extension_count);
|
||||
std::vector<const char*> extensions;
|
||||
|
||||
std::vector<const char*> 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);
|
||||
@@ -351,7 +397,7 @@ void Device::createVulkanInstance() {
|
||||
}
|
||||
|
||||
vk::ApplicationInfo app_info = vk::ApplicationInfo(
|
||||
"Hephaestus", VK_MAKE_VERSION(1, 0, 0), "Hephaestus Vulkan Engine",
|
||||
"Sophia", VK_MAKE_VERSION(1, 0, 0), "Sophia Vulkan Engine",
|
||||
VK_MAKE_VERSION(1, 0, 0), VK_API_VERSION_1_3);
|
||||
|
||||
auto extensions = getRequiredExtensions();
|
||||
@@ -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<vk::DeviceQueueCreateInfo> queueCreateInfos;
|
||||
std::set<uint32_t> uniqueQueueFamilies = {indices.graphicsFamily.value(),
|
||||
indices.presentFamily.value()};
|
||||
std::set<uint32_t> uniqueQueueFamilies = {indices.graphicsFamily.value()};
|
||||
if (!this->headless) {
|
||||
uniqueQueueFamilies.insert(indices.presentFamily.value());
|
||||
}
|
||||
|
||||
std::vector<vk::DeviceQueueCreateInfo> 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<uint32_t>(queueCreateInfos.size()),
|
||||
queueCreateInfos.data());
|
||||
createInfo.pEnabledFeatures = &deviceFeatures;
|
||||
createInfo.pNext = &vulkan13Features;
|
||||
|
||||
createInfo.enabledExtensionCount =
|
||||
static_cast<uint32_t>(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(
|
||||
|
||||
+22
-4
@@ -3,6 +3,7 @@
|
||||
#include <imgui_impl_vulkan.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <sophia/types.hpp>
|
||||
#include <vector>
|
||||
@@ -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<const char*> enabledLayers = {
|
||||
"VK_LAYER_KHRONOS_validation"};
|
||||
#endif
|
||||
const std::vector<const char*> enabledExtensions = {
|
||||
VK_KHR_SWAPCHAIN_EXTENSION_NAME};
|
||||
const std::vector<const char*> enabledExtensions;
|
||||
};
|
||||
|
||||
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
|
||||
|
||||
+14
-8
@@ -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<Window>(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 =
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#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> window;
|
||||
std::unique_ptr<Device> device;
|
||||
|
||||
bool isRunning = true;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
namespace sophia {} // namespace sophia
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,7 @@
|
||||
file(GLOB EXAMPLE_DIRS CONFIGURE_DEPENDS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/*)
|
||||
|
||||
foreach(dir ${EXAMPLE_DIRS})
|
||||
if(IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${dir} AND EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${dir}/CMakeLists.txt)
|
||||
add_subdirectory(${dir})
|
||||
endif()
|
||||
endforeach()
|
||||
@@ -0,0 +1,3 @@
|
||||
add_executable(triangle main.cpp)
|
||||
|
||||
target_link_libraries(triangle PRIVATE ${PROJECT_NAME})
|
||||
@@ -0,0 +1,9 @@
|
||||
#include <sophia/application.hpp>
|
||||
|
||||
int main() {
|
||||
sophia::Application app{{.width = 800, .height = 600, .name = "Triangle"}};
|
||||
|
||||
app.run();
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
set(NAME testbed)
|
||||
set(ENGINE_NAME ${PROJECT_NAME})
|
||||
|
||||
file(GLOB_RECURSE SOURCES *.cpp src/*.cpp)
|
||||
file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS *.cpp src/*.cpp)
|
||||
|
||||
add_library(deps INTERFACE)
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
+15
-2
@@ -1,11 +1,24 @@
|
||||
#include <iostream>
|
||||
// #include <sophia/application.hpp>
|
||||
#include <sophia/compute_smoke_test.hpp>
|
||||
#include <stdexcept>
|
||||
|
||||
int main(int argc, const char **argv)
|
||||
{
|
||||
int main(int argc, const char** argv) {
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
std::cout << __FILE__ << "::" << __LINE__ << '\n';
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// sophia::Application app{
|
||||
// {.width = 750, .height = 1000, .name = "Testbed", .headless = true}};
|
||||
|
||||
// app.run();
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user