Initial commit

This commit is contained in:
2022-10-09 17:25:45 +03:00
commit da10f7c5cd
60 changed files with 6255 additions and 0 deletions

View File

@ -0,0 +1,69 @@
#include "glfw_mgmt_details.h"
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>
#include <iostream>
#include "vulkan_mgmt.h"
namespace progressia {
namespace desktop {
static GLFWwindow *window = nullptr;
static void onGlfwError(int errorCode, const char *description);
static void onWindowGeometryChange(GLFWwindow *window, int width, int height);
void initializeGlfw() {
std::cout << "Beginning GLFW init" << std::endl;
glfwSetErrorCallback(onGlfwError);
if (!glfwInit()) {
std::cout << "glfwInit() failed" << std::endl;
// REPORT_ERROR
exit(1);
}
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
window = glfwCreateWindow(800, 800, "Progressia", nullptr, nullptr);
glfwSetWindowSizeCallback(window, onWindowGeometryChange);
std::cout << "GLFW init complete" << std::endl;
}
void showWindow() {
glfwShowWindow(window);
std::cout << "Window now visible" << std::endl;
}
bool shouldRun() { return !glfwWindowShouldClose(window); }
void doGlfwRoutine() { glfwPollEvents(); }
void shutdownGlfw() { glfwTerminate(); }
void onGlfwError(int errorCode, const char *description) {
std::cout << "[GLFW] " << description << " (" << errorCode << ")"
<< std::endl;
// REPORT_ERROR
exit(1);
}
void onWindowGeometryChange(GLFWwindow *window, [[maybe_unused]] int width,
[[maybe_unused]] int height) {
if (window != progressia::desktop::window) {
return;
}
resizeVulkanSurface();
}
GLFWwindow *getGLFWWindowHandle() { return window; }
} // namespace desktop
} // namespace progressia

View File

@ -0,0 +1,13 @@
#pragma once
namespace progressia {
namespace desktop {
void initializeGlfw();
void showWindow();
void shutdownGlfw();
bool shouldRun();
void doGlfwRoutine();
} // namespace desktop
} // namespace progressia

View File

@ -0,0 +1,14 @@
#pragma once
#include "glfw_mgmt.h"
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>
namespace progressia {
namespace desktop {
GLFWwindow *getGLFWWindowHandle();
} // namespace desktop
} // namespace progressia

View File

@ -0,0 +1,12 @@
#version 450
layout(set = 1, binding = 0) uniform sampler2D texSampler;
layout(location = 0) in vec4 fragColor;
layout(location = 2) in vec2 fragTexCoord;
layout(location = 0) out vec4 outColor;
void main() {
outColor = fragColor * texture(texSampler, fragTexCoord);
}

View File

@ -0,0 +1,62 @@
#version 450
layout(set = 0, binding = 0) uniform Projection {
mat4 m;
} projection;
layout(set = 0, binding = 1) uniform View {
mat4 m;
} view;
layout(push_constant) uniform PushContants {
layout(offset = 0) mat3x4 model;
} push;
layout(set = 2, binding = 0) uniform Light {
vec4 color;
vec4 from;
float contrast;
float softness;
} light;
layout(location = 0) in vec3 inPosition;
layout(location = 1) in vec4 inColor;
layout(location = 2) in vec3 inNormal;
layout(location = 3) in vec2 inTexCoord;
layout(location = 0) out vec4 fragColor;
layout(location = 2) out vec2 fragTexCoord;
void main() {
mat4 model = mat4(push.model);
gl_Position = projection.m * view.m * model * vec4(inPosition, 1);
fragColor.a = inColor.a;
float exposure = dot(light.from.xyz, (model * vec4(inNormal, 1)).xyz);
if (exposure < -light.softness) {
fragColor.rgb = inColor.rgb * (
(exposure + 1) * ((0.5 - light.contrast) / (1 - light.softness))
);
} else if (exposure < light.softness) {
// FIXME
fragColor.rgb =
inColor.rgb
* (
0.5 + exposure * light.contrast / light.softness
)
* (
(+exposure / light.contrast + 1) / 2 * light.color.rgb +
(-exposure / light.contrast + 1) / 2 * vec3(1, 1, 1)
);
} else {
fragColor.rgb =
inColor.rgb
* (
0.5 + light.contrast + (exposure - light.softness) * ((0.5 - light.contrast) / (1 - light.softness))
)
* light.color.rgb;
}
fragTexCoord = inTexCoord;
}

View File

@ -0,0 +1,336 @@
#include "vulkan_adapter.h"
#include "vulkan_common.h"
#include <cstddef>
#include <fstream>
#include <memory>
#include <type_traits>
#define GLM_FORCE_RADIANS
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
#include <glm/gtx/euler_angles.hpp>
#include <glm/mat4x4.hpp>
#include <glm/vec2.hpp>
#include <glm/vec3.hpp>
#include <glm/vec4.hpp>
#include "../../main/rendering.h"
#include "vulkan_buffer.h"
#include "vulkan_frame.h"
#include "vulkan_pipeline.h"
#include "vulkan_swap_chain.h"
#include "vulkan_texture_descriptors.h"
#include <embedded_resources.h>
namespace progressia {
namespace desktop {
using progressia::main::Vertex;
namespace {
struct FieldProperties {
uint32_t offset;
VkFormat format;
};
auto getVertexFieldProperties() {
return std::array{
FieldProperties{offsetof(Vertex, position), VK_FORMAT_R32G32B32_SFLOAT},
FieldProperties{offsetof(Vertex, color), VK_FORMAT_R32G32B32A32_SFLOAT},
FieldProperties{offsetof(Vertex, normal), VK_FORMAT_R32G32B32_SFLOAT},
FieldProperties{offsetof(Vertex, texCoord), VK_FORMAT_R32G32_SFLOAT},
};
}
} // namespace
namespace {
std::vector<char> tmp_readFile(const std::string &path) {
auto resource = __embedded_resources::getEmbeddedResource(path.c_str());
if (resource.data == nullptr) {
// REPORT_ERROR
std::cerr << "Could not find resource \"" << path << "\"" << std::endl;
exit(1);
}
return std::vector<char>(resource.data, resource.data + resource.length);
}
} // namespace
Adapter::Adapter(Vulkan &vulkan)
: vulkan(vulkan), viewUniform(0, vulkan), lightUniform(2, vulkan) {
attachments.push_back(
{"Depth buffer",
vulkan.findSupportedFormat(
{VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT,
VK_FORMAT_D24_UNORM_S8_UINT},
VK_IMAGE_TILING_OPTIMAL,
VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT),
VK_IMAGE_ASPECT_DEPTH_BIT,
VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT,
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
VK_ATTACHMENT_LOAD_OP_CLEAR,
VK_ATTACHMENT_STORE_OP_DONT_CARE,
{1.0f, 0},
nullptr});
}
Adapter::~Adapter() {
// Do nothing
}
std::vector<Attachment> &Adapter::getAttachments() { return attachments; }
std::vector<char> Adapter::loadVertexShader() {
return tmp_readFile("shader.vert.spv");
}
std::vector<char> Adapter::loadFragmentShader() {
return tmp_readFile("shader.frag.spv");
}
VkVertexInputBindingDescription Adapter::getVertexInputBindingDescription() {
VkVertexInputBindingDescription bindingDescription{};
bindingDescription.binding = 0;
bindingDescription.stride = sizeof(Vertex);
bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
return bindingDescription;
}
std::vector<VkVertexInputAttributeDescription>
Adapter::getVertexInputAttributeDescriptions() {
std::vector<VkVertexInputAttributeDescription> attributeDescriptions;
uint32_t i = 0;
for (auto props : getVertexFieldProperties()) {
attributeDescriptions.push_back({});
attributeDescriptions[i].binding = 0;
attributeDescriptions[i].location = i;
attributeDescriptions[i].format = props.format;
attributeDescriptions[i].offset = props.offset;
i++;
}
return attributeDescriptions;
}
std::vector<VkDescriptorSetLayout> Adapter::getUsedDSLayouts() const {
return {viewUniform.getLayout(), vulkan.getTextureDescriptors().getLayout(),
lightUniform.getLayout()};
}
Adapter::ViewUniform::State Adapter::createView() {
return viewUniform.addState();
}
Adapter::LightUniform::State Adapter::createLight() {
return lightUniform.addState();
}
void Adapter::onPreFrame() {
viewUniform.doUpdates();
lightUniform.doUpdates();
}
/*
* graphics_interface implementation
*/
} // namespace desktop
namespace main {
using namespace progressia::desktop;
namespace {
struct DrawRequest {
progressia::desktop::Texture *texture;
IndexedBuffer<Vertex> *vertices;
glm::mat4 modelTransform;
};
std::vector<DrawRequest> pendingDrawCommands;
glm::mat4 currentModelTransform;
} // namespace
progressia::main::Texture::Texture(Backend backend) : backend(backend) {}
progressia::main::Texture::~Texture() {
delete static_cast<progressia::desktop::Texture *>(this->backend);
}
namespace {
struct PrimitiveBackend {
IndexedBuffer<Vertex> buf;
progressia::main::Texture *tex;
};
} // namespace
Primitive::Primitive(Backend backend) : backend(backend) {}
Primitive::~Primitive() {
delete static_cast<PrimitiveBackend *>(this->backend);
}
void Primitive::draw() {
auto backend = static_cast<PrimitiveBackend *>(this->backend);
if (pendingDrawCommands.size() > 100000) {
backend->buf.getVulkan().getGint().flush();
}
pendingDrawCommands.push_back(
{static_cast<progressia::desktop::Texture *>(backend->tex->backend),
&backend->buf, currentModelTransform});
}
const progressia::main::Texture *Primitive::getTexture() const {
return static_cast<PrimitiveBackend *>(this->backend)->tex;
}
View::View(Backend backend) : backend(backend) {}
View::~View() {
delete static_cast<Adapter::ViewUniform::State *>(this->backend);
}
void View::configure(const glm::mat4 &proj, const glm::mat4 &view) {
static_cast<Adapter::ViewUniform::State *>(this->backend)
->update(proj, view);
}
void View::use() {
auto backend = static_cast<Adapter::ViewUniform::State *>(this->backend);
backend->uniform->getVulkan().getGint().flush();
backend->bind();
}
Light::Light(Backend backend) : backend(backend) {}
Light::~Light() {
delete static_cast<Adapter::LightUniform::State *>(this->backend);
}
void Light::configure(const glm::vec3 &color, const glm::vec3 &from,
float contrast, float softness) {
static_cast<Adapter::LightUniform::State *>(this->backend)
->update(Adapter::Light{glm::vec4(color, 1.0f),
glm::vec4(glm::normalize(from), 1.0f), contrast,
softness});
}
void Light::use() {
auto backend = static_cast<Adapter::LightUniform::State *>(this->backend);
backend->uniform->getVulkan().getGint().flush();
backend->bind();
}
GraphicsInterface::GraphicsInterface(Backend backend) : backend(backend) {}
GraphicsInterface::~GraphicsInterface() {
// Do nothing
}
progressia::main::Texture *
GraphicsInterface::newTexture(const progressia::main::Image &src) {
auto backend = new progressia::desktop::Texture(
src, *static_cast<Vulkan *>(this->backend));
return new Texture(backend);
}
Primitive *
GraphicsInterface::newPrimitive(const std::vector<Vertex> &vertices,
const std::vector<Vertex::Index> &indices,
progressia::main::Texture *texture) {
auto backend = new PrimitiveBackend{
IndexedBuffer<Vertex>(vertices.size(), indices.size(),
*static_cast<Vulkan *>(this->backend)),
texture};
backend->buf.load(vertices.data(), indices.data());
return new Primitive(backend);
}
View *GraphicsInterface::newView() {
return new View(new Adapter::ViewUniform::State(
static_cast<Vulkan *>(this->backend)->getAdapter().createView()));
}
Light *GraphicsInterface::newLight() {
return new Light(new Adapter::LightUniform::State(
static_cast<Vulkan *>(this->backend)->getAdapter().createLight()));
}
glm::vec2 GraphicsInterface::getViewport() const {
auto extent =
static_cast<const Vulkan *>(this->backend)->getSwapChain().getExtent();
return {extent.width, extent.height};
}
void GraphicsInterface::setModelTransform(const glm::mat4 &m) {
currentModelTransform = m;
}
void GraphicsInterface::flush() {
auto commandBuffer = static_cast<Vulkan *>(this->backend)
->getCurrentFrame()
->getCommandBuffer();
auto pipelineLayout =
static_cast<Vulkan *>(this->backend)->getPipeline().getLayout();
progressia::desktop::Texture *lastTexture = nullptr;
for (auto &cmd : pendingDrawCommands) {
if (cmd.texture != lastTexture) {
lastTexture = cmd.texture;
cmd.texture->bind();
}
auto &m = cmd.modelTransform;
// Evil transposition: column_major -> row_major
// clang-format off
std::remove_reference_t<decltype(m)>::value_type src[3*4] {
m[0][0], m[0][1], m[0][2], m[0][3],
m[1][0], m[1][1], m[1][2], m[1][3],
m[2][0], m[2][1], m[2][2], m[2][3]
};
// clang-format on
vkCmdPushConstants(
// REPORT_ERROR if getCurrentFrame() == nullptr
commandBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0,
sizeof(src), &src);
cmd.vertices->draw(commandBuffer);
}
pendingDrawCommands.clear();
}
float GraphicsInterface::tmp_getTime() { return glfwGetTime(); }
uint64_t GraphicsInterface::getLastStartedFrame() {
return static_cast<Vulkan *>(this->backend)->getLastStartedFrame();
}
} // namespace main
} // namespace progressia

View File

@ -0,0 +1,72 @@
#pragma once
#include "boost/core/noncopyable.hpp"
#include "vulkan_common.h"
#include "vulkan_descriptor_set.h"
#include "vulkan_image.h"
#include "vulkan_uniform.h"
namespace progressia {
namespace desktop {
class Attachment {
public:
const char *name;
VkFormat format;
VkImageAspectFlags aspect;
VkImageUsageFlags usage;
VkImageLayout workLayout;
VkImageLayout finalLayout;
VkAttachmentLoadOp loadOp;
VkAttachmentStoreOp storeOp;
VkClearValue clearValue;
std::unique_ptr<Image> image;
};
class Adapter : public VkObjectWrapper {
public:
using ViewUniform = Uniform<glm::mat4, glm::mat4>;
struct Light {
glm::vec4 color;
glm::vec4 from;
float contrast;
float softness;
};
using LightUniform = Uniform<Light>;
private:
Vulkan &vulkan;
ViewUniform viewUniform;
LightUniform lightUniform;
std::vector<Attachment> attachments;
public:
Adapter(Vulkan &);
~Adapter();
std::vector<Attachment> &getAttachments();
VkVertexInputBindingDescription getVertexInputBindingDescription();
std::vector<VkVertexInputAttributeDescription>
getVertexInputAttributeDescriptions();
std::vector<char> loadVertexShader();
std::vector<char> loadFragmentShader();
ViewUniform::State createView();
LightUniform::State createLight();
std::vector<VkDescriptorSetLayout> getUsedDSLayouts() const;
void onPreFrame();
};
} // namespace desktop
} // namespace progressia

View File

@ -0,0 +1,196 @@
#pragma once
#include <boost/core/noncopyable.hpp>
#include <vector>
#include "vulkan_common.h"
namespace progressia {
namespace desktop {
/*
* A single buffer with a chunk of allocated memory.
*/
template <typename Item> class Buffer : public VkObjectWrapper {
private:
std::size_t itemCount;
public:
VkBuffer buffer;
VkDeviceMemory memory;
Vulkan &vulkan;
Buffer(std::size_t itemCount, VkBufferUsageFlags usage,
VkMemoryPropertyFlags properties, Vulkan &vulkan)
:
itemCount(itemCount), vulkan(vulkan) {
VkBufferCreateInfo bufferInfo{};
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
bufferInfo.size = getSize();
bufferInfo.usage = usage;
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
vulkan.handleVkResult(
"Could not create a buffer",
vkCreateBuffer(vulkan.getDevice(), &bufferInfo, nullptr, &buffer));
VkMemoryRequirements memRequirements;
vkGetBufferMemoryRequirements(vulkan.getDevice(), buffer,
&memRequirements);
VkMemoryAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
allocInfo.allocationSize = memRequirements.size;
allocInfo.memoryTypeIndex =
vulkan.findMemoryType(memRequirements.memoryTypeBits, properties);
vulkan.handleVkResult(
"Could not allocate memory for a buffer",
vkAllocateMemory(vulkan.getDevice(), &allocInfo, nullptr, &memory));
vkBindBufferMemory(vulkan.getDevice(), buffer, memory, 0);
}
~Buffer() {
if (buffer != VK_NULL_HANDLE) {
vkDestroyBuffer(vulkan.getDevice(), buffer, nullptr);
}
if (memory != VK_NULL_HANDLE) {
vkFreeMemory(vulkan.getDevice(), memory, nullptr);
}
}
std::size_t getItemCount() const { return itemCount; }
std::size_t getSize() const { return sizeof(Item) * itemCount; }
void *map() {
void *dst;
vkMapMemory(vulkan.getDevice(), memory, 0, getSize(), 0, &dst);
return dst;
}
void unmap() { vkUnmapMemory(vulkan.getDevice(), memory); }
};
/*
* A buffer that is optimized for reading by the device. This buffer uses a
* secondary staging buffer.
*/
template <typename Item> class FastReadBuffer : public VkObjectWrapper {
private:
VkCommandBuffer commandBuffer;
Vulkan &vulkan;
public:
Buffer<Item> stagingBuffer;
Buffer<Item> remoteBuffer;
FastReadBuffer(std::size_t itemCount, VkBufferUsageFlags usage,
Vulkan &vulkan)
:
vulkan(vulkan),
stagingBuffer(itemCount, usage | VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
vulkan),
remoteBuffer(itemCount, usage | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, vulkan) {
recordCopyCommands();
}
~FastReadBuffer() { vulkan.getCommandPool().freeMultiUse(commandBuffer); }
private:
void recordCopyCommands() {
commandBuffer = vulkan.getCommandPool().beginMultiUse();
VkBufferCopy copyRegion{};
copyRegion.srcOffset = 0;
copyRegion.dstOffset = 0;
copyRegion.size = getSize();
vkCmdCopyBuffer(commandBuffer, stagingBuffer.buffer,
remoteBuffer.buffer, 1, &copyRegion);
vkEndCommandBuffer(commandBuffer);
}
public:
void flush() const {
vulkan.getCommandPool().submitMultiUse(commandBuffer, true);
}
void load(const Item *data) const {
void *dst;
vkMapMemory(vulkan.getDevice(), stagingBuffer.memory, 0, getSize(), 0,
&dst);
memcpy(dst, data, getSize());
vkUnmapMemory(vulkan.getDevice(), stagingBuffer.memory);
flush();
}
std::size_t getItemCount() const { return stagingBuffer.getItemCount(); }
std::size_t getSize() const { return stagingBuffer.getSize(); }
Vulkan &getVulkan() { return vulkan; }
const Vulkan &getVulkan() const { return vulkan; }
};
/*
* A pair of a vertex buffer and an index buffer.
*/
template <typename Vertex, typename Index, VkIndexType INDEX_TYPE>
class IndexedBufferBase : public VkObjectWrapper {
private:
FastReadBuffer<Vertex> vertexBuffer;
FastReadBuffer<Index> indexBuffer;
public:
IndexedBufferBase(std::size_t vertexCount, std::size_t indexCount,
Vulkan &vulkan)
:
vertexBuffer(vertexCount, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, vulkan),
indexBuffer(indexCount, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, vulkan) {
// Do nothing
}
void load(const Vertex *vertices, const Index *indices) const {
vertexBuffer.load(vertices);
indexBuffer.load(indices);
}
void draw(VkCommandBuffer commandBuffer) {
VkDeviceSize offset = 0;
vkCmdBindVertexBuffers(commandBuffer, 0, 1,
&vertexBuffer.remoteBuffer.buffer, &offset);
vkCmdBindIndexBuffer(commandBuffer, indexBuffer.remoteBuffer.buffer, 0,
INDEX_TYPE);
vkCmdDrawIndexed(commandBuffer,
static_cast<uint32_t>(indexBuffer.getItemCount()), 1,
0, 0, 0);
}
Vulkan &getVulkan() { return vertexBuffer.getVulkan(); }
const Vulkan &getVulkan() const { return vertexBuffer.getVulkan(); }
};
template <typename Vertex>
using IndexedBuffer = IndexedBufferBase<Vertex, uint16_t, VK_INDEX_TYPE_UINT16>;
} // namespace desktop
} // namespace progressia

View File

@ -0,0 +1,776 @@
#include "vulkan_common.h"
#include "vulkan_adapter.h"
#include "vulkan_frame.h"
#include "vulkan_pick_device.h"
#include "vulkan_pipeline.h"
#include "vulkan_render_pass.h"
#include "vulkan_swap_chain.h"
#include "vulkan_texture_descriptors.h"
#include "../../main/meta.h"
#include "glfw_mgmt_details.h"
namespace progressia {
namespace desktop {
/*
* Vulkan
*/
Vulkan::Vulkan(std::vector<const char *> instanceExtensions,
std::vector<const char *> deviceExtensions,
std::vector<const char *> validationLayers)
:
frames(MAX_FRAMES_IN_FLIGHT), isRenderingFrame(false),
lastStartedFrame(0) {
/*
* Create error handler
*/
errorHandler = std::make_unique<VulkanErrorHandler>(*this);
/*
* Create instance
*/
{
VkInstanceCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
// Set application data
using namespace progressia::main::meta;
VkApplicationInfo appInfo{};
appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
appInfo.pApplicationName = NAME;
appInfo.applicationVersion =
VK_MAKE_VERSION(VERSION.major, VERSION.minor, VERSION.patch);
appInfo.pEngineName = nullptr;
appInfo.engineVersion = 0;
appInfo.apiVersion = VK_API_VERSION_1_0;
createInfo.pApplicationInfo = &appInfo;
// Enable extensions
{
uint32_t extensionCount;
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount,
nullptr);
std::vector<VkExtensionProperties> available(extensionCount);
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount,
available.data());
CstrUtils::CstrHashSet toFind(instanceExtensions.cbegin(),
instanceExtensions.cend());
for (const auto &extensionProperties : available) {
toFind.erase(extensionProperties.extensionName);
}
if (!toFind.empty()) {
std::cout << "Could not locate following requested Vulkan "
"extensions:";
for (const auto &extension : toFind) {
std::cout << "\n\t- " << extension;
}
std::cout << std::endl;
// REPORT_ERROR
exit(1);
}
}
createInfo.enabledExtensionCount =
static_cast<uint32_t>(instanceExtensions.size());
createInfo.ppEnabledExtensionNames = instanceExtensions.data();
// Enable validation layers
{
uint32_t layerCount;
vkEnumerateInstanceLayerProperties(&layerCount, nullptr);
std::vector<VkLayerProperties> available(layerCount);
vkEnumerateInstanceLayerProperties(&layerCount, available.data());
CstrUtils::CstrHashSet toFind(validationLayers.cbegin(),
validationLayers.cend());
for (const auto &layerProperties : available) {
toFind.erase(layerProperties.layerName);
}
if (!toFind.empty()) {
std::cout << "Could not locate following requested Vulkan "
"validation layers:";
for (const auto &layer : toFind) {
std::cout << "\n\t- " << layer;
}
std::cout << std::endl;
// REPORT_ERROR
exit(1);
}
}
createInfo.enabledLayerCount =
static_cast<uint32_t>(validationLayers.size());
createInfo.ppEnabledLayerNames = validationLayers.data();
// Setup one-use debug listener if necessary
// cppcheck-suppress unreadVariable; bug in cppcheck <2.9
auto debugProbe = errorHandler->attachDebugProbe(createInfo);
// Create instance
handleVkResult("Could not create VkInstance",
vkCreateInstance(&createInfo, nullptr, &instance));
}
/*
* Setup debug
*/
errorHandler->onInstanceReady();
/*
* Create surface
*/
surface = std::make_unique<Surface>(*this);
/*
* Pick physical device
*/
{
uint32_t deviceCount = 0;
vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);
if (deviceCount == 0) {
std::cout << "No GPUs with Vulkan support found" << std::endl;
// REPORT_ERROR
exit(1);
}
std::vector<VkPhysicalDevice> devices(deviceCount);
vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());
std::vector<PhysicalDeviceData> choices;
for (const auto &device : devices) {
PhysicalDeviceData data = {};
data.device = device;
vkGetPhysicalDeviceProperties(device, &data.properties);
vkGetPhysicalDeviceFeatures(device, &data.features);
choices.push_back(data);
}
const auto &result =
pickPhysicalDevice(choices, *this, deviceExtensions);
physicalDevice = result.device;
}
/*
* Setup queues
*/
queues = std::make_unique<Queues>(physicalDevice, *this);
/*
* Create logical device
*/
{
VkDeviceCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
// Specify queues
// cppcheck-suppress unreadVariable; bug in cppcheck <2.9
auto queueRequests = queues->requestCreation(createInfo);
// Specify features
VkPhysicalDeviceFeatures deviceFeatures{};
createInfo.pEnabledFeatures = &deviceFeatures;
// Specify device extensions
createInfo.enabledExtensionCount =
static_cast<uint32_t>(deviceExtensions.size());
createInfo.ppEnabledExtensionNames = deviceExtensions.data();
// Provide a copy of instance validation layers
createInfo.enabledLayerCount =
static_cast<uint32_t>(validationLayers.size());
createInfo.ppEnabledLayerNames = validationLayers.data();
// Create logical device
handleVkResult(
"Could not create logical device",
vkCreateDevice(physicalDevice, &createInfo, nullptr, &device));
// Store queue handles
queues->storeHandles(device);
}
/*
* Create command pool
*/
commandPool =
std::make_unique<CommandPool>(*this, queues->getGraphicsQueue());
/*
* Create texture descriptor manager
*/
textureDescriptors = std::make_unique<TextureDescriptors>(*this);
/*
* Initialize adapter
*/
adapter = std::make_unique<Adapter>(*this);
/*
* Initialize swap chain
*/
swapChain = std::make_unique<SwapChain>(*this);
/*
* Create render pass
*/
renderPass = std::make_unique<RenderPass>(*this);
/*
* Create pipeline
*/
pipeline = std::make_unique<Pipeline>(*this);
/*
* Create swap chain
*/
swapChain->recreate();
/*
* Create frames
*/
for (auto &container : frames) {
container.emplace(*this);
}
currentFrame = 0;
gint = std::make_unique<progressia::main::GraphicsInterface>(this);
}
Vulkan::~Vulkan() {
gint.reset();
frames.clear();
swapChain.reset();
pipeline.reset();
renderPass.reset();
adapter.reset();
textureDescriptors.reset();
commandPool.reset();
vkDestroyDevice(device, nullptr);
surface.reset();
errorHandler.reset();
vkDestroyInstance(instance, nullptr);
}
VkInstance Vulkan::getInstance() const { return instance; }
VkPhysicalDevice Vulkan::getPhysicalDevice() const { return physicalDevice; }
VkDevice Vulkan::getDevice() const { return device; }
Surface &Vulkan::getSurface() { return *surface; }
const Surface &Vulkan::getSurface() const { return *surface; }
Queues &Vulkan::getQueues() { return *queues; }
const Queues &Vulkan::getQueues() const { return *queues; }
CommandPool &Vulkan::getCommandPool() { return *commandPool; }
const CommandPool &Vulkan::getCommandPool() const { return *commandPool; }
RenderPass &Vulkan::getRenderPass() { return *renderPass; }
const RenderPass &Vulkan::getRenderPass() const { return *renderPass; }
Pipeline &Vulkan::getPipeline() { return *pipeline; }
const Pipeline &Vulkan::getPipeline() const { return *pipeline; }
SwapChain &Vulkan::getSwapChain() { return *swapChain; }
const SwapChain &Vulkan::getSwapChain() const { return *swapChain; }
TextureDescriptors &Vulkan::getTextureDescriptors() {
return *textureDescriptors;
}
const TextureDescriptors &Vulkan::getTextureDescriptors() const {
return *textureDescriptors;
}
Adapter &Vulkan::getAdapter() { return *adapter; }
const Adapter &Vulkan::getAdapter() const { return *adapter; }
progressia::main::GraphicsInterface &Vulkan::getGint() { return *gint; }
const progressia::main::GraphicsInterface &Vulkan::getGint() const {
return *gint;
}
VkFormat Vulkan::findSupportedFormat(const std::vector<VkFormat> &candidates,
VkImageTiling tiling,
VkFormatFeatureFlags features) {
for (VkFormat format : candidates) {
VkFormatProperties props;
vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &props);
if (tiling == VK_IMAGE_TILING_LINEAR &&
(props.linearTilingFeatures & features) == features) {
return format;
} else if (tiling == VK_IMAGE_TILING_OPTIMAL &&
(props.optimalTilingFeatures & features) == features) {
return format;
}
}
std::cout << "Could not find a suitable format" << std::endl;
// REPORT_ERROR
exit(1);
}
uint32_t Vulkan::findMemoryType(uint32_t allowedByDevice,
VkMemoryPropertyFlags desiredProperties) {
VkPhysicalDeviceMemoryProperties memProperties;
vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties);
for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
if (((1 << i) & allowedByDevice) == 0) {
continue;
}
if ((memProperties.memoryTypes[i].propertyFlags & desiredProperties) !=
desiredProperties) {
continue;
}
return i;
}
std::cout << "Could not find suitable memory type" << std::endl;
// REPORT_ERROR
exit(1);
return -1;
}
void Vulkan::handleVkResult(const char *errorMessage, VkResult result) {
errorHandler->handleVkResult(errorMessage, result);
}
Frame *Vulkan::getCurrentFrame() {
if (isRenderingFrame) {
return &*frames.at(currentFrame);
}
return nullptr;
}
uint64_t Vulkan::getLastStartedFrame() { return lastStartedFrame; }
std::size_t Vulkan::getFrameInFlightIndex() { return currentFrame; }
bool Vulkan::startRender() {
if (currentFrame >= MAX_FRAMES_IN_FLIGHT - 1) {
currentFrame = 0;
} else {
currentFrame++;
}
bool shouldContinue = frames.at(currentFrame)->startRender();
if (!shouldContinue) {
return false;
}
isRenderingFrame = true;
lastStartedFrame++;
return true;
}
void Vulkan::endRender() {
gint->flush();
isRenderingFrame = false;
frames.at(currentFrame)->endRender();
}
void Vulkan::waitIdle() {
if (device != VK_NULL_HANDLE) {
vkDeviceWaitIdle(device);
}
}
/*
* VulkanErrorHandler
*/
VulkanErrorHandler::VulkanErrorHandler(Vulkan &vulkan) : vulkan(vulkan) {
// do nothing
}
VulkanErrorHandler::~VulkanErrorHandler() {
#ifdef VULKAN_ERROR_CHECKING
vulkan.callVoid("vkDestroyDebugUtilsMessengerEXT",
(VkDebugUtilsMessengerEXT)debugMessenger, nullptr);
#endif
}
#ifdef VULKAN_ERROR_CHECKING
namespace {
VKAPI_ATTR VkBool32 VKAPI_CALL
debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
VkDebugUtilsMessageTypeFlagsEXT messageType,
const VkDebugUtilsMessengerCallbackDataEXT *pCallbackData,
void *pUserData) {
if (messageSeverity < VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) {
return VK_FALSE;
}
[[maybe_unused]] auto &vk = *reinterpret_cast<const Vulkan *>(pUserData);
const char *severityStr =
messageSeverity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT
? "\x1B[1;91m\x1B[40mERROR\x1B[0m"
: messageSeverity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT
? "\x1B[1;93m\x1B[40mWARNING\x1B[0m"
: messageSeverity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT
? "info"
: "verbose";
const char *typeStr;
switch (messageType) {
case VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT:
typeStr = "general";
break;
case VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT:
typeStr = "violation";
break;
default:
typeStr = "performance";
break;
}
std::cout << "[Vulkan] [" << typeStr << " / " << severityStr << "]\t"
<< pCallbackData->pMessage << std::endl;
// REPORT_ERROR
return VK_FALSE;
}
void populateDebugMessengerCreateInfo(
VkDebugUtilsMessengerCreateInfoEXT &createInfo, Vulkan &vulkan) {
createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
createInfo.messageSeverity =
VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
createInfo.pfnUserCallback = debugCallback;
createInfo.pUserData = &vulkan;
}
} // namespace
#endif
std::unique_ptr<VkDebugUtilsMessengerCreateInfoEXT>
VulkanErrorHandler::attachDebugProbe(VkInstanceCreateInfo &createInfo) {
#ifdef VULKAN_ERROR_CHECKING
std::unique_ptr result =
std::make_unique<VkDebugUtilsMessengerCreateInfoEXT>();
populateDebugMessengerCreateInfo(*result, vulkan);
result->pNext = createInfo.pNext;
createInfo.pNext = &*result;
return result;
#else
(void)createInfo;
return std::unique_ptr<VkDebugUtilsMessengerCreateInfoEXT>();
#endif
}
void VulkanErrorHandler::onInstanceReady() {
#ifdef VULKAN_ERROR_CHECKING
std::cout << "Registering debug callback" << std::endl;
VkDebugUtilsMessengerCreateInfoEXT createInfo{};
populateDebugMessengerCreateInfo(createInfo, vulkan);
handleVkResult("Could not register debug messanger",
vulkan.call("vkCreateDebugUtilsMessengerEXT", &createInfo,
nullptr, &debugMessenger));
#endif
}
void VulkanErrorHandler::handleVkResult(const char *errorMessage,
VkResult result) {
if (result == VK_SUCCESS) {
return;
}
std::cout << "Vulkan error (" << result << "): " << errorMessage
<< std::endl;
// REPORT_ERROR
exit(1);
}
/*
* Surface
*/
Surface::Surface(Vulkan &vulkan) : vulkan(vulkan) {
vulkan.handleVkResult("Could not create window surface (what?)",
glfwCreateWindowSurface(vulkan.getInstance(),
getGLFWWindowHandle(),
nullptr, &vk));
}
Surface::~Surface() { vkDestroySurfaceKHR(vulkan.getInstance(), vk, nullptr); }
VkSurfaceKHR Surface::getVk() { return vk; }
/*
* Queue
*/
Queue::Queue(Test test) : test(test) {
// do nothing
}
bool Queue::isSuitable(VkPhysicalDevice physicalDevice, uint32_t familyIndex,
Vulkan &vulkan,
const VkQueueFamilyProperties &properties) const {
return test(physicalDevice, familyIndex, vulkan, properties);
}
VkQueue Queue::getVk() const { return vk; }
uint32_t Queue::getFamilyIndex() const { return *familyIndex; }
void Queue::waitIdle() const { vkQueueWaitIdle(vk); }
/*
* Queues
*/
namespace {
bool graphicsQueueTest(VkPhysicalDevice, uint32_t, Vulkan &,
const VkQueueFamilyProperties &properties) {
return properties.queueFlags & VK_QUEUE_GRAPHICS_BIT;
}
bool presentQueueTest(VkPhysicalDevice physicalDevice, uint32_t familyIndex,
Vulkan &vulkan, const VkQueueFamilyProperties &) {
VkBool32 presentSupport = false;
vkGetPhysicalDeviceSurfaceSupportKHR(physicalDevice, familyIndex,
vulkan.getSurface().getVk(),
&presentSupport);
return presentSupport;
}
} // namespace
Queues::Queues(VkPhysicalDevice physicalDevice, Vulkan &vulkan)
: graphicsQueue(graphicsQueueTest), presentQueue(presentQueueTest) {
uint32_t queueFamilyCount = 0;
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount,
nullptr);
std::vector<VkQueueFamilyProperties> properties(queueFamilyCount);
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount,
properties.data());
for (std::size_t index = 0; index < queueFamilyCount; index++) {
for (auto queue : {&graphicsQueue, &presentQueue}) {
if (!queue->isSuitable(physicalDevice, index, vulkan,
properties[index])) {
continue;
}
queue->familyIndex = index;
}
if (isComplete()) {
break;
}
}
}
Queues::~Queues() {
// do nothing
}
void Queues::storeHandles(VkDevice device) {
for (auto queue : {&graphicsQueue, &presentQueue}) {
vkGetDeviceQueue(device, queue->getFamilyIndex(), 0, &queue->vk);
}
}
std::unique_ptr<Queues::CreationRequest>
Queues::requestCreation(VkDeviceCreateInfo &createInfo) const {
std::unique_ptr result = std::make_unique<CreationRequest>();
result->priority = 1.0f;
std::unordered_set<uint32_t> uniqueQueues;
for (const auto *queue : {&graphicsQueue, &presentQueue}) {
uniqueQueues.insert(queue->getFamilyIndex());
}
for (const auto &index : uniqueQueues) {
VkDeviceQueueCreateInfo queueCreateInfo{};
queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queueCreateInfo.queueFamilyIndex = index;
queueCreateInfo.pQueuePriorities = &result->priority;
queueCreateInfo.queueCount = 1;
result->queueCreateInfos.push_back(queueCreateInfo);
}
createInfo.pQueueCreateInfos = result->queueCreateInfos.data();
createInfo.queueCreateInfoCount =
static_cast<uint32_t>(result->queueCreateInfos.size());
return result;
}
bool Queues::isComplete() const {
for (auto queue : {&graphicsQueue, &presentQueue}) {
if (!queue->familyIndex.has_value()) {
return false;
}
}
return true;
}
const Queue &Queues::getGraphicsQueue() const { return graphicsQueue; }
const Queue &Queues::getPresentQueue() const { return presentQueue; }
/*
* CommandPool
*/
CommandPool::CommandPool(Vulkan &vulkan, const Queue &queue)
: queue(queue), vulkan(vulkan) {
VkCommandPoolCreateInfo poolInfo{};
poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
poolInfo.queueFamilyIndex = queue.getFamilyIndex();
vulkan.handleVkResult(
"Could not create CommandPool",
vkCreateCommandPool(vulkan.getDevice(), &poolInfo, nullptr, &pool));
}
CommandPool::~CommandPool() {
vkDestroyCommandPool(vulkan.getDevice(), pool, nullptr);
}
VkCommandBuffer CommandPool::allocateCommandBuffer() {
VkCommandBufferAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
allocInfo.commandPool = pool;
allocInfo.commandBufferCount = 1;
VkCommandBuffer commandBuffer;
vkAllocateCommandBuffers(vulkan.getDevice(), &allocInfo, &commandBuffer);
return commandBuffer;
}
void CommandPool::beginCommandBuffer(VkCommandBuffer commandBuffer,
VkCommandBufferUsageFlags usage) {
VkCommandBufferBeginInfo beginInfo{};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
beginInfo.flags = usage;
vkBeginCommandBuffer(commandBuffer, &beginInfo);
}
VkCommandBuffer CommandPool::beginSingleUse() {
VkCommandBuffer buffer = allocateCommandBuffer();
beginCommandBuffer(buffer, VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT);
return buffer;
}
void CommandPool::runSingleUse(VkCommandBuffer buffer, bool waitIdle) {
vkEndCommandBuffer(buffer);
submitMultiUse(buffer, false);
if (waitIdle) {
queue.waitIdle();
}
freeMultiUse(buffer);
}
VkCommandBuffer CommandPool::allocateMultiUse() {
return allocateCommandBuffer();
}
VkCommandBuffer CommandPool::beginMultiUse() {
VkCommandBuffer buffer = allocateMultiUse();
beginCommandBuffer(buffer, 0);
return buffer;
}
void CommandPool::submitMultiUse(VkCommandBuffer buffer, bool waitIdle) {
VkSubmitInfo submitInfo{};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &buffer;
vkQueueSubmit(queue.getVk(), 1, &submitInfo, VK_NULL_HANDLE);
if (waitIdle) {
queue.waitIdle();
}
}
void CommandPool::freeMultiUse(VkCommandBuffer buffer) {
vkFreeCommandBuffers(vulkan.getDevice(), pool, 1, &buffer);
}
} // namespace desktop
} // namespace progressia

View File

@ -0,0 +1,289 @@
#pragma once
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>
#include <cstring>
#include <functional>
#include <iostream>
#include <memory>
#include <optional>
#include <unordered_set>
#include <vector>
#define GLM_FORCE_RADIANS
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
#include <glm/mat4x4.hpp>
#include <glm/vec2.hpp>
#include <glm/vec3.hpp>
#include <glm/vec4.hpp>
#include <boost/core/noncopyable.hpp>
#include "../../main/rendering/graphics_interface.h"
namespace progressia {
namespace desktop {
namespace CstrUtils {
struct CstrHash {
std::size_t operator()(const char *s) const noexcept {
std::size_t acc = 0;
while (*s != 0) {
acc = acc * 31 + *s;
s++;
}
return acc;
}
};
struct CstrEqual {
bool operator()(const char *lhs, const char *rhs) const noexcept {
return strcmp(lhs, rhs) == 0;
}
};
struct CstrCompare {
bool operator()(const char *lhs, const char *rhs) const noexcept {
return strcmp(lhs, rhs) < 0;
}
};
using CstrHashSet = std::unordered_set<const char *, CstrHash, CstrEqual>;
} // namespace CstrUtils
class VkObjectWrapper : private boost::noncopyable {
// empty
};
constexpr std::size_t MAX_FRAMES_IN_FLIGHT = 2;
class VulkanErrorHandler;
class Surface;
class Queue;
class Queues;
class CommandPool;
class RenderPass;
class Pipeline;
class SwapChain;
class TextureDescriptors;
class Adapter;
class Frame;
class Vulkan : public VkObjectWrapper {
private:
VkInstance instance = VK_NULL_HANDLE;
VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;
VkDevice device = VK_NULL_HANDLE;
std::unique_ptr<VulkanErrorHandler> errorHandler;
std::unique_ptr<Surface> surface;
std::unique_ptr<Queues> queues;
std::unique_ptr<CommandPool> commandPool;
std::unique_ptr<RenderPass> renderPass;
std::unique_ptr<Pipeline> pipeline;
std::unique_ptr<SwapChain> swapChain;
std::unique_ptr<TextureDescriptors> textureDescriptors;
std::unique_ptr<Adapter> adapter;
std::unique_ptr<progressia::main::GraphicsInterface> gint;
std::vector<std::optional<Frame>> frames;
std::size_t currentFrame;
bool isRenderingFrame;
uint64_t lastStartedFrame;
public:
Vulkan(std::vector<const char *> instanceExtensions,
std::vector<const char *> deviceExtensions,
std::vector<const char *> validationLayers);
~Vulkan();
VkInstance getInstance() const;
VkPhysicalDevice getPhysicalDevice() const;
VkDevice getDevice() const;
Surface &getSurface();
const Surface &getSurface() const;
Queues &getQueues();
const Queues &getQueues() const;
SwapChain &getSwapChain();
const SwapChain &getSwapChain() const;
CommandPool &getCommandPool();
const CommandPool &getCommandPool() const;
RenderPass &getRenderPass();
const RenderPass &getRenderPass() const;
Pipeline &getPipeline();
const Pipeline &getPipeline() const;
TextureDescriptors &getTextureDescriptors();
const TextureDescriptors &getTextureDescriptors() const;
Adapter &getAdapter();
const Adapter &getAdapter() const;
Frame *getCurrentFrame();
const Frame *getCurrentFrame() const;
progressia::main::GraphicsInterface &getGint();
const progressia::main::GraphicsInterface &getGint() const;
/*
* Returns false when the frame should be skipped
*/
bool startRender();
void endRender();
uint64_t getLastStartedFrame();
std::size_t getFrameInFlightIndex();
void waitIdle();
VkFormat findSupportedFormat(const std::vector<VkFormat> &, VkImageTiling,
VkFormatFeatureFlags);
uint32_t findMemoryType(uint32_t allowedByDevice,
VkMemoryPropertyFlags desiredProperties);
template <typename... Args>
VkResult call(const char *functionName, Args &&...args) {
using FunctionSignature = VkResult(VkInstance, Args...);
auto func = reinterpret_cast<FunctionSignature *>(
vkGetInstanceProcAddr(instance, functionName));
if (func != nullptr) {
return func(instance, std::forward<Args>(args)...);
} else {
std::cout << "[Vulkan] [dynVkCall / VkResult]\tFunction not found "
"for name \""
<< functionName << "\"" << std::endl;
// REPORT_ERROR
return VK_ERROR_EXTENSION_NOT_PRESENT;
}
}
template <typename... Args>
VkResult callVoid(const char *functionName, Args &&...args) {
using FunctionSignature = void(VkInstance, Args...);
auto func = reinterpret_cast<FunctionSignature *>(
vkGetInstanceProcAddr(instance, functionName));
if (func != nullptr) {
func(instance, std::forward<Args>(args)...);
return VK_SUCCESS;
} else {
std::cout
<< "[Vulkan] [dynVkCall / void]\tFunction not found for name \""
<< functionName << "\"" << std::endl;
// REPORT_ERROR
return VK_ERROR_EXTENSION_NOT_PRESENT;
}
}
void handleVkResult(const char *errorMessage, VkResult);
};
class VulkanErrorHandler : public VkObjectWrapper {
private:
VkDebugUtilsMessengerEXT debugMessenger;
Vulkan &vulkan;
public:
VulkanErrorHandler(Vulkan &);
std::unique_ptr<VkDebugUtilsMessengerCreateInfoEXT>
attachDebugProbe(VkInstanceCreateInfo &);
void onInstanceReady();
~VulkanErrorHandler();
void handleVkResult(const char *errorMessage, VkResult result);
};
class Surface : public VkObjectWrapper {
private:
VkSurfaceKHR vk;
Vulkan &vulkan;
public:
Surface(Vulkan &);
~Surface();
VkSurfaceKHR getVk();
};
class Queue {
private:
using Test = std::function<bool(VkPhysicalDevice, uint32_t, Vulkan &,
const VkQueueFamilyProperties &)>;
Test test;
std::optional<uint32_t> familyIndex;
VkQueue vk;
friend class Queues;
Queue(Test);
public:
bool isSuitable(VkPhysicalDevice, uint32_t familyIndex, Vulkan &,
const VkQueueFamilyProperties &) const;
VkQueue getVk() const;
uint32_t getFamilyIndex() const;
void waitIdle() const;
};
class Queues {
private:
Queue graphicsQueue;
Queue presentQueue;
public:
Queues(VkPhysicalDevice physicalDevice, Vulkan &vulkan);
~Queues();
// cppcheck-suppress functionConst; this method modifies the Queue fields
void storeHandles(VkDevice device);
bool isComplete() const;
struct CreationRequest {
float priority;
std::vector<VkDeviceQueueCreateInfo> queueCreateInfos;
};
std::unique_ptr<CreationRequest>
requestCreation(VkDeviceCreateInfo &) const;
const Queue &getGraphicsQueue() const;
const Queue &getPresentQueue() const;
};
class CommandPool : public VkObjectWrapper {
private:
VkCommandPool pool;
const Queue &queue;
Vulkan &vulkan;
VkCommandBuffer allocateCommandBuffer();
void beginCommandBuffer(VkCommandBuffer commandBuffer,
VkCommandBufferUsageFlags usage);
public:
CommandPool(Vulkan &, const Queue &);
~CommandPool();
VkCommandBuffer beginSingleUse();
void runSingleUse(VkCommandBuffer, bool waitIdle = false);
VkCommandBuffer allocateMultiUse();
VkCommandBuffer beginMultiUse();
void submitMultiUse(VkCommandBuffer, bool waitIdle = false);
void freeMultiUse(VkCommandBuffer);
};
} // namespace desktop
} // namespace progressia

View File

@ -0,0 +1,19 @@
#include "vulkan_descriptor_set.h"
namespace progressia {
namespace desktop {
DescriptorSetInterface::DescriptorSetInterface(uint32_t setNumber,
Vulkan &vulkan)
: setNumber(setNumber), vulkan(vulkan) {}
VkDescriptorSetLayout DescriptorSetInterface::getLayout() const {
return layout;
}
uint32_t DescriptorSetInterface::getSetNumber() const { return setNumber; }
Vulkan &DescriptorSetInterface::getVulkan() { return vulkan; }
} // namespace desktop
} // namespace progressia

View File

@ -0,0 +1,23 @@
#pragma once
#include "vulkan_common.h"
namespace progressia {
namespace desktop {
class DescriptorSetInterface : public VkObjectWrapper {
protected:
VkDescriptorSetLayout layout;
uint32_t setNumber;
Vulkan &vulkan;
DescriptorSetInterface(uint32_t setNumber, Vulkan &);
public:
VkDescriptorSetLayout getLayout() const;
uint32_t getSetNumber() const;
Vulkan &getVulkan();
};
} // namespace desktop
} // namespace progressia

View File

@ -0,0 +1,171 @@
#include "vulkan_frame.h"
#include <limits>
#include "vulkan_adapter.h"
#include "vulkan_common.h"
#include "vulkan_pipeline.h"
#include "vulkan_render_pass.h"
#include "vulkan_swap_chain.h"
namespace progressia {
namespace desktop {
Frame::Frame(Vulkan &vulkan)
: vulkan(vulkan),
commandBuffer(vulkan.getCommandPool().allocateMultiUse()) {
VkSemaphoreCreateInfo semaphoreInfo{};
semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
VkFenceCreateInfo fenceInfo{};
fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
vulkan.handleVkResult("Could not create imageAvailableSemaphore",
vkCreateSemaphore(vulkan.getDevice(), &semaphoreInfo,
nullptr, &imageAvailableSemaphore));
vulkan.handleVkResult("Could not create renderFinishedSemaphore",
vkCreateSemaphore(vulkan.getDevice(), &semaphoreInfo,
nullptr, &renderFinishedSemaphore));
vulkan.handleVkResult(
"Could not create inFlightFence",
vkCreateFence(vulkan.getDevice(), &fenceInfo, nullptr, &inFlightFence));
for (const auto &attachment : vulkan.getAdapter().getAttachments()) {
clearValues.push_back(attachment.clearValue);
}
}
Frame::~Frame() {
vulkan.waitIdle();
vkDestroySemaphore(vulkan.getDevice(), imageAvailableSemaphore, nullptr);
vkDestroySemaphore(vulkan.getDevice(), renderFinishedSemaphore, nullptr);
vkDestroyFence(vulkan.getDevice(), inFlightFence, nullptr);
}
bool Frame::startRender() {
// Wait for frame
vkWaitForFences(vulkan.getDevice(), 1, &inFlightFence, VK_TRUE, UINT64_MAX);
// Acquire an image
VkResult result = vkAcquireNextImageKHR(
vulkan.getDevice(), vulkan.getSwapChain().getVk(), UINT64_MAX,
imageAvailableSemaphore, VK_NULL_HANDLE, &*imageIndexInFlight);
switch (result) {
case VK_ERROR_OUT_OF_DATE_KHR:
vulkan.getSwapChain().recreate();
// Skip this frame, try again later
return false;
case VK_SUBOPTIMAL_KHR:
// Continue as normal
break;
default:
vulkan.handleVkResult("Could not acquire next image", result);
break;
}
vulkan.getAdapter().onPreFrame();
// Reset command buffer
vkResetCommandBuffer(commandBuffer, 0);
// Setup command buffer
VkCommandBufferBeginInfo beginInfo{};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
vulkan.handleVkResult("Could not begin recording command buffer",
vkBeginCommandBuffer(commandBuffer, &beginInfo));
auto extent = vulkan.getSwapChain().getExtent();
VkRenderPassBeginInfo renderPassInfo{};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
renderPassInfo.renderPass = vulkan.getRenderPass().getVk();
renderPassInfo.framebuffer =
vulkan.getSwapChain().getFramebuffer(*imageIndexInFlight);
renderPassInfo.renderArea.offset = {0, 0};
renderPassInfo.renderArea.extent = extent;
renderPassInfo.clearValueCount = static_cast<uint32_t>(clearValues.size());
renderPassInfo.pClearValues = clearValues.data();
vkCmdBeginRenderPass(commandBuffer, &renderPassInfo,
VK_SUBPASS_CONTENTS_INLINE);
vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
vulkan.getPipeline().getVk());
VkViewport viewport{};
viewport.x = 0.0f;
viewport.y = 0.0f;
viewport.width = (float)extent.width;
viewport.height = (float)extent.height;
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
vkCmdSetViewport(commandBuffer, 0, 1, &viewport);
VkRect2D scissor{};
scissor.offset = {0, 0};
scissor.extent = extent;
vkCmdSetScissor(commandBuffer, 0, 1, &scissor);
return true;
}
void Frame::endRender() {
// End command buffer
vkCmdEndRenderPass(commandBuffer);
vulkan.handleVkResult("Could not end recording command buffer",
vkEndCommandBuffer(commandBuffer));
// Submit command buffer
VkSubmitInfo submitInfo{};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
VkSemaphore waitSemaphores[] = {imageAvailableSemaphore};
VkPipelineStageFlags waitStages[] = {
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};
submitInfo.waitSemaphoreCount = 1;
submitInfo.pWaitSemaphores = waitSemaphores;
submitInfo.pWaitDstStageMask = waitStages;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &commandBuffer;
VkSemaphore signalSemaphores[] = {renderFinishedSemaphore};
submitInfo.signalSemaphoreCount = 1;
submitInfo.pSignalSemaphores = signalSemaphores;
vkResetFences(vulkan.getDevice(), 1, &inFlightFence);
vulkan.handleVkResult(
"Could not submit draw command buffer",
vkQueueSubmit(vulkan.getQueues().getGraphicsQueue().getVk(), 1,
&submitInfo, inFlightFence));
// Present result
VkPresentInfoKHR presentInfo{};
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
presentInfo.waitSemaphoreCount = 1;
presentInfo.pWaitSemaphores = signalSemaphores;
VkSwapchainKHR swapChains[] = {vulkan.getSwapChain().getVk()};
presentInfo.swapchainCount = 1;
presentInfo.pSwapchains = swapChains;
presentInfo.pImageIndices = &*imageIndexInFlight;
VkResult result = vkQueuePresentKHR(
vulkan.getQueues().getPresentQueue().getVk(), &presentInfo);
if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) {
// We're at the end of this frame already, no need to skip
vulkan.getSwapChain().recreate();
} else {
vulkan.handleVkResult("Could not present", result);
}
}
VkCommandBuffer Frame::getCommandBuffer() { return commandBuffer; }
} // namespace desktop
} // namespace progressia

View File

@ -0,0 +1,36 @@
#pragma once
#include "vulkan_common.h"
namespace progressia {
namespace desktop {
class Frame : public VkObjectWrapper {
private:
Vulkan &vulkan;
VkCommandBuffer commandBuffer;
VkSemaphore imageAvailableSemaphore;
VkSemaphore renderFinishedSemaphore;
VkFence inFlightFence;
std::vector<VkClearValue> clearValues;
std::optional<uint32_t> imageIndexInFlight;
public:
Frame(Vulkan &vulkan);
~Frame();
/*
* Returns false when the frame should be skipped
*/
bool startRender();
void endRender();
VkCommandBuffer getCommandBuffer();
};
} // namespace desktop
} // namespace progressia

View File

@ -0,0 +1,247 @@
#include "vulkan_image.h"
#include <cstring>
#include <iostream>
#include "vulkan_buffer.h"
#include "vulkan_common.h"
#include "vulkan_frame.h"
#include "vulkan_pipeline.h"
#include "vulkan_texture_descriptors.h"
namespace progressia {
namespace desktop {
/*
* Image
*/
Image::Image(VkImage vk, VkImageView view, VkFormat format)
: vk(vk), view(view), format(format) {
// do nothing
}
Image::~Image() {
// do nothing
}
/*
* ManagedImage
*/
ManagedImage::ManagedImage(std::size_t width, std::size_t height,
VkFormat format, VkImageAspectFlags aspect,
VkImageUsageFlags usage, Vulkan &vulkan)
:
Image(VK_NULL_HANDLE, VK_NULL_HANDLE, format), vulkan(vulkan),
state{VK_IMAGE_LAYOUT_UNDEFINED, 0, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT} {
/*
* Create VkImage
*/
VkImageCreateInfo imageInfo{};
imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
imageInfo.imageType = VK_IMAGE_TYPE_2D;
imageInfo.extent.width = static_cast<uint32_t>(width);
imageInfo.extent.height = static_cast<uint32_t>(height);
imageInfo.extent.depth = 1;
imageInfo.mipLevels = 1;
imageInfo.arrayLayers = 1;
imageInfo.format = format;
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
imageInfo.usage = usage;
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
imageInfo.flags = 0; // Optional
vulkan.handleVkResult(
"Could not create an image",
vkCreateImage(vulkan.getDevice(), &imageInfo, nullptr, &vk));
/*
* Allocate memory
*/
VkMemoryRequirements memRequirements;
vkGetImageMemoryRequirements(vulkan.getDevice(), vk, &memRequirements);
VkMemoryAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
allocInfo.allocationSize = memRequirements.size;
allocInfo.memoryTypeIndex = vulkan.findMemoryType(
memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
vulkan.handleVkResult(
"Could not allocate memory for image",
vkAllocateMemory(vulkan.getDevice(), &allocInfo, nullptr, &memory));
/*
* Bind memory to image
*/
vkBindImageMemory(vulkan.getDevice(), vk, memory, 0);
/*
* Create image view
*/
VkImageViewCreateInfo viewInfo{};
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
viewInfo.image = vk;
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
viewInfo.format = format;
viewInfo.subresourceRange.aspectMask = aspect;
viewInfo.subresourceRange.baseMipLevel = 0;
viewInfo.subresourceRange.levelCount = 1;
viewInfo.subresourceRange.baseArrayLayer = 0;
viewInfo.subresourceRange.layerCount = 1;
vulkan.handleVkResult(
"Could not create image view",
vkCreateImageView(vulkan.getDevice(), &viewInfo, nullptr, &view));
}
ManagedImage::~ManagedImage() {
vkDestroyImageView(vulkan.getDevice(), view, nullptr);
vkDestroyImage(vulkan.getDevice(), vk, nullptr);
vkFreeMemory(vulkan.getDevice(), memory, nullptr);
}
void ManagedImage::transition(State newState) {
VkCommandBuffer commandBuffer = vulkan.getCommandPool().beginSingleUse();
VkImageMemoryBarrier barrier{};
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
barrier.oldLayout = state.layout;
barrier.newLayout = newState.layout;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.image = vk;
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
barrier.subresourceRange.baseMipLevel = 0;
barrier.subresourceRange.levelCount = 1;
barrier.subresourceRange.baseArrayLayer = 0;
barrier.subresourceRange.layerCount = 1;
barrier.srcAccessMask = state.accessMask;
barrier.dstAccessMask = newState.accessMask;
vkCmdPipelineBarrier(commandBuffer, state.stageMask, newState.stageMask, 0,
0, nullptr, 0, nullptr, 1, &barrier);
vulkan.getCommandPool().runSingleUse(commandBuffer, true);
state = newState;
}
/*
* Texture
*/
Texture::Texture(const progressia::main::Image &src, Vulkan &vulkan)
:
ManagedImage(src.width, src.height, VK_FORMAT_R8G8B8A8_SRGB,
VK_IMAGE_ASPECT_COLOR_BIT,
VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
vulkan) {
/*
* Create a staging buffer
*/
Buffer<progressia::main::Image::Byte> stagingBuffer(
src.getSize(), VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
vulkan);
/*
* Transfer pixels to staging buffer
*/
void *dst = stagingBuffer.map();
memcpy(dst, src.getData(), src.getSize());
stagingBuffer.unmap();
/*
* Transfer pixels from staging buffer to image
*/
transition({VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT});
VkCommandBuffer commandBuffer = vulkan.getCommandPool().beginSingleUse();
VkBufferImageCopy region{};
region.bufferOffset = 0;
region.bufferRowLength = 0;
region.bufferImageHeight = 0;
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.imageSubresource.mipLevel = 0;
region.imageSubresource.baseArrayLayer = 0;
region.imageSubresource.layerCount = 1;
region.imageOffset = {0, 0, 0};
region.imageExtent = {static_cast<uint32_t>(src.width),
static_cast<uint32_t>(src.height), 1};
vkCmdCopyBufferToImage(commandBuffer, stagingBuffer.buffer, vk,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
vulkan.getCommandPool().runSingleUse(commandBuffer, true);
transition({VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
VK_ACCESS_SHADER_READ_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT});
/*
* Create a sampler
*/
VkSamplerCreateInfo samplerInfo{};
samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
samplerInfo.magFilter = VK_FILTER_NEAREST;
samplerInfo.minFilter = VK_FILTER_NEAREST;
samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
samplerInfo.anisotropyEnable = VK_FALSE;
samplerInfo.maxAnisotropy = 0;
samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
samplerInfo.unnormalizedCoordinates = VK_FALSE;
samplerInfo.compareEnable = VK_FALSE;
samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
samplerInfo.mipLodBias = 0.0f;
samplerInfo.minLod = 0.0f;
samplerInfo.maxLod = 0.0f;
vulkan.handleVkResult(
"Could not create texture sampler",
vkCreateSampler(vulkan.getDevice(), &samplerInfo, nullptr, &sampler));
/*
* Create descriptor set
*/
descriptorSet = vulkan.getTextureDescriptors().addTexture(view, sampler);
}
Texture::~Texture() {
vkDestroySampler(vulkan.getDevice(), sampler, nullptr);
// TODO free descriptorSet
}
void Texture::bind() {
// REPORT_ERROR if getCurrentFrame() == nullptr
auto commandBuffer = vulkan.getCurrentFrame()->getCommandBuffer();
auto pipelineLayout = vulkan.getPipeline().getLayout();
vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
pipelineLayout,
vulkan.getTextureDescriptors().getSetNumber(), 1,
&descriptorSet, 0, nullptr);
}
} // namespace desktop
} // namespace progressia

View File

@ -0,0 +1,60 @@
#pragma once
#include <boost/core/noncopyable.hpp>
#include <vector>
#include "vulkan_buffer.h"
#include "vulkan_common.h"
#include "../../main/rendering/image.h"
namespace progressia {
namespace desktop {
class Image : public VkObjectWrapper {
public:
VkImage vk;
VkImageView view;
VkFormat format;
Image(VkImage, VkImageView, VkFormat);
virtual ~Image();
};
class ManagedImage : public Image {
public:
VkDeviceMemory memory;
Vulkan &vulkan;
struct State {
VkImageLayout layout;
VkAccessFlags accessMask;
VkPipelineStageFlags stageMask;
};
private:
State state;
public:
ManagedImage(std::size_t width, std::size_t height, VkFormat,
VkImageAspectFlags, VkImageUsageFlags, Vulkan &);
~ManagedImage();
void transition(State);
};
class Texture : public ManagedImage {
public:
VkSampler sampler;
VkDescriptorSet descriptorSet;
Texture(const progressia::main::Image &, Vulkan &vulkan);
~Texture();
void bind();
};
} // namespace desktop
} // namespace progressia

View File

@ -0,0 +1,68 @@
#include "vulkan_mgmt.h"
#include "vulkan_common.h"
#include "vulkan_swap_chain.h"
namespace progressia {
namespace desktop {
Vulkan *vulkan;
void initializeVulkan() {
std::cout << "Vulkan initializing" << std::endl;
// Instance extensions
std::vector<const char *> instanceExtensions;
{
uint32_t glfwExtensionCount;
const char **glfwExtensions;
glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
for (std::size_t i = 0; i < glfwExtensionCount; i++) {
instanceExtensions.push_back(glfwExtensions[i]);
}
#ifdef VULKAN_ERROR_CHECKING
instanceExtensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
#endif
}
// Device extensions
std::vector<const char *> deviceExtensions{VK_KHR_SWAPCHAIN_EXTENSION_NAME};
// Validation layers
std::vector<const char *> validationLayers{
#ifdef VULKAN_ERROR_CHECKING
"VK_LAYER_KHRONOS_validation"
#endif
};
vulkan = new Vulkan(instanceExtensions, deviceExtensions, validationLayers);
std::cout << "Vulkan initialized" << std::endl;
}
Vulkan *getVulkan() { return vulkan; }
bool startRender() { return vulkan->startRender(); }
void endRender() { return vulkan->endRender(); }
void resizeVulkanSurface() { vulkan->getSwapChain().recreate(); }
void shutdownVulkan() {
std::cout << "Vulkan terminating" << std::endl;
if (vulkan != nullptr) {
delete vulkan;
vulkan = nullptr;
}
std::cout << "Vulkan terminated" << std::endl;
}
} // namespace desktop
} // namespace progressia

View File

@ -0,0 +1,23 @@
#pragma once
#include "vulkan_common.h"
namespace progressia {
namespace desktop {
void initializeVulkan();
Vulkan *getVulkan();
void resizeVulkanSurface();
/*
* Returns false when the frame should be skipped
*/
bool startRender();
void endRender();
void shutdownVulkan();
} // namespace desktop
} // namespace progressia

View File

@ -0,0 +1,98 @@
#include "vulkan_pick_device.h"
#include "vulkan_swap_chain.h"
namespace progressia {
namespace desktop {
namespace {
bool checkDeviceExtensions(VkPhysicalDevice device,
const std::vector<const char *> &deviceExtensions) {
CstrUtils::CstrHashSet toFind(deviceExtensions.cbegin(),
deviceExtensions.cend());
uint32_t extensionCount;
vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount,
nullptr);
std::vector<VkExtensionProperties> available(extensionCount);
vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount,
available.data());
for (const auto &extension : available) {
toFind.erase(extension.extensionName);
}
return toFind.empty();
}
bool isDeviceSuitable(const PhysicalDeviceData &data, Vulkan &vulkan,
const std::vector<const char *> &deviceExtensions) {
if (!Queues(data.device, vulkan).isComplete()) {
return false;
}
if (!checkDeviceExtensions(data.device, deviceExtensions)) {
return false;
}
// Check requires that the swap chain extension is present
if (!SwapChain::isSwapChainSuitable(
SwapChain::querySwapChainSupport(data.device, vulkan))) {
return false;
}
return true;
}
} // namespace
const PhysicalDeviceData &
pickPhysicalDevice(std::vector<PhysicalDeviceData> &choices, Vulkan &vulkan,
const std::vector<const char *> &deviceExtensions) {
// Remove unsuitable devices
auto it = std::remove_if(choices.begin(), choices.end(), [&](auto x) {
return !isDeviceSuitable(x, vulkan, deviceExtensions);
});
choices.erase(it, choices.end());
if (choices.empty()) {
std::cout << "No suitable GPUs found" << std::endl;
// REPORT_ERROR
exit(1);
}
const auto *pick = &choices.front();
std::cout << "Suitable devices:";
for (const auto &option : choices) {
struct {
const char *description;
int value;
} opinions[] = {{"<unknown>", 0},
{"Integrated GPU", 0},
{"Discrete GPU", +1},
{"Virtual GPU", +1},
{"CPU", -1}};
auto type = option.properties.deviceType;
std::cout << "\n\t- " << opinions[type].description << " "
<< option.properties.deviceName;
if (opinions[pick->properties.deviceType].value <
opinions[type].value) {
pick = &option;
}
}
std::cout << std::endl;
std::cout << "Picked device " << pick->properties.deviceName << std::endl;
return *pick;
}
} // namespace desktop
} // namespace progressia

View File

@ -0,0 +1,21 @@
#pragma once
#include "vulkan_common.h"
#include <vector>
namespace progressia {
namespace desktop {
struct PhysicalDeviceData {
VkPhysicalDevice device;
VkPhysicalDeviceProperties properties;
VkPhysicalDeviceFeatures features;
};
const PhysicalDeviceData &
pickPhysicalDevice(std::vector<PhysicalDeviceData> &, Vulkan &,
const std::vector<const char *> &deviceExtensions);
} // namespace desktop
} // namespace progressia

View File

@ -0,0 +1,223 @@
#include "vulkan_pipeline.h"
#include "vulkan_adapter.h"
#include "vulkan_common.h"
#include "vulkan_descriptor_set.h"
#include "vulkan_render_pass.h"
namespace progressia {
namespace desktop {
Pipeline::Pipeline(Vulkan &vulkan) : vulkan(vulkan) {
auto &adapter = vulkan.getAdapter();
// Shaders
auto vertShader = createShaderModule(adapter.loadVertexShader());
auto fragShader = createShaderModule(adapter.loadFragmentShader());
VkPipelineShaderStageCreateInfo vertShaderStageInfo{};
vertShaderStageInfo.sType =
VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;
vertShaderStageInfo.module = vertShader;
vertShaderStageInfo.pName = "main";
VkPipelineShaderStageCreateInfo fragShaderStageInfo{};
fragShaderStageInfo.sType =
VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
fragShaderStageInfo.module = fragShader;
fragShaderStageInfo.pName = "main";
VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo,
fragShaderStageInfo};
// Dynamic states
std::vector<VkDynamicState> dynamicStates = {VK_DYNAMIC_STATE_VIEWPORT,
VK_DYNAMIC_STATE_SCISSOR};
VkPipelineDynamicStateCreateInfo dynamicState{};
dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
dynamicState.dynamicStateCount =
static_cast<uint32_t>(dynamicStates.size());
dynamicState.pDynamicStates = dynamicStates.data();
auto bindingDescription = adapter.getVertexInputBindingDescription();
auto attributeDescriptions = adapter.getVertexInputAttributeDescriptions();
VkPipelineVertexInputStateCreateInfo vertexInputInfo{};
vertexInputInfo.sType =
VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
vertexInputInfo.vertexBindingDescriptionCount = 1;
vertexInputInfo.pVertexBindingDescriptions = &bindingDescription;
vertexInputInfo.vertexAttributeDescriptionCount =
static_cast<uint32_t>(attributeDescriptions.size());
vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data();
// Input assembly
VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
inputAssembly.sType =
VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
inputAssembly.primitiveRestartEnable = VK_FALSE;
// Viewport & scissor
VkPipelineViewportStateCreateInfo viewportState{};
viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
viewportState.viewportCount = 1;
viewportState.scissorCount = 1;
// Rasterizer
VkPipelineRasterizationStateCreateInfo rasterizer{};
rasterizer.sType =
VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
rasterizer.depthClampEnable = VK_FALSE;
rasterizer.rasterizerDiscardEnable = VK_FALSE;
rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
rasterizer.lineWidth = 1.0f;
rasterizer.cullMode = VK_CULL_MODE_BACK_BIT;
rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
rasterizer.depthBiasEnable = VK_FALSE;
rasterizer.depthBiasConstantFactor = 0.0f; // Optional
rasterizer.depthBiasClamp = 0.0f; // Optional
rasterizer.depthBiasSlopeFactor = 0.0f; // Optional
// Multisampling (disabled)
VkPipelineMultisampleStateCreateInfo multisampling{};
multisampling.sType =
VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
multisampling.sampleShadingEnable = VK_FALSE;
multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
multisampling.minSampleShading = 1.0f; // Optional
multisampling.pSampleMask = nullptr; // Optional
multisampling.alphaToCoverageEnable = VK_FALSE; // Optional
multisampling.alphaToOneEnable = VK_FALSE; // Optional
// Depth testing
VkPipelineDepthStencilStateCreateInfo depthStencil{};
depthStencil.sType =
VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
depthStencil.depthTestEnable = VK_TRUE;
depthStencil.depthWriteEnable = VK_TRUE;
depthStencil.depthCompareOp = VK_COMPARE_OP_LESS;
depthStencil.depthBoundsTestEnable = VK_FALSE;
depthStencil.stencilTestEnable = VK_FALSE;
// Stencil testing (disabled)
// do nothing
// Color blending
VkPipelineColorBlendAttachmentState colorBlendAttachment{};
colorBlendAttachment.colorWriteMask =
VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
colorBlendAttachment.blendEnable = VK_TRUE;
colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
colorBlendAttachment.dstColorBlendFactor =
VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
VkPipelineColorBlendStateCreateInfo colorBlending{};
colorBlending.sType =
VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
colorBlending.logicOpEnable = VK_FALSE;
colorBlending.logicOp = VK_LOGIC_OP_COPY; // Optional
colorBlending.attachmentCount = 1;
colorBlending.pAttachments = &colorBlendAttachment;
colorBlending.blendConstants[0] = 0.0f; // Optional
colorBlending.blendConstants[1] = 0.0f; // Optional
colorBlending.blendConstants[2] = 0.0f; // Optional
colorBlending.blendConstants[3] = 0.0f; // Optional
// Pipeline
VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
auto layouts = vulkan.getAdapter().getUsedDSLayouts();
pipelineLayoutInfo.setLayoutCount = layouts.size();
pipelineLayoutInfo.pSetLayouts = layouts.data();
VkPushConstantRange pushConstantRange{};
pushConstantRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
pushConstantRange.offset = 0;
pushConstantRange.size = sizeof(glm::mat3x4);
pipelineLayoutInfo.pushConstantRangeCount = 1;
pipelineLayoutInfo.pPushConstantRanges = &pushConstantRange;
vulkan.handleVkResult("Could not create PipelineLayout",
vkCreatePipelineLayout(vulkan.getDevice(),
&pipelineLayoutInfo, nullptr,
&layout));
VkGraphicsPipelineCreateInfo pipelineInfo{};
pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
pipelineInfo.stageCount = 2;
pipelineInfo.pStages = shaderStages;
pipelineInfo.pVertexInputState = &vertexInputInfo;
pipelineInfo.pInputAssemblyState = &inputAssembly;
pipelineInfo.pViewportState = &viewportState;
pipelineInfo.pRasterizationState = &rasterizer;
pipelineInfo.pMultisampleState = &multisampling;
pipelineInfo.pDepthStencilState = &depthStencil;
pipelineInfo.pColorBlendState = &colorBlending;
pipelineInfo.pDynamicState = &dynamicState;
pipelineInfo.layout = layout;
pipelineInfo.renderPass = vulkan.getRenderPass().getVk();
pipelineInfo.subpass = 0;
pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; // Optional
pipelineInfo.basePipelineIndex = -1; // Optional
vulkan.handleVkResult(
"Could not create Pipeline",
vkCreateGraphicsPipelines(vulkan.getDevice(), VK_NULL_HANDLE, 1,
&pipelineInfo, nullptr, &vk));
// Cleanup
vkDestroyShaderModule(vulkan.getDevice(), fragShader, nullptr);
vkDestroyShaderModule(vulkan.getDevice(), vertShader, nullptr);
}
VkShaderModule Pipeline::createShaderModule(const std::vector<char> &bytecode) {
VkShaderModuleCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
createInfo.codeSize = bytecode.size();
// Important - the buffer must be aligned properly. std::vector does that.
createInfo.pCode = reinterpret_cast<const uint32_t *>(bytecode.data());
VkShaderModule shaderModule;
vulkan.handleVkResult("Could not load shader",
vkCreateShaderModule(vulkan.getDevice(), &createInfo,
nullptr, &shaderModule));
return shaderModule;
}
Pipeline::~Pipeline() {
vkDestroyPipeline(vulkan.getDevice(), vk, nullptr);
vkDestroyPipelineLayout(vulkan.getDevice(), layout, nullptr);
}
VkPipeline Pipeline::getVk() { return vk; }
VkPipelineLayout Pipeline::getLayout() { return layout; }
} // namespace desktop
} // namespace progressia

View File

@ -0,0 +1,27 @@
#pragma once
#include "vulkan_common.h"
namespace progressia {
namespace desktop {
class Pipeline : public VkObjectWrapper {
private:
VkPipelineLayout layout;
VkPipeline vk;
Vulkan &vulkan;
VkShaderModule createShaderModule(const std::vector<char> &bytecode);
public:
Pipeline(Vulkan &);
~Pipeline();
VkPipeline getVk();
VkPipelineLayout getLayout();
};
} // namespace desktop
} // namespace progressia

View File

@ -0,0 +1,83 @@
#include "vulkan_render_pass.h"
#include "vulkan_adapter.h"
#include "vulkan_common.h"
namespace progressia {
namespace desktop {
RenderPass::RenderPass(Vulkan &vulkan) : vulkan(vulkan) {
std::vector<VkAttachmentDescription> attachmentDescriptions;
std::vector<VkAttachmentReference> attachmentReferences;
VkAttachmentReference depthAttachmentRef{};
const auto &attachments = vulkan.getAdapter().getAttachments();
for (std::size_t i = 0; i < attachments.size(); i++) {
const auto &attachment = attachments[i];
VkAttachmentDescription *desc;
VkAttachmentReference *ref;
attachmentDescriptions.push_back({});
desc = &attachmentDescriptions.back();
if (attachment.aspect == VK_IMAGE_ASPECT_DEPTH_BIT) {
ref = &depthAttachmentRef;
} else {
attachmentReferences.push_back({});
ref = &attachmentReferences.back();
}
desc->format = attachment.image == nullptr ? attachment.format
: attachment.image->format;
desc->samples = VK_SAMPLE_COUNT_1_BIT;
desc->loadOp = attachment.loadOp;
desc->storeOp = attachment.storeOp;
desc->stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
desc->stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
desc->initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
desc->finalLayout = attachment.finalLayout;
ref->attachment = i;
ref->layout = attachment.workLayout;
}
VkSubpassDescription subpass{};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass.colorAttachmentCount = attachmentReferences.size();
subpass.pColorAttachments = attachmentReferences.data();
subpass.pDepthStencilAttachment = &depthAttachmentRef;
VkSubpassDependency dependency{};
dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
dependency.dstSubpass = 0;
dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
dependency.srcAccessMask = 0;
dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
VkRenderPassCreateInfo renderPassInfo{};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
renderPassInfo.attachmentCount =
static_cast<uint32_t>(attachmentDescriptions.size());
renderPassInfo.pAttachments = attachmentDescriptions.data();
renderPassInfo.subpassCount = 1;
renderPassInfo.pSubpasses = &subpass;
renderPassInfo.dependencyCount = 1;
renderPassInfo.pDependencies = &dependency;
vulkan.handleVkResult(
"Could not create render pass",
vkCreateRenderPass(vulkan.getDevice(), &renderPassInfo, nullptr, &vk));
}
RenderPass::~RenderPass() {
vkDestroyRenderPass(vulkan.getDevice(), vk, nullptr);
}
VkRenderPass RenderPass::getVk() { return vk; }
} // namespace desktop
} // namespace progressia

View File

@ -0,0 +1,23 @@
#pragma once
#include "vulkan_common.h"
namespace progressia {
namespace desktop {
class RenderPass : public VkObjectWrapper {
private:
VkRenderPass vk;
Vulkan &vulkan;
public:
RenderPass(Vulkan &);
~RenderPass();
VkRenderPass getVk();
};
} // namespace desktop
} // namespace progressia

View File

@ -0,0 +1,328 @@
#include "vulkan_swap_chain.h"
#include <algorithm>
#include <cstdint>
#include <limits>
#include "glfw_mgmt_details.h"
#include "vulkan_adapter.h"
#include "vulkan_common.h"
#include "vulkan_render_pass.h"
namespace progressia {
namespace desktop {
SwapChain::SupportDetails
SwapChain::querySwapChainSupport(VkPhysicalDevice device, Vulkan &vulkan) {
SupportDetails details;
auto surface = vulkan.getSurface().getVk();
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface,
&details.capabilities);
uint32_t formatCount;
vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount,
nullptr);
if (formatCount != 0) {
details.formats.resize(formatCount);
vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount,
details.formats.data());
}
uint32_t presentModeCount;
vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface,
&presentModeCount, nullptr);
if (presentModeCount != 0) {
details.presentModes.resize(presentModeCount);
vkGetPhysicalDeviceSurfacePresentModesKHR(
device, surface, &presentModeCount, details.presentModes.data());
}
return details;
}
bool SwapChain::isSwapChainSuitable(const SupportDetails &details) {
return !details.formats.empty() && !details.presentModes.empty();
}
void SwapChain::create() {
auto details = querySwapChainSupport(vulkan.getPhysicalDevice(), vulkan);
auto surfaceFormat = chooseSurfaceFormat(details.formats);
auto presentMode = choosePresentMode(details.presentModes, true);
this->extent = chooseExtent(details.capabilities);
uint32_t imageCount = details.capabilities.minImageCount + 1;
uint32_t maxImageCount = details.capabilities.maxImageCount;
if (maxImageCount > 0 && imageCount > maxImageCount) {
imageCount = maxImageCount;
}
// Fill out the createInfo
VkSwapchainCreateInfoKHR createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
createInfo.surface = vulkan.getSurface().getVk();
createInfo.minImageCount = imageCount;
createInfo.imageFormat = surfaceFormat.format;
createInfo.imageColorSpace = surfaceFormat.colorSpace;
createInfo.imageExtent = extent;
createInfo.imageArrayLayers = 1;
createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
createInfo.preTransform = details.capabilities.currentTransform;
createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
createInfo.presentMode = presentMode;
createInfo.clipped = VK_TRUE;
createInfo.oldSwapchain =
VK_NULL_HANDLE; // TODO Figure out if this should be used
// Specify queues
uint32_t queueFamilyIndices[] = {
vulkan.getQueues().getGraphicsQueue().getFamilyIndex(),
vulkan.getQueues().getPresentQueue().getFamilyIndex()};
if (queueFamilyIndices[0] != queueFamilyIndices[1]) {
createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
createInfo.queueFamilyIndexCount = 2;
createInfo.pQueueFamilyIndices = queueFamilyIndices;
} else {
createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
}
// Create swap chain object
vulkan.handleVkResult(
"Could not create swap chain",
vkCreateSwapchainKHR(vulkan.getDevice(), &createInfo, nullptr, &vk));
// Store color buffers
std::vector<VkImage> colorBufferImages;
vkGetSwapchainImagesKHR(vulkan.getDevice(), vk, &imageCount, nullptr);
colorBufferImages.resize(imageCount);
vkGetSwapchainImagesKHR(vulkan.getDevice(), vk, &imageCount,
colorBufferImages.data());
colorBufferViews.resize(colorBufferImages.size());
for (size_t i = 0; i < colorBufferImages.size(); i++) {
VkImageViewCreateInfo viewCreateInfo{};
viewCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
viewCreateInfo.image = colorBufferImages[i];
viewCreateInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
viewCreateInfo.format = surfaceFormat.format;
viewCreateInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
viewCreateInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
viewCreateInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
viewCreateInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
viewCreateInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
viewCreateInfo.subresourceRange.baseMipLevel = 0;
viewCreateInfo.subresourceRange.levelCount = 1;
viewCreateInfo.subresourceRange.baseArrayLayer = 0;
viewCreateInfo.subresourceRange.layerCount = 1;
vulkan.handleVkResult("Cound not create ImageView",
vkCreateImageView(vulkan.getDevice(),
&viewCreateInfo, nullptr,
&colorBufferViews[i]));
}
// Create attachment images
for (auto &attachment : vulkan.getAdapter().getAttachments()) {
if (attachment.format == VK_FORMAT_UNDEFINED) {
if (!attachment.image) {
std::cout << "Attachment " << attachment.name
<< " format is VK_FORMAT_UNDEFINED but it does not "
"have an image"
<< std::endl;
// REPORT_ERROR
exit(1);
}
continue;
}
attachment.image = std::make_unique<ManagedImage>(
extent.width, extent.height, attachment.format, attachment.aspect,
attachment.usage, vulkan);
}
// Create framebuffer
framebuffers.resize(colorBufferViews.size());
for (size_t i = 0; i < framebuffers.size(); i++) {
std::vector<VkImageView> attachmentViews;
for (const auto &attachment : vulkan.getAdapter().getAttachments()) {
if (&attachment == colorBuffer) {
attachmentViews.push_back(colorBufferViews[i]);
} else if (attachment.image) {
attachmentViews.push_back(attachment.image->view);
} else {
std::cout << "Attachment " << attachment.name
<< " is not colorBuffer but it does not have an image"
<< std::endl;
// REPORT_ERROR
exit(1);
}
}
VkFramebufferCreateInfo framebufferInfo{};
framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
framebufferInfo.renderPass = vulkan.getRenderPass().getVk();
framebufferInfo.attachmentCount =
static_cast<uint32_t>(attachmentViews.size());
framebufferInfo.pAttachments = attachmentViews.data();
framebufferInfo.width = extent.width;
framebufferInfo.height = extent.height;
framebufferInfo.layers = 1;
vulkan.handleVkResult("Could not create Framebuffer",
vkCreateFramebuffer(vulkan.getDevice(),
&framebufferInfo, nullptr,
&framebuffers[i]));
}
}
VkSurfaceFormatKHR SwapChain::chooseSurfaceFormat(
const std::vector<VkSurfaceFormatKHR> &supported) {
for (const auto &option : supported) {
if (option.format == VK_FORMAT_B8G8R8A8_SRGB &&
option.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
return option;
}
}
std::cout << "No suitable formats available" << std::endl;
// REPORT_ERROR
exit(1);
}
bool SwapChain::isTripleBufferingSupported(
const std::vector<VkPresentModeKHR> &supported) {
return std::find(supported.begin(), supported.end(),
VK_PRESENT_MODE_MAILBOX_KHR) != supported.end();
}
VkPresentModeKHR
SwapChain::choosePresentMode(const std::vector<VkPresentModeKHR> &supported,
bool avoidVsync) {
if (avoidVsync && isTripleBufferingSupported(supported)) {
return VK_PRESENT_MODE_MAILBOX_KHR;
}
return VK_PRESENT_MODE_FIFO_KHR;
}
VkExtent2D
SwapChain::chooseExtent(const VkSurfaceCapabilitiesKHR &capabilities) {
if (capabilities.currentExtent.width !=
std::numeric_limits<uint32_t>::max()) {
return capabilities.currentExtent;
}
int width, height;
glfwGetFramebufferSize(getGLFWWindowHandle(), &width, &height);
VkExtent2D actualExtent = {static_cast<uint32_t>(width),
static_cast<uint32_t>(height)};
actualExtent.width =
std::clamp(actualExtent.width, capabilities.minImageExtent.width,
capabilities.maxImageExtent.width);
actualExtent.height =
std::clamp(actualExtent.height, capabilities.minImageExtent.height,
capabilities.maxImageExtent.height);
return actualExtent;
}
void SwapChain::destroy() {
for (auto framebuffer : framebuffers) {
vkDestroyFramebuffer(vulkan.getDevice(), framebuffer, nullptr);
}
framebuffers.clear();
if (depthBuffer != nullptr) {
delete depthBuffer;
depthBuffer = nullptr;
}
auto &attachments = vulkan.getAdapter().getAttachments();
for (auto &attachment : attachments) {
if (attachment.format != VK_FORMAT_UNDEFINED) {
attachment.image.reset();
}
}
for (auto colorBufferView : colorBufferViews) {
vkDestroyImageView(vulkan.getDevice(), colorBufferView, nullptr);
}
colorBufferViews.clear();
if (vk != VK_NULL_HANDLE) {
vkDestroySwapchainKHR(vulkan.getDevice(), vk, nullptr);
vk = VK_NULL_HANDLE;
}
}
SwapChain::SwapChain(Vulkan &vulkan)
: vk(VK_NULL_HANDLE), colorBuffer(nullptr),
colorBufferViews(), extent{0, 0}, depthBuffer(nullptr), framebuffers(),
vulkan(vulkan) {
auto details = querySwapChainSupport(vulkan.getPhysicalDevice(), vulkan);
auto surfaceFormat = chooseSurfaceFormat(details.formats);
vulkan.getAdapter().getAttachments().push_back(
{"Color buffer",
VK_FORMAT_UNDEFINED,
0,
0,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
VK_ATTACHMENT_LOAD_OP_CLEAR,
VK_ATTACHMENT_STORE_OP_STORE,
{{{0.0f, 0.0f, 0.0f, 1.0f}}},
std::make_unique<Image>(static_cast<VkImage>(VK_NULL_HANDLE),
static_cast<VkImageView>(VK_NULL_HANDLE),
surfaceFormat.format)});
colorBuffer = &vulkan.getAdapter().getAttachments().back();
}
SwapChain::~SwapChain() {
destroy();
auto &attachments = vulkan.getAdapter().getAttachments();
for (auto it = attachments.begin(); it != attachments.end(); it++) {
if (&(*it) == colorBuffer) {
attachments.erase(it);
colorBuffer = nullptr;
break;
}
}
}
void SwapChain::recreate() {
vulkan.waitIdle();
destroy();
create();
}
VkSwapchainKHR SwapChain::getVk() const { return vk; }
VkFramebuffer SwapChain::getFramebuffer(std::size_t index) const {
return framebuffers.at(index);
}
VkExtent2D SwapChain::getExtent() const { return extent; }
} // namespace desktop
} // namespace progressia

View File

@ -0,0 +1,58 @@
#pragma once
#include "vulkan_adapter.h"
#include "vulkan_common.h"
namespace progressia {
namespace desktop {
class SwapChain : public VkObjectWrapper {
public:
struct SupportDetails {
VkSurfaceCapabilitiesKHR capabilities;
std::vector<VkSurfaceFormatKHR> formats;
std::vector<VkPresentModeKHR> presentModes;
};
static SupportDetails querySwapChainSupport(VkPhysicalDevice device,
Vulkan &vulkan);
static bool isSwapChainSuitable(const SupportDetails &details);
private:
VkSwapchainKHR vk;
Attachment *colorBuffer;
std::vector<VkImageView> colorBufferViews;
VkExtent2D extent;
Image *depthBuffer;
std::vector<VkFramebuffer> framebuffers;
Vulkan &vulkan;
void create();
void destroy();
VkSurfaceFormatKHR
chooseSurfaceFormat(const std::vector<VkSurfaceFormatKHR> &);
bool isTripleBufferingSupported(const std::vector<VkPresentModeKHR> &);
VkPresentModeKHR choosePresentMode(const std::vector<VkPresentModeKHR> &,
bool avoidVsync);
VkExtent2D chooseExtent(const VkSurfaceCapabilitiesKHR &);
public:
SwapChain(Vulkan &);
~SwapChain();
void recreate();
VkSwapchainKHR getVk() const;
VkFramebuffer getFramebuffer(std::size_t index) const;
VkExtent2D getExtent() const;
};
} // namespace desktop
} // namespace progressia

View File

@ -0,0 +1,106 @@
#include "vulkan_texture_descriptors.h"
namespace progressia {
namespace desktop {
void TextureDescriptors::allocatePool() {
pools.resize(pools.size() + 1);
VkDescriptorPoolSize poolSize = {};
poolSize.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
poolSize.descriptorCount = POOL_SIZE;
VkDescriptorPoolCreateInfo poolInfo{};
poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
poolInfo.poolSizeCount = 1;
poolInfo.pPoolSizes = &poolSize;
poolInfo.maxSets = POOL_SIZE;
auto output = &pools[pools.size() - 1];
vulkan.handleVkResult(
"Could not create texture descriptor pool",
vkCreateDescriptorPool(vulkan.getDevice(), &poolInfo, nullptr, output));
lastPoolCapacity = POOL_SIZE;
}
TextureDescriptors::TextureDescriptors(Vulkan &vulkan)
: DescriptorSetInterface(SET_NUMBER, vulkan) {
VkDescriptorSetLayoutCreateInfo layoutInfo{};
layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
VkDescriptorSetLayoutBinding binding = {};
binding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
binding.descriptorCount = 1;
binding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
binding.pImmutableSamplers = nullptr;
binding.binding = 0;
layoutInfo.bindingCount = 1;
layoutInfo.pBindings = &binding;
vulkan.handleVkResult("Could not create texture descriptor set layout",
vkCreateDescriptorSetLayout(vulkan.getDevice(),
&layoutInfo, nullptr,
&layout));
allocatePool();
}
TextureDescriptors::~TextureDescriptors() {
for (auto pool : pools) {
vkDestroyDescriptorPool(vulkan.getDevice(), pool, nullptr);
}
vkDestroyDescriptorSetLayout(vulkan.getDevice(), layout, nullptr);
}
VkDescriptorSet TextureDescriptors::addTexture(VkImageView view,
VkSampler sampler) {
/*
* Allocate descriptor set
*/
if (lastPoolCapacity == 0) {
allocatePool();
}
VkDescriptorSetAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
allocInfo.descriptorPool = pools.back();
allocInfo.descriptorSetCount = 1;
allocInfo.pSetLayouts = &layout;
VkDescriptorSet descriptorSet;
vulkan.handleVkResult("Could not create texture descriptor set",
vkAllocateDescriptorSets(vulkan.getDevice(),
&allocInfo, &descriptorSet));
lastPoolCapacity--;
/*
* Write to descriptor set
*/
VkDescriptorImageInfo imageInfo = {};
imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
imageInfo.imageView = view;
imageInfo.sampler = sampler;
VkWriteDescriptorSet write = {};
write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
write.dstSet = descriptorSet;
write.dstBinding = 0;
write.dstArrayElement = 0;
write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
write.descriptorCount = 1;
write.pImageInfo = &imageInfo;
vkUpdateDescriptorSets(vulkan.getDevice(), 1, &write, 0, nullptr);
return descriptorSet;
}
} // namespace desktop
} // namespace progressia

View File

@ -0,0 +1,29 @@
#pragma once
#include <vector>
#include "vulkan_common.h"
#include "vulkan_descriptor_set.h"
namespace progressia {
namespace desktop {
class TextureDescriptors : public DescriptorSetInterface {
private:
constexpr static uint32_t POOL_SIZE = 64;
constexpr static uint32_t SET_NUMBER = 1;
std::vector<VkDescriptorPool> pools;
uint32_t lastPoolCapacity;
void allocatePool();
public:
TextureDescriptors(Vulkan &);
~TextureDescriptors();
VkDescriptorSet addTexture(VkImageView, VkSampler);
};
} // namespace desktop
} // namespace progressia

View File

@ -0,0 +1,76 @@
#pragma once
#include <memory>
#include <vector>
#include "vulkan_buffer.h"
#include "vulkan_common.h"
#include "vulkan_descriptor_set.h"
namespace progressia {
namespace desktop {
template <typename... Entries> class Uniform : public DescriptorSetInterface {
private:
constexpr static uint32_t POOL_SIZE = 64;
std::vector<VkDescriptorPool> pools;
struct StateImpl {
struct Set {
VkDescriptorSet vk;
Buffer<unsigned char> contents;
Set(VkDescriptorSet, Vulkan &);
};
std::array<std::optional<Set>, MAX_FRAMES_IN_FLIGHT> sets;
std::array<unsigned char, (sizeof(Entries) + ...)> newContents;
uint64_t setsToUpdate;
StateImpl(const std::array<VkDescriptorSet, MAX_FRAMES_IN_FLIGHT> &vks,
Vulkan &);
};
std::vector<std::unique_ptr<StateImpl>> states;
uint32_t lastPoolCapacity;
void allocatePool();
public:
class State {
private:
std::size_t id;
public:
Uniform<Entries...> *uniform;
private:
friend class Uniform<Entries...>;
State(std::size_t id, Uniform<Entries...> *);
void doUpdate();
public:
State();
void update(const Entries &...entries);
void bind();
};
Uniform(uint32_t setNumber, Vulkan &);
~Uniform();
State addState();
void doUpdates();
};
} // namespace desktop
} // namespace progressia
#include "vulkan_uniform.inl"

View File

@ -0,0 +1,194 @@
#pragma once
#include <cstring>
#include "../../main/util.h"
#include "vulkan_frame.h"
#include "vulkan_pipeline.h"
namespace progressia {
namespace desktop {
template <typename... Entries>
Uniform<Entries...>::StateImpl::Set::Set(VkDescriptorSet vk, Vulkan &vulkan)
: vk(vk),
contents((sizeof(Entries) + ...), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
vulkan) {}
template <typename... Entries>
Uniform<Entries...>::StateImpl::StateImpl(
const std::array<VkDescriptorSet, MAX_FRAMES_IN_FLIGHT> &vks,
Vulkan &vulkan)
: setsToUpdate(0) {
constexpr std::size_t COUNT = sizeof...(Entries) * MAX_FRAMES_IN_FLIGHT;
std::array<VkDescriptorBufferInfo, COUNT> bufferInfos;
std::array<VkWriteDescriptorSet, COUNT> writes;
std::size_t index = 0;
for (std::size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
auto &set = sets.at(i);
set.emplace(vks.at(i), vulkan);
std::size_t offset = 0;
FOR_PACK_S(Entries, Entry, {
bufferInfos[index] = {};
bufferInfos[index].buffer = set->contents.buffer;
bufferInfos[index].offset = offset;
bufferInfos[index].range = sizeof(Entry);
writes[index] = {};
writes[index].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
writes[index].dstSet = set->vk;
writes[index].dstBinding = index % sizeof...(Entries);
writes[index].dstArrayElement = 0;
writes[index].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
writes[index].descriptorCount = 1;
writes[index].pBufferInfo = &bufferInfos[index];
offset += sizeof(Entry);
index++;
})
}
vkUpdateDescriptorSets(vulkan.getDevice(), writes.size(), writes.data(), 0,
nullptr);
}
template <typename... Entries>
Uniform<Entries...>::State::State(std::size_t id, Uniform *uniform)
: id(id), uniform(uniform) {}
template <typename... Entries>
Uniform<Entries...>::State::State() : id(-1), uniform(nullptr) {}
template <typename... Entries>
void Uniform<Entries...>::State::update(const Entries &...entries) {
auto &state = *uniform->states.at(id);
auto *dst = state.newContents.data();
FOR_PACK(Entries, entries, e, {
std::memcpy(dst, &e, sizeof(e));
dst += sizeof(e);
})
state.setsToUpdate = state.sets.size();
}
template <typename... Entries> void Uniform<Entries...>::State::bind() {
auto &state = *uniform->states.at(id);
auto &set = *state.sets.at(uniform->vulkan.getFrameInFlightIndex());
// REPORT_ERROR if getCurrentFrame() == nullptr
auto commandBuffer = uniform->vulkan.getCurrentFrame()->getCommandBuffer();
auto pipelineLayout = uniform->vulkan.getPipeline().getLayout();
vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
pipelineLayout, uniform->getSetNumber(), 1, &set.vk,
0, nullptr);
}
template <typename... Entries>
Uniform<Entries...>::Uniform(uint32_t setNumber, Vulkan &vulkan)
: DescriptorSetInterface(setNumber, vulkan) {
VkDescriptorSetLayoutCreateInfo layoutInfo{};
layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
std::array<VkDescriptorSetLayoutBinding, sizeof...(Entries)> bindings;
for (std::size_t i = 0; i < bindings.size(); i++) {
bindings[i] = {};
bindings[i].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
bindings[i].descriptorCount = 1;
bindings[i].stageFlags = VK_SHADER_STAGE_VERTEX_BIT |
VK_SHADER_STAGE_FRAGMENT_BIT; // TODO optimize?
bindings[i].pImmutableSamplers = nullptr;
bindings[i].binding = i;
}
layoutInfo.bindingCount = bindings.size();
layoutInfo.pBindings = bindings.data();
vulkan.handleVkResult("Could not create uniform descriptor set layout",
vkCreateDescriptorSetLayout(vulkan.getDevice(),
&layoutInfo, nullptr,
&layout));
allocatePool();
}
template <typename... Entries> Uniform<Entries...>::~Uniform() {
for (auto pool : pools) {
vkDestroyDescriptorPool(vulkan.getDevice(), pool, nullptr);
}
vkDestroyDescriptorSetLayout(vulkan.getDevice(), layout, nullptr);
}
template <typename... Entries> void Uniform<Entries...>::allocatePool() {
pools.resize(pools.size() + 1);
std::array<VkDescriptorPoolSize, 1> poolSizes{};
poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
poolSizes[0].descriptorCount = sizeof...(Entries) * POOL_SIZE;
VkDescriptorPoolCreateInfo poolInfo{};
poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
poolInfo.poolSizeCount = poolSizes.size();
poolInfo.pPoolSizes = poolSizes.data();
poolInfo.maxSets = POOL_SIZE;
auto output = &pools[pools.size() - 1];
vulkan.handleVkResult(
"Could not create uniform descriptor pool",
vkCreateDescriptorPool(vulkan.getDevice(), &poolInfo, nullptr, output));
lastPoolCapacity = POOL_SIZE;
}
template <typename... Entries>
typename Uniform<Entries...>::State Uniform<Entries...>::addState() {
if (lastPoolCapacity < MAX_FRAMES_IN_FLIGHT) {
allocatePool();
}
std::array<VkDescriptorSet, MAX_FRAMES_IN_FLIGHT> vks;
VkDescriptorSetAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
allocInfo.descriptorPool = pools.back();
allocInfo.descriptorSetCount = MAX_FRAMES_IN_FLIGHT;
std::array<VkDescriptorSetLayout, MAX_FRAMES_IN_FLIGHT> layouts;
layouts.fill(layout);
allocInfo.pSetLayouts = layouts.data();
vulkan.handleVkResult(
"Could not create descriptor set",
vkAllocateDescriptorSets(vulkan.getDevice(), &allocInfo, vks.data()));
lastPoolCapacity -= MAX_FRAMES_IN_FLIGHT;
states.push_back(std::make_unique<StateImpl>(vks, vulkan));
return State(states.size() - 1, this);
}
template <typename... Entries> void Uniform<Entries...>::doUpdates() {
for (auto &state : states) {
auto &buffer = state->sets.at(vulkan.getFrameInFlightIndex())->contents;
auto &src = state->newContents;
if (state->setsToUpdate > 0) {
auto *dst = buffer.map();
std::memcpy(dst, src.data(), src.size());
buffer.unmap();
state->setsToUpdate--;
}
}
}
} // namespace desktop
} // namespace progressia