initial commit

This commit is contained in:
matthias@arch 2022-10-07 23:30:44 +02:00
commit db90bb2c31
33 changed files with 3926 additions and 0 deletions

3
.clangd Normal file
View File

@ -0,0 +1,3 @@
CompileFlags: # Tweak the parse settings
Add: [-std=c++2a, -Wall, -I/usr/include/freetype2, -I.., -I.]
# https://clangd.llvm.org/config

14
.gitignore vendored Normal file
View File

@ -0,0 +1,14 @@
build
fonts
models
old
shaders
stb_image.h
textures
tiny_obj_loader.h
vk_enum_string.h
vulkan.log
vulkan_specs.html
vulkan_test
Vulkan Tutorial en.pdf

22
.vimspector.json Normal file
View File

@ -0,0 +1,22 @@
{
"configurations": {
"gze": {
"adapter": "vscode-cpptools",
"configuration": {
"name": "vulkan (cpp)",
"type": "cppdbg",
"request": "launch",
"externalConsole": true,
"logging": {
"engineLogging": true
},
"stopOnEntry": true,
"stopAtEntry": true,
"debugOptions": [],
"MIMode": "gdb",
"cwd": "~/c++/vulkan",
"program": "vulkan_test"
}
}
}
}

65
Makefile Executable file
View File

@ -0,0 +1,65 @@
CXX = /usr/bin/g++
CXXFLAGS = -std=c++20 -MMD -MP -Wextra #-O3: optimierungsstufe 3, -MMD .d files, -MP leeres Target, Wextra alle Warnungen
# CXXFLAGS += -ftemplate-backtrace-limit=4 #-fno-pretty-templates
# most stuff glfw deps
LDFLAGS = -lglfw -lvulkan -ldl -lpthread -lX11 -lXxf86vm -lXrandr -lXi -lgzutil -lfreetype
IFLAGS = -I/usr/include/freetype2
OBJECT_DIR = build
EXEC = vulkan_test
SRC = $(wildcard *.cpp)
OBJECTS = $($(notdir SRC):%.cpp=$(OBJECT_DIR)/%.o)
DEPENDS = ${OBJECTS:.o=.d}
CXXFLAGS += $(IFLAGS)
default: $(EXEC)
echo $(OBJECTS)
release: CXXFLAGS += -O3
release : default
# rule for the executable
$(EXEC): $(OBJECTS)
$(CXX) $(OBJECTS) -o $@ $(CXXFLAGS) $(LDFLAGS)
# include the makefiles generated by the -M flag
-include $(DEPENDS)
# rule for all ../build/*.o files
$(OBJECT_DIR)/%.o: %.cpp $(OBJECT_DIR)/.dirstamp
$(CXX) -c $< -o $@ $(CXXFLAGS) $(LDFLAGS)
# if build dir does not exist, create and put stamp inside
$(OBJECT_DIR)/.dirstamp:
mkdir -p $(OBJECT_DIR)
touch $@
#
# Extras Options
#
# with debug flags
debug: CXXFLAGS += -g # -DDEBUG
debug: default
# make with debug flags and run afterwards
run: CXXFLAGS += -g
run: default
$(CXX) $(OBJECTS) -o $(EXEC) $(CXXFLAGS) $(LDFLAGS)
./$(EXEC)
# with debug flags and run gnu debugger
gdb: CXXFLAGS += -g
gdb: default
gdb $(EXEC) -ex "layout src"
# build pch
pch:
$(CXX) pch.hpp -std=c++20 -O3 -g $(IFLAGS)
# remove all object and dependecy files
clean:
# -rm -f $(OBJECT_DIR)/*.o
# -rm -f $(OBJECT_DIR)/*.d
-rm -r $(OBJECT_DIR)

12
compile.sh Executable file
View File

@ -0,0 +1,12 @@
#!/bin/sh
WDIR=$HOME/c++/vulkan
mkdir -p $WDIR/shaders
glslc $WDIR/shader.vert -o $WDIR/shaders/vert.spv
glslc $WDIR/shader.frag -o $WDIR/shaders/frag.spv
glslc $WDIR/shader2D.vert -o $WDIR/shaders/vert2D.spv
glslc $WDIR/shader2D.frag -o $WDIR/shaders/frag2D.spv

21
exceptions.cpp Normal file
View File

@ -0,0 +1,21 @@
#include "exceptions.hpp"
#include "vk_enum_string.h"
namespace gz {
VkException getVkException(VkResult result, std::string&& what, std::string&& functionName) {
std::string whatStr;
if (!functionName.empty()) {
whatStr += "Error in function: " + functionName + ": ";
}
else {
whatStr += "Error: ";
}
if (!what.empty()) {
whatStr += what + ": ";
}
whatStr += "VkResult=";
whatStr += STR_VK_RESULT(result);
return VkException(whatStr);
}
}

36
exceptions.hpp Normal file
View File

@ -0,0 +1,36 @@
#pragma once
#include <gz-util/exceptions.hpp>
#include <vulkan/vulkan_core.h>
#include <exception>
#include <string>
namespace gz {
/**
* @brief An error that has something to with vulkan
*/
class VkException : public Exception {
public:
VkException(const std::string& what) : Exception(what) {};
VkException(const std::string&& what, const std::string&& functionName)
: Exception(std::move(what), std::move(functionName)) {};
};
/**
* @brief A user error that has something to do with vulkan
* @details
* This error comes may come from bad function parameters
*/
class VkUserError : public VkException {
public:
VkUserError(const std::string&& what, const std::string&& functionName)
: VkException(std::move(what), std::move(functionName)) {};
};
/**
* @brief Return a VkException with a formatted string
*/
VkException getVkException(VkResult result, std::string&& what="", std::string&& functionName="");
}

28
font.cpp Normal file
View File

@ -0,0 +1,28 @@
#include "font.hpp"
#include <gz-util/exceptions.hpp>
namespace gz {
FontManager::FontManager(const std::string&& fontDir)
: fontDir(fontDir) {
FT_Error error = FT_Init_FreeType(&freetype);
if (error != FT_Err_Ok) {
throw Exception("Could not load freetype library", "FontManager");
}
}
void FontManager::loadFont(const std::string& font) {
FT_Error error = FT_New_Face(freetype, (fontDir + "/" + font).c_str(), 0, &faces[font]);
if (error != FT_Err_Ok) {
throw Exception("Could not load font.", "FontManager::loadFont");
}
FT_Set_Pixel_Sizes(faces[font], 0, 48);
if (FT_Load_Char(faces[font], 'X', FT_LOAD_RENDER)) {
throw Exception("Could not load char.", "FontManager::loadFont");
}
}
}

20
font.hpp Normal file
View File

@ -0,0 +1,20 @@
#pragma once
#include <string>
#include <unordered_map>
#include <ft2build.h>
#include FT_FREETYPE_H
namespace gz {
class FontManager {
public:
FontManager(const std::string&& fontDir);
void loadFont(const std::string& font);
const std::unordered_map<std::string, FT_Face>& getFaces() const { return faces; }
private:
FT_Library freetype;
std::string fontDir;
std::unordered_map<std::string, FT_Face> faces;
};
}

60
main.cpp Normal file
View File

@ -0,0 +1,60 @@
#include "main.hpp"
#include "vulkan_instance.hpp"
#include "renderer2D.hpp"
#include <thread>
namespace gz::vk {
int mainLoop() {
gz::SettingsManagerCreateInfo<SettingsTypes> smCI{};
smCI.filepath = gz::vk::CONFIG_FILE;
smCI.readFileOnCreation = true;
smCI.writeFileOnExit = true;
smCI.initialValues = gz::vk::INITIAL_SETTINGS;
smCI.throwExceptionWhenNewValueNotAllowed = true;
VulkanInstance vulkanInstance(smCI);
vulkanInstance.init();
Renderer2D r2D(vulkanInstance);
Rectangle rect1( 90, 91, 92, 93, { 1.0f, 0.9f, 0.8f});
Rectangle rect2(190, 191, 192, 193, { 0.7f, 0.6f, 0.5f});
Rectangle rect3(420, 64, 512, 64, { 0.0f, 1.0f, 0.0f});
Rectangle rect4( 32, 120, 400, 400, { 0.0f, 0.0f, 1.0f});
Rectangle rect5( -600, -600, 800, 400, { 1.0f, 0.0f, 0.0f});
r2D.drawShape(rect1);
r2D.drawShape(rect2);
r2D.drawShape(rect3);
r2D.drawShape(rect4);
r2D.drawShape(rect5);
r2D.fillVertexBufferWithShapes();
r2D.fillIndexBufferWithShapes();
/* gz::FontManager fm("fonts"); */
/* fm.loadFont("menu.ttf"); */
/* /1* fm.getFaces().at("menu.ttf"). *1/ */
try {
uint32_t imageIndex;
while (! glfwWindowShouldClose(vulkanInstance.window)) {
glfwPollEvents();
imageIndex = vulkanInstance.beginFrameDraw();
r2D.drawFrame(imageIndex);
vulkanInstance.endFrameDraw(imageIndex);
auto SLEEP_TIME = std::chrono::milliseconds(1000 / vulkanInstance.settings.get<uint32_t>("framerate"));
std::this_thread::sleep_for(SLEEP_TIME);
}
r2D.cleanup();
vulkanInstance.cleanup();
}
catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
return 1;
}
return 0;
}
}
int main() {
return gz::vk::mainLoop();
}

19
main.hpp Normal file
View File

@ -0,0 +1,19 @@
#pragma once
namespace gz::vk {
} // namespace gz::vk

17
renderer.cpp Normal file
View File

@ -0,0 +1,17 @@
#include "renderer.hpp"
#include <vulkan/vulkan_core.h>
namespace gz::vk {
void Renderer::cleanup_(){
vkFreeCommandBuffers(vk.device, vk.commandPoolGraphics, static_cast<uint32_t>(commandBuffers.size()), commandBuffers.data());
vkDestroyBuffer(vk.device, indexBuffer, nullptr);
vkFreeMemory(vk.device, indexBufferMemory, nullptr);
vkDestroyBuffer(vk.device, vertexBuffer, nullptr);
vkFreeMemory(vk.device, vertexBufferMemory, nullptr);
}
}

19
renderer.hpp Normal file
View File

@ -0,0 +1,19 @@
#include "vulkan_instance.hpp"
namespace gz::vk {
class Renderer {
public:
Renderer(VulkanInstance& instance) : vk(instance) {};
protected:
void cleanup_();
VulkanInstance& vk;
std::vector<VkCommandBuffer> commandBuffers;
/// On device local memory
VkBuffer vertexBuffer;
VkDeviceMemory vertexBufferMemory;
VkDeviceSize vertexBufferSize;
VkBuffer indexBuffer;
VkDeviceMemory indexBufferMemory;
VkDeviceSize indexBufferSize;
}; // class RendererBase
} // namespace gz::vk

247
renderer2D.cpp Normal file
View File

@ -0,0 +1,247 @@
#include "renderer2D.hpp"
#include "exceptions.hpp"
#include "vk_enum_string.h"
#include <cstring>
#include <vulkan/vulkan_core.h>
namespace gz::vk {
Renderer2D::Renderer2D(VulkanInstance& instance) :
Renderer(instance),
rLog("renderer2D.log", true, false, "2D-Renderer", Color::BMAGENTA, true, 100) {
vk.createCommandBuffers(commandBuffers);
const size_t vertexCount = 500;
const size_t indexCount = 1000;
vk.createVertexBuffer<Vertex2D>(vertexCount, vertexBuffer, vertexBufferMemory, vertexBufferSize);
vk.createIndexBuffer<uint32_t>(indexCount, indexBuffer, indexBufferMemory, indexBufferSize);
createRenderPass();
createImages();
renderPassID = vk.createFramebuffers(imageViews, renderPass);
rLog("Created Renderer2D");
}
void Renderer2D::cleanup() {
cleanup_();
}
void Renderer2D::createImages() {
images.resize(vk.scImages.size());
imageMemory.resize(vk.scImages.size());
imageViews.resize(vk.scImages.size());
VkImageUsageFlags usage= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
for (size_t i = 0; i < images.size(); i++) {
vk.createImage(vk.scExtent.width, vk.scExtent.height, vk.scImageFormat, VK_IMAGE_TILING_OPTIMAL, usage, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, images[i], imageMemory[i]);
vk.createImageView(vk.scImageFormat, images[i], imageViews[i], VK_IMAGE_ASPECT_COLOR_BIT);
}
}
void Renderer2D::createRenderPass() {
VkAttachmentDescription colorBlendAttachment{};
colorBlendAttachment.format = vk.scImageFormat;
colorBlendAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
colorBlendAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
colorBlendAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
colorBlendAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
colorBlendAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
colorBlendAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
colorBlendAttachment.finalLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
VkAttachmentReference colorAttachmentRef{};
colorAttachmentRef.attachment = 0;
colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
/* VkAttachmentDescription depthAttachment{}; */
/* depthAttachment.format = findDepthFormat(); */
/* depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT; */
/* depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; */
/* depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; */
/* depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; */
/* depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; */
/* depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; */
/* depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; */
/* VkAttachmentReference depthAttachmentRef{}; */
/* depthAttachmentRef.attachment = 1; */
/* depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; */
VkSubpassDescription subpass{};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass.colorAttachmentCount = 1;
subpass.pColorAttachments = &colorAttachmentRef;
/* subpass.pDepthStencilAttachment = &depthAttachmentRef; */
VkSubpassDependency dependency{};
dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
dependency.dstSubpass = 0;
dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dependency.srcAccessMask = 0;
dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
/* 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.srcAccessMask = 0; */
/* dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; */
/* dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; */
/* std::array<VkAttachmentDescription, 2> attachments = { colorBlendAttachment, depthAttachment }; */
std::vector<VkAttachmentDescription> attachments = { colorBlendAttachment };
VkRenderPassCreateInfo renderPassCI{};
renderPassCI.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
renderPassCI.attachmentCount = static_cast<uint32_t>(attachments.size());
renderPassCI.pAttachments = attachments.data();
renderPassCI.subpassCount = 1;
renderPassCI.pSubpasses = &subpass;
renderPassCI.dependencyCount = 1;
renderPassCI.pDependencies = &dependency;
/* renderPassCI.dependencyCount = 0; */
/* renderPassCI.pDependencies = nullptr; */
/* renderPassCI.correlatedViewMaskCount = 0; */
/* renderPassCI.pCorrelatedViewMasks = nullptr; */
VkResult result = vkCreateRenderPass(vk.device, &renderPassCI, nullptr, &renderPass);
if (result != VK_SUCCESS) {
throw getVkException(result, "Could not create render pass", "Renderer2D::createRenderPass");
}
rLog("createRenderPass: Created render pass.");
}
void Renderer2D::recordCommandBuffer(uint32_t imageIndex, uint32_t currentFrame) {
VkCommandBufferBeginInfo commandBufferBI{};
commandBufferBI.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
/* commandBufferBI.flags = 0; */
/* commandBufferBI.pInheritanceInfo = nullptr; */
VkResult result = vkBeginCommandBuffer(commandBuffers[currentFrame], &commandBufferBI);
if (result != VK_SUCCESS) {
throw getVkException(result, "Failed to begin 2D command buffer", "Renderer2D::recordCommandBuffer");
}
VkRenderPassBeginInfo renderPassBI{};
renderPassBI.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
renderPassBI.renderPass = renderPass;
renderPassBI.framebuffer = vk.getFramebuffers(renderPassID)[imageIndex];
renderPassBI.renderArea.offset = { 0, 0 };
renderPassBI.renderArea.extent = vk.scExtent;
// clear
std::array<VkClearValue, 2> clearValues{};
clearValues[0].color = {{1.0f, 0.0f, 0.0f, 1.0f}};
clearValues[1].depthStencil = {1.0f, 0};
renderPassBI.clearValueCount = static_cast<uint32_t>(clearValues.size());
renderPassBI.pClearValues = clearValues.data();
vkCmdBeginRenderPass(commandBuffers[currentFrame], &renderPassBI, VK_SUBPASS_CONTENTS_INLINE);
vkCmdBindPipeline(commandBuffers[currentFrame], VK_PIPELINE_BIND_POINT_GRAPHICS, vk.pipelines[PL_2D].pipeline);
VkBuffer vertexBuffers[] = { vertexBuffer };
VkDeviceSize offsets[] = {0};
uint32_t bindingCount = 1;
vkCmdBindVertexBuffers(commandBuffers[currentFrame], BINDING, bindingCount, vertexBuffers, offsets);
// TODO use correct index type!
vkCmdBindIndexBuffer(commandBuffers[currentFrame], indexBuffer, NO_OFFSET, VK_INDEX_TYPE_UINT32);
/* uint32_t descriptorCount = 1; */
/* uint32_t firstSet = 0; */
/* uint32_t dynamicOffsetCount = 0; */
/* uint32_t* dynamicOffsets = nullptr; */
/* vkCmdBindDescriptorSets(commandBuffers[currentFrame], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines[PL_2D].layout, firstSet, descriptorCount, &descriptorSets[currentFrame], dynamicOffsetCount, dynamicOffsets); */
int instanceCount = 1;
int firstIndex = 0;
int firstInstance = 0;
vkCmdDrawIndexed(commandBuffers[currentFrame], static_cast<uint32_t>(shapesIndicesCount), instanceCount, firstIndex, NO_OFFSET, firstInstance);
vkCmdEndRenderPass(commandBuffers[currentFrame]);
vk.copyImageToImage(commandBuffers[currentFrame], images[imageIndex], vk.scImages[imageIndex], vk.scExtent.width, vk.scExtent.height);
result = vkEndCommandBuffer(commandBuffers[currentFrame]);
if (result != VK_SUCCESS) {
rLog.error("Failed to record 2D - command buffer", "VkResult:", STR_VK_RESULT(result));
throw getVkException(result, "Failed to record 2D - command buffer", "Renderer2D::recordCommandBuffer");
}
vk.commandBuffersToSubmitThisFrame.push_back(commandBuffers[currentFrame]);
}
void Renderer2D::fillVertexBufferWithShapes() {
rLog("fillVertexBufferWithShapes");
if (vertexBufferSize < shapesVerticesCount * sizeof(Vertex2D)) {
throw VkException("vertex buffer too small! vertexBufferSize: " + std::to_string(vertexBufferSize) + ", required size: " + std::to_string(shapesVerticesCount), "fillVertexBufferWithShapes");
}
// create staging buffer
VkBuffer stagingBuffer;
VkDeviceMemory stagingBufferMemory;
vk.createBuffer(vertexBufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory);
// fill staging buffer
void* data;
vkMapMemory(vk.device, stagingBufferMemory, NO_OFFSET, vertexBufferSize, NO_FLAGS, &data);
Vertex2D* vdata = reinterpret_cast<Vertex2D*>(data);
size_t offset = 0;
for (auto it = shapes.begin(); it != shapes.end(); it++) {
rLog("fillVertexBufferWithShapes: copying vertex buffer nr", it - shapes.begin(), "-", it->getVertices(), "to address:", long(vdata + offset), " offset:", offset);
memcpy(vdata+offset, it->getVertices().data(), it->getVertices().size() * sizeof(Vertex2D));
offset += it->getVertices().size();
}
vkUnmapMemory(vk.device, stagingBufferMemory);
// fill vertex buffer
vk.copyBuffer(stagingBuffer, vertexBuffer, vertexBufferSize);
vkDestroyBuffer(vk.device, stagingBuffer, nullptr);
vkFreeMemory(vk.device, stagingBufferMemory, nullptr);
}
void Renderer2D::fillIndexBufferWithShapes() {
rLog("fillIndexBufferWithShapes");
if (indexBufferSize < shapesIndicesCount * sizeof(uint32_t)) {
throw VkException("index buffer too small! indexBufferSize: " + std::to_string(vertexBufferSize) + ", required size: " + std::to_string(shapesVerticesCount), "fillVertexBufferWithShapes");
}
// create staging buffer
VkBuffer stagingBuffer;
VkDeviceMemory stagingBufferMemory;
vk.createBuffer(indexBufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory);
// fill staging buffer
void* data;
vkMapMemory(vk.device, stagingBufferMemory, NO_OFFSET, indexBufferSize, NO_FLAGS, &data);
uint32_t* idata = reinterpret_cast<uint32_t*>(data);
size_t offset = 0;
for (auto it = shapes.begin(); it != shapes.end(); it++) {
rLog("fillIndexBufferWithShapes: copying index buffer nr", it - shapes.begin(), "-", it->getIndices(), "to address:", long(idata + offset), " offset:", offset);
memcpy(idata+offset, it->getIndices().data(), it->getIndices().size() * sizeof(uint32_t));
offset += it->getIndices().size();
}
rLog("fillIndexBufferWithShapes: indices count:", shapesIndicesCount);
vkUnmapMemory(vk.device, stagingBufferMemory);
// fill index buffer
vk.copyBuffer(stagingBuffer, indexBuffer, indexBufferSize);
vkDestroyBuffer(vk.device, stagingBuffer, nullptr);
vkFreeMemory(vk.device, stagingBufferMemory, nullptr);
}
void Renderer2D::drawShape(const Shape& shape) {
shapes.emplace_back(shape);
// make indices valid
shapes.rbegin()->setIndexOffset(shapesVerticesCount);
shapes.rbegin()->setNormalize(vk.scExtent.width, vk.scExtent.height);
shapesVerticesCount += shape.getVertices().size();
shapesIndicesCount += shape.getIndices().size();
}
void Renderer2D::drawFrame(uint32_t imageIndex) {
vkResetCommandBuffer(commandBuffers[vk.currentFrame], NO_FLAGS);
recordCommandBuffer(imageIndex, vk.currentFrame);
}
} // namespace gz::vk

62
renderer2D.hpp Normal file
View File

@ -0,0 +1,62 @@
#include "renderer.hpp"
namespace gz::vk {
class Renderer2D : public Renderer {
public:
Renderer2D(VulkanInstance& instance);
void drawShape(const Shape& shape);
/**
* @brief Copies the vertices from shapes into the vertex buffer, using a staging buffer
*/
void fillVertexBufferWithShapes();
void fillIndexBufferWithShapes();
void drawFrame(uint32_t imageIndex);
void cleanup();
/**
* @brief Create a render pass
* @details
* Attachments:
* - color blend:
* - loadOp = VK_ATTACHMENT_LOAD_OP_LOAD (not clear!)
* - storeOp = VK_ATTACHMENT_STORE_OP_STORE
* - initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL
* - finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL
* - stencil load/store = dont care
*/
void createRenderPass();
private:
std::vector<Shape> shapes;
size_t shapesVerticesCount = 0;
size_t shapesIndicesCount = 0;
/**
* @brief Render everything
* @details
* As of now, this does (in the same command buffer from graphicsPool)
* -# begin render pass
* - image layout: VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL
* - clear image
* -# bind 2d pipeline, vertex and index buffer
* -# draw indexed: draw the shapes from shapes vector
* -# end render pass
* - image layout: VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL
* -# copy image to swapChain image with same imageIndex
*
*/
void recordCommandBuffer(uint32_t imageIndex, uint32_t currentFrame);
// IMAGE
std::vector<VkImage> images;
std::vector<VkDeviceMemory> imageMemory;
std::vector<VkImageView> imageViews;
/**
* @brief Creates the images and imageViews with the format of the VulkanInstance::swapChain images
*/
void createImages();
VkRenderPass renderPass;
int renderPassID;
Log rLog;
};
} // namespace gz::vk

105
renderer3D.cpp Normal file
View File

@ -0,0 +1,105 @@
#include "renderer3D.hpp"
#include "exceptions.hpp"
#include "vk_enum_string.h"
#include <cstring>
namespace gz::vk {
Renderer3D::Renderer3D(VulkanInstance& instance) :
Renderer(instance),
rLog("renderer3D.log", true, false, "3D-Renderer", Color::BCYAN, true, 100) {
vk.createCommandBuffers(commandBuffers);
const size_t vertexCount = 500;
const size_t indexCount = 1000;
vk.createVertexBuffer<Vertex2D>(vertexCount, vertexBuffer, vertexBufferMemory, vertexBufferSize);
vk.createIndexBuffer<uint32_t>(indexCount, indexBuffer, indexBufferMemory, indexBufferSize);
rLog("Created Renderer3D");
}
void Renderer3D::cleanup() {
for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
vkDestroyBuffer(vk.device, uniformBuffers[i], nullptr);
vkFreeMemory(vk.device, uniformBuffersMemory[i], nullptr);
}
cleanup_();
}
void Renderer3D::updateUniformBuffer() {
static auto startTime = std::chrono::high_resolution_clock::now();
auto currentTime = std::chrono::high_resolution_clock::now();
float time = std::chrono::duration<float, std::chrono::seconds::period>(currentTime - startTime).count();
// TODO use push constant instead of ubo
UniformBufferObject ubo{};
/* ubo.model = glm::rotate(glm::mat4(1.0f), time * std::numbers::pi_v<float> / 2, glm::vec3(0.0f, 0.0f, 1.0f)); */
/* ubo.view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); */
/* ubo.projection = glm::perspective(glm::radians(45.0f), static_cast<float>(scExtent.width) / scExtent.height, 1.0f, 10.0f); */
ubo.model = glm::mat4(1);
ubo.view = glm::mat4(1);
ubo.projection = glm::mat4(1);
/* ubo.projection[1][1] *= -1; // y coordinate inverted in opengl */
void* data;
vkMapMemory(vk.device, uniformBuffersMemory[vk.currentFrame], NO_OFFSET, sizeof(ubo), NO_FLAGS, &data);
memcpy(data, &ubo, sizeof(ubo));
vkUnmapMemory(vk.device, uniformBuffersMemory[vk.currentFrame]);
}
void Renderer3D::recordCommandBuffer(uint32_t imageIndex, uint32_t currentFrame) {
VkCommandBufferBeginInfo commandBufferBI{};
commandBufferBI.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
/* commandBufferBI.flags = 0; */
/* commandBufferBI.pInheritanceInfo = nullptr; */
VkResult result = vkBeginCommandBuffer(commandBuffers[currentFrame], &commandBufferBI);
if (result != VK_SUCCESS) {
throw getVkException(result, "Failed to begin 3D - command buffer", "recordCommandBuffer");
}
VkRenderPassBeginInfo renderPassBI{};
renderPassBI.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
renderPassBI.renderPass = vk.renderPassBegin;
// TODO
/* renderPassBI.framebuffer = vk.scFramebuffers[0][imageIndex]; */
renderPassBI.renderArea.offset = { 0, 0 };
renderPassBI.renderArea.extent = vk.scExtent;
std::array<VkClearValue, 2> clearValues{};
clearValues[0].color = {{0.0f, 0.0f, 0.0f, 1.0f}};
clearValues[1].depthStencil = {1.0f, 0};
renderPassBI.clearValueCount = static_cast<uint32_t>(clearValues.size());
renderPassBI.pClearValues = clearValues.data();
vkCmdBeginRenderPass(commandBuffers[currentFrame], &renderPassBI, VK_SUBPASS_CONTENTS_INLINE);
vkCmdBindPipeline(commandBuffers[currentFrame], VK_PIPELINE_BIND_POINT_GRAPHICS, vk.pipelines[PL_3D].pipeline);
VkBuffer vertexBuffers[] = { vertexBuffer };
VkDeviceSize offsets[] = {0};
uint32_t bindingCount = 1;
vkCmdBindVertexBuffers(commandBuffers[currentFrame], BINDING, bindingCount, vertexBuffers, offsets);
// TODO use correct index type!
vkCmdBindIndexBuffer(commandBuffers[currentFrame], indexBuffer, NO_OFFSET, VK_INDEX_TYPE_UINT32);
uint32_t descriptorCount = 1;
uint32_t firstSet = 0;
uint32_t dynamicOffsetCount = 0;
uint32_t* dynamicOffsets = nullptr;
vkCmdBindDescriptorSets(commandBuffers[currentFrame], VK_PIPELINE_BIND_POINT_GRAPHICS, vk.pipelines[PL_3D].layout, firstSet, descriptorCount, &vk.descriptorSets[currentFrame], dynamicOffsetCount, dynamicOffsets);
int instanceCount = 1;
int firstIndex = 0;
int firstInstance = 0;
/* vkCmdDrawIndexed(commandBuffers[currentFrame], static_cast<uint32_t>(shapesIndicesCount), instanceCount, firstIndex, NO_OFFSET, firstInstance); */
vkCmdEndRenderPass(commandBuffers[currentFrame]);
result = vkEndCommandBuffer(commandBuffers[currentFrame]);
if (result != VK_SUCCESS) {
rLog.error("Failed to record command buffer", "VkResult:", STR_VK_RESULT(result));
throw getVkException(result, "Failed to record command buffer");
}
}
} // namespace gz::vk

50
renderer3D.hpp Normal file
View File

@ -0,0 +1,50 @@
#include "renderer.hpp"
namespace gz::vk {
struct UniformBufferObject {
alignas(16) glm::mat4 model;
alignas(16) glm::mat4 view;
alignas(16) glm::mat4 projection;
};
const std::vector<Vertex3D> vertices2 = {
{{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 1.0f}, {0.0f, 0.0f}},
{{ 0.5f, -0.5f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}},
{{ 0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}},
{{-0.5f, 0.5f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}},
{{-0.5f, -0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}},
{{ 0.5f, -0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}},
{{ 0.5f, 0.5f, -0.5f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}},
{{-0.5f, 0.5f, -0.5f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}},
};
/* const std::vector<Vertex> vertices = { */
/* {{-1.0f, -1.0f}, {0.0f, 0.0f, 1.0f}}, */
/* {{0.5f, 0.5f}, {0.0, 1.0f, 1.0f}}, */
/* {{-1.0, 0.0f}, {1.0f, 0.0f, 0.0f}}, */
/* {{1.0f, 1.0f}, {1.0f, 0.0f, 0.0f}}, */
/* {{-0.5f, -0.5f}, {0.0, 1.0f, 1.0f}}, */
/* {{1.0, 0.0f}, {1.0f, 0.0f, 1.0f}}, */
/* }; */
const std::vector<uint16_t> indices2 = {
0, 1, 2, 2, 3, 0,
4, 5, 6, 6, 7, 4
};
class Renderer3D : public Renderer {
public:
Renderer3D(VulkanInstance& instance);
void cleanup();
protected:
void recordCommandBuffer(uint32_t imageIndex, uint32_t currentFrame);
std::vector<VkBuffer> uniformBuffers;
std::vector<VkDeviceMemory> uniformBuffersMemory;
void updateUniformBuffer();
Log rLog;
};
}

16
shader.frag Normal file
View File

@ -0,0 +1,16 @@
#version 450
#extension GL_EXT_debug_printf : enable
layout(binding = 1) uniform sampler2D textureSampler;
layout(location = 0) in vec3 fragColor;
layout(location = 1) in vec2 fragTextureCoordinate;
layout(location = 0) out vec4 outColor;
void main() {
/* outColor = vec4(fragColor, 1.0); */
/* debugPrintfEXT("outColor %v3f", fragColor); */
outColor = vec4(fragTextureCoordinate, 0.0, 1.0);
/* outColor = vec4(fragColor * texture(textureSampler, fragTextureCoordinate).rgb, 1.0); */
}

25
shader.vert Normal file
View File

@ -0,0 +1,25 @@
#version 450
#extension GL_EXT_debug_printf : enable
layout(binding = 0) uniform UniformBufferObject {
mat4 model;
mat4 view;
mat4 proj;
} ubo;
layout(location = 0) in vec3 inPosition;
layout(location = 1) in vec3 inColor;
layout(location = 2) in vec2 inTextureCoordinate;
layout(location = 0) out vec3 fragColor;
layout(location = 1) out vec2 fragTextureCoordinate;
void main() {
gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 1.0);
/* gl_Position = vec4(inPosition, 1.0); */
debugPrintfEXT("inPosition %v3f, inColor %v3f", inPosition, inColor);
fragColor = inColor;
fragTextureCoordinate = inTextureCoordinate;
}

16
shader2D.frag Normal file
View File

@ -0,0 +1,16 @@
#version 450
#extension GL_EXT_debug_printf : enable
/* layout(binding = 1) uniform sampler2D textureSampler; */
layout(location = 0) in vec3 fragColor;
layout(location = 1) in vec2 fragTextureCoordinate;
layout(location = 0) out vec4 outColor;
void main() {
outColor = vec4(fragColor, 1.0);
/* debugPrintfEXT("outColor %v3f", fragColor); */
/* outColor = vec4(fragTextureCoordinate, 0.0, 1.0); */
/* outColor = vec4(fragColor * texture(textureSampler, fragTextureCoordinate).rgb, 1.0); */
}

25
shader2D.vert Normal file
View File

@ -0,0 +1,25 @@
#version 450
#extension GL_EXT_debug_printf : enable
/* layout(binding = 0) uniform UniformBufferObject { */
/* mat4 model; */
/* mat4 view; */
/* mat4 proj; */
/* } ubo; */
layout(location = 0) in vec2 inPosition;
layout(location = 1) in vec3 inColor;
layout(location = 2) in vec2 inTextureCoordinate;
layout(location = 0) out vec3 fragColor;
layout(location = 1) out vec2 fragTextureCoordinate;
void main() {
/* gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 0.0, 1.0); */
gl_Position = vec4(inPosition, 0.0, 1.0);
debugPrintfEXT("inPosition %v2f, inColor %v3f", inPosition, inColor);
fragColor = inColor;
fragTextureCoordinate = inTextureCoordinate;
}

34
shape.cpp Normal file
View File

@ -0,0 +1,34 @@
#include "shape.hpp"
namespace gz::vk {
Rectangle::Rectangle(float top, float left, uint32_t width, uint32_t height, glm::vec3 color)
: top(top), left(left), width(width), height(height), color(color) {
generateVertices();
}
void Rectangle::generateVertices() {
vertices.clear();
vertices.emplace_back(Vertex2D{glm::vec2(top, left), color, glm::vec2(0, 0)});
vertices.emplace_back(Vertex2D{glm::vec2(top, left + width), color, glm::vec2(1, 0)});
vertices.emplace_back(Vertex2D{glm::vec2(top + height, left + width), color, glm::vec2(1, 1)});
vertices.emplace_back(Vertex2D{glm::vec2(top + height, left), color, glm::vec2(0, 1)});
indices = { 0, 1, 2, 2, 3, 0 };
/* indices = { 2, 1, 0, 2, 3, 0 }; */
}
void Shape::setIndexOffset(uint32_t offset) {
for (size_t i = 0; i < indices.size(); i++) {
indices[i] += offset;
}
}
void Shape::setNormalize(float width, float height) {
for (size_t i = 0; i < vertices.size(); i++) {
vertices[i].pos.x /= width;
vertices[i].pos.y /= height;
}
}
} // namespaec gz::vk

29
shape.hpp Normal file
View File

@ -0,0 +1,29 @@
#pragma once
#include "vertex.hpp"
#include <cstdint>
namespace gz::vk {
class Shape {
public:
const std::vector<Vertex2D>& getVertices() const { return vertices; }
const std::vector<uint32_t>& getIndices() const { return indices; }
void setIndexOffset(uint32_t offset);
void setNormalize(float width, float height);
protected:
std::vector<Vertex2D> vertices;
std::vector<uint32_t> indices;
};
class Rectangle : public Shape {
public:
Rectangle(float top, float left, uint32_t width, uint32_t height, glm::vec3 color);
private:
float top, left;
uint32_t width, height;
glm::vec3 color;
void generateVertices();
};
} // namespace gz::vk

73
vertex.cpp Normal file
View File

@ -0,0 +1,73 @@
#include "vertex.hpp"
#include <vulkan/vulkan_core.h>
#include <gz-util/util/string_conversion.hpp>
#include <glm/gtc/matrix_transform.hpp>
namespace gz::vk {
const uint32_t BINDING = 0;
template<GLM_vec2_or_3 PosVec>
VkFormat getVkFormat() {
if (std::same_as<PosVec, glm::vec3>) {
return VK_FORMAT_R32G32B32_SFLOAT;
}
else if (std::same_as<PosVec, glm::vec2>) {
return VK_FORMAT_R32G32_SFLOAT;
}
}
//
// 3D VERTEX
//
template<GLM_vec2_or_3 PosVec>
std::string Vertex<PosVec>::toString() const {
return "pos: " + gz::toString(pos) + ", color: " + gz::toString(color) + ", texCoords: <TODO>"; // + gz::toString(texCoord);
};
template<GLM_vec2_or_3 PosVec>
VkVertexInputBindingDescription Vertex<PosVec>::getBindingDescription() {
VkVertexInputBindingDescription bindingD{};
bindingD.binding = BINDING;
bindingD.stride = sizeof(Vertex<PosVec>);
bindingD.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
return bindingD;
}
template<GLM_vec2_or_3 PosVec>
std::array<VkVertexInputAttributeDescription, 3> Vertex<PosVec>::getAttributeDescriptions() {
std::array<VkVertexInputAttributeDescription, 3> inputAttributeD{};
inputAttributeD[0].binding = BINDING;
inputAttributeD[0].location = 0;
inputAttributeD[0].format = getVkFormat<PosVec>();
inputAttributeD[0].offset = offsetof(Vertex, pos);
inputAttributeD[1].binding = BINDING;
inputAttributeD[1].location = 1;
inputAttributeD[1].format = VK_FORMAT_R32G32B32_SFLOAT;
inputAttributeD[1].offset = offsetof(Vertex, color);
inputAttributeD[2].binding = BINDING;
inputAttributeD[2].location = 2;
inputAttributeD[2].format = VK_FORMAT_R32G32_SFLOAT;
inputAttributeD[2].offset = offsetof(Vertex, texCoord);
return inputAttributeD;
}
template<GLM_vec2_or_3 PosVec>
bool Vertex<PosVec>::operator==(const Vertex<PosVec>& other) const {
return pos == other.pos and color == other.color and texCoord == other.texCoord;
}
template std::string Vertex<glm::vec2>::toString() const;
template std::string Vertex<glm::vec3>::toString() const;
template VkVertexInputBindingDescription Vertex<glm::vec2>::getBindingDescription();
template VkVertexInputBindingDescription Vertex<glm::vec3>::getBindingDescription();
template std::array<VkVertexInputAttributeDescription, 3> Vertex<glm::vec2>::getAttributeDescriptions();
template std::array<VkVertexInputAttributeDescription, 3> Vertex<glm::vec3>::getAttributeDescriptions();
template bool Vertex<glm::vec2>::operator==(const Vertex<glm::vec2>& other) const;
template bool Vertex<glm::vec3>::operator==(const Vertex<glm::vec3>& other) const;
} // namespace gz::vk

59
vertex.hpp Normal file
View File

@ -0,0 +1,59 @@
#pragma once
/* #include <bits/ranges_base.h> */
#include <glm/glm.hpp>
#include <glm/gtx/hash.hpp>
#include <array>
#include <string>
/* #include <vulkan/vulkan_core.h> */
struct VkVertexInputBindingDescription;
struct VkVertexInputAttributeDescription;
namespace gz::vk {
template<typename T>
concept VertexType = requires {
requires std::same_as<decltype(T::getBindingDescription()), VkVertexInputBindingDescription>;
requires std::ranges::forward_range<decltype(T::getAttributeDescriptions())>;
requires std::same_as<std::ranges::range_value_t<decltype(T::getAttributeDescriptions())>, VkVertexInputAttributeDescription>;
};
template<typename T>
concept GLM_vec2_or_3 = std::same_as<glm::vec3, T> or std::same_as<glm::vec2, T>;
//
// VERTEX
//
/**
* @brief Base for 2D and 3D vertices with texture and/or color
*/
template<GLM_vec2_or_3 PosVec>
struct Vertex {
PosVec pos;
glm::vec3 color;
glm::vec2 texCoord;
std::string toString() const;
bool operator==(const Vertex& other) const;
static VkVertexInputBindingDescription getBindingDescription();
static std::array<VkVertexInputAttributeDescription, 3> getAttributeDescriptions();
}; // struct Vertex
using Vertex3D = Vertex<glm::vec3>;
using Vertex2D = Vertex<glm::vec2>;
} // namespace gz::vk
//
// HASHES
//
namespace std {
template<gz::vk::GLM_vec2_or_3 PosVec>
struct hash<gz::vk::Vertex<PosVec>> {
size_t operator()(gz::vk::Vertex<PosVec> const& vertex) const {
return ((hash<PosVec>()(vertex.pos)) ^
(hash<glm::vec3>()(vertex.color) << 1) >> 1 ) ^
(hash<glm::vec2>()(vertex.texCoord) << 1);
}
};
}

37
vk_convert.cpp Normal file
View File

@ -0,0 +1,37 @@
#include "vk_convert.hpp"
bool vkBool2Bool(const VkBool32& b) {
if (b == VK_TRUE) {
return true;
}
else {
return false;
}
};
VkBool32 bool2VkBool(const bool& b) {
if (b) {
return VK_TRUE;
}
else {
return VK_FALSE;
}
}
std::string vkBool2String(const VkBool32& b) {
if (b == VK_TRUE) {
return "true";
}
else {
return "false";
}
};
VkBool32 string2VkBool(const std::string& s) {
if (s == "true") {
return VK_TRUE;
}
else {
return VK_FALSE;
}
}

9
vk_convert.hpp Normal file
View File

@ -0,0 +1,9 @@
#include <vulkan/vulkan_core.h>
#include <string>
VkBool32 bool2VkBool(const bool& b);
bool vkBool2Bool(const VkBool32& b);
VkBool32 string2VkBool(const std::string& s);
std::string vkBool2String(const VkBool32& b);

2
vk_layer_settings.txt Normal file
View File

@ -0,0 +1,2 @@
# https://github.com/KhronosGroup/Vulkan-ValidationLayers/blob/master/layers/vk_layer_settings.txt
khronos_validation.enables = VK_LAYER_ENABLES=VK_VALIDATION_FEATURE_ENABLE_BEST_PRACTICES_EXT;VALIDATION_CHECK_ENABLE_VENDOR_SPECIFIC_AMD

4
vulkan.conf Normal file
View File

@ -0,0 +1,4 @@
# Written by writeKeyValueFile
max_anisotropy = 1
anisotropy_enable = false
framerate = 60

2200
vulkan_instance.cpp Normal file

File diff suppressed because it is too large Load Diff

487
vulkan_instance.hpp Normal file
View File

@ -0,0 +1,487 @@
#pragma once
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
#include <glm/glm.hpp>
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>
#include "vk_convert.hpp"
#include "vertex.hpp"
#include "shape.hpp"
#include "vulkan_util.hpp"
#include <gz-util/log.hpp>
#include <gz-util/settings_manager.hpp>
#include <gz-util/util/string.hpp>
#include <vulkan/vulkan_core.h>
#include <cstdint>
#include <vector>
namespace gz::vk {
const std::string CONFIG_FILE = "vulkan.conf";
const int MAX_FRAMES_IN_FLIGHT = 3;
#define SettingsTypes uint32_t, bool, float
/* const std::string MODEL_PATH = "models/gazebo-3d-model/gazebo.obj"; */
/* const std::string TEXTURE_PATH = "models/gazebo-3d-model/gazebo_diffuse.png"; */
const std::string MODEL_PATH = "models/armoire-3d-model/Armoire.obj";
const std::string TEXTURE_PATH = "models/armoire-3d-model/Armoire_diffuse.png";
const gz::util::unordered_string_map<std::string> INITIAL_SETTINGS = {
{ "framerate", "60" },
{ "anisotropy_enable", "false" },
{ "max_anisotropy", "1" },
/* { "", "" } */
};
const int BINDING = 0;
const uint32_t NO_FLAGS = 0;
const uint32_t NO_OFFSET = 0;
const size_t VERTEX_BUFFER_SIZE = 512;
const size_t INDEX_BUFFER_SIZE = 512;
class VulkanInstance {
friend class Renderer;
friend class Renderer2D;
friend class Renderer3D;
public:
VulkanInstance(gz::SettingsManagerCreateInfo<SettingsTypes>smCI) : settings(smCI) {};
void init();
uint32_t beginFrameDraw();
void submitThisFrame(VkCommandBuffer);
void endFrameDraw(uint32_t imageIndex);
void deInit();
GLFWwindow* window;
void cleanup();
/// The frame in the swap chain that is currently drawn to
uint32_t currentFrame = 0;
//
// SETTINGS
//
gz::SettingsManager<SettingsTypes> settings;
private:
void mainLoop();
std::vector<VkCommandBuffer> commandBuffersToSubmitThisFrame;
void createWindow();
//
// INSTANCE
//
VkInstance instance;
/**
* @brief Create the vulkan instance
* @details
* - check if validationLayers are available (if enabled)
* - create instance with info
* - check if all extensions required by glfw are available
*/
void createInstance();
//
// PHYSICAL DEVICE
//
VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;
VkPhysicalDeviceProperties phDevProperties;
VkPhysicalDeviceFeatures phDevFeatures;
/**
* @brief Assign the physicalDevice handle to the @ref rateDevice "best rated" GPU
* @details
* After this method, physicalDevice, phDevProperties and phDevFeatures will be initialized
*/
void selectPhysicalDevice();
/**
* @brief Assigns a score to a physical device.
* @details
* score = GPU_TYPE_FACTOR + VRAM_SIZE (in MB) + SUPPORTED_QUEUES_FACTOR + FEATURES_FACTORS
* A GPU is unsuitable and gets score 0 if it does not:
* - have the needed queue families
* - support all necessary extensions
* - have swap chain support
*/
unsigned int rateDevice(VkPhysicalDevice device);
/**
* @brief Set valid values for the SettingsManager according to phDevFeatures and phDevProperties
* @details
* Must be called after selectPhysicalDevice
* Sets valid values for:
* - anisotropy_enable
* - max_anisotropy
*/
void setValidSettings();
/**
* @brief Find the best of the supported formats
* @param candidates Candidate format, from best to worst
* @returns The first format from candidates that is supported
*/
VkFormat findSupportedFormat(const std::vector<VkFormat>& candidates, VkImageTiling tiling, VkFormatFeatureFlags features);
//
// QUEUE
//
QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device);
QueueFamilyIndices qFamilyIndices;
VkQueue graphicsQ;
VkQueue presentQ;
// if possible, use dedicated transferQ. if not available, transferQ = graphicsQ
VkQueue transferQ;
//
// LOGICAL DEVICE
//
VkDevice device;
/**
* @details
* request anisotropic sampling feature
*/
void createLogicalDevice();
//
// SURFACE
//
VkSurfaceKHR surface;
void createSurface();
//
// SWAP CHAIN
//
VkSwapchainKHR swapChain;
std::vector<VkImage> scImages;
VkFormat scImageFormat;
VkExtent2D scExtent;
SwapChainSupport querySwapChainSupport(VkPhysicalDevice device);
/**
* @todo Rate formats if preferred is not available
*/
VkSurfaceFormatKHR selectSwapChainSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& availableFormats);
/**
* @todo Check settings for preferred mode
*/
VkPresentModeKHR selectSwapChainPresentMode(const std::vector<VkPresentModeKHR>& availableModes);
VkExtent2D selectSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities);
void createSwapChain();
void recreateSwapChain();
void cleanupSwapChain();
//
// IMAGE VIEW
//
std::vector<VkImageView> scImageViews;
void createImageViews();
/**
* @brief Create a 2D imageView with format for image.
*/
void createImageView(VkFormat format, VkImage& image, VkImageView& imageView, VkImageAspectFlags aspectFlags);
//
// RENDER PASS
//
VkRenderPass renderPassBegin;
VkRenderPass renderPassEnd;
/**
* @brief Create the render pass
* @details
* The subpass will contain the following attachments
* -# color attachment
* -# depth stencil attachment
*/
void createRenderPassBegin();
void createRenderPassEnd();
//
// DESCRIPTORS
//
/**
* @name Create desciptors
* @details These functions create a desciptor with bindings for
* -# UniformBufferObject (DESCRIPTOR_TYPE_UNIFORM_BUFFER)
* -# Combined Image Sampler (DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER)
* @{
*/
VkDescriptorSetLayout descriptorSetLayout;
/**
* @brief Create a descriptor layout binding for the MVP uniform buffer object
*/
void createDescriptorSetLayout();
VkDescriptorPool descriptorPool;
/**
* @brief Create a descriptor layout binding for the MVP uniform buffer object
* @details Create a desciptor set layout with bindings for
* -# UniformBufferObject (DESCRIPTOR_TYPE_UNIFORM_BUFFER)
* -# Combined Image Sampler (DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER)
*/
void createDescriptorPool();
std::vector<VkDescriptorSet> descriptorSets;
/*
* @bried Create one descriptor set with layout descriptorSetLayout for each frame
*/
void createDescriptorSets();
const uint32_t bindingUniformBuffer = 0;
const uint32_t bindingCombinedImageSampler = 1;
/* const uint32_t bindingCombinedImageSampler = 1; */
/**
* @}
*/
//
// PIPELINE
//
PipelineContainer pipelines;
/* void createGraphicsPipeline(); */
VkShaderModule createShaderModule(const std::vector<char>& code);
/**
* @brief Create a new graphics pipeline
* @param vertexShader Path to the SPIR-V vertex shader
* @param fragmentShader Path to the SPIR-V fragment shader
* @param useDepthStencil Wether to use depth // TODO
* @param renderPass The (already created) render pass
* @param pipelineLayout Pipeline layout handle to bind
* @param pipeline Pipeline handle to bind
* @details
* Create a pipeline with:
* - 2 shader stages: vertex and fragment shader
* - VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, vertices are triangles
* - viewport viewing the whole image as described by scExtent
* - scissor with offset (0, 0)
* - rasterizer:
* - triangles are filled with the colors from the vertex (VK_POLYGON_FILL)
* - counter clockwise front face (VK_FRONT_FACE_COUNTER_CLOCKWISE)
*
*
*/
template<VertexType T>
void createGraphicsPipeline(const std::string& vertexShader, const std::string& fragmentShader, bool useDepthStencil, VkRenderPass& renderPass, Pipeline& pipeline);
//
// FONT
//
// TODO
/* VkPipeline fontPipeline; */
/* VkPipelineLayout fontPipelineLayout; */
/* void createFontPipeline(); */
//
// BUFFERS
//
void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory, VkSharingMode sharingMode=VK_SHARING_MODE_EXCLUSIVE, std::vector<uint32_t>* qFamiliesWithAccess=nullptr);
uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties);
/**
* @brief Begin a command buffer that is going to be used once
* @param commandPool: The command pool from which the buffer should be allocated
*/
VkCommandBuffer beginSingleTimeCommands(VkCommandPool commandPool);
/**
* @brief Submit cmdBuffer on the queue and free it afterwards
* @param cmdBuffer: Command buffer returned by beginSingleTimeCommands()
* @param q: The queue belonging to the commandPool parameter from beginSingleTimeCommands()
*/
void endSingleTimeCommands(VkCommandBuffer cmdBuffer, VkCommandPool commandPool, VkQueue q);
/**
* @brief Copy from srcBuffer to dstBuffer
*/
void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size);
//
// VERTEX BUFFER
//
/**
* @brief Create a vertex buffer
* @param vertexCount Number of vertices the buffer should contain
* @todo Basically the same as createIndexBuffer
*/
template<VertexType VertexT>
void createVertexBuffer(size_t vertexCount, VkBuffer& vertexBuffer, VkDeviceMemory& vertexBufferMemory, VkDeviceSize& vertexBufferSize);
//
// INDEX BUFFER
//
template<SupportedIndexType T>
void createIndexBuffer(size_t indexCount, VkBuffer& indexBuffer, VkDeviceMemory& indexBufferMemory, VkDeviceSize& indexBufferSize);
//
// UNIFORM BUFFER
//
template<typename T>
void createUniformBuffers(std::vector<VkBuffer>& uniformBuffers, std::vector<VkDeviceMemory>& uniformBuffersMemory);
/* void createUniformBuffers(); */
//
// FRAMEBUFFERS
//
/* std::vector<VkFramebuffer> scFramebuffers; */
std::set<int> framebufferIDs;
// render pass is need for recreation of framebuffer
std::unordered_map<int, std::pair<std::vector<VkFramebuffer>, VkRenderPass>> scFramebuffers;
/**
* @brief Create a vector of framebuffers and return the id as a handle
* @returns id that can be used with getFramebuffers and destroyFramebuffers
*/
int createFramebuffers(std::vector<VkImageView>& imageViews, VkRenderPass& renderPass);
/**
* @brief Destroy the framebuffers from id
*/
void destroyFramebuffers(int id);
void createFramebuffers_(std::vector<VkFramebuffer>& framebuffers, std::vector<VkImageView>& imageViews, VkRenderPass& renderPass);
/**
* @brief Recreate all framebuffers in scFramebuffers
*/
void recreateFramebuffers();
/**
* @brief Get the framebuffer vector to id
*/
std::vector<VkFramebuffer>& getFramebuffers(int id);
bool frameBufferResized = false;
static void frameBufferResizedCallback(GLFWwindow* window, int width, int height);
//
// COMMAND POOL
//
VkCommandPool commandPoolGraphics;
VkCommandPool commandPoolTransfer;
void createCommandPools();
//
// COMMAND BUFFER
//
void createCommandBuffers(std::vector<VkCommandBuffer>& commandBuffers);
void destroyCommandBuffers(std::vector<VkCommandBuffer>& commandBuffers);
std::vector<VkCommandBuffer> commandBuffersBegin;
std::vector<VkCommandBuffer> commandBuffersEnd;
//
// IMAGE UTILITY
//
void createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags memoryProperties, VkImage& image, VkDeviceMemory& imageMemory);
void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height);
/**
* @todo make a version using vkCmdResolveImage for multisampled images
* @brief Copy srcImage to dstImage
* @details
* Both images must have:
* - same extent
* - mipLevel 0
* - layerCount 1
*
* Calls vkCmdBlitImage, but does NOT submit the commandBuffer
* @param cmdBuffer The command buffer where the command will be recorded to
* @param srcImage Image with layout TRANSFER_SRC_OPTIMAL
* @param dstImage Image with layout TRANSFER_DST_OPTIMAL
*/
void copyImageToImage(VkCommandBuffer& cmdBuffer, VkImage srcImage, VkImage dstImage, uint32_t width, uint32_t height); //, VkCommandPool& commandPool=commandPoolTransfer);
/**
* @brief Transition the layout of image from oldLayout to newLayout
* @details
* Supported transitions:
* - undefined -> depth stencil attachment (graphics q)
* - undefined -> transfer dst optimal (transfer q)
* - transfer dst optimal -> shader read only optimal (graphics q)
* - transfer dst optimal -> present src (graphics q)
*
* If you do not provide a command buffer, a command buffer from the indicated queue will be created and submitted.
* If you do provide a command buffer, the command is recorded but NOT submitted.
* @param cmdBuffer [Optional] The command buffer where the command will be recorded to
* @throws VkUserError if the transition is not supported.
*/
void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout, VkCommandBuffer* cmdBuffer=nullptr);
//
// DEPTH
//
VkImage depthImage;
VkDeviceMemory depthImageMemory;
VkImageView depthImageView;
VkFormat findDepthFormat();
void createDepthImageAndView();
//
// TEXTURES
//
VkImage textureImage;
VkDeviceMemory textureImageMemory;
VkImageView textureImageView;
VkSampler textureSampler;
/// Load textures/image.png
void createTextureImageAndView();
void createTextureSampler();
//
// SYNCHRONIZATION
//
std::vector<VkSemaphore> imageAvailableSemaphores;
std::vector<VkSemaphore> renderFinishedSemaphores;
std::vector<VkFence> inFlightFences;
void createSyncObjects();
//
// SHADERS
//
static std::vector<char> readFile(const std::string& filename);
//
// MODELS
//
void loadModel();
VerticesAndIndices<uint32_t> model;
//
// DEBUG
//
void setupDebugMessenger();
void cleanupDebugMessenger();
VkDebugUtilsMessengerEXT debugMessenger;
static gz::Log vLog;
public:
//
// UTILITY
//
/**
* @brief Log messages from validation layers with the Apps logger
* @details
* Using the static vLog to log vulkan messages with a prefix dependant on the messageType.
*/
static VKAPI_ATTR VkBool32 VKAPI_CALL debugLog(
VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverety,
VkDebugUtilsMessageTypeFlagsEXT messageType,
const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
void* pUserData);
template<typename T, typename... Args>
VkResult runVkResultFunction(const char* name, Args&&... args) {
auto f = reinterpret_cast<T>(vkGetInstanceProcAddr(instance, name));
if (f == nullptr) {
vLog.error("getVkFunction: Could not get function:", name);
throw std::runtime_error("getVkFunction: Could not get function.");
}
else {
return f(std::forward<Args>(args)...);
}
};
template<typename T, typename... Args>
void runVkVoidFunction(const char* name, Args&&... args) {
auto f = reinterpret_cast<T>(vkGetInstanceProcAddr(instance, name));
if (f == nullptr) {
vLog.error("getVkFunction: Could not get function:", name);
throw std::runtime_error("getVkFunction: Could not get function.");
}
else {
f(std::forward<Args>(args)...);
}
}
}; // VulkanInstance
//
// IMPLEMENTATIONS
//
//
// UNIFORM BUFFERS
//
template<typename T>
void VulkanInstance::createUniformBuffers(std::vector<VkBuffer>& uniformBuffers, std::vector<VkDeviceMemory>& uniformBuffersMemory) {
VkDeviceSize bufferSize = sizeof(T);
uniformBuffers.resize(MAX_FRAMES_IN_FLIGHT);
uniformBuffersMemory.resize(MAX_FRAMES_IN_FLIGHT);
for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
createBuffer(bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, uniformBuffers[i], uniformBuffersMemory[i]);
}
}
}
/**
* @file Creating a vulkan instance
* @todo Write/get allocator for buffers
*/

21
vulkan_util.cpp Normal file
View File

@ -0,0 +1,21 @@
#include "vulkan_util.hpp"
#include <vulkan/vulkan_core.h>
namespace gz::vk {
void PipelineContainer::erase(const PipelineT& key, VkDevice& device, const VkAllocationCallbacks* pAllocator) {
vkDestroyPipeline(device, pipelines[key].pipeline, pAllocator);
vkDestroyPipelineLayout(device, pipelines[key].layout, pAllocator);
pipelines.erase(pipelines.find(key));
}
PipelineContainer::iterator PipelineContainer::erase(const PipelineContainer::iterator& it, VkDevice& device, const VkAllocationCallbacks* pAllocator) {
vkDestroyPipeline(device, it->second.pipeline, pAllocator);
vkDestroyPipelineLayout(device, it->second.layout, pAllocator);
return pipelines.erase(it);
}
} // namespace gz::vk

89
vulkan_util.hpp Normal file
View File

@ -0,0 +1,89 @@
#include "vertex.hpp"
#include <optional>
#include <unordered_map>
#include <vulkan/vulkan_core.h>
namespace gz::vk {
enum PipelineT {
PL_3D, PL_2D
};
struct Pipeline {
VkPipeline pipeline;
VkPipelineLayout layout;
void operator=(const Pipeline& other) = delete;
};
/**
* @brief Map holding pipelines
*/
class PipelineContainer {
public:
using iterator = std::unordered_map<PipelineT, Pipeline>::iterator;
Pipeline& operator[](const PipelineT& key) { return pipelines[key]; }
/**
* @brief Destroy the pipeline+layout and then remove the handles
*/
void erase(const PipelineT& key, VkDevice& device, const VkAllocationCallbacks* pAllocator=nullptr);
iterator erase(const iterator& it, VkDevice& device, const VkAllocationCallbacks* pAllocator=nullptr);
iterator begin() { return pipelines.begin(); }
iterator end() { return pipelines.end(); }
size_t size() const { return pipelines.size(); }
private:
std::unordered_map<PipelineT, Pipeline> pipelines;
};
template<typename T>
concept SupportedIndexType = std::same_as<T, uint16_t> or std::same_as<T, uint32_t>;
template<SupportedIndexType T>
struct VerticesAndIndices {
std::vector<Vertex3D> vertices;
std::vector<T> indices;
constexpr VkIndexType getIndexType() const {
if (std::same_as<T, uint16_t>) {
return VK_INDEX_TYPE_UINT16;
}
else if (std::same_as<T, uint32_t>) {
return VK_INDEX_TYPE_UINT32;
}
}
};
struct QueueFamilyIndices {
std::optional<uint32_t> graphicsFamily;
std::optional<uint32_t> presentFamily;
std::optional<uint32_t> transferFamily;
bool hasNecessaryValues() {
return graphicsFamily.has_value() && presentFamily.has_value();
}
bool hasAllValues() {
return graphicsFamily.has_value() && presentFamily.has_value() && transferFamily.has_value();
}
std::string toString() const {
std::string s = "[ ";
s += "graphicsFamily: ";
if (graphicsFamily.has_value()) { s += std::to_string(graphicsFamily.value()); }
else { s += "not set"; }
s += ", presentFamily: ";
if (presentFamily.has_value()) { s += std::to_string(presentFamily.value()); }
else { s += "not set"; }
s += ", transferFamily: ";
if (transferFamily.has_value()) { s += std::to_string(transferFamily.value()); }
else { s += "not set"; }
return s + " ]";
}
};
struct SwapChainSupport {
VkSurfaceCapabilitiesKHR capabilities;
std::vector<VkSurfaceFormatKHR> formats;
std::vector<VkPresentModeKHR> presentModes;
};
} // namespace gz::vk