Big code changes
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
#include <sophia/application.hpp>
|
||||
|
||||
#include "engine.hpp"
|
||||
|
||||
namespace sophia {
|
||||
|
||||
class Application::Impl {
|
||||
public:
|
||||
Impl(const Application::Config& config)
|
||||
: engine{{config.width, config.height, config.name}} {}
|
||||
|
||||
void run() { engine.run(); }
|
||||
|
||||
private:
|
||||
Engine engine;
|
||||
};
|
||||
|
||||
Application::Application(const Config& config)
|
||||
: impl{std::make_unique<Impl>(config)} {}
|
||||
|
||||
Application::~Application() = default;
|
||||
|
||||
void Application::run() { impl->run(); }
|
||||
|
||||
} // namespace sophia
|
||||
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* Based on
|
||||
* https://github.com/blurrypiano/littleVulkanEngine/blob/main/src/lve_buffer.cpp
|
||||
*/
|
||||
#include "buffer.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) {
|
||||
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) {
|
||||
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 "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,72 @@
|
||||
#include "compute_pipeline.hpp"
|
||||
|
||||
#include <sophia/util/logger.hpp>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace sophia {
|
||||
|
||||
ComputePipeline::ComputePipeline(Device& device) : device{device} {}
|
||||
|
||||
ComputePipeline::~ComputePipeline() {
|
||||
log::trace("destoryed vk::Pipeline (compute)");
|
||||
}
|
||||
|
||||
void ComputePipeline::create(Shader& shader,
|
||||
vk::PipelineLayout pipelineLayout) {
|
||||
if (shader.getStage() != ShaderStage::COMPUTE) {
|
||||
log::fatal(
|
||||
"failed to create compute pipeline: shader is not a compute shader");
|
||||
throw std::runtime_error(
|
||||
"failed to create compute pipeline: shader is not a compute shader");
|
||||
}
|
||||
|
||||
if (pipelineLayout == VK_NULL_HANDLE) {
|
||||
log::fatal(
|
||||
"failed to create compute pipeline: no vk::PipelineLayout provided");
|
||||
throw std::runtime_error(
|
||||
"failed to create compute pipeline: no vk::PipelineLayout provided");
|
||||
}
|
||||
|
||||
vk::UniqueShaderModule computeShaderModule =
|
||||
createShaderModule(shader.getSpirv());
|
||||
log::trace("created compute shader module");
|
||||
|
||||
vk::PipelineShaderStageCreateInfo shaderStage{
|
||||
vk::PipelineShaderStageCreateFlags(), vk::ShaderStageFlagBits::eCompute,
|
||||
computeShaderModule.get(), "main"};
|
||||
|
||||
vk::ComputePipelineCreateInfo pipelineInfo{};
|
||||
pipelineInfo.stage = shaderStage;
|
||||
pipelineInfo.layout = pipelineLayout;
|
||||
pipelineInfo.basePipelineHandle = nullptr;
|
||||
|
||||
try {
|
||||
this->computePipeline =
|
||||
this->device.get()
|
||||
->createComputePipelineUnique(nullptr, pipelineInfo)
|
||||
.value;
|
||||
log::trace("created vk::Pipeline (compute)");
|
||||
} catch (const vk::SystemError& err) {
|
||||
log::fatal("failed to create vk::Pipeline (compute)");
|
||||
throw std::runtime_error("failed to create vk::Pipeline (compute)");
|
||||
}
|
||||
}
|
||||
|
||||
void ComputePipeline::bind(vk::CommandBuffer commandBuffer) {
|
||||
commandBuffer.bindPipeline(vk::PipelineBindPoint::eCompute,
|
||||
this->computePipeline.get());
|
||||
}
|
||||
|
||||
vk::UniqueShaderModule ComputePipeline::createShaderModule(
|
||||
const std::vector<u32>& spirv) {
|
||||
try {
|
||||
return device.get()->createShaderModuleUnique(
|
||||
{vk::ShaderModuleCreateFlags(), spirv.size() * sizeof(u32),
|
||||
spirv.data()});
|
||||
} catch (const vk::SystemError& err) {
|
||||
log::fatal("failed to create shader module");
|
||||
throw std::runtime_error("failed to create shader module");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace sophia
|
||||
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <vulkan/vulkan.hpp>
|
||||
|
||||
#include "device.hpp"
|
||||
#include "shader.hpp"
|
||||
|
||||
namespace sophia {
|
||||
|
||||
class ComputePipeline {
|
||||
public:
|
||||
ComputePipeline(const ComputePipeline&) = delete;
|
||||
ComputePipeline& operator=(const ComputePipeline&) = delete;
|
||||
|
||||
ComputePipeline(Device& device);
|
||||
~ComputePipeline();
|
||||
|
||||
void create(Shader& shader, vk::PipelineLayout pipelineLayout);
|
||||
|
||||
void bind(vk::CommandBuffer commandBuffer);
|
||||
|
||||
private:
|
||||
vk::UniqueShaderModule createShaderModule(const std::vector<u32>& spirv);
|
||||
|
||||
Device& device;
|
||||
vk::UniquePipeline computePipeline;
|
||||
};
|
||||
|
||||
} // namespace sophia
|
||||
+45
-37
@@ -3,24 +3,23 @@
|
||||
#include <string.h>
|
||||
|
||||
#include <set>
|
||||
#include <sophia/util/logger.hpp>
|
||||
|
||||
#include "util/logger.hpp"
|
||||
namespace sophia {
|
||||
|
||||
namespace hep {
|
||||
|
||||
static VKAPI_ATTR VkBool32 VKAPI_CALL
|
||||
debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT m_severity,
|
||||
VkDebugUtilsMessageTypeFlagsEXT m_type,
|
||||
const VkDebugUtilsMessengerCallbackDataEXT* pCallback_data,
|
||||
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_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) {
|
||||
if (m_severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError) {
|
||||
log::error(pCallback_data->pMessage);
|
||||
return VK_SUCCESS;
|
||||
} else if (m_severity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) {
|
||||
} else if (m_severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) {
|
||||
log::warning(pCallback_data->pMessage);
|
||||
} else {
|
||||
log::info(pCallback_data->pMessage);
|
||||
@@ -30,8 +29,7 @@ debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT m_severity,
|
||||
}
|
||||
|
||||
VkResult createDebugUtilsMessengerEXT(
|
||||
VkInstance instance,
|
||||
const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo,
|
||||
VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo,
|
||||
const VkAllocationCallbacks* pAllocator,
|
||||
VkDebugUtilsMessengerEXT* pCallback) {
|
||||
auto func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(
|
||||
@@ -48,7 +46,9 @@ void destroyDebugUtilsMessengerEXT(VkInstance instance,
|
||||
const VkAllocationCallbacks* pAllocator) {
|
||||
auto func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(
|
||||
instance, "vkDestroyDebugUtilsMessengerEXT");
|
||||
if (func != nullptr) { func(instance, callback, pAllocator); }
|
||||
if (func != nullptr) {
|
||||
func(instance, callback, pAllocator);
|
||||
}
|
||||
}
|
||||
|
||||
Device::Device(Window& window) : window{window} {
|
||||
@@ -91,8 +91,7 @@ u32 Device::findMemoryType(u32 typeFilter, vk::MemoryPropertyFlags properties) {
|
||||
}
|
||||
|
||||
vk::Format Device::findSupportedFormat(
|
||||
const std::vector<vk::Format>& candidates,
|
||||
vk::ImageTiling tiling,
|
||||
const std::vector<vk::Format>& candidates, vk::ImageTiling tiling,
|
||||
vk::FormatFeatureFlags features) {
|
||||
for (vk::Format format : candidates) {
|
||||
vk::FormatProperties properties =
|
||||
@@ -110,11 +109,9 @@ vk::Format Device::findSupportedFormat(
|
||||
throw std::runtime_error("failed to find supported format");
|
||||
}
|
||||
|
||||
void Device::createBuffer(vk::DeviceSize size,
|
||||
vk::BufferUsageFlags usage,
|
||||
void Device::createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage,
|
||||
vk::MemoryPropertyFlags properties,
|
||||
vk::Buffer& buffer,
|
||||
vk::DeviceMemory& bufferMemory) {
|
||||
vk::Buffer& buffer, vk::DeviceMemory& bufferMemory) {
|
||||
vk::BufferCreateInfo bufferInfo = {};
|
||||
bufferInfo.size = size;
|
||||
bufferInfo.usage = usage;
|
||||
@@ -156,7 +153,9 @@ vk::CommandBuffer Device::beginSingleTimeCommands() {
|
||||
try {
|
||||
vk::Result result =
|
||||
this->device->allocateCommandBuffers(&allocInfo, &commandBuffer);
|
||||
if (result != vk::Result::eSuccess) { throw vk::SystemError(result); }
|
||||
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());
|
||||
@@ -168,7 +167,9 @@ vk::CommandBuffer Device::beginSingleTimeCommands() {
|
||||
|
||||
try {
|
||||
vk::Result result = commandBuffer.begin(&beginInfo);
|
||||
if (result != vk::Result::eSuccess) { throw vk::SystemError(result); }
|
||||
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());
|
||||
@@ -191,8 +192,7 @@ void Device::endSingleTimeCommands(vk::CommandBuffer commandBuffer) {
|
||||
this->device->freeCommandBuffers(commandPool, commandBuffer);
|
||||
}
|
||||
|
||||
void Device::copyBuffer(vk::Buffer sourceBuffer,
|
||||
vk::Buffer destinationBuffer,
|
||||
void Device::copyBuffer(vk::Buffer sourceBuffer, vk::Buffer destinationBuffer,
|
||||
vk::DeviceSize size) {
|
||||
vk::CommandBuffer commandBuffer = beginSingleTimeCommands();
|
||||
|
||||
@@ -210,7 +210,9 @@ void Device::createImageWithInfo(const vk::ImageCreateInfo& imageInfo,
|
||||
vk::DeviceMemory& imageMemory) {
|
||||
try {
|
||||
vk::Result result = this->device->createImage(&imageInfo, nullptr, &image);
|
||||
if (result != vk::Result::eSuccess) { throw vk::SystemError(result); }
|
||||
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");
|
||||
@@ -227,7 +229,9 @@ void Device::createImageWithInfo(const vk::ImageCreateInfo& imageInfo,
|
||||
try {
|
||||
vk::Result result =
|
||||
this->device->allocateMemory(&allocateInfo, nullptr, &imageMemory);
|
||||
if (result != vk::Result::eSuccess) { throw vk::SystemError(result); }
|
||||
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");
|
||||
@@ -243,7 +247,7 @@ void Device::createImageWithInfo(const vk::ImageCreateInfo& imageInfo,
|
||||
|
||||
void Device::populateImGuiInitInfo(ImGui_ImplVulkan_InitInfo& initInfo) {
|
||||
initInfo.Instance = this->instance.get();
|
||||
initInfo.ApiVersion = HEP_VULKAN_API_VERSION;
|
||||
initInfo.ApiVersion = SOPHIA_VULKAN_API_VERSION;
|
||||
initInfo.PhysicalDevice = this->physicalDevice;
|
||||
initInfo.Device = this->device.get();
|
||||
|
||||
@@ -286,7 +290,9 @@ bool Device::checkValidationLayerSupport() {
|
||||
}
|
||||
}
|
||||
|
||||
if (!layerFound) { return false; }
|
||||
if (!layerFound) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -309,7 +315,9 @@ std::vector<const char*> Device::getRequiredExtensions() {
|
||||
vk::Result result = vk::enumerateInstanceExtensionProperties(
|
||||
nullptr, &available_extension_count, nullptr);
|
||||
|
||||
if (result != vk::Result::eSuccess) { throw std::exception(); }
|
||||
if (result != vk::Result::eSuccess) {
|
||||
throw std::exception();
|
||||
}
|
||||
|
||||
std::vector<vk::ExtensionProperties> available_extensions(
|
||||
available_extension_count);
|
||||
@@ -317,7 +325,9 @@ std::vector<const char*> Device::getRequiredExtensions() {
|
||||
result = vk::enumerateInstanceExtensionProperties(
|
||||
nullptr, &available_extension_count, available_extensions.data());
|
||||
|
||||
if (result != vk::Result::eSuccess) { throw std::exception(); }
|
||||
if (result != vk::Result::eSuccess) {
|
||||
throw std::exception();
|
||||
}
|
||||
|
||||
log::verbose("Number of available extensions: ", available_extension_count);
|
||||
|
||||
@@ -326,7 +336,9 @@ std::vector<const char*> Device::getRequiredExtensions() {
|
||||
std::cout << '\t' << e.extensionName << '\n';
|
||||
}
|
||||
std::cout << "Required extensions:\n";
|
||||
for (const auto& e : extensions) { std::cout << "\t" << e << '\n'; }
|
||||
for (const auto& e : extensions) {
|
||||
std::cout << "\t" << e << '\n';
|
||||
}
|
||||
#endif
|
||||
|
||||
return extensions;
|
||||
@@ -373,7 +385,9 @@ QueueFamilyIndices Device::findQueueFamilies(vk::PhysicalDevice device) {
|
||||
indices.presentFamily = i;
|
||||
}
|
||||
|
||||
if (indices.isComplete()) { break; }
|
||||
if (indices.isComplete()) {
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
@@ -457,12 +471,6 @@ void Device::createLogicalDevice() {
|
||||
static_cast<uint32_t>(this->enabledExtensions.size());
|
||||
createInfo.ppEnabledExtensionNames = this->enabledExtensions.data();
|
||||
|
||||
if (this->enableValidationLayers) {
|
||||
createInfo.enabledLayerCount =
|
||||
static_cast<uint32_t>(this->enabledLayers.size());
|
||||
createInfo.ppEnabledLayerNames = this->enabledLayers.data();
|
||||
}
|
||||
|
||||
try {
|
||||
this->device = this->physicalDevice.createDeviceUnique(createInfo);
|
||||
log::trace("created vk::Device.");
|
||||
@@ -502,4 +510,4 @@ void Device::createCommandPool() {
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace hep
|
||||
} // namespace sophia
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
#pragma once
|
||||
|
||||
#include <imgui_impl_vulkan.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <optional>
|
||||
#include <sophia/types.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 {
|
||||
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;
|
||||
|
||||
Window& window;
|
||||
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 = {
|
||||
VK_KHR_SWAPCHAIN_EXTENSION_NAME};
|
||||
};
|
||||
|
||||
} // namespace sophia
|
||||
@@ -0,0 +1,76 @@
|
||||
#include "engine.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <sophia/frame_info.hpp>
|
||||
#include <sophia/util/logger.hpp>
|
||||
#include <vulkan/vulkan.hpp>
|
||||
|
||||
namespace sophia {
|
||||
|
||||
Engine::Engine(const Config& config)
|
||||
: config{config},
|
||||
window{config.width, config.height, config.name},
|
||||
device{this->window} // renderer{this->window, this->device}
|
||||
{
|
||||
// this->imguiDescriptorPool =
|
||||
// DescriptorPool::Builder(this->device)
|
||||
// .addPoolSize(vk::DescriptorType::eCombinedImageSampler,
|
||||
// IMGUI_IMPL_VULKAN_MINIMUM_IMAGE_SAMPLER_POOL_SIZE
|
||||
// + 5)
|
||||
// .setPoolFlags(vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet)
|
||||
// .setMaxSets(IMGUI_IMPL_VULKAN_MINIMUM_IMAGE_SAMPLER_POOL_SIZE
|
||||
// + 5)
|
||||
// .build();
|
||||
|
||||
// this->uiManager =
|
||||
// UIManager::Builder(this->window, this->device, this->renderer,
|
||||
// imguiDescriptorPool->get())
|
||||
// .darkTheme()
|
||||
// .setDocking(true)
|
||||
// .setKeyboard(true)
|
||||
// .build();
|
||||
};
|
||||
|
||||
Engine::~Engine() { this->device.waitIdle(); };
|
||||
|
||||
void Engine::run() {
|
||||
auto startTime = std::chrono::high_resolution_clock::now();
|
||||
auto currentTime = startTime;
|
||||
|
||||
// EventSystem::get().addListener<KeyReleasedEvent>(
|
||||
// std::bind(&Application::onEvent, this, std::placeholders::_1));
|
||||
|
||||
while (this->isRunning) {
|
||||
glfwPollEvents();
|
||||
|
||||
auto newTime = std::chrono::high_resolution_clock::now();
|
||||
double deltaTime =
|
||||
std::chrono::duration<double>(newTime - currentTime).count();
|
||||
currentTime = newTime;
|
||||
|
||||
// // Attempt to start a new frame
|
||||
// vk::CommandBuffer commandBuffer = this->renderer.beginFrame();
|
||||
// if (commandBuffer != nullptr) {
|
||||
// double elapsedTime =
|
||||
// std::chrono::duration<double>(currentTime - startTime).count();
|
||||
|
||||
// vk::Extent2D extent = this->renderer.getCurrentFramebufferExtent();
|
||||
// glm::vec2 extentVec2(static_cast<float>(extent.width),
|
||||
// static_cast<float>(extent.height));
|
||||
|
||||
// FrameInfo frameInfo{this->renderer.getFrameIndex(), elapsedTime,
|
||||
// deltaTime, extentVec2};
|
||||
// }
|
||||
|
||||
this->isRunning = false;
|
||||
}
|
||||
|
||||
this->device.waitIdle();
|
||||
|
||||
auto endTime = std::chrono::high_resolution_clock::now();
|
||||
double totalRuntime =
|
||||
std::chrono::duration<double>(endTime - startTime).count();
|
||||
log::info("Application ran for", totalRuntime, "s");
|
||||
}
|
||||
|
||||
} // namespace sophia
|
||||
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "device.hpp"
|
||||
#include "window.hpp"
|
||||
|
||||
namespace sophia {
|
||||
|
||||
class Engine {
|
||||
public:
|
||||
struct Config {
|
||||
int width;
|
||||
int height;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
Engine(const Engine&) = delete;
|
||||
Engine& operator=(const Engine&) = delete;
|
||||
|
||||
Engine(const Config& config);
|
||||
~Engine();
|
||||
|
||||
void run();
|
||||
|
||||
private:
|
||||
Config config;
|
||||
|
||||
Window window;
|
||||
Device device;
|
||||
|
||||
bool isRunning = true;
|
||||
};
|
||||
|
||||
} // namespace sophia
|
||||
@@ -0,0 +1,324 @@
|
||||
#include "image.hpp"
|
||||
|
||||
#include <cstring>
|
||||
#include <sophia/util/logger.hpp>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "device.hpp"
|
||||
|
||||
namespace sophia {
|
||||
|
||||
void Image::transitionLayout(vk::CommandBuffer commandBuffer,
|
||||
vk::ImageLayout newLayout) {
|
||||
vk::ImageMemoryBarrier barrier{};
|
||||
barrier.oldLayout = this->layout;
|
||||
barrier.newLayout = newLayout;
|
||||
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.image = this->get();
|
||||
barrier.subresourceRange.aspectMask = this->aspectMask;
|
||||
barrier.subresourceRange.baseMipLevel = 0;
|
||||
barrier.subresourceRange.levelCount = this->mipLevels;
|
||||
barrier.subresourceRange.baseArrayLayer = 0;
|
||||
barrier.subresourceRange.layerCount = this->arrayLayers;
|
||||
|
||||
vk::PipelineStageFlags srcStage;
|
||||
vk::PipelineStageFlags dstStage;
|
||||
|
||||
if (this->layout == vk::ImageLayout::eUndefined &&
|
||||
newLayout == vk::ImageLayout::eTransferDstOptimal) {
|
||||
barrier.srcAccessMask = {};
|
||||
barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite;
|
||||
srcStage = vk::PipelineStageFlagBits::eTopOfPipe;
|
||||
dstStage = vk::PipelineStageFlagBits::eTransfer;
|
||||
} else if (this->layout == vk::ImageLayout::eTransferDstOptimal &&
|
||||
newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) {
|
||||
barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
|
||||
barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;
|
||||
srcStage = vk::PipelineStageFlagBits::eTransfer;
|
||||
dstStage = vk::PipelineStageFlagBits::eFragmentShader;
|
||||
} else if (this->layout == vk::ImageLayout::eUndefined &&
|
||||
newLayout == vk::ImageLayout::eDepthStencilAttachmentOptimal) {
|
||||
barrier.srcAccessMask = {};
|
||||
barrier.dstAccessMask = vk::AccessFlagBits::eDepthStencilAttachmentRead |
|
||||
vk::AccessFlagBits::eDepthStencilAttachmentWrite;
|
||||
srcStage = vk::PipelineStageFlagBits::eTopOfPipe;
|
||||
dstStage = vk::PipelineStageFlagBits::eEarlyFragmentTests;
|
||||
} else if (this->layout == vk::ImageLayout::eUndefined &&
|
||||
newLayout == vk::ImageLayout::eColorAttachmentOptimal) {
|
||||
barrier.srcAccessMask = {};
|
||||
barrier.dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite;
|
||||
srcStage = vk::PipelineStageFlagBits::eTopOfPipe;
|
||||
dstStage = vk::PipelineStageFlagBits::eColorAttachmentOutput;
|
||||
} else if (this->layout == vk::ImageLayout::eUndefined &&
|
||||
newLayout == vk::ImageLayout::eGeneral) {
|
||||
barrier.srcAccessMask = {};
|
||||
barrier.dstAccessMask =
|
||||
vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite;
|
||||
srcStage = vk::PipelineStageFlagBits::eTopOfPipe;
|
||||
dstStage = vk::PipelineStageFlagBits::eComputeShader;
|
||||
} else if (this->layout == vk::ImageLayout::eColorAttachmentOptimal &&
|
||||
newLayout == vk::ImageLayout::ePresentSrcKHR) {
|
||||
barrier.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite;
|
||||
barrier.dstAccessMask = {};
|
||||
srcStage = vk::PipelineStageFlagBits::eColorAttachmentOutput;
|
||||
dstStage = vk::PipelineStageFlagBits::eBottomOfPipe;
|
||||
} else {
|
||||
log::fatal("Unsupported image layout transition.");
|
||||
throw std::runtime_error("Unsupported image layout transition.");
|
||||
}
|
||||
|
||||
commandBuffer.pipelineBarrier(srcStage, dstStage, {}, 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);
|
||||
}
|
||||
|
||||
log::trace("created vk::Image.");
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace sophia
|
||||
@@ -0,0 +1,124 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <sophia/types.hpp>
|
||||
#include <vulkan/vulkan.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; }
|
||||
|
||||
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,125 @@
|
||||
#include "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;
|
||||
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->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->precompiled) {
|
||||
shader->load(this->path, this->stage);
|
||||
} else {
|
||||
shader->compile(this->path, this->stage);
|
||||
}
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
} // namespace sophia
|
||||
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <sophia/types.hpp>
|
||||
#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& setStage(ShaderStage shaderStage);
|
||||
|
||||
std::unique_ptr<Shader> build();
|
||||
|
||||
private:
|
||||
std::string path;
|
||||
ShaderStage stage;
|
||||
bool stageSet = false;
|
||||
bool precompiled = false;
|
||||
};
|
||||
|
||||
} // namespace sophia
|
||||
@@ -1,9 +1,9 @@
|
||||
#include "window.hpp"
|
||||
|
||||
#include "key_event.hpp"
|
||||
#include "util/logger.hpp"
|
||||
#include <sophia/key_event.hpp>
|
||||
#include <sophia/util/logger.hpp>
|
||||
|
||||
namespace hep {
|
||||
namespace sophia {
|
||||
|
||||
Window::Window(int width, int height, const std::string& name)
|
||||
: width{width}, height{height}, name{name} {
|
||||
@@ -28,11 +28,8 @@ void Window::createSurface(const vk::Instance& instance,
|
||||
log::trace("Created vk::SurfaceKHR.");
|
||||
}
|
||||
|
||||
void Window::keyEventCallback(GLFWwindow* window,
|
||||
int key,
|
||||
int scancode,
|
||||
int action,
|
||||
int mods) {
|
||||
void Window::keyEventCallback(GLFWwindow* window, int key, int scancode,
|
||||
int action, int mods) {
|
||||
if (action == GLFW_PRESS) {
|
||||
KeyPressedEvent event{static_cast<KeyCode>(key)};
|
||||
EventSystem::get().dispatch(event);
|
||||
@@ -61,4 +58,4 @@ void Window::initialize() {
|
||||
glfwSetKeyCallback(this->window, keyEventCallback);
|
||||
}
|
||||
|
||||
} // namespace hep
|
||||
} // namespace sophia
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#define GLFW_INCLUDE_VULKAN
|
||||
#include <GLFW/glfw3.h>
|
||||
|
||||
#include <string>
|
||||
#include <vulkan/vulkan.hpp>
|
||||
|
||||
#include <sophia/event.hpp>
|
||||
#include <sophia/types.hpp>
|
||||
|
||||
namespace sophia {
|
||||
|
||||
class Window {
|
||||
public:
|
||||
Window(const Window&) = delete;
|
||||
Window& operator=(const Window&) = delete;
|
||||
|
||||
Window(int width, int height, const std::string& name = "sophiahaestus");
|
||||
~Window();
|
||||
|
||||
bool shouldClose() { return glfwWindowShouldClose(window); }
|
||||
|
||||
int getWidth() const { return this->width; }
|
||||
int getHeight() const { return this->height; }
|
||||
|
||||
vk::Extent2D getExtent() {
|
||||
return {static_cast<u32>(width), static_cast<u32>(height)};
|
||||
}
|
||||
void createSurface(const vk::Instance& instance, vk::SurfaceKHR& surface);
|
||||
bool wasResized() const { return this->resized; };
|
||||
void resetResizedFlag() { this->resized = false; }
|
||||
|
||||
GLFWwindow* getGLFWwindow() { return this->window; }
|
||||
|
||||
private:
|
||||
static void resizeCallback(GLFWwindow* window, int width, int height);
|
||||
static void keyEventCallback(GLFWwindow* window,
|
||||
int key,
|
||||
int scancode,
|
||||
int action,
|
||||
int mods);
|
||||
void initialize();
|
||||
|
||||
int width, height;
|
||||
bool resized = false;
|
||||
std::string name;
|
||||
GLFWwindow* window;
|
||||
};
|
||||
|
||||
} // namespace sophia
|
||||
Reference in New Issue
Block a user