mirror of
https://gitea.windcorp.ru/Wind-Corporation/Progressia.git
synced 2025-07-01 13:53:35 +03:00
Initial commit
This commit is contained in:
69
desktop/graphics/glfw_mgmt.cpp
Normal file
69
desktop/graphics/glfw_mgmt.cpp
Normal 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
|
13
desktop/graphics/glfw_mgmt.h
Normal file
13
desktop/graphics/glfw_mgmt.h
Normal 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
|
14
desktop/graphics/glfw_mgmt_details.h
Normal file
14
desktop/graphics/glfw_mgmt_details.h
Normal 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
|
12
desktop/graphics/shaders/shader.frag
Normal file
12
desktop/graphics/shaders/shader.frag
Normal 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);
|
||||
}
|
62
desktop/graphics/shaders/shader.vert
Normal file
62
desktop/graphics/shaders/shader.vert
Normal 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;
|
||||
}
|
336
desktop/graphics/vulkan_adapter.cpp
Normal file
336
desktop/graphics/vulkan_adapter.cpp
Normal 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
|
72
desktop/graphics/vulkan_adapter.h
Normal file
72
desktop/graphics/vulkan_adapter.h
Normal 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
|
196
desktop/graphics/vulkan_buffer.h
Normal file
196
desktop/graphics/vulkan_buffer.h
Normal 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, ©Region);
|
||||
|
||||
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
|
776
desktop/graphics/vulkan_common.cpp
Normal file
776
desktop/graphics/vulkan_common.cpp
Normal 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
|
289
desktop/graphics/vulkan_common.h
Normal file
289
desktop/graphics/vulkan_common.h
Normal 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
|
19
desktop/graphics/vulkan_descriptor_set.cpp
Normal file
19
desktop/graphics/vulkan_descriptor_set.cpp
Normal 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
|
23
desktop/graphics/vulkan_descriptor_set.h
Normal file
23
desktop/graphics/vulkan_descriptor_set.h
Normal 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
|
171
desktop/graphics/vulkan_frame.cpp
Normal file
171
desktop/graphics/vulkan_frame.cpp
Normal 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
|
36
desktop/graphics/vulkan_frame.h
Normal file
36
desktop/graphics/vulkan_frame.h
Normal 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
|
247
desktop/graphics/vulkan_image.cpp
Normal file
247
desktop/graphics/vulkan_image.cpp
Normal 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, ®ion);
|
||||
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
|
60
desktop/graphics/vulkan_image.h
Normal file
60
desktop/graphics/vulkan_image.h
Normal 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
|
68
desktop/graphics/vulkan_mgmt.cpp
Normal file
68
desktop/graphics/vulkan_mgmt.cpp
Normal 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
|
23
desktop/graphics/vulkan_mgmt.h
Normal file
23
desktop/graphics/vulkan_mgmt.h
Normal 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
|
98
desktop/graphics/vulkan_pick_device.cpp
Normal file
98
desktop/graphics/vulkan_pick_device.cpp
Normal 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
|
21
desktop/graphics/vulkan_pick_device.h
Normal file
21
desktop/graphics/vulkan_pick_device.h
Normal 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
|
223
desktop/graphics/vulkan_pipeline.cpp
Normal file
223
desktop/graphics/vulkan_pipeline.cpp
Normal 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
|
27
desktop/graphics/vulkan_pipeline.h
Normal file
27
desktop/graphics/vulkan_pipeline.h
Normal 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
|
83
desktop/graphics/vulkan_render_pass.cpp
Normal file
83
desktop/graphics/vulkan_render_pass.cpp
Normal 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
|
23
desktop/graphics/vulkan_render_pass.h
Normal file
23
desktop/graphics/vulkan_render_pass.h
Normal 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
|
328
desktop/graphics/vulkan_swap_chain.cpp
Normal file
328
desktop/graphics/vulkan_swap_chain.cpp
Normal 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
|
58
desktop/graphics/vulkan_swap_chain.h
Normal file
58
desktop/graphics/vulkan_swap_chain.h
Normal 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
|
106
desktop/graphics/vulkan_texture_descriptors.cpp
Normal file
106
desktop/graphics/vulkan_texture_descriptors.cpp
Normal 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
|
29
desktop/graphics/vulkan_texture_descriptors.h
Normal file
29
desktop/graphics/vulkan_texture_descriptors.h
Normal 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
|
76
desktop/graphics/vulkan_uniform.h
Normal file
76
desktop/graphics/vulkan_uniform.h
Normal 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"
|
194
desktop/graphics/vulkan_uniform.inl
Normal file
194
desktop/graphics/vulkan_uniform.inl
Normal 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
|
Reference in New Issue
Block a user