moving stuff around
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
#include "vulkan/access.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <sophia/util/logger.hpp>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace sophia {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::array<Access, 7> ALL_ACCESSES = {
|
||||
Access::ColorWrite, Access::DepthWrite, Access::SampledRead,
|
||||
Access::StorageReadWrite, Access::TransferSrc, Access::TransferDst,
|
||||
Access::Present,
|
||||
};
|
||||
|
||||
constexpr vk::AccessFlags WRITE_ACCESSES =
|
||||
vk::AccessFlagBits::eColorAttachmentWrite |
|
||||
vk::AccessFlagBits::eDepthStencilAttachmentWrite |
|
||||
vk::AccessFlagBits::eShaderWrite | vk::AccessFlagBits::eTransferWrite;
|
||||
|
||||
} // namespace
|
||||
|
||||
AccessState getAccessState(Access access) {
|
||||
switch (access) {
|
||||
case Access::ColorWrite:
|
||||
return {vk::ImageLayout::eColorAttachmentOptimal,
|
||||
vk::PipelineStageFlagBits::eColorAttachmentOutput,
|
||||
vk::AccessFlagBits::eColorAttachmentRead |
|
||||
vk::AccessFlagBits::eColorAttachmentWrite};
|
||||
case Access::DepthWrite:
|
||||
return {vk::ImageLayout::eDepthStencilAttachmentOptimal,
|
||||
vk::PipelineStageFlagBits::eEarlyFragmentTests |
|
||||
vk::PipelineStageFlagBits::eLateFragmentTests,
|
||||
vk::AccessFlagBits::eDepthStencilAttachmentRead |
|
||||
vk::AccessFlagBits::eDepthStencilAttachmentWrite};
|
||||
case Access::SampledRead:
|
||||
return {vk::ImageLayout::eShaderReadOnlyOptimal,
|
||||
vk::PipelineStageFlagBits::eFragmentShader,
|
||||
vk::AccessFlagBits::eShaderRead};
|
||||
case Access::StorageReadWrite:
|
||||
return {
|
||||
vk::ImageLayout::eGeneral, vk::PipelineStageFlagBits::eComputeShader,
|
||||
vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite};
|
||||
case Access::TransferSrc:
|
||||
return {vk::ImageLayout::eTransferSrcOptimal,
|
||||
vk::PipelineStageFlagBits::eTransfer,
|
||||
vk::AccessFlagBits::eTransferRead};
|
||||
case Access::TransferDst:
|
||||
return {vk::ImageLayout::eTransferDstOptimal,
|
||||
vk::PipelineStageFlagBits::eTransfer,
|
||||
vk::AccessFlagBits::eTransferWrite};
|
||||
case Access::Present:
|
||||
return {vk::ImageLayout::ePresentSrcKHR,
|
||||
vk::PipelineStageFlagBits::eBottomOfPipe, vk::AccessFlags{}};
|
||||
}
|
||||
|
||||
log::fatal("Unknown Access value.");
|
||||
throw std::runtime_error("Unknown Access value.");
|
||||
}
|
||||
|
||||
AccessState getStateForLayout(vk::ImageLayout layout) {
|
||||
if (layout == vk::ImageLayout::eUndefined) {
|
||||
return AccessState{};
|
||||
}
|
||||
|
||||
for (Access access : ALL_ACCESSES) {
|
||||
AccessState state = getAccessState(access);
|
||||
if (state.layout == layout) {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
log::fatal("Unsupported image layout:", vk::to_string(layout));
|
||||
throw std::runtime_error("Unsupported image layout.");
|
||||
}
|
||||
|
||||
bool writesImage(const AccessState& state) {
|
||||
return static_cast<bool>(state.access & WRITE_ACCESSES);
|
||||
}
|
||||
|
||||
bool needsBarrier(const AccessState& from, const AccessState& to) {
|
||||
return from.layout != to.layout || writesImage(from) || writesImage(to);
|
||||
}
|
||||
|
||||
vk::ImageMemoryBarrier makeImageBarrier(vk::Image image,
|
||||
const vk::ImageSubresourceRange& range,
|
||||
const AccessState& from,
|
||||
const AccessState& to) {
|
||||
vk::ImageMemoryBarrier barrier{};
|
||||
barrier.oldLayout = from.layout;
|
||||
barrier.newLayout = to.layout;
|
||||
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.image = image;
|
||||
barrier.subresourceRange = range;
|
||||
// Only writes need to be made available; reads just need the execution
|
||||
// dependency the stage masks already give.
|
||||
barrier.srcAccessMask = from.access & WRITE_ACCESSES;
|
||||
barrier.dstAccessMask = to.access;
|
||||
return barrier;
|
||||
}
|
||||
|
||||
} // namespace sophia
|
||||
@@ -0,0 +1,63 @@
|
||||
#pragma once
|
||||
|
||||
#include <vulkan/vulkan.hpp>
|
||||
|
||||
namespace sophia {
|
||||
|
||||
/**
|
||||
* How an image is used at a point in a frame. Each value implies an image
|
||||
* layout plus the pipeline stages and memory accesses that go with it, so a
|
||||
* barrier between any two of them can be derived instead of hand-written.
|
||||
*/
|
||||
enum class Access {
|
||||
ColorWrite,
|
||||
DepthWrite,
|
||||
SampledRead,
|
||||
StorageReadWrite,
|
||||
TransferSrc,
|
||||
TransferDst,
|
||||
Present,
|
||||
};
|
||||
|
||||
/**
|
||||
* The layout, stages and accesses an image is in after some use. Barriers go
|
||||
* from one of these to another.
|
||||
*/
|
||||
struct AccessState {
|
||||
vk::ImageLayout layout = vk::ImageLayout::eUndefined;
|
||||
vk::PipelineStageFlags stage = vk::PipelineStageFlagBits::eTopOfPipe;
|
||||
vk::AccessFlags access;
|
||||
};
|
||||
|
||||
/**
|
||||
* The stage RenderContext waits on for the swapchain image-available
|
||||
* semaphore. A barrier that takes a swapchain image out of its
|
||||
* just-acquired state has to use this as its source stage, otherwise the
|
||||
* layout transition isn't ordered after the acquire.
|
||||
*/
|
||||
inline constexpr vk::PipelineStageFlags SWAPCHAIN_ACQUIRE_STAGE =
|
||||
vk::PipelineStageFlagBits::eColorAttachmentOutput;
|
||||
|
||||
AccessState getAccessState(Access access);
|
||||
|
||||
/**
|
||||
* The state of an image sitting in `layout` with no outstanding work.
|
||||
* eUndefined maps to "nothing to wait on"; layouts with no matching Access
|
||||
* throw.
|
||||
*/
|
||||
AccessState getStateForLayout(vk::ImageLayout layout);
|
||||
|
||||
bool writesImage(const AccessState& state);
|
||||
|
||||
/**
|
||||
* Whether going from `from` to `to` needs a barrier: a layout change, or any
|
||||
* hazard involving a write. Read-after-read in the same layout doesn't.
|
||||
*/
|
||||
bool needsBarrier(const AccessState& from, const AccessState& to);
|
||||
|
||||
vk::ImageMemoryBarrier makeImageBarrier(vk::Image image,
|
||||
const vk::ImageSubresourceRange& range,
|
||||
const AccessState& from,
|
||||
const AccessState& to);
|
||||
|
||||
} // namespace sophia
|
||||
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* Based on
|
||||
* https://github.com/blurrypiano/littleVulkanEngine/blob/main/src/lve_buffer.cpp
|
||||
*/
|
||||
#include "vulkan/buffer.hpp"
|
||||
|
||||
#include <sophia/util/assert.hpp>
|
||||
|
||||
namespace sophia {
|
||||
|
||||
VkDeviceSize Buffer::getAlignment(VkDeviceSize instanceSize,
|
||||
VkDeviceSize minOffsetAlignment) {
|
||||
if (minOffsetAlignment > 0) {
|
||||
return (instanceSize + minOffsetAlignment - 1) & ~(minOffsetAlignment - 1);
|
||||
}
|
||||
return instanceSize;
|
||||
}
|
||||
|
||||
Buffer::Buffer(Device& device, vk::DeviceSize instanceSize, u32 instanceCount,
|
||||
vk::BufferUsageFlags usageFlags,
|
||||
vk::MemoryPropertyFlags memoryPropertyFlags,
|
||||
vk::DeviceSize minOffsetAlignment)
|
||||
: device{device},
|
||||
instanceSize{instanceSize},
|
||||
instanceCount{instanceCount},
|
||||
usageFlags{usageFlags},
|
||||
memoryPropertyFlags{memoryPropertyFlags} {
|
||||
alignmentSize = getAlignment(instanceSize, minOffsetAlignment);
|
||||
bufferSize = alignmentSize * instanceCount;
|
||||
device.createBuffer(bufferSize, usageFlags, memoryPropertyFlags, buffer,
|
||||
memory);
|
||||
}
|
||||
|
||||
Buffer::~Buffer() {
|
||||
unmap();
|
||||
this->device.get()->destroyBuffer(this->buffer);
|
||||
this->device.get()->freeMemory(this->memory);
|
||||
}
|
||||
|
||||
vk::Result Buffer::map(vk::DeviceSize size, vk::DeviceSize offset) {
|
||||
SOPHIA_ASSERT(buffer && memory, "Called map on buffer before create");
|
||||
|
||||
vk::MemoryMapFlags emptyFlag{};
|
||||
return this->device.get()->mapMemory(this->memory, offset, size, emptyFlag,
|
||||
&this->mapped);
|
||||
}
|
||||
|
||||
void Buffer::unmap() {
|
||||
if (mapped) {
|
||||
this->device.get()->unmapMemory(this->memory);
|
||||
mapped = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void Buffer::writeToBuffer(void* data, vk::DeviceSize size,
|
||||
vk::DeviceSize offset) {
|
||||
SOPHIA_ASSERT(mapped, "Cannot copy to unmapped buffer");
|
||||
|
||||
if (size == VK_WHOLE_SIZE) {
|
||||
memcpy(mapped, data, bufferSize);
|
||||
} else {
|
||||
char* memOffset = (char*)mapped;
|
||||
memOffset += offset;
|
||||
memcpy(memOffset, data, size);
|
||||
}
|
||||
}
|
||||
|
||||
vk::Result Buffer::flush(vk::DeviceSize size, vk::DeviceSize offset) {
|
||||
vk::MappedMemoryRange mappedRange = {};
|
||||
mappedRange.memory = memory;
|
||||
mappedRange.offset = offset;
|
||||
mappedRange.size = size;
|
||||
return this->device.get()->flushMappedMemoryRanges(1, &mappedRange);
|
||||
}
|
||||
|
||||
vk::Result Buffer::invalidate(vk::DeviceSize size, vk::DeviceSize offset) {
|
||||
vk::MappedMemoryRange mappedRange = {};
|
||||
mappedRange.memory = memory;
|
||||
mappedRange.offset = offset;
|
||||
mappedRange.size = size;
|
||||
return this->device.get()->invalidateMappedMemoryRanges(1, &mappedRange);
|
||||
}
|
||||
|
||||
vk::DescriptorBufferInfo Buffer::descriptorInfo(vk::DeviceSize size,
|
||||
vk::DeviceSize offset) {
|
||||
return vk::DescriptorBufferInfo{
|
||||
this->buffer,
|
||||
offset,
|
||||
size,
|
||||
};
|
||||
}
|
||||
|
||||
void Buffer::writeToIndex(void* data, int index) {
|
||||
writeToBuffer(data, instanceSize, index * alignmentSize);
|
||||
}
|
||||
|
||||
vk::Result Buffer::flushIndex(int index) {
|
||||
return flush(alignmentSize, index * alignmentSize);
|
||||
}
|
||||
|
||||
vk::DescriptorBufferInfo Buffer::descriptorInfoForIndex(int index) {
|
||||
return descriptorInfo(alignmentSize, index * alignmentSize);
|
||||
}
|
||||
|
||||
vk::Result Buffer::invalidateIndex(int index) {
|
||||
return invalidate(alignmentSize, index * alignmentSize);
|
||||
}
|
||||
|
||||
} // namespace sophia
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Based on
|
||||
* https://github.com/blurrypiano/littleVulkanEngine/blob/main/src/lve_buffer.hpp
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "vulkan/device.hpp"
|
||||
|
||||
namespace sophia {
|
||||
|
||||
class Buffer {
|
||||
public:
|
||||
Buffer(const Buffer&) = delete;
|
||||
Buffer& operator=(const Buffer&) = delete;
|
||||
|
||||
Buffer(Device& device, vk::DeviceSize instanceSize, u32 instanceCount,
|
||||
vk::BufferUsageFlags usageFlags,
|
||||
vk::MemoryPropertyFlags memoryPropertyFlags,
|
||||
vk::DeviceSize minOffsetAlignment = 1);
|
||||
~Buffer();
|
||||
|
||||
vk::Result map(vk::DeviceSize size = VK_WHOLE_SIZE,
|
||||
vk::DeviceSize offset = 0);
|
||||
void unmap();
|
||||
|
||||
void writeToBuffer(void* data, vk::DeviceSize size = VK_WHOLE_SIZE,
|
||||
vk::DeviceSize offset = 0);
|
||||
vk::Result flush(vk::DeviceSize size = VK_WHOLE_SIZE,
|
||||
vk::DeviceSize offset = 0);
|
||||
vk::DescriptorBufferInfo descriptorInfo(vk::DeviceSize size = VK_WHOLE_SIZE,
|
||||
vk::DeviceSize offset = 0);
|
||||
vk::Result invalidate(vk::DeviceSize size = VK_WHOLE_SIZE,
|
||||
vk::DeviceSize offset = 0);
|
||||
|
||||
void writeToIndex(void* data, int index);
|
||||
vk::Result flushIndex(int index);
|
||||
vk::DescriptorBufferInfo descriptorInfoForIndex(int index);
|
||||
vk::Result invalidateIndex(int index);
|
||||
|
||||
vk::Buffer getBuffer() const { return buffer; }
|
||||
void* getMappedMemory() const { return mapped; }
|
||||
uint32_t getInstanceCount() const { return instanceCount; }
|
||||
vk::DeviceSize getInstanceSize() const { return instanceSize; }
|
||||
vk::DeviceSize getAlignmentSize() const { return instanceSize; }
|
||||
vk::BufferUsageFlags getUsageFlags() const { return usageFlags; }
|
||||
vk::MemoryPropertyFlags getMemoryPropertyFlags() const {
|
||||
return memoryPropertyFlags;
|
||||
}
|
||||
vk::DeviceSize getBufferSize() const { return bufferSize; }
|
||||
|
||||
private:
|
||||
static vk::DeviceSize getAlignment(vk::DeviceSize instanceSize,
|
||||
vk::DeviceSize minOffsetAlignment);
|
||||
|
||||
Device& device;
|
||||
void* mapped = nullptr;
|
||||
vk::Buffer buffer = VK_NULL_HANDLE;
|
||||
vk::DeviceMemory memory = VK_NULL_HANDLE;
|
||||
|
||||
vk::DeviceSize bufferSize;
|
||||
uint32_t instanceCount;
|
||||
vk::DeviceSize instanceSize;
|
||||
vk::DeviceSize alignmentSize;
|
||||
vk::BufferUsageFlags usageFlags;
|
||||
vk::MemoryPropertyFlags memoryPropertyFlags;
|
||||
};
|
||||
|
||||
} // 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 "vulkan/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 <sophia/util/assert.hpp>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace sophia {
|
||||
|
||||
DescriptorSetLayout::Builder& DescriptorSetLayout::Builder::addBinding(
|
||||
u32 binding, vk::DescriptorType descriptorType,
|
||||
vk::ShaderStageFlags stageFlags, u32 count) {
|
||||
SOPHIA_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 "vulkan/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 <sophia/util/assert.hpp>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace sophia {
|
||||
|
||||
DescriptorWriter::DescriptorWriter(DescriptorSetLayout& setLayout,
|
||||
DescriptorPool& pool)
|
||||
: setLayout{setLayout}, pool{pool} {}
|
||||
|
||||
DescriptorWriter& DescriptorWriter::writeBuffer(
|
||||
u32 binding, vk::DescriptorBufferInfo* bufferInfo) {
|
||||
SOPHIA_ASSERT(this->setLayout.bindings.count(binding) == 1,
|
||||
"setLayout does not contain specified binding");
|
||||
|
||||
auto& bindingDescription = this->setLayout.bindings[binding];
|
||||
|
||||
SOPHIA_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) {
|
||||
SOPHIA_ASSERT(this->setLayout.bindings.count(binding) == 1,
|
||||
"setLayout does not contain specified binding");
|
||||
|
||||
auto& bindingDescription = this->setLayout.bindings[binding];
|
||||
|
||||
SOPHIA_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 "vulkan/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
|
||||
@@ -0,0 +1,540 @@
|
||||
#include "vulkan/device.hpp"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <set>
|
||||
#include <sophia/util/logger.hpp>
|
||||
|
||||
namespace sophia {
|
||||
|
||||
static VKAPI_ATTR vk::Bool32 VKAPI_CALL
|
||||
debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT m_severity,
|
||||
vk::DebugUtilsMessageTypeFlagsEXT m_type,
|
||||
const vk::DebugUtilsMessengerCallbackDataEXT* pCallback_data,
|
||||
void* pUser_data) {
|
||||
(void)m_type;
|
||||
(void)pCallback_data;
|
||||
(void)pUser_data;
|
||||
|
||||
if (m_severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError) {
|
||||
log::error(pCallback_data->pMessage);
|
||||
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);
|
||||
}
|
||||
|
||||
return VK_FALSE;
|
||||
}
|
||||
|
||||
VkResult createDebugUtilsMessengerEXT(
|
||||
VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo,
|
||||
const VkAllocationCallbacks* pAllocator,
|
||||
VkDebugUtilsMessengerEXT* pCallback) {
|
||||
auto func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(
|
||||
instance, "vkCreateDebugUtilsMessengerEXT");
|
||||
if (func != nullptr) {
|
||||
return func(instance, pCreateInfo, pAllocator, pCallback);
|
||||
} else {
|
||||
return VK_ERROR_EXTENSION_NOT_PRESENT;
|
||||
}
|
||||
}
|
||||
|
||||
void destroyDebugUtilsMessengerEXT(VkInstance instance,
|
||||
VkDebugUtilsMessengerEXT callback,
|
||||
const VkAllocationCallbacks* pAllocator) {
|
||||
auto func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(
|
||||
instance, "vkDestroyDebugUtilsMessengerEXT");
|
||||
if (func != nullptr) {
|
||||
func(instance, callback, pAllocator);
|
||||
}
|
||||
}
|
||||
|
||||
Device::Device(Window* window)
|
||||
: headless{window == nullptr},
|
||||
enabledExtensions{headless ? std::vector<const char*>{}
|
||||
: std::vector<const char*>{
|
||||
VK_KHR_SWAPCHAIN_EXTENSION_NAME}} {
|
||||
createVulkanInstance();
|
||||
setupDebugMessenger();
|
||||
if (!this->headless) {
|
||||
window->createSurface(*instance, surface);
|
||||
}
|
||||
pickPhysicalDevice();
|
||||
createLogicalDevice();
|
||||
createCommandPool();
|
||||
}
|
||||
|
||||
Device::~Device() {
|
||||
if (this->enableValidationLayers) {
|
||||
destroyDebugUtilsMessengerEXT(this->instance.get(), this->debugMessenger,
|
||||
nullptr);
|
||||
log::trace("destroyed Vulkan Debugger");
|
||||
}
|
||||
|
||||
this->device->destroyCommandPool(commandPool);
|
||||
log::trace("destroyed vk::CommandPool");
|
||||
|
||||
if (!this->headless) {
|
||||
this->instance->destroySurfaceKHR(surface);
|
||||
log::trace("destroyed vk::SurfaceKHR");
|
||||
}
|
||||
}
|
||||
|
||||
u32 Device::findMemoryType(u32 typeFilter, vk::MemoryPropertyFlags properties) {
|
||||
vk::PhysicalDeviceMemoryProperties memoryProperties =
|
||||
this->physicalDevice.getMemoryProperties();
|
||||
|
||||
for (u32 i = 0; i < memoryProperties.memoryTypeCount; i++) {
|
||||
if ((typeFilter & (1 << i)) &&
|
||||
(memoryProperties.memoryTypes[i].propertyFlags & properties) ==
|
||||
properties) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
log::fatal("failed to find suitable memory type");
|
||||
throw std::runtime_error("failed to find suitable memory type");
|
||||
}
|
||||
|
||||
vk::Format Device::findSupportedFormat(
|
||||
const std::vector<vk::Format>& candidates, vk::ImageTiling tiling,
|
||||
vk::FormatFeatureFlags features) {
|
||||
for (vk::Format format : candidates) {
|
||||
vk::FormatProperties properties =
|
||||
physicalDevice.getFormatProperties(format);
|
||||
|
||||
if ((tiling == vk::ImageTiling::eLinear &&
|
||||
(properties.linearTilingFeatures & features) == features) ||
|
||||
(tiling == vk::ImageTiling::eOptimal &&
|
||||
(properties.optimalTilingFeatures & features) == features)) {
|
||||
return format;
|
||||
}
|
||||
}
|
||||
|
||||
log::fatal("failed to find supported format");
|
||||
throw std::runtime_error("failed to find supported format");
|
||||
}
|
||||
|
||||
void Device::createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage,
|
||||
vk::MemoryPropertyFlags properties,
|
||||
vk::Buffer& buffer, vk::DeviceMemory& bufferMemory) {
|
||||
vk::BufferCreateInfo bufferInfo = {};
|
||||
bufferInfo.size = size;
|
||||
bufferInfo.usage = usage;
|
||||
bufferInfo.sharingMode = vk::SharingMode::eExclusive;
|
||||
|
||||
try {
|
||||
buffer = this->device->createBuffer(bufferInfo);
|
||||
} catch (const vk::SystemError& error) {
|
||||
log::fatal("failed to create vertex buffer");
|
||||
throw std::runtime_error("failed to create vertex buffer");
|
||||
}
|
||||
|
||||
vk::MemoryRequirements memoryRequirements =
|
||||
this->device->getBufferMemoryRequirements(buffer);
|
||||
|
||||
vk::MemoryAllocateInfo allocInfo = {};
|
||||
allocInfo.allocationSize = memoryRequirements.size;
|
||||
allocInfo.memoryTypeIndex =
|
||||
findMemoryType(memoryRequirements.memoryTypeBits, properties);
|
||||
|
||||
try {
|
||||
bufferMemory = this->device->allocateMemory(allocInfo);
|
||||
} catch (const vk::SystemError& error) {
|
||||
log::fatal("failed to allocate vertex buffer memory");
|
||||
throw std::runtime_error("failed to allocate vertex buffer memory");
|
||||
}
|
||||
|
||||
this->device->bindBufferMemory(buffer, bufferMemory, 0);
|
||||
}
|
||||
|
||||
vk::CommandBuffer Device::beginSingleTimeCommands() {
|
||||
vk::CommandBufferAllocateInfo allocInfo{};
|
||||
allocInfo.level = vk::CommandBufferLevel::ePrimary;
|
||||
allocInfo.commandPool = this->commandPool;
|
||||
allocInfo.commandBufferCount = 1;
|
||||
|
||||
vk::CommandBuffer commandBuffer;
|
||||
|
||||
try {
|
||||
vk::Result result =
|
||||
this->device->allocateCommandBuffers(&allocInfo, &commandBuffer);
|
||||
if (result != vk::Result::eSuccess) {
|
||||
throw vk::SystemError(result);
|
||||
}
|
||||
} catch (const vk::SystemError& error) {
|
||||
log::error("failed to allocate single time commandBuffer. Error: ",
|
||||
error.what());
|
||||
throw std::runtime_error("failed to allocate single time commandBuffer");
|
||||
}
|
||||
|
||||
vk::CommandBufferBeginInfo beginInfo{};
|
||||
beginInfo.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit;
|
||||
|
||||
try {
|
||||
vk::Result result = commandBuffer.begin(&beginInfo);
|
||||
if (result != vk::Result::eSuccess) {
|
||||
throw vk::SystemError(result);
|
||||
}
|
||||
} catch (const vk::SystemError& error) {
|
||||
log::error("failed to begin single time commandBuffer. Error: ",
|
||||
error.what());
|
||||
throw std::runtime_error("failed to begin single time commandBuffer");
|
||||
}
|
||||
|
||||
return commandBuffer;
|
||||
}
|
||||
|
||||
void Device::endSingleTimeCommands(vk::CommandBuffer commandBuffer) {
|
||||
commandBuffer.end();
|
||||
|
||||
vk::SubmitInfo submitInfo = {};
|
||||
submitInfo.commandBufferCount = 1;
|
||||
submitInfo.pCommandBuffers = &commandBuffer;
|
||||
|
||||
this->graphicsQueue.submit(submitInfo, nullptr);
|
||||
this->graphicsQueue.waitIdle();
|
||||
|
||||
this->device->freeCommandBuffers(commandPool, commandBuffer);
|
||||
}
|
||||
|
||||
void Device::copyBuffer(vk::Buffer sourceBuffer, vk::Buffer destinationBuffer,
|
||||
vk::DeviceSize size) {
|
||||
vk::CommandBuffer commandBuffer = beginSingleTimeCommands();
|
||||
|
||||
vk::BufferCopy copyRegion = {};
|
||||
copyRegion.size = size;
|
||||
|
||||
commandBuffer.copyBuffer(sourceBuffer, destinationBuffer, copyRegion);
|
||||
|
||||
endSingleTimeCommands(commandBuffer);
|
||||
}
|
||||
|
||||
void Device::createImageWithInfo(const vk::ImageCreateInfo& imageInfo,
|
||||
vk::MemoryPropertyFlags properties,
|
||||
vk::Image& image,
|
||||
vk::DeviceMemory& imageMemory) {
|
||||
try {
|
||||
vk::Result result = this->device->createImage(&imageInfo, nullptr, &image);
|
||||
if (result != vk::Result::eSuccess) {
|
||||
throw vk::SystemError(result);
|
||||
}
|
||||
} catch (const vk::SystemError& error) {
|
||||
log::fatal("failed to create image. Error: ", error.what());
|
||||
throw std::runtime_error("failed to create image");
|
||||
}
|
||||
|
||||
vk::MemoryRequirements memoryRequirements =
|
||||
this->device->getImageMemoryRequirements(image);
|
||||
|
||||
vk::MemoryAllocateInfo allocateInfo{};
|
||||
allocateInfo.allocationSize = memoryRequirements.size;
|
||||
allocateInfo.memoryTypeIndex =
|
||||
findMemoryType(memoryRequirements.memoryTypeBits, properties);
|
||||
|
||||
try {
|
||||
vk::Result result =
|
||||
this->device->allocateMemory(&allocateInfo, nullptr, &imageMemory);
|
||||
if (result != vk::Result::eSuccess) {
|
||||
throw vk::SystemError(result);
|
||||
}
|
||||
} catch (const vk::SystemError& error) {
|
||||
log::fatal("failed to allocate image memory. Error: ", error.what());
|
||||
throw std::runtime_error("failed to allocate image memory");
|
||||
}
|
||||
|
||||
try {
|
||||
this->device->bindImageMemory(image, imageMemory, 0);
|
||||
} catch (const vk::SystemError& error) {
|
||||
log::fatal("failed to bind image memory. Error: ", error.what());
|
||||
throw std::runtime_error("failed to bind image memory");
|
||||
}
|
||||
}
|
||||
|
||||
void Device::populateImGuiInitInfo(ImGui_ImplVulkan_InitInfo& initInfo) {
|
||||
initInfo.Instance = this->instance.get();
|
||||
initInfo.ApiVersion = SOPHIA_VULKAN_API_VERSION;
|
||||
initInfo.PhysicalDevice = this->physicalDevice;
|
||||
initInfo.Device = this->device.get();
|
||||
|
||||
QueueFamilyIndices indices = findQueueFamilies(this->physicalDevice);
|
||||
initInfo.QueueFamily = indices.graphicsFamily.value();
|
||||
initInfo.Queue = getGraphicsQueue();
|
||||
}
|
||||
|
||||
void Device::setupDebugMessenger() {
|
||||
if (!this->enableValidationLayers) return;
|
||||
|
||||
auto createInfo = vk::DebugUtilsMessengerCreateInfoEXT(
|
||||
vk::DebugUtilsMessengerCreateFlagsEXT(),
|
||||
vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose |
|
||||
vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning |
|
||||
vk::DebugUtilsMessageSeverityFlagBitsEXT::eError,
|
||||
vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral |
|
||||
vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation |
|
||||
vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance,
|
||||
debugCallback, nullptr);
|
||||
|
||||
if (createDebugUtilsMessengerEXT(
|
||||
*instance,
|
||||
reinterpret_cast<const VkDebugUtilsMessengerCreateInfoEXT*>(
|
||||
&createInfo),
|
||||
nullptr, &this->debugMessenger) != VK_SUCCESS) {
|
||||
throw std::runtime_error("failed to set up debug callback!");
|
||||
}
|
||||
}
|
||||
|
||||
bool Device::checkValidationLayerSupport() {
|
||||
auto availableLayers = vk::enumerateInstanceLayerProperties();
|
||||
for (const char* layerName : enabledLayers) {
|
||||
bool layerFound = false;
|
||||
|
||||
for (const auto& layerProperties : availableLayers) {
|
||||
if (strcmp(layerName, layerProperties.layerName) == 0) {
|
||||
layerFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!layerFound) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<const char*> Device::getRequiredExtensions() {
|
||||
std::vector<const char*> extensions;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
u32 available_extension_count = 0;
|
||||
vk::Result result = vk::enumerateInstanceExtensionProperties(
|
||||
nullptr, &available_extension_count, nullptr);
|
||||
|
||||
if (result != vk::Result::eSuccess) {
|
||||
throw std::exception();
|
||||
}
|
||||
|
||||
std::vector<vk::ExtensionProperties> available_extensions(
|
||||
available_extension_count);
|
||||
|
||||
result = vk::enumerateInstanceExtensionProperties(
|
||||
nullptr, &available_extension_count, available_extensions.data());
|
||||
|
||||
if (result != vk::Result::eSuccess) {
|
||||
throw std::exception();
|
||||
}
|
||||
|
||||
log::verbose("Number of available extensions: ", available_extension_count);
|
||||
|
||||
log::verbose("Available extensions:");
|
||||
for (const auto& e : available_extensions) {
|
||||
std::cout << '\t' << e.extensionName << '\n';
|
||||
}
|
||||
std::cout << "Required extensions:\n";
|
||||
for (const auto& e : extensions) {
|
||||
std::cout << "\t" << e << '\n';
|
||||
}
|
||||
#endif
|
||||
|
||||
return extensions;
|
||||
}
|
||||
|
||||
void Device::createVulkanInstance() {
|
||||
if (this->enableValidationLayers && !checkValidationLayerSupport()) {
|
||||
log::fatal("Validation layers requested but not available.");
|
||||
throw std::exception();
|
||||
}
|
||||
|
||||
vk::ApplicationInfo app_info = vk::ApplicationInfo(
|
||||
"Sophia", VK_MAKE_VERSION(1, 0, 0), "Sophia Vulkan Engine",
|
||||
VK_MAKE_VERSION(1, 0, 0), VK_API_VERSION_1_3);
|
||||
|
||||
auto extensions = getRequiredExtensions();
|
||||
|
||||
auto createInfo = vk::InstanceCreateInfo(
|
||||
vk::InstanceCreateFlags(), &app_info, 0, nullptr,
|
||||
static_cast<uint32_t>(extensions.size()), extensions.data());
|
||||
|
||||
createInfo.enabledLayerCount = static_cast<uint32_t>(enabledLayers.size());
|
||||
createInfo.ppEnabledLayerNames = enabledLayers.data();
|
||||
|
||||
try {
|
||||
this->instance = vk::createInstanceUnique(createInfo, nullptr);
|
||||
} catch (const vk::SystemError& err) {
|
||||
throw std::runtime_error("Failed to create instance!");
|
||||
}
|
||||
}
|
||||
|
||||
QueueFamilyIndices Device::findQueueFamilies(vk::PhysicalDevice device) {
|
||||
QueueFamilyIndices indices;
|
||||
auto queueFamilies = device.getQueueFamilyProperties();
|
||||
int i = 0;
|
||||
|
||||
for (const auto& queueFamily : queueFamilies) {
|
||||
if (queueFamily.queueCount > 0 &&
|
||||
queueFamily.queueFlags & vk::QueueFlagBits::eGraphics) {
|
||||
indices.graphicsFamily = i;
|
||||
}
|
||||
|
||||
if (!this->headless && queueFamily.queueCount > 0 &&
|
||||
device.getSurfaceSupportKHR(i, surface)) {
|
||||
indices.presentFamily = i;
|
||||
}
|
||||
|
||||
if (indices.isComplete()) {
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
return indices;
|
||||
}
|
||||
|
||||
bool Device::checkDeviceExtensionSupport(const vk::PhysicalDevice& device) {
|
||||
std::set<std::string> requiredExtensions(this->enabledExtensions.begin(),
|
||||
this->enabledExtensions.end());
|
||||
|
||||
for (const auto& extension : device.enumerateDeviceExtensionProperties()) {
|
||||
requiredExtensions.erase(extension.extensionName);
|
||||
}
|
||||
|
||||
return requiredExtensions.empty();
|
||||
}
|
||||
|
||||
bool Device::isPhysicalDeviceSuitable(const vk::PhysicalDevice& device) {
|
||||
QueueFamilyIndices indices = findQueueFamilies(device);
|
||||
|
||||
bool extensionsSupported = checkDeviceExtensionSupport(device);
|
||||
|
||||
if (this->headless) {
|
||||
return indices.graphicsFamily.has_value() && extensionsSupported;
|
||||
}
|
||||
|
||||
bool swapchainAdequate = false;
|
||||
if (extensionsSupported) {
|
||||
SwapchainSupportDetails swapchainSupport = querySwapchainSupport(device);
|
||||
swapchainAdequate = !swapchainSupport.formats.empty() &&
|
||||
!swapchainSupport.presentModes.empty();
|
||||
}
|
||||
return indices.isComplete() && extensionsSupported && swapchainAdequate;
|
||||
}
|
||||
|
||||
void Device::pickPhysicalDevice() {
|
||||
// Get all physical devies
|
||||
std::vector<vk::PhysicalDevice> physicalDevices =
|
||||
instance->enumeratePhysicalDevices();
|
||||
|
||||
if (physicalDevices.empty()) {
|
||||
log::fatal("Failed to find any physical devices.");
|
||||
throw std::exception();
|
||||
}
|
||||
log::verbose("Physical Devices count: ", physicalDevices.size());
|
||||
|
||||
// Select first physical device that is suitable
|
||||
for (const auto& device : physicalDevices) {
|
||||
if (isPhysicalDeviceSuitable(device)) {
|
||||
this->physicalDevice = device;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (this->physicalDevice == VK_NULL_HANDLE) {
|
||||
log::fatal("Failed to find a suitable physical device");
|
||||
throw std::exception();
|
||||
}
|
||||
|
||||
this->properties = this->physicalDevice.getProperties();
|
||||
log::verbose("Physical Device: ", this->properties.deviceName);
|
||||
}
|
||||
|
||||
void Device::createLogicalDevice() {
|
||||
QueueFamilyIndices indices = findQueueFamilies(this->physicalDevice);
|
||||
|
||||
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) {
|
||||
queueCreateInfos.push_back(
|
||||
{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());
|
||||
createInfo.ppEnabledExtensionNames = this->enabledExtensions.data();
|
||||
|
||||
try {
|
||||
this->device = this->physicalDevice.createDeviceUnique(createInfo);
|
||||
log::trace("created vk::Device.");
|
||||
} catch (const vk::SystemError& err) {
|
||||
log::fatal("failed to create logical device.");
|
||||
throw std::exception();
|
||||
}
|
||||
|
||||
this->graphicsQueue = device->getQueue(indices.graphicsFamily.value(), 0);
|
||||
if (!this->headless) {
|
||||
this->presentQueue = device->getQueue(indices.presentFamily.value(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
SwapchainSupportDetails Device::querySwapchainSupport(
|
||||
vk::PhysicalDevice device) {
|
||||
SwapchainSupportDetails details;
|
||||
details.capabilities = device.getSurfaceCapabilitiesKHR(surface);
|
||||
details.formats = device.getSurfaceFormatsKHR(surface);
|
||||
details.presentModes = device.getSurfacePresentModesKHR(surface);
|
||||
|
||||
return details;
|
||||
}
|
||||
|
||||
void Device::createCommandPool() {
|
||||
QueueFamilyIndices queueFamilyIndices =
|
||||
findQueueFamilies(this->physicalDevice);
|
||||
|
||||
vk::CommandPoolCreateInfo createInfo = {};
|
||||
createInfo.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer;
|
||||
createInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value();
|
||||
|
||||
try {
|
||||
this->commandPool = this->device->createCommandPool(createInfo);
|
||||
log::trace("created vk::CommandPool");
|
||||
} catch (const vk::SystemError& err) {
|
||||
log::fatal("failed to create vk::CommandPool");
|
||||
throw std::runtime_error("failed to create vk::CommandPool");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace sophia
|
||||
@@ -0,0 +1,130 @@
|
||||
#pragma once
|
||||
|
||||
#include <imgui_impl_vulkan.h>
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <sophia/types.hpp>
|
||||
#include <sophia/util/assert.hpp>
|
||||
#include <vector>
|
||||
#include <vulkan/vulkan.hpp>
|
||||
|
||||
#include "window.hpp"
|
||||
|
||||
#define SOPHIA_VULKAN_API_VERSION VK_API_VERSION_1_3
|
||||
|
||||
namespace sophia {
|
||||
|
||||
struct QueueFamilyIndices {
|
||||
std::optional<uint32_t> graphicsFamily;
|
||||
std::optional<uint32_t> presentFamily;
|
||||
|
||||
bool isComplete() {
|
||||
return graphicsFamily.has_value() && presentFamily.has_value();
|
||||
}
|
||||
};
|
||||
|
||||
struct SwapchainSupportDetails {
|
||||
std::vector<vk::SurfaceFormatKHR> formats;
|
||||
std::vector<vk::PresentModeKHR> presentModes;
|
||||
vk::SurfaceCapabilitiesKHR capabilities;
|
||||
};
|
||||
|
||||
class Device {
|
||||
public:
|
||||
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 {
|
||||
SOPHIA_ASSERT(this->device);
|
||||
return &this->device.get();
|
||||
}
|
||||
|
||||
void waitIdle() { this->device->waitIdle(); }
|
||||
|
||||
vk::CommandPool getCommandPool() const { return this->commandPool; }
|
||||
vk::SurfaceKHR getSurface() const { return surface; }
|
||||
vk::Queue getGraphicsQueue() const { return graphicsQueue; }
|
||||
vk::Queue getPresentQueue() const { return presentQueue; }
|
||||
|
||||
QueueFamilyIndices getQueueIndices() {
|
||||
return findQueueFamilies(this->physicalDevice);
|
||||
}
|
||||
|
||||
SwapchainSupportDetails getSwapchainSupport() {
|
||||
return querySwapchainSupport(this->physicalDevice);
|
||||
}
|
||||
|
||||
u32 findMemoryType(u32 typeFilter, vk::MemoryPropertyFlags properties);
|
||||
|
||||
vk::Format findSupportedFormat(const std::vector<vk::Format>& candidates,
|
||||
vk::ImageTiling tiling,
|
||||
vk::FormatFeatureFlags features);
|
||||
|
||||
void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage,
|
||||
vk::MemoryPropertyFlags properties, vk::Buffer& buffer,
|
||||
vk::DeviceMemory& bufferMemory);
|
||||
|
||||
vk::CommandBuffer beginSingleTimeCommands();
|
||||
|
||||
void endSingleTimeCommands(vk::CommandBuffer commandBuffer);
|
||||
|
||||
void copyBuffer(vk::Buffer sourceBuffer, vk::Buffer destinationBuffer,
|
||||
vk::DeviceSize size);
|
||||
|
||||
void createImageWithInfo(const vk::ImageCreateInfo& imageInfo,
|
||||
vk::MemoryPropertyFlags properties, vk::Image& image,
|
||||
vk::DeviceMemory& imageMemory);
|
||||
|
||||
void populateImGuiInitInfo(ImGui_ImplVulkan_InitInfo& initInfo);
|
||||
|
||||
vk::PhysicalDeviceProperties properties;
|
||||
|
||||
private:
|
||||
void setupDebugMessenger();
|
||||
|
||||
bool checkValidationLayerSupport();
|
||||
std::vector<const char*> getRequiredExtensions();
|
||||
void createVulkanInstance();
|
||||
|
||||
QueueFamilyIndices findQueueFamilies(vk::PhysicalDevice device);
|
||||
bool checkDeviceExtensionSupport(const vk::PhysicalDevice& device);
|
||||
bool isPhysicalDeviceSuitable(const vk::PhysicalDevice& device);
|
||||
void pickPhysicalDevice();
|
||||
|
||||
SwapchainSupportDetails querySwapchainSupport(vk::PhysicalDevice device);
|
||||
|
||||
void createLogicalDevice();
|
||||
void createCommandPool();
|
||||
|
||||
vk::UniqueInstance instance;
|
||||
VkDebugUtilsMessengerEXT debugMessenger;
|
||||
|
||||
const bool headless;
|
||||
vk::SurfaceKHR surface;
|
||||
|
||||
vk::PhysicalDevice physicalDevice = VK_NULL_HANDLE;
|
||||
vk::UniqueDevice device;
|
||||
|
||||
vk::Queue graphicsQueue;
|
||||
vk::Queue presentQueue;
|
||||
|
||||
vk::CommandPool commandPool;
|
||||
|
||||
#ifdef NDEBUG
|
||||
const bool enableValidationLayers = false;
|
||||
const std::vector<const char*> enabledLayers;
|
||||
#else
|
||||
const bool enableValidationLayers = true;
|
||||
const std::vector<const char*> enabledLayers = {
|
||||
"VK_LAYER_KHRONOS_validation"};
|
||||
#endif
|
||||
const std::vector<const char*> enabledExtensions;
|
||||
};
|
||||
|
||||
} // namespace sophia
|
||||
@@ -0,0 +1,291 @@
|
||||
#include "vulkan/image.hpp"
|
||||
|
||||
#include <cstring>
|
||||
#include <sophia/util/logger.hpp>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "vulkan/device.hpp"
|
||||
|
||||
namespace sophia {
|
||||
|
||||
vk::ImageSubresourceRange Image::getSubresourceRange() const {
|
||||
return vk::ImageSubresourceRange{this->aspectMask, 0, this->mipLevels, 0,
|
||||
this->arrayLayers};
|
||||
}
|
||||
|
||||
AccessState Image::getAccessState() const {
|
||||
AccessState state = getStateForLayout(this->layout);
|
||||
|
||||
// A wrapped (swapchain) image is handed to us by the acquire semaphore, so
|
||||
// whatever we do next has to be ordered after that wait.
|
||||
if (this->isExternal() && (this->layout == vk::ImageLayout::eUndefined ||
|
||||
this->layout == vk::ImageLayout::ePresentSrcKHR)) {
|
||||
state.stage = SWAPCHAIN_ACQUIRE_STAGE;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
void Image::transitionLayout(vk::CommandBuffer commandBuffer,
|
||||
vk::ImageLayout newLayout) {
|
||||
AccessState from = this->getAccessState();
|
||||
AccessState to = getStateForLayout(newLayout);
|
||||
|
||||
if (needsBarrier(from, to)) {
|
||||
vk::ImageMemoryBarrier barrier =
|
||||
makeImageBarrier(this->get(), this->getSubresourceRange(), from, to);
|
||||
commandBuffer.pipelineBarrier(from.stage, to.stage, {}, nullptr, nullptr,
|
||||
barrier);
|
||||
}
|
||||
|
||||
this->layout = newLayout;
|
||||
}
|
||||
|
||||
void Image::transitionLayoutImmediate(vk::ImageLayout newLayout) {
|
||||
vk::CommandBuffer commandBuffer = this->device.beginSingleTimeCommands();
|
||||
this->transitionLayout(commandBuffer, newLayout);
|
||||
this->device.endSingleTimeCommands(commandBuffer);
|
||||
}
|
||||
|
||||
Image::Builder& Image::Builder::setExtent(u32 width, u32 height, u32 depth) {
|
||||
this->extent = vk::Extent3D{width, height, depth};
|
||||
return *this;
|
||||
}
|
||||
|
||||
Image::Builder& Image::Builder::setFormat(vk::Format format) {
|
||||
this->format = format;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Image::Builder& Image::Builder::setUsage(vk::ImageUsageFlags usage) {
|
||||
this->usage = usage;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Image::Builder& Image::Builder::setTiling(vk::ImageTiling tiling) {
|
||||
this->tiling = tiling;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Image::Builder& Image::Builder::setMemoryProperties(
|
||||
vk::MemoryPropertyFlags properties) {
|
||||
this->memoryProperties = properties;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Image::Builder& Image::Builder::setSamples(vk::SampleCountFlagBits samples) {
|
||||
this->samples = samples;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Image::Builder& Image::Builder::setImageType(vk::ImageType imageType) {
|
||||
this->imageType = imageType;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Image::Builder& Image::Builder::setMipLevels(u32 mipLevels) {
|
||||
this->mipLevels = mipLevels;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Image::Builder& Image::Builder::setArrayLayers(u32 arrayLayers) {
|
||||
this->arrayLayers = arrayLayers;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Image::Builder& Image::Builder::setAspectMask(vk::ImageAspectFlags aspectMask) {
|
||||
this->aspectMask = aspectMask;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Image::Builder& Image::Builder::asComputeStorage() {
|
||||
// Storage images commonly can't use sRGB formats without an extension, so
|
||||
// default to a plain UNORM format; override with setFormat() if needed.
|
||||
this->format = vk::Format::eR8G8B8A8Unorm;
|
||||
this->usage = vk::ImageUsageFlagBits::eStorage;
|
||||
this->aspectMask = vk::ImageAspectFlagBits::eColor;
|
||||
this->tiling = vk::ImageTiling::eOptimal;
|
||||
this->memoryProperties = vk::MemoryPropertyFlagBits::eDeviceLocal;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Image::Builder& Image::Builder::asColorAttachment() {
|
||||
this->usage = vk::ImageUsageFlagBits::eColorAttachment |
|
||||
vk::ImageUsageFlagBits::eSampled;
|
||||
this->aspectMask = vk::ImageAspectFlagBits::eColor;
|
||||
this->tiling = vk::ImageTiling::eOptimal;
|
||||
this->memoryProperties = vk::MemoryPropertyFlagBits::eDeviceLocal;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Image::Builder& Image::Builder::asDepthAttachment() {
|
||||
this->format = vk::Format::eD32Sfloat;
|
||||
this->usage = vk::ImageUsageFlagBits::eDepthStencilAttachment;
|
||||
this->aspectMask = vk::ImageAspectFlagBits::eDepth;
|
||||
this->tiling = vk::ImageTiling::eOptimal;
|
||||
this->memoryProperties = vk::MemoryPropertyFlagBits::eDeviceLocal;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Image::Builder& Image::Builder::wrapExisting(vk::Image image) {
|
||||
this->isExternalImage = true;
|
||||
this->externalImage = image;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Image::Builder& Image::Builder::setInitialData(const void* data,
|
||||
vk::DeviceSize size) {
|
||||
this->initialData = data;
|
||||
this->initialDataSize = size;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Image::Builder& Image::Builder::withView(bool enabled) {
|
||||
this->createView = enabled;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Image::Builder& Image::Builder::withSampler(
|
||||
vk::Filter filter, vk::SamplerAddressMode addressMode) {
|
||||
this->createSampler = true;
|
||||
this->samplerFilter = filter;
|
||||
this->samplerAddressMode = addressMode;
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::unique_ptr<Image> Image::Builder::build() {
|
||||
vk::ImageUsageFlags usage = this->usage;
|
||||
|
||||
auto result = std::unique_ptr<Image>(new Image(this->device));
|
||||
result->format = this->format;
|
||||
result->extent = this->extent;
|
||||
result->aspectMask = this->aspectMask;
|
||||
result->mipLevels = this->mipLevels;
|
||||
result->arrayLayers = this->arrayLayers;
|
||||
|
||||
if (this->isExternalImage) {
|
||||
if (this->initialData != nullptr) {
|
||||
log::warning(
|
||||
"Image::Builder::setInitialData is ignored when wrapExisting is "
|
||||
"used.");
|
||||
}
|
||||
result->externalImage = this->externalImage;
|
||||
// Wrapped images (e.g. swapchain images) are handed to us already in a
|
||||
// known layout controlled by their owner; we can't observe it here, so
|
||||
// leave `layout` at eUndefined and let the caller call
|
||||
// transitionLayout() to bring it in sync before relying on it.
|
||||
} else {
|
||||
if (this->initialData != nullptr) {
|
||||
usage |= vk::ImageUsageFlagBits::eTransferDst;
|
||||
}
|
||||
if (this->createView || this->createSampler) {
|
||||
usage |= vk::ImageUsageFlagBits::eSampled;
|
||||
}
|
||||
|
||||
vk::ImageCreateInfo imageInfo{};
|
||||
imageInfo.imageType = this->imageType;
|
||||
imageInfo.extent = this->extent;
|
||||
imageInfo.mipLevels = this->mipLevels;
|
||||
imageInfo.arrayLayers = this->arrayLayers;
|
||||
imageInfo.format = this->format;
|
||||
imageInfo.tiling = this->tiling;
|
||||
imageInfo.initialLayout = vk::ImageLayout::eUndefined;
|
||||
imageInfo.usage = usage;
|
||||
imageInfo.sharingMode = vk::SharingMode::eExclusive;
|
||||
imageInfo.samples = this->samples;
|
||||
|
||||
result->image = this->device.get()->createImageUnique(imageInfo);
|
||||
|
||||
vk::MemoryRequirements memoryRequirements =
|
||||
this->device.get()->getImageMemoryRequirements(result->image.get());
|
||||
|
||||
vk::MemoryAllocateInfo allocInfo{};
|
||||
allocInfo.allocationSize = memoryRequirements.size;
|
||||
allocInfo.memoryTypeIndex = this->device.findMemoryType(
|
||||
memoryRequirements.memoryTypeBits, this->memoryProperties);
|
||||
|
||||
result->memory = this->device.get()->allocateMemoryUnique(allocInfo);
|
||||
this->device.get()->bindImageMemory(result->image.get(),
|
||||
result->memory.get(), 0);
|
||||
}
|
||||
|
||||
if (!this->isExternalImage && this->initialData != nullptr) {
|
||||
vk::Buffer stagingBuffer;
|
||||
vk::DeviceMemory stagingMemory;
|
||||
this->device.createBuffer(this->initialDataSize,
|
||||
vk::BufferUsageFlagBits::eTransferSrc,
|
||||
vk::MemoryPropertyFlagBits::eHostVisible |
|
||||
vk::MemoryPropertyFlagBits::eHostCoherent,
|
||||
stagingBuffer, stagingMemory);
|
||||
|
||||
void* mapped =
|
||||
this->device.get()->mapMemory(stagingMemory, 0, this->initialDataSize);
|
||||
memcpy(mapped, this->initialData,
|
||||
static_cast<size_t>(this->initialDataSize));
|
||||
this->device.get()->unmapMemory(stagingMemory);
|
||||
|
||||
vk::BufferImageCopy region{};
|
||||
region.bufferOffset = 0;
|
||||
region.bufferRowLength = 0;
|
||||
region.bufferImageHeight = 0;
|
||||
region.imageSubresource.aspectMask = this->aspectMask;
|
||||
region.imageSubresource.mipLevel = 0;
|
||||
region.imageSubresource.baseArrayLayer = 0;
|
||||
region.imageSubresource.layerCount = this->arrayLayers;
|
||||
region.imageOffset = vk::Offset3D{0, 0, 0};
|
||||
region.imageExtent = this->extent;
|
||||
|
||||
vk::CommandBuffer commandBuffer = this->device.beginSingleTimeCommands();
|
||||
result->transitionLayout(commandBuffer,
|
||||
vk::ImageLayout::eTransferDstOptimal);
|
||||
commandBuffer.copyBufferToImage(stagingBuffer, result->get(),
|
||||
vk::ImageLayout::eTransferDstOptimal,
|
||||
region);
|
||||
if (usage & vk::ImageUsageFlagBits::eSampled) {
|
||||
result->transitionLayout(commandBuffer,
|
||||
vk::ImageLayout::eShaderReadOnlyOptimal);
|
||||
}
|
||||
this->device.endSingleTimeCommands(commandBuffer);
|
||||
|
||||
this->device.get()->destroyBuffer(stagingBuffer);
|
||||
this->device.get()->freeMemory(stagingMemory);
|
||||
}
|
||||
|
||||
if (this->createView) {
|
||||
vk::ImageViewCreateInfo viewInfo{};
|
||||
viewInfo.image = result->get();
|
||||
viewInfo.viewType = vk::ImageViewType::e2D;
|
||||
viewInfo.format = this->format;
|
||||
viewInfo.subresourceRange.aspectMask = this->aspectMask;
|
||||
viewInfo.subresourceRange.baseMipLevel = 0;
|
||||
viewInfo.subresourceRange.levelCount = this->mipLevels;
|
||||
viewInfo.subresourceRange.baseArrayLayer = 0;
|
||||
viewInfo.subresourceRange.layerCount = this->arrayLayers;
|
||||
|
||||
result->view = this->device.get()->createImageViewUnique(viewInfo);
|
||||
}
|
||||
|
||||
if (this->createSampler) {
|
||||
vk::SamplerCreateInfo samplerInfo{};
|
||||
samplerInfo.magFilter = this->samplerFilter;
|
||||
samplerInfo.minFilter = this->samplerFilter;
|
||||
samplerInfo.addressModeU = this->samplerAddressMode;
|
||||
samplerInfo.addressModeV = this->samplerAddressMode;
|
||||
samplerInfo.addressModeW = this->samplerAddressMode;
|
||||
samplerInfo.anisotropyEnable = VK_FALSE;
|
||||
samplerInfo.maxAnisotropy = 1.0f;
|
||||
samplerInfo.borderColor = vk::BorderColor::eIntOpaqueBlack;
|
||||
samplerInfo.unnormalizedCoordinates = VK_FALSE;
|
||||
samplerInfo.compareEnable = VK_FALSE;
|
||||
samplerInfo.compareOp = vk::CompareOp::eAlways;
|
||||
samplerInfo.mipmapMode = vk::SamplerMipmapMode::eLinear;
|
||||
samplerInfo.minLod = 0.0f;
|
||||
samplerInfo.maxLod = static_cast<float>(this->mipLevels);
|
||||
|
||||
result->sampler = this->device.get()->createSamplerUnique(samplerInfo);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace sophia
|
||||
@@ -0,0 +1,140 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <sophia/types.hpp>
|
||||
#include <vulkan/vulkan.hpp>
|
||||
|
||||
#include "vulkan/access.hpp"
|
||||
|
||||
namespace sophia {
|
||||
|
||||
class Device;
|
||||
|
||||
class Image {
|
||||
public:
|
||||
class Builder;
|
||||
|
||||
Image(const Image&) = delete;
|
||||
Image& operator=(const Image&) = delete;
|
||||
Image(Image&&) = default;
|
||||
Image& operator=(Image&&) = delete;
|
||||
|
||||
vk::Image get() const {
|
||||
return this->image ? this->image.get() : this->externalImage;
|
||||
}
|
||||
vk::ImageView getView() const { return this->view.get(); }
|
||||
vk::Sampler getSampler() const { return this->sampler.get(); }
|
||||
|
||||
bool isExternal() const { return !this->image; }
|
||||
|
||||
vk::Format getFormat() const { return this->format; }
|
||||
vk::Extent3D getExtent() const { return this->extent; }
|
||||
vk::ImageLayout getLayout() const { return this->layout; }
|
||||
vk::ImageSubresourceRange getSubresourceRange() const;
|
||||
|
||||
/**
|
||||
* The layout this image was last left in, plus the stages and accesses that
|
||||
* a barrier out of it has to wait on.
|
||||
*/
|
||||
AccessState getAccessState() const;
|
||||
|
||||
/**
|
||||
* Updates the tracked layout without recording a barrier. For code that
|
||||
* does its own transitions (the RenderGraph) and needs to hand the final
|
||||
* layout back.
|
||||
*/
|
||||
void recordLayout(vk::ImageLayout newLayout) { this->layout = newLayout; }
|
||||
|
||||
void transitionLayout(vk::CommandBuffer commandBuffer,
|
||||
vk::ImageLayout newLayout);
|
||||
|
||||
void transitionLayoutImmediate(vk::ImageLayout newLayout);
|
||||
|
||||
private:
|
||||
Image(Device& device) : device{device} {}
|
||||
|
||||
Device& device;
|
||||
|
||||
vk::Image externalImage;
|
||||
vk::UniqueImage image;
|
||||
vk::UniqueDeviceMemory memory;
|
||||
vk::UniqueImageView view;
|
||||
vk::UniqueSampler sampler;
|
||||
|
||||
vk::Format format = vk::Format::eUndefined;
|
||||
vk::Extent3D extent{};
|
||||
vk::ImageAspectFlags aspectMask = vk::ImageAspectFlagBits::eColor;
|
||||
u32 mipLevels = 1;
|
||||
u32 arrayLayers = 1;
|
||||
vk::ImageLayout layout = vk::ImageLayout::eUndefined;
|
||||
};
|
||||
|
||||
class Image::Builder {
|
||||
public:
|
||||
Builder(Device& device) : device{device} {}
|
||||
|
||||
Builder& setExtent(u32 width, u32 height, u32 depth = 1);
|
||||
Builder& setFormat(vk::Format format);
|
||||
Builder& setUsage(vk::ImageUsageFlags usage);
|
||||
Builder& setTiling(vk::ImageTiling tiling);
|
||||
Builder& setMemoryProperties(vk::MemoryPropertyFlags properties);
|
||||
Builder& setSamples(vk::SampleCountFlagBits samples);
|
||||
Builder& setImageType(vk::ImageType imageType);
|
||||
Builder& setMipLevels(u32 mipLevels);
|
||||
Builder& setArrayLayers(u32 arrayLayers);
|
||||
Builder& setAspectMask(vk::ImageAspectFlags aspectMask);
|
||||
|
||||
// High-level usage presets for GPGPU + Graphics. Each sets usage/aspect/
|
||||
// tiling/memory-property defaults for the common case; call the
|
||||
// individual setters afterward to override anything preset-specific.
|
||||
Builder& asComputeStorage();
|
||||
Builder& asColorAttachment();
|
||||
Builder& asDepthAttachment();
|
||||
|
||||
// Wraps an image this Builder does not own instead of creating one —
|
||||
// e.g. a swapchain image. build() will skip image/memory creation and
|
||||
// teardown entirely; setUsage/setTiling/setMemoryProperties/setSamples/
|
||||
// setInitialData are ignored, but setFormat/setExtent/setAspectMask still
|
||||
// matter, since view/sampler creation relies on them.
|
||||
Builder& wrapExisting(vk::Image image);
|
||||
|
||||
// Uploads `size` bytes to the image via a staging buffer once it is
|
||||
// created, transitioning it to eTransferDstOptimal (and on to
|
||||
// eShaderReadOnlyOptimal if the image is sampled) in the process.
|
||||
Builder& setInitialData(const void* data, vk::DeviceSize size);
|
||||
|
||||
Builder& withView(bool enabled = true);
|
||||
Builder& withSampler(
|
||||
vk::Filter filter = vk::Filter::eLinear,
|
||||
vk::SamplerAddressMode addressMode = vk::SamplerAddressMode::eRepeat);
|
||||
|
||||
std::unique_ptr<Image> build();
|
||||
|
||||
private:
|
||||
Device& device;
|
||||
|
||||
vk::Extent3D extent{1, 1, 1};
|
||||
vk::Format format = vk::Format::eR8G8B8A8Srgb;
|
||||
vk::ImageUsageFlags usage = vk::ImageUsageFlagBits::eSampled;
|
||||
vk::ImageTiling tiling = vk::ImageTiling::eOptimal;
|
||||
vk::MemoryPropertyFlags memoryProperties =
|
||||
vk::MemoryPropertyFlagBits::eDeviceLocal;
|
||||
vk::SampleCountFlagBits samples = vk::SampleCountFlagBits::e1;
|
||||
vk::ImageType imageType = vk::ImageType::e2D;
|
||||
vk::ImageAspectFlags aspectMask = vk::ImageAspectFlagBits::eColor;
|
||||
u32 mipLevels = 1;
|
||||
u32 arrayLayers = 1;
|
||||
|
||||
const void* initialData = nullptr;
|
||||
vk::DeviceSize initialDataSize = 0;
|
||||
|
||||
bool isExternalImage = false;
|
||||
vk::Image externalImage;
|
||||
|
||||
bool createView = false;
|
||||
bool createSampler = false;
|
||||
vk::Filter samplerFilter = vk::Filter::eLinear;
|
||||
vk::SamplerAddressMode samplerAddressMode = vk::SamplerAddressMode::eRepeat;
|
||||
};
|
||||
|
||||
} // namespace sophia
|
||||
@@ -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 "pipeline.hpp"
|
||||
#include "vulkan/device.hpp"
|
||||
#include "vulkan/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 "pipeline.hpp"
|
||||
#include "vulkan/device.hpp"
|
||||
#include "vulkan/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 "vulkan/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,140 @@
|
||||
#include "vulkan/shader.hpp"
|
||||
|
||||
#include <fstream>
|
||||
#include <shaderc/shaderc.hpp>
|
||||
#include <sophia/util/logger.hpp>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace sophia {
|
||||
|
||||
namespace {
|
||||
|
||||
shaderc_shader_kind toShadercKind(ShaderStage shaderStage) {
|
||||
switch (shaderStage) {
|
||||
case ShaderStage::VERTEX:
|
||||
return shaderc_vertex_shader;
|
||||
case ShaderStage::FRAGMENT:
|
||||
return shaderc_fragment_shader;
|
||||
case ShaderStage::COMPUTE:
|
||||
return shaderc_compute_shader;
|
||||
}
|
||||
throw std::invalid_argument("unsupported shader stage");
|
||||
}
|
||||
|
||||
std::vector<char> readFile(const std::string& path) {
|
||||
std::ifstream file(path, std::ios::ate | std::ios::binary);
|
||||
|
||||
if (!file.is_open()) {
|
||||
log::error("failed to open shader: " + path);
|
||||
throw std::runtime_error("failed to open: " + path);
|
||||
}
|
||||
|
||||
size_t fileSize = static_cast<size_t>(file.tellg());
|
||||
std::vector<char> buffer(fileSize);
|
||||
|
||||
file.seekg(0);
|
||||
file.read(buffer.data(), fileSize);
|
||||
file.close();
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void Shader::compile(const std::string& path, ShaderStage shaderStage) {
|
||||
this->stage = shaderStage;
|
||||
|
||||
std::vector<char> source = readFile(path);
|
||||
|
||||
log::info("Compiling shader: " + path);
|
||||
|
||||
shaderc::Compiler compiler;
|
||||
shaderc::SpvCompilationResult result = compiler.CompileGlslToSpv(
|
||||
source.data(), source.size(), toShadercKind(shaderStage), path.c_str());
|
||||
|
||||
if (result.GetCompilationStatus() != shaderc_compilation_status_success) {
|
||||
log::fatal("failed to compile shader:", path, result.GetErrorMessage());
|
||||
throw std::runtime_error("failed to compile shader: " + path + ": " +
|
||||
result.GetErrorMessage());
|
||||
}
|
||||
|
||||
this->spirv.assign(result.cbegin(), result.cend());
|
||||
|
||||
log::info("Successfully compiled shader: " + path + " (" +
|
||||
std::to_string(this->spirv.size() * 4) + " bytes)");
|
||||
}
|
||||
|
||||
void Shader::load(const std::string& path, ShaderStage shaderStage) {
|
||||
this->stage = shaderStage;
|
||||
|
||||
std::vector<char> bytes = readFile(path);
|
||||
|
||||
if (bytes.size() % sizeof(u32) != 0) {
|
||||
log::fatal("invalid SPIR-V binary (size not a multiple of 4):", path);
|
||||
throw std::runtime_error("invalid SPIR-V binary: " + path);
|
||||
}
|
||||
|
||||
this->spirv.assign(reinterpret_cast<const u32*>(bytes.data()),
|
||||
reinterpret_cast<const u32*>(bytes.data() + bytes.size()));
|
||||
|
||||
log::info("Loaded precompiled shader: " + path + " (" +
|
||||
std::to_string(bytes.size()) + " bytes)");
|
||||
}
|
||||
|
||||
Shader::Builder& Shader::Builder::fromGLSL(const std::string& path) {
|
||||
this->path = path;
|
||||
this->precompiled = false;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Shader::Builder& Shader::Builder::fromSPIRV(const std::string& path) {
|
||||
this->path = path;
|
||||
this->precompiled = true;
|
||||
this->embedded = false;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Shader::Builder& Shader::Builder::fromSPIRV(std::vector<u32> spirv) {
|
||||
this->embeddedSpirv = std::move(spirv);
|
||||
this->embedded = true;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Shader::Builder& Shader::Builder::fromSPIRV(std::span<const u32> spirv) {
|
||||
this->embeddedSpirv.assign(spirv.begin(), spirv.end());
|
||||
this->embedded = true;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Shader::Builder& Shader::Builder::setStage(ShaderStage shaderStage) {
|
||||
this->stage = shaderStage;
|
||||
this->stageSet = true;
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::unique_ptr<Shader> Shader::Builder::build() {
|
||||
if (!this->embedded && this->path.empty()) {
|
||||
log::fatal("failed to build shader: no source path provided");
|
||||
throw std::runtime_error("failed to build shader: no source path provided");
|
||||
}
|
||||
|
||||
if (!this->stageSet) {
|
||||
log::fatal("failed to build shader: no ShaderStage provided");
|
||||
throw std::runtime_error("failed to build shader: no ShaderStage provided");
|
||||
}
|
||||
|
||||
auto shader = std::unique_ptr<Shader>(new Shader());
|
||||
|
||||
if (this->embedded) {
|
||||
shader->stage = this->stage;
|
||||
shader->spirv = std::move(this->embeddedSpirv);
|
||||
} else if (this->precompiled) {
|
||||
shader->load(this->path, this->stage);
|
||||
} else {
|
||||
shader->compile(this->path, this->stage);
|
||||
}
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
} // namespace sophia
|
||||
@@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <sophia/types.hpp>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace sophia {
|
||||
|
||||
enum class ShaderStage { VERTEX, FRAGMENT, COMPUTE };
|
||||
|
||||
class Shader {
|
||||
public:
|
||||
class Builder;
|
||||
|
||||
Shader(const Shader&) = delete;
|
||||
Shader& operator=(const Shader&) = delete;
|
||||
|
||||
~Shader() = default;
|
||||
|
||||
const std::vector<u32>& getSpirv() const { return this->spirv; }
|
||||
ShaderStage getStage() const { return this->stage; }
|
||||
|
||||
private:
|
||||
Shader() = default;
|
||||
|
||||
void compile(const std::string& path, ShaderStage shaderStage);
|
||||
void load(const std::string& path, ShaderStage shaderStage);
|
||||
|
||||
ShaderStage stage;
|
||||
std::vector<u32> spirv;
|
||||
};
|
||||
|
||||
class Shader::Builder {
|
||||
public:
|
||||
Builder() = default;
|
||||
|
||||
Builder& fromGLSL(const std::string& path);
|
||||
Builder& fromSPIRV(const std::string& path);
|
||||
Builder& fromSPIRV(std::vector<u32> spirv);
|
||||
Builder& fromSPIRV(std::span<const u32> spirv);
|
||||
Builder& setStage(ShaderStage shaderStage);
|
||||
|
||||
std::unique_ptr<Shader> build();
|
||||
|
||||
private:
|
||||
std::string path;
|
||||
std::vector<u32> embeddedSpirv;
|
||||
ShaderStage stage;
|
||||
bool stageSet = false;
|
||||
bool precompiled = false;
|
||||
bool embedded = false;
|
||||
};
|
||||
|
||||
} // namespace sophia
|
||||
@@ -0,0 +1,224 @@
|
||||
#include "vulkan/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();
|
||||
|
||||
log::trace("created swapchain");
|
||||
}
|
||||
|
||||
Swapchain::Swapchain(Device& device, vk::Extent2D extent,
|
||||
std::shared_ptr<Swapchain> previous)
|
||||
: device{device}, extent{extent}, oldSwapchain{previous} {
|
||||
initialize();
|
||||
this->oldSwapchain = nullptr;
|
||||
|
||||
log::trace("created swapchain (from previous)");
|
||||
}
|
||||
|
||||
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::trace("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,80 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <sophia/types.hpp>
|
||||
#include <vector>
|
||||
#include <vulkan/vulkan.hpp>
|
||||
|
||||
#include "vulkan/device.hpp"
|
||||
#include "vulkan/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 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
|
||||
Reference in New Issue
Block a user