Added texture atlas

This commit is contained in:
matthias@arch 2022-10-14 20:58:20 +02:00
parent db90bb2c31
commit 7049f2c708
25 changed files with 2288 additions and 925 deletions

View File

@ -1,6 +1,6 @@
{
"configurations": {
"gze": {
"vulkan_test": {
"adapter": "vscode-cpptools",
"configuration": {
"name": "vulkan (cpp)",

View File

@ -18,6 +18,7 @@ CXXFLAGS += $(IFLAGS)
default: $(EXEC)
echo $(OBJECTS)
# TODO: REMOVE -g!
release: CXXFLAGS += -O3
release : default

View File

@ -1,11 +1,15 @@
#include "main.hpp"
#include "shape.hpp"
#include "vulkan_instance.hpp"
#include "renderer2D.hpp"
#include <chrono>
#include <ratio>
#include <thread>
namespace gz::vk {
int mainLoop() {
Log log("", true, false, "Main", Color::BG_RED);
gz::SettingsManagerCreateInfo<SettingsTypes> smCI{};
smCI.filepath = gz::vk::CONFIG_FILE;
smCI.readFileOnCreation = true;
@ -15,18 +19,32 @@ namespace gz::vk {
VulkanInstance vulkanInstance(smCI);
vulkanInstance.init();
Renderer2D r2D(vulkanInstance);
TextureManager textureManager(vulkanInstance);
Renderer2D r2D(vulkanInstance, textureManager);
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);
log("Startup complete. Drawing shapes");
Rectangle rect1( 90, 91, 92, 93, { 1.0f, 0.9f, 0.8f}, "blocks/leaves.png");
Rectangle rect2( 190, 191, 192, 193, { 0.7f, 0.6f, 0.5f}, "blocks/crate.png");
Rectangle rect3( 32, 120, 400, 400, { 0.0f, 0.0f, 1.0f}, "blocks/dirt.png");
Rectangle rect4(-600, -600, 800, 400, { 1.0f, 0.0f, 0.0f}, "blocks/ice.png");
Rectangle rect5( 90, 91, 92, 93, { 1.0f, 0.9f, 0.8f}, "items/sword.png");
Rectangle rect6( 190, 191, 192, 193, { 0.7f, 0.6f, 0.5f}, "items/crossbow.png");
Rectangle rect7( 32, 120, 400, 400, { 0.0f, 0.0f, 1.0f}, "special/hotbar.png");
Rectangle rect8(-600, -600, 800, 400, { 1.0f, 0.0f, 0.0f}, "special/func_nocol.png");
Rectangle rect9(-200, -400, 200, 200, { 0.0f, 1.0f, 0.0f}, "entities/sheep.png");
Rectangle rect10(-400, -400, 800, 800, { 0.0f, 1.0f, 0.0f}, "atlas");
r2D.drawShape(&rect1);
r2D.drawShape(&rect2);
r2D.drawShape(&rect3);
r2D.drawShape(&rect4);
r2D.drawShape(&rect5);
r2D.drawShape(&rect6);
r2D.drawShape(&rect7);
r2D.drawShape(&rect8);
r2D.drawShape(&rect9);
r2D.drawShape(&rect10);
log("Drawing complete. Filling r2D with shapes.");
r2D.fillVertexBufferWithShapes();
r2D.fillIndexBufferWithShapes();
/* gz::FontManager fm("fonts"); */
@ -34,6 +52,7 @@ namespace gz::vk {
/* /1* fm.getFaces().at("menu.ttf"). *1/ */
try {
uint32_t imageIndex;
/* std::chrono::time_point now = std::chrono::system_clock::now(); */
while (! glfwWindowShouldClose(vulkanInstance.window)) {
glfwPollEvents();
imageIndex = vulkanInstance.beginFrameDraw();
@ -41,8 +60,11 @@ namespace gz::vk {
vulkanInstance.endFrameDraw(imageIndex);
auto SLEEP_TIME = std::chrono::milliseconds(1000 / vulkanInstance.settings.get<uint32_t>("framerate"));
std::this_thread::sleep_for(SLEEP_TIME);
/* std::chrono::time_point now2 = std::chrono::system_clock::now(); */
/* std::chrono::nanoseconds dur = std::chrono::duration_cast<std::chrono::nanoseconds>(now2 - now); */
/* std::cout << "Frametime: (ns)" << dur.count() << " in fps: " << 1e9 / (dur.count()) << "\n"; */
/* now = now2; */
}
r2D.cleanup();
vulkanInstance.cleanup();
}
catch (const std::exception& e) {

View File

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

View File

@ -1,12 +1,28 @@
#include "vulkan_instance.hpp"
#pragma once
// includes for child classes
#include "texture_manager.hpp"
#include "vertex.hpp"
#include <gz-util/log.hpp>
#include <vulkan/vulkan_core.h>
#include <vector>
namespace gz::vk {
/// Defined in texture_manager.hpp
class TextureManager;
/// Defined in vulkan_instance.hpp
class VulkanInstance;
class Renderer {
public:
Renderer(VulkanInstance& instance) : vk(instance) {};
Renderer(VulkanInstance& instance, TextureManager& textureManager) : vk(instance), textureManager(textureManager) {};
protected:
void cleanup_();
VulkanInstance& vk;
TextureManager& textureManager;
std::vector<VkCommandBuffer> commandBuffers;
/// On device local memory
VkBuffer vertexBuffer;

View File

@ -2,32 +2,83 @@
#include "exceptions.hpp"
#include "vk_enum_string.h"
#include "vulkan_instance.hpp"
#include "texture_manager.hpp"
#include <cstring>
#include <thread>
#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) {
//
// INIT & CLEANUP
//
Renderer2D::Renderer2D(VulkanInstance& instance, TextureManager& textureManager) :
Renderer(instance, textureManager),
rLog("renderer2D.log", true, false, "2D-Renderer", Color::LI_MAGENTA, VULKAN_MESSAGE_TIME_COLOR, true, 100)
{
vk.registerCleanupCallback(std::bind(&Renderer2D::cleanup, this));
vk.registerSwapChainRecreateCallback(std::bind(&Renderer2D::swapChainRecreateCallback, this));
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);
initSwapChainDependantResources();
VulkanInstance::registerObjectUsingVulkan(ObjectUsingVulkan("Renderer2D",
{ &pipelines[PL_2D].pipeline, &renderPass, &vertexBuffer, &vertexBufferMemory, &indexBuffer, &indexBufferMemory },
{ &framebuffers, &images, &imageMemory, &imageViews, &commandBuffers }));
rLog("Created Renderer2D");
}
void Renderer2D::cleanup() {
/* vk.destroyCommandBuffers(commandBuffers); */
cleanupSwapChainDependantResources();
cleanup_();
}
//
// SWAPCHAIN DEPENDANT
//
void Renderer2D::initSwapChainDependantResources() {
createRenderPass();
createImages();
vk.createFramebuffers(framebuffers, imageViews, renderPass);
std::vector<VkDescriptorSetLayout> descriptorSetLayouts = { textureManager.getDescriptorSetLayout() };
vk.createGraphicsPipeline<Vertex2D>("shaders/vert2D.spv", "shaders/frag2D.spv", descriptorSetLayouts, false, renderPass, pipelines[PL_2D]);
}
void Renderer2D::cleanupSwapChainDependantResources() {
// destroy pipelines
pipelines.destroy(vk.device);
vk.destroyFramebuffers(framebuffers);
for (size_t i = 0; i < images.size(); i++) {
vkDestroyImageView(vk.device, imageViews[i], nullptr);
vkDestroyImage(vk.device, images[i], nullptr);
vkFreeMemory(vk.device, imageMemory[i], nullptr);
}
vkDestroyRenderPass(vk.device, renderPass, nullptr);
}
void Renderer2D::swapChainRecreateCallback() {
cleanupSwapChainDependantResources();
initSwapChainDependantResources();
}
//
// IMAGES
//
void Renderer2D::createImages() {
images.resize(vk.scImages.size());
imageMemory.resize(vk.scImages.size());
@ -37,11 +88,14 @@ void Renderer2D::createImages() {
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);
}
}
//
// RENDER PASS
//
void Renderer2D::createRenderPass() {
VkAttachmentDescription colorBlendAttachment{};
VkAttachmentDescription2 colorBlendAttachment{};
colorBlendAttachment.sType = VK_STRUCTURE_TYPE_ATTACHMENT_DESCRIPTION_2;
colorBlendAttachment.format = vk.scImageFormat;
colorBlendAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
colorBlendAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
@ -51,7 +105,8 @@ void Renderer2D::createRenderPass() {
colorBlendAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
colorBlendAttachment.finalLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
VkAttachmentReference colorAttachmentRef{};
VkAttachmentReference2 colorAttachmentRef{};
colorAttachmentRef.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2;
colorAttachmentRef.attachment = 0;
colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
@ -69,19 +124,32 @@ void Renderer2D::createRenderPass() {
/* depthAttachmentRef.attachment = 1; */
/* depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; */
VkSubpassDescription subpass{};
VkSubpassDescription2 subpass{};
subpass.sType = VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_2;
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;
VkSubpassDependency2 colorAttachmentSD{};
colorAttachmentSD.sType = VK_STRUCTURE_TYPE_SUBPASS_DEPENDENCY_2;
colorAttachmentSD.srcSubpass = VK_SUBPASS_EXTERNAL;
colorAttachmentSD.dstSubpass = 0;
colorAttachmentSD.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
colorAttachmentSD.srcAccessMask = 0;
colorAttachmentSD.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
colorAttachmentSD.dstAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT;
// dependecy for the image layout transition to transfer dst
VkSubpassDependency2 layoutTransitionSD{};
colorAttachmentSD.sType = VK_STRUCTURE_TYPE_SUBPASS_DEPENDENCY_2;
colorAttachmentSD.srcSubpass = 0;
colorAttachmentSD.dstSubpass = VK_SUBPASS_EXTERNAL;
colorAttachmentSD.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
colorAttachmentSD.srcAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT;
colorAttachmentSD.dstStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT;
colorAttachmentSD.dstAccessMask = VK_ACCESS_2_TRANSFER_READ_BIT;
colorAttachmentSD.dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
/* VkSubpassDependency dependency{}; */
/* dependency.srcSubpass = VK_SUBPASS_EXTERNAL; */
/* dependency.dstSubpass = 0; */
@ -91,20 +159,21 @@ void Renderer2D::createRenderPass() {
/* 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;
std::vector<VkAttachmentDescription2> attachments = { colorBlendAttachment };
std::vector<VkSubpassDependency2> dependencies = { colorAttachmentSD, layoutTransitionSD };
VkRenderPassCreateInfo2 renderPassCI{};
renderPassCI.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO_2;
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 = dependencies.size();
renderPassCI.pDependencies = dependencies.data();
/* renderPassCI.dependencyCount = 0; */
/* renderPassCI.pDependencies = nullptr; */
/* renderPassCI.correlatedViewMaskCount = 0; */
/* renderPassCI.pCorrelatedViewMasks = nullptr; */
VkResult result = vkCreateRenderPass(vk.device, &renderPassCI, nullptr, &renderPass);
VkResult result = vkCreateRenderPass2(vk.device, &renderPassCI, nullptr, &renderPass);
if (result != VK_SUCCESS) {
throw getVkException(result, "Could not create render pass", "Renderer2D::createRenderPass");
}
@ -113,6 +182,9 @@ void Renderer2D::createRenderPass() {
}
//
// RENDERING
//
void Renderer2D::recordCommandBuffer(uint32_t imageIndex, uint32_t currentFrame) {
VkCommandBufferBeginInfo commandBufferBI{};
commandBufferBI.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
@ -126,19 +198,19 @@ void Renderer2D::recordCommandBuffer(uint32_t imageIndex, uint32_t currentFrame)
VkRenderPassBeginInfo renderPassBI{};
renderPassBI.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
renderPassBI.renderPass = renderPass;
renderPassBI.framebuffer = vk.getFramebuffers(renderPassID)[imageIndex];
renderPassBI.framebuffer = framebuffers[imageIndex];
renderPassBI.renderArea.offset = { 0, 0 };
renderPassBI.renderArea.extent = vk.scExtent;
// clear
std::array<VkClearValue, 2> clearValues{};
std::array<VkClearValue, 1> clearValues{};
clearValues[0].color = {{1.0f, 0.0f, 0.0f, 1.0f}};
clearValues[1].depthStencil = {1.0f, 0};
/* 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);
vkCmdBindPipeline(commandBuffers[currentFrame], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines[PL_2D].pipeline);
VkBuffer vertexBuffers[] = { vertexBuffer };
VkDeviceSize offsets[] = {0};
uint32_t bindingCount = 1;
@ -146,25 +218,25 @@ void Renderer2D::recordCommandBuffer(uint32_t imageIndex, uint32_t currentFrame)
// 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); */
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, &textureManager.getDescriptorSet(), 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);
vk.copyImageToImage(commandBuffers[currentFrame], images[imageIndex], vk.scImages[imageIndex], vk.scExtent);
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");
throw getVkException(result, "Failed to record 2D - command buffer", "Renderer2D::recordCommandBufferWithTexture");
}
vk.commandBuffersToSubmitThisFrame.push_back(commandBuffers[currentFrame]);
vk.submitThisFrame(commandBuffers[currentFrame]);
}
@ -227,18 +299,21 @@ void Renderer2D::fillIndexBufferWithShapes() {
}
void Renderer2D::drawShape(const Shape& shape) {
shapes.emplace_back(shape);
void Renderer2D::drawShape(Shape* 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();
shape->setIndexOffset(shapesVerticesCount);
shape->normalizeVertices(vk.scExtent.width, vk.scExtent.height);
shape->setTextureCoordinates(textureManager);
// object slicing here, need to call virtual setTextureCoordinates before this!
shapes.push_back(*shape);
shapesVerticesCount += shape->getVertices().size();
shapesIndicesCount += shape->getIndices().size();
}
void Renderer2D::drawFrame(uint32_t imageIndex) {
vkResetCommandBuffer(commandBuffers[vk.currentFrame], NO_FLAGS);
/* recordCommandBuffer(imageIndex, vk.currentFrame); */
recordCommandBuffer(imageIndex, vk.currentFrame);
}

View File

@ -1,31 +1,45 @@
#pragma once
#include "renderer.hpp"
#include "shape.hpp"
#include "vulkan_util.hpp"
namespace gz::vk {
class Renderer2D : public Renderer {
public:
Renderer2D(VulkanInstance& instance);
void drawShape(const Shape& shape);
/**
* @brief Create a 2D renderer
* @details
* -# @ref VulkanInstance::registerCleanupCallback "register" @ref cleanup() "cleanup callback"
* -# @ref VulkanInstance::registerSwapChainRecreateCallback "register" @ref swapChainRecreateCallback "swapChain recreation callback"
* -# create command buffers
* -# create vertex & index buffers
* -# call initSwapChainDependantResources
*/
Renderer2D(VulkanInstance& instance, TextureManager& textureManager);
/**
* @name Rendering
*/
/// @{
void drawShape(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:
/**
* @brief Destroy all vulkan objects owned by this object
* @details:
* Does:
* -# call cleanupSwapChainDependantResources()
* -# call Renderer::cleanup_()
*/
void cleanup();
std::vector<Shape> shapes;
size_t shapesVerticesCount = 0;
size_t shapesIndicesCount = 0;
@ -37,26 +51,82 @@ namespace gz::vk {
* - image layout: VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL
* - clear image
* -# bind 2d pipeline, vertex and index buffer
* -# bind texture sampler
* -# 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
/**
* @name Image (render targets)
* @details
* The images are used as render targets. After rendering, the current image gets blitted onto the current swap chain 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
* @brief Creates the images (on imageMemory) and imageViews with the format of the VulkanInstance::swapChain images
*/
void createImages();
/// @}
/**
* @name 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
*/
/// @{
/**
* @brief Create a render pass
*/
void createRenderPass();
VkRenderPass renderPass;
int renderPassID;
/// @}
std::vector<VkFramebuffer> framebuffers;
// PIPELINE
PipelineContainer pipelines;
// SWAPCHAIN RECREATION
/**
* @brief Swapchain recreation
*/
/// @{
/**
* @brief Cleans up resources that were initialized by initSwapChainDependantResources
*/
void cleanupSwapChainDependantResources();
/**
* @brief Sets up resources that depend on the swap chain or its attributes
* @details
* Initializes up:
* - render pass
* - images, imageMemory, imageViews
* - framebuffers
* - pipeline
*/
void initSwapChainDependantResources();
/**
* @brief Recreates swap chain dependant resources
* @details
* Calls:
* -# cleanupSwapChainDependantResources
* -# initSwapChainDependantResources
*/
void swapChainRecreateCallback();
/// @}
Log rLog;
};
} // namespace gz::vk

0
renderer2D.log Normal file
View File

View File

@ -1,21 +1,35 @@
#include "renderer3D.hpp"
#include "vulkan_instance.hpp"
#include "texture_manager.hpp"
#include "exceptions.hpp"
#include "vk_enum_string.h"
#include <cstring>
namespace gz::vk {
//
// INIT & CLEANUP
//
Renderer3D::Renderer3D(VulkanInstance& instance, TextureManager& textureManager) :
Renderer(instance, textureManager),
rLog("renderer3D.log", true, false, "3D-Renderer", Color::CYAN, VULKAN_MESSAGE_TIME_COLOR, true, 100) {
vk.registerCleanupCallback(std::bind(&Renderer3D::cleanup, this));
vk.registerSwapChainRecreateCallback(std::bind(&Renderer3D::swapChainRecreateCallback, this));
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.createVertexBuffer<Vertex3D>(vertexCount, vertexBuffer, vertexBufferMemory, vertexBufferSize);
vk.createIndexBuffer<uint32_t>(indexCount, indexBuffer, indexBufferMemory, indexBufferSize);
rLog("Created Renderer3D");
initSwapChainDependantResources();
VulkanInstance::registerObjectUsingVulkan(ObjectUsingVulkan("Renderer3D",
{ &pipelines[PL_3D].pipeline, &renderPass, &vertexBuffer, &vertexBufferMemory, &indexBuffer, &indexBufferMemory },
{ &framebuffers, &images, &imageMemory, &imageViews, &commandBuffers }));
rLog("Created Renderer3D");
}
void Renderer3D::cleanup() {
@ -23,10 +37,156 @@ void Renderer3D::cleanup() {
vkDestroyBuffer(vk.device, uniformBuffers[i], nullptr);
vkFreeMemory(vk.device, uniformBuffersMemory[i], nullptr);
}
cleanupSwapChainDependantResources();
cleanup_();
}
//
// SWAPCHAIN DEPENDANT
//
void Renderer3D::initSwapChainDependantResources() {
createRenderPass();
createImages();
vk.createFramebuffers(framebuffers, imageViews, renderPass);
std::vector<VkDescriptorSetLayout> descriptorSetLayouts = { textureManager.getDescriptorSetLayout() };
vk.createGraphicsPipeline<Vertex2D>("shaders/vert2D.spv", "shaders/frag2D.spv", descriptorSetLayouts, false, renderPass, pipelines[PL_2D]);
}
void Renderer3D::cleanupSwapChainDependantResources() {
// destroy pipelines
pipelines.destroy(vk.device);
vk.destroyFramebuffers(framebuffers);
for (size_t i = 0; i < images.size(); i++) {
vkDestroyImageView(vk.device, imageViews[i], nullptr);
vkDestroyImage(vk.device, images[i], nullptr);
vkFreeMemory(vk.device, imageMemory[i], nullptr);
}
vkDestroyRenderPass(vk.device, renderPass, nullptr);
}
void Renderer3D::swapChainRecreateCallback() {
cleanupSwapChainDependantResources();
initSwapChainDependantResources();
}
//
// IMAGES
//
void Renderer3D::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);
}
}
//
// RENDER PASS
//
void Renderer3D::createRenderPass() {
VkAttachmentDescription2 colorBlendAttachment{};
colorBlendAttachment.sType = VK_STRUCTURE_TYPE_ATTACHMENT_DESCRIPTION_2;
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;
VkAttachmentReference2 colorAttachmentRef{};
colorAttachmentRef.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2;
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; */
VkSubpassDescription2 subpass{};
subpass.sType = VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_2;
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass.colorAttachmentCount = 1;
subpass.pColorAttachments = &colorAttachmentRef;
/* subpass.pDepthStencilAttachment = &depthAttachmentRef; */
VkSubpassDependency2 colorAttachmentSD{};
colorAttachmentSD.sType = VK_STRUCTURE_TYPE_SUBPASS_DEPENDENCY_2;
colorAttachmentSD.srcSubpass = VK_SUBPASS_EXTERNAL;
colorAttachmentSD.dstSubpass = 0;
colorAttachmentSD.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
colorAttachmentSD.srcAccessMask = 0;
colorAttachmentSD.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
colorAttachmentSD.dstAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT;
// dependecy for the image layout transition to transfer dst
VkSubpassDependency2 layoutTransitionSD{};
colorAttachmentSD.sType = VK_STRUCTURE_TYPE_SUBPASS_DEPENDENCY_2;
colorAttachmentSD.srcSubpass = 0;
colorAttachmentSD.dstSubpass = VK_SUBPASS_EXTERNAL;
colorAttachmentSD.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
colorAttachmentSD.srcAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT;
colorAttachmentSD.dstStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT;
colorAttachmentSD.dstAccessMask = VK_ACCESS_2_TRANSFER_READ_BIT;
colorAttachmentSD.dependencyFlags = VK_DEPENDENCY_BY_REGION_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<VkAttachmentDescription2> attachments = { colorBlendAttachment };
std::vector<VkSubpassDependency2> dependencies = { colorAttachmentSD, layoutTransitionSD };
VkRenderPassCreateInfo2 renderPassCI{};
renderPassCI.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO_2;
renderPassCI.attachmentCount = static_cast<uint32_t>(attachments.size());
renderPassCI.pAttachments = attachments.data();
renderPassCI.subpassCount = 1;
renderPassCI.pSubpasses = &subpass;
renderPassCI.dependencyCount = dependencies.size();
renderPassCI.pDependencies = dependencies.data();
/* renderPassCI.dependencyCount = 0; */
/* renderPassCI.pDependencies = nullptr; */
/* renderPassCI.correlatedViewMaskCount = 0; */
/* renderPassCI.pCorrelatedViewMasks = nullptr; */
VkResult result = vkCreateRenderPass2(vk.device, &renderPassCI, nullptr, &renderPass);
if (result != VK_SUCCESS) {
throw getVkException(result, "Could not create render pass", "Renderer3D::createRenderPass");
}
rLog("createRenderPass: Created render pass.");
}
void Renderer3D::updateUniformBuffer() {
static auto startTime = std::chrono::high_resolution_clock::now();
auto currentTime = std::chrono::high_resolution_clock::now();
@ -34,12 +194,12 @@ void Renderer3D::updateUniformBuffer() {
// 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.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>(vk.scExtent.width) / vk.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);
@ -47,59 +207,231 @@ void Renderer3D::updateUniformBuffer() {
vkUnmapMemory(vk.device, uniformBuffersMemory[vk.currentFrame]);
}
//
// DESCRIPTORS
//
void Renderer3D::createDescriptorResources() {
// LAYOUT
// 1) uniform buffer object
VkDescriptorSetLayoutBinding uboLayoutBinding{};
uboLayoutBinding.binding = 0;
uboLayoutBinding.descriptorCount = 1;
uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
/* uboLayoutBinding.pImmutableSamplers = nullptr; */
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);
// 2) combined image sampler
VkDescriptorSetLayoutBinding samplerLayoutBinding{};
samplerLayoutBinding.binding = 1;
samplerLayoutBinding.descriptorCount = 1;
samplerLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
samplerLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
/* samplerLayoutBinding.pImmutableSamplers = nullptr; */
std::vector<VkDescriptorSetLayoutBinding> bindings = { uboLayoutBinding, samplerLayoutBinding };
vk.createDescriptorSetLayout(bindings, descriptorSetLayout);
// POOL
std::array<VkDescriptorPoolSize, 2> poolSizes;
poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
poolSizes[0].descriptorCount = static_cast<uint32_t>(MAX_FRAMES_IN_FLIGHT);
poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
poolSizes[1].descriptorCount = static_cast<uint32_t>(MAX_FRAMES_IN_FLIGHT);
VkDescriptorPoolCreateInfo poolCI{};
poolCI.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
poolCI.poolSizeCount = static_cast<uint32_t>(poolSizes.size());
poolCI.pPoolSizes = poolSizes.data();
poolCI.maxSets = static_cast<uint32_t>(MAX_FRAMES_IN_FLIGHT);
VkResult result = vkCreateDescriptorPool(vk.device, &poolCI, nullptr, &descriptorPool);
if (result != VK_SUCCESS) {
throw getVkException(result, "Failed to begin 3D - command buffer", "recordCommandBuffer");
throw getVkException(result, "Failed to create descriptor pool", "Renderer3D::createDescriptorResources");
}
VkRenderPassBeginInfo renderPassBI{};
renderPassBI.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
renderPassBI.renderPass = vk.renderPassBegin;
// SETS
std::vector<VkDescriptorSetLayout> layouts(MAX_FRAMES_IN_FLIGHT, descriptorSetLayout);
VkDescriptorSetAllocateInfo setAI{};
setAI.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
setAI.descriptorPool = descriptorPool;
setAI.descriptorSetCount = static_cast<uint32_t>(MAX_FRAMES_IN_FLIGHT);
setAI.pSetLayouts = layouts.data();
descriptorSets.resize(MAX_FRAMES_IN_FLIGHT);
result = vkAllocateDescriptorSets(vk.device, &setAI, descriptorSets.data());
if (result != VK_SUCCESS) {
throw getVkException(result, "Failed to create descriptor sets", "Renderer3D::createDescriptorResources");
}
// configure sets
for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
VkDescriptorBufferInfo bufferI{};
bufferI.buffer = uniformBuffers[i];
bufferI.offset = 0;
bufferI.range = VK_WHOLE_SIZE; // sizeof(UniformBufferObject);
VkDescriptorImageInfo imageI{};
imageI.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
// 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();
/* imageI.imageView = textureImageView; */
/* imageI.sampler = textureSampler; */
vkCmdBeginRenderPass(commandBuffers[currentFrame], &renderPassBI, VK_SUBPASS_CONTENTS_INLINE);
std::array<VkWriteDescriptorSet, 2> descriptorW{};
descriptorW[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorW[0].dstSet = descriptorSets[i];
descriptorW[0].dstBinding = bindingUniformBuffer;
descriptorW[0].dstArrayElement = 0;
descriptorW[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
descriptorW[0].descriptorCount = 1;
descriptorW[0].pBufferInfo = &bufferI;
/* descriptorW[0].pImageInfo = nullptr; */
/* descriptorW[0].pTexelBufferView = nullptr; */
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);
descriptorW[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorW[1].dstSet = descriptorSets[i];
descriptorW[1].dstBinding = bindingCombinedImageSampler;
descriptorW[1].dstArrayElement = 0;
descriptorW[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
descriptorW[1].descriptorCount = 1;
/* descriptorW[1].pBufferInfo = &bufferI; */
descriptorW[1].pImageInfo = &imageI;
/* descriptorW[1].pTexelBufferView = nullptr; */
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);
uint32_t descriptorWriteCount = static_cast<uint32_t>(descriptorW.size());
uint32_t descriptorCopyCount = 0;
vkUpdateDescriptorSets(vk.device, descriptorWriteCount, descriptorW.data(), descriptorCopyCount, nullptr);
}
rLog("createDescriptorResources: Created descriptor layouts, pool and sets.");
}
int instanceCount = 1;
int firstIndex = 0;
int firstInstance = 0;
//
// RENDERING
//
void Renderer3D::recordCommandBuffer(uint32_t imageIndex, uint32_t currentFrame) {}
/* VkCommandBufferBeginInfo commandBufferBI{}; */
/* commandBufferBI.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; */
/* /1* commandBufferBI.flags = 0; *1/ */
/* /1* commandBufferBI.pInheritanceInfo = nullptr; *1/ */
/* VkResult result = vkBeginCommandBuffer(commandBuffers[currentFrame], &commandBufferBI); */
/* if (result != VK_SUCCESS) { */
/* throw getVkException(result, "Failed to begin 2D command buffer", "Renderer3D::recordCommandBuffer"); */
/* } */
/* VkRenderPassBeginInfo renderPassBI{}; */
/* renderPassBI.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; */
/* renderPassBI.renderPass = renderPass; */
/* renderPassBI.framebuffer = framebuffers[imageIndex]; */
/* renderPassBI.renderArea.offset = { 0, 0 }; */
/* renderPassBI.renderArea.extent = vk.scExtent; */
/* // clear */
/* std::array<VkClearValue, 1> clearValues{}; */
/* clearValues[0].color = {{1.0f, 0.0f, 0.0f, 1.0f}}; */
/* /1* clearValues[1].depthStencil = {1.0f, 0}; *1/ */
/* 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, 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, &textureManager.getDescriptorSet(), 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); */
/* 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", "Renderer3D::recordCommandBufferWithTexture"); */
/* } */
/* vk.submitThisFrame(commandBuffers[currentFrame]); */
/* } */
/* void Renderer3D::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 Renderer3D::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); */
/* } */
//
// RENDERING
//
void Renderer3D::drawFrame(uint32_t imageIndex) {
vkResetCommandBuffer(commandBuffers[vk.currentFrame], NO_FLAGS);
/* recordCommandBuffer(imageIndex, vk.currentFrame); */
recordCommandBuffer(imageIndex, vk.currentFrame);
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

View File

@ -1,5 +1,9 @@
#pragma once
#include "renderer.hpp"
#include "vulkan_util.hpp"
namespace gz::vk {
struct UniformBufferObject {
alignas(16) glm::mat4 model;
@ -7,43 +11,134 @@ namespace gz::vk {
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:
/**
* @brief Create a 3D renderer
* @details
* -# @ref VulkanInstance::registerCleanupCallback "register" @ref cleanup() "cleanup callback"
* -# @ref VulkanInstance::registerSwapChainRecreateCallback "register" @ref swapChainRecreateCallback "swapChain recreation callback"
* -# create command buffers
* -# create vertex & index buffers
* -# call initSwapChainDependantResources
*/
Renderer3D(VulkanInstance& instance, TextureManager& textureManager);
/**
* @name Rendering
*/
/// @{
void drawFrame(uint32_t imageIndex);
/// @}
private:
void recordCommandBuffer(uint32_t imageIndex, uint32_t currentFrame);
std::vector<VkBuffer> uniformBuffers;
std::vector<VkDeviceMemory> uniformBuffersMemory;
void updateUniformBuffer();
/**
* @name Image (render targets)
* @details
* The images are used as render targets. After rendering, the current image gets blitted onto the current swap chain image.
*/
/// @{
std::vector<VkImage> images;
std::vector<VkDeviceMemory> imageMemory;
std::vector<VkImageView> imageViews;
/**
* @brief Creates the images (on imageMemory) and imageViews with the format of the VulkanInstance::swapChain images
*/
void createImages();
/// @}
/**
* @name 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
*/
/// @{
/**
* @brief Create a render pass
*/
void createRenderPass();
VkRenderPass renderPass;
/// @}
std::vector<VkFramebuffer> framebuffers;
// PIPELINE
PipelineContainer pipelines;
/**
* @name Desciptors
* @details These functions create a desciptor with bindings for
* -# UniformBufferObject (DESCRIPTOR_TYPE_UNIFORM_BUFFER)
* -# Combined Image Sampler (DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER)
*/
/// @{
VkDescriptorPool descriptorPool;
VkDescriptorSetLayout descriptorSetLayout;
std::vector<VkDescriptorSet> descriptorSets;
/**
* @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();
/*
* @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; */
void createDescriptorResources();
/**
* @brief Swapchain recreation
*/
/// @{
/**
* @brief Cleans up resources that were initialized by initSwapChainDependantResources
*/
void cleanupSwapChainDependantResources();
/**
* @brief Sets up resources that depend on the swap chain or its attributes
* @details
* Initializes up:
* - images, imageMemory, imageViews
* - framebuffers
* - render pass
* - pipeline
*/
void initSwapChainDependantResources();
/**
* @brief Recreates swap chain dependant resources
* @details
* Calls:
* -# cleanupSwapChainDependantResources
* -# initSwapChainDependantResources
*/
void swapChainRecreateCallback();
/// @}
/*
* @brief Destroy all vulkan objects owned by this object
* @details:
* Does:
* -# destroy uniform buffers
* -# call cleanupSwapChainDependantResources()
* -# call Renderer::cleanup_()
*/
void cleanup();
Log rLog;
};

View File

@ -19,7 +19,7 @@ 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);
/* debugPrintfEXT("inPosition %v3f, inColor %v3f", inPosition, inColor); */
fragColor = inColor;
fragTextureCoordinate = inTextureCoordinate;
}

View File

@ -1,7 +1,7 @@
#version 450
#extension GL_EXT_debug_printf : enable
/* layout(binding = 1) uniform sampler2D textureSampler; */
layout(binding = 0) uniform sampler2D textureSampler;
layout(location = 0) in vec3 fragColor;
layout(location = 1) in vec2 fragTextureCoordinate;
@ -9,8 +9,9 @@ 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(fragColor, 1.0); */
/* outColor = vec4(fragTextureCoordinate, 0.0, 1.0); */
/* outColor = vec4(fragColor * texture(textureSampler, fragTextureCoordinate).rgb, 1.0); */
outColor = texture(textureSampler, fragTextureCoordinate);
/* debugPrintfEXT("outColor %v3f", outColor); */
}

View File

@ -19,7 +19,7 @@ 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);
/* debugPrintfEXT("inPosition %v2f, inColor %v3f", inPosition, inColor); */
fragColor = inColor;
fragTextureCoordinate = inTextureCoordinate;
}

View File

@ -1,8 +1,12 @@
#include "shape.hpp"
#include "texture_manager.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) {
Rectangle::Rectangle(float top, float left, uint32_t width, uint32_t height, glm::vec3 color, std::string&& texture)
: top(top), left(left), width(width), height(height), color(color)
{
this->texture = std::move(texture);
generateVertices();
}
@ -11,9 +15,9 @@ namespace gz::vk {
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, left + width), color, glm::vec2(0, 1)});
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)});
vertices.emplace_back(Vertex2D{glm::vec2(top + height, left), color, glm::vec2(1, 0)});
indices = { 0, 1, 2, 2, 3, 0 };
/* indices = { 2, 1, 0, 2, 3, 0 }; */
}
@ -25,10 +29,30 @@ namespace gz::vk {
}
void Shape::setNormalize(float width, float height) {
void Shape::normalizeVertices(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
void Rectangle::setTextureCoordinates(glm::vec2 topLeft, glm::vec2 bottomRight) {
vertices[0].texCoord = topLeft;
vertices[1].texCoord.x = bottomRight.x;
vertices[1].texCoord.y = topLeft.y;
vertices[2].texCoord = bottomRight;
vertices[3].texCoord.x = topLeft.x;
vertices[3].texCoord.y = bottomRight.y;
}
void Rectangle::setTextureCoordinates(TextureManager& textureManager) {
if (texture != "atlas") {
textureManager.getTexCoords(texture, vertices[0].texCoord);
textureManager.getTexCoords(texture, vertices[1].texCoord);
textureManager.getTexCoords(texture, vertices[2].texCoord);
textureManager.getTexCoords(texture, vertices[3].texCoord);
}
}
} // namespace gz::vk

View File

@ -4,14 +4,26 @@
#include <cstdint>
namespace gz::vk {
// defined in texture_manager.hpp
class TextureManager;
class Shape {
public:
const std::vector<Vertex2D>& getVertices() const { return vertices; }
const std::vector<uint32_t>& getIndices() const { return indices; }
const std::string& getTexture() const { return texture; }
/**
* @brief Add an offset to all indices
*/
void setIndexOffset(uint32_t offset);
void setNormalize(float width, float height);
/**
* @brief Normalize the vertices, so that (1, 1) is (width, height)
*/
void normalizeVertices(float width, float height);
virtual void setTextureCoordinates(glm::vec2 topLeft, glm::vec2 bottomRight) {};
virtual void setTextureCoordinates(TextureManager& textureManager) {};
virtual ~Shape() {};
protected:
std::string texture = "texture.png";
std::vector<Vertex2D> vertices;
std::vector<uint32_t> indices;
@ -19,7 +31,15 @@ namespace gz::vk {
class Rectangle : public Shape {
public:
Rectangle(float top, float left, uint32_t width, uint32_t height, glm::vec3 color);
Rectangle(float top, float left, uint32_t width, uint32_t height, glm::vec3 color, std::string&& texture);
void setTextureCoordinates(glm::vec2 topLeft, glm::vec2 bottomRight) override;
/**
* @brief Get the correct texture coordinates from a TextureManager
* @details
* If the texture is "atlas", the texture coordinates will remain at (0,0), (0,1), (1, 1), (1, 0) and
* thus show the entire atlas.
*/
void setTextureCoordinates(TextureManager& textureManager) override;
private:
float top, left;
uint32_t width, height;

208
texture_atlas.cpp Normal file
View File

@ -0,0 +1,208 @@
#include "texture_atlas.hpp"
#include "vulkan_instance.hpp"
#include "exceptions.hpp"
#include "stb_image.h"
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <gz-util/exceptions.hpp>
#include <gz-util/util/string_concepts.hpp>
#include <gz-util/util/string_conversion.hpp>
#include <ratio>
#include <vulkan/vulkan_core.h>
#include <glm/glm.hpp>
namespace gz::vk {
std::string TextureImageArea::toString() const {
return "<(" + gz::toString(x) + "," + gz::toString(y) + "), (" + gz::toString(width) + "x" + gz::toString(height) + ")>";
}
//
// INIT & CLEANUP
//
TextureAtlas::TextureAtlas(VulkanInstance& instance, uint16_t slotWidth, uint16_t slotHeight, uint16_t slotCountX, uint16_t slotCountY)
: vk(instance), slotWidth(slotWidth), slotHeight(slotHeight), slotCountX(slotCountX), slotCountY(slotCountY)
{
// whole image
freeAreas.insert({ 0, 0, slotCountX, slotCountY});
createImageResources();
vk.registerCleanupCallback(std::bind(&TextureAtlas::cleanup, this));
VulkanInstance::registerObjectUsingVulkan(ObjectUsingVulkan(std::string("TextureAtlas-") + gz::toString(slotWidth) + "x" + gz::toString(slotHeight),
{ &textureImage, &textureImageMemory, &textureImageView, &textureSampler },
{}));
}
void TextureAtlas::cleanup() {
VulkanInstance::vLog("TextureAtlas::cleanup, textureSampler", reinterpret_cast<uint64_t>(textureSampler), "textureImageView", reinterpret_cast<uint64_t>(textureImageView));
vkDestroySampler(vk.device, textureSampler, nullptr);
vkDestroyImageView(vk.device, textureImageView, nullptr);
vkDestroyImage(vk.device, textureImage, nullptr);
vkFreeMemory(vk.device, textureImageMemory, nullptr);
}
void TextureAtlas::createImageResources() {
vk.createImage(slotWidth * slotCountX, slotHeight * slotCountY, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_TILING_LINEAR,
VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
textureImage, textureImageMemory);
VkCommandBuffer cmdBuffer = vk.beginSingleTimeCommands(vk.commandPoolGraphics);
vk.transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &cmdBuffer);
VkImageSubresourceRange subresourceRange{};
subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
subresourceRange.baseMipLevel = 0;
subresourceRange.levelCount = 1;
subresourceRange.baseArrayLayer = 0;
subresourceRange.layerCount = 1;
vkCmdClearColorImage(cmdBuffer, textureImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &missingTextureColor, 1, &subresourceRange);
vk.transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, &cmdBuffer);
vk.endSingleTimeCommands(cmdBuffer, vk.commandPoolGraphics, vk.graphicsQ);
vk.createImageView(VK_FORMAT_R8G8B8A8_SRGB, textureImage, textureImageView, VK_IMAGE_ASPECT_COLOR_BIT);
vk.createTextureSampler(textureSampler);
}
//
// ADD TEXTURE
//
std::pair<glm::vec2, glm::vec2> TextureAtlas::addTexture(uint8_t* pixels, uint16_t width, uint16_t height) {
if (freeAreas.empty()) {
throw Exception("No texture slots available", "TextureAtlas::addTexture");
}
// needed slot count in x/y direction
uint16_t slotsX, slotsY;
// width / slotHeight (+ 1 if another slot is not fully filled)
slotsX = width / slotWidth + ((width % slotWidth != 0) ? 1 : 0);
slotsY = height / slotHeight + ((height % slotHeight != 0) ? 1 : 0);
// find smallest area that fits the texture
auto freeArea = freeAreas.end();
for (auto it = freeAreas.begin(); it != freeAreas.end(); it++) {
// if fits the texture
if (it->width >= slotsX and it->height >= slotsY) {
freeArea = it;
break;
}
}
// check if match
if (freeArea == freeAreas.end()) {
throw Exception("Not enough texture slots available to fit the texture", "TextureAtlas::addTexture");
}
// consume free area
auto node = freeAreas.extract(freeArea);
TextureImageArea& selectedArea = node.value();
// topleft slot for texture
uint16_t x = selectedArea.x;
uint16_t y = selectedArea.y;
// update selectedArea
uint16_t selectedAreaNewY = selectedArea.y + slotsY;
uint16_t selectedAreaNewWidth = slotsX;
uint16_t selectedAreaNewHeight = selectedArea.height - slotsY;
// if split needed
if (slotsX < selectedArea.width and slotsY < selectedArea.height) {
// split so that the size difference between the new free area and the remaining free area is maximal
TextureImageArea newFreeArea;
newFreeArea.x = selectedArea.x + slotsX; // right of new texture
newFreeArea.y = selectedArea.y;
newFreeArea.width = selectedArea.width - slotsX;
// try full height first first
newFreeArea.height = selectedArea.height;
// size difference with (newArea.height == full height) < size difference with (newArea.height == textureHeight)
int sizeDiffNewAreaFullHeight = static_cast<int>(newFreeArea.width * newFreeArea.height) - selectedAreaNewWidth * selectedAreaNewHeight;
int sizeDiffNewAreaTextureHeight = static_cast<int>(newFreeArea.width * (selectedArea.height - slotsY)) - selectedArea.width * selectedAreaNewHeight;
if (sizeDiffNewAreaFullHeight * sizeDiffNewAreaFullHeight < sizeDiffNewAreaTextureHeight * sizeDiffNewAreaTextureHeight) {
newFreeArea.height = slotsY;
selectedAreaNewWidth = selectedArea.width;
}
// insert new area
freeAreas.insert(std::move(newFreeArea));
}
if (selectedAreaNewHeight > 0 and selectedAreaNewWidth > 0) {
// insert selected area with updated width, height and y
selectedArea.width = selectedAreaNewWidth;
selectedArea.height = selectedAreaNewHeight;
selectedArea.y = selectedAreaNewY;
freeAreas.insert(std::move(node));
}
mergeFreeAreas();
VulkanInstance::vLog("TextureAtlas::addTexture: Adding texture at position x,y=(", x, y, "), with slotCountX,Y=(", slotsX, slotsY, "), with dimensions w,h=(", width, height, ")");
blitTextureOnImage(pixels, x, y, width, height);
// topleft normalized: slot / slotCount
// bottomright normalized: (slot + (textureSize/slotCountize)) / slotCount
return { glm::vec2(static_cast<float>(x) / slotCountX, static_cast<float>(y) / slotCountY),
glm::vec2((static_cast<float>(x) + static_cast<float>(width) / slotWidth) / slotCountX,
(static_cast<float>(y) + static_cast<float>(height) / slotHeight) / slotCountY) };
}
void TextureAtlas::blitTextureOnImage(uint8_t* pixels, uint16_t slotX, uint16_t slotY, uint16_t textureWidth, uint16_t textureHeight) {
constexpr size_t BYTES_PER_PIXEL = 4;
VkDeviceSize imageSize = textureWidth * textureHeight * BYTES_PER_PIXEL;
VkBuffer stagingBuffer;
VkDeviceMemory stagingBufferMemory;
vk.createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
stagingBuffer, stagingBufferMemory);
void* data;
vkMapMemory(vk.device, stagingBufferMemory, NO_OFFSET, imageSize, NO_FLAGS, &data);
memcpy(data, pixels, static_cast<size_t>(imageSize));
vkUnmapMemory(vk.device, stagingBufferMemory);
vk.transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
vk.copyBufferToImage(stagingBuffer, textureImage,
static_cast<int32_t>(slotWidth * slotX), static_cast<int32_t>(slotHeight * slotY), // offset
static_cast<uint32_t>(textureWidth), static_cast<uint32_t>(textureHeight)); // dimensions
vk.transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
vkDestroyBuffer(vk.device, stagingBuffer, nullptr);
vkFreeMemory(vk.device, stagingBufferMemory, nullptr);
}
void TextureAtlas::mergeFreeAreas() {
begin:
VulkanInstance::vLog("TextureAtlas::mergeFreeAreas before merge:", freeAreas);
// surely not the most efficient way, but it should only be run at load time and thus not be too problematic
// iterate over the areas for each area
for (auto it = freeAreas.begin(); it != freeAreas.end(); it++) {
for (auto it2 = freeAreas.begin(); it2 != freeAreas.end(); it2++) {
if (it == it2) { continue; }
// if same y and same height and next to each other
if (it->y == it2->y and it->height == it2->height and it2->x == it->x + it->width) {
auto area1 = freeAreas.extract(it); // node
area1.value().width += it2->width;
freeAreas.insert(std::move(area1));
freeAreas.erase(it2);
goto begin; // start over since the iterators are invalid and new merges might be possible
}
// if same x and same with and below/above each other
if (it->x == it2->x and it->width == it2->width and it2->y == it->y + it->height) {
auto area1 = freeAreas.extract(it); // node
area1.value().height += it2->height;
freeAreas.insert(std::move(area1));
freeAreas.erase(it2);
goto begin; // start over since the iterators are invalid and new merges might be possible
}
}
}
VulkanInstance::vLog("TextureAtlas::mergeFreeAreas after merge:", freeAreas);
}
std::string TextureAtlas::toString() const {
return "TextureAtlas(SlotSize: " + gz::toString(slotWidth) + "x" + gz::toString(slotHeight)
+ ", SlotCount: " + gz::toString(slotCountX) + "x" + gz::toString(slotCountY) + ", FreeAreas: " + gz::toString(freeAreas) + ")";
}
} // namespace gz

91
texture_atlas.hpp Normal file
View File

@ -0,0 +1,91 @@
#pragma once
#include <glm/glm.hpp>
#include <vulkan/vulkan_core.h>
#include <vector>
#include <string>
#include <set>
namespace gz::vk {
/**
* @brief Describes an area in the textureImage that is not in use
*/
struct TextureImageArea {
// topleft slot
uint16_t x;
uint16_t y;
// in slots
uint16_t width;
uint16_t height;
std::string toString() const;
};
/// strict weak ordering comparison, true if a < b
inline auto freeAreaCmp = [](const TextureImageArea& a, const TextureImageArea& b) {
// if a size < b size
if (a.width * a.height < b.width * b.height) { return true; }
if (a.width * a.height != b.width * b.height) { return false; }
// if a size == b size, return true if a is toplefter
if (a.x + a.y < b.x + b.y) { return true; }
if (a.x + a.y != b.x + b.y) { return false; }
// if both are same topleft return if a is lefter
return a.x < b.x;
};
/// Defined in vulkan_instance.hpp
class VulkanInstance;
class TextureAtlas {
public:
/**
* @brief Create a texture atlas
* @details
* -# @ref VulkanInstance::registerCleanupCallback "register" @ref cleanup() "cleanup callback"
* -# @ref createImageResources "create texture image, view and sampler"
*
* The textureImage will have the dimensions slotWidth * slotCountX, slotHeight * slotCountY
*/
TextureAtlas(VulkanInstance& instance, uint16_t slotWidth, uint16_t slotHeight, uint16_t slotCountX, uint16_t slotCountY);
/**
* @brief Add a texture to the atlas
* @param textureWidth Width in pixels
* @param textureHeight Height in pixels
* @returns The topleft and bottomright texture coordinates in the image of the atlas
* @throws Exception when there is no space in the atlas to add the texture
*/
std::pair<glm::vec2, glm::vec2> addTexture(uint8_t* pixels, uint16_t textureWidth, uint16_t textureHeight);
const VkImageView& getTextureImageView() const { return textureImageView; }
const VkSampler& getTextureSampler() const { return textureSampler; }
std::string toString() const;
private:
VulkanInstance& vk;
/**
* @brief Destroy all vulkan objects owned by this object
*/
void cleanup();
void blitTextureOnImage(uint8_t* pixels, uint16_t slotX, uint16_t slotY, uint16_t textureWidth, uint16_t textureHeight);
/**
* @brief Create textureImage, textureImageView and textureSampler
* @details
* the textureImage is created with missingTextureColor and then transitioned to SHADER_READ_ONLY_OPTIMAL layout.
*/
void createImageResources();
VkImage textureImage;
VkDeviceMemory textureImageMemory;
VkImageView textureImageView;
VkSampler textureSampler;
std::set<TextureImageArea, decltype(freeAreaCmp)> freeAreas;
/**
* @todo implement merge
*/
void mergeFreeAreas();
uint16_t slotWidth;
uint16_t slotHeight;
uint16_t slotCountX;
uint16_t slotCountY;
};
} // namespace gz::vk

145
texture_manager.cpp Normal file
View File

@ -0,0 +1,145 @@
#include "texture_manager.hpp"
#include "exceptions.hpp"
#include "vulkan_instance.hpp"
#include <glm/glm.hpp>
#include <vulkan/vulkan_core.h>
namespace gz::vk {
TextureManager::TextureManager(VulkanInstance& instance)
: vk(instance)
{
vk.registerCleanupCallback(std::bind(&TextureManager::cleanup, this));
atlases.insert({ 0, TextureAtlas(vk, 24, 24, 24, 24) });
createDescriptorSetLayout();
createDescriptorPool();
createDescriptorSet();
VulkanInstance::registerObjectUsingVulkan(ObjectUsingVulkan("TextureManager",
{ &descriptorSetLayout, &descriptorPool, &descriptorSet },
{}));
}
void TextureManager::cleanup() {
/* vkFreeDescriptorSets(vk.device, descriptorPool, 1, &descriptorSet); */
vkDestroyDescriptorSetLayout(vk.device, descriptorSetLayout, NO_ALLOC);
vkDestroyDescriptorPool(vk.device, descriptorPool, NO_ALLOC);
}
void TextureManager::getTexCoords(const std::string& textureName, glm::vec2& texCoords) {
if (!textureInfos.contains(textureName)) {
loadTextureFromFile(textureName);
}
TextureInfo& texI = textureInfos[textureName];
VulkanInstance::vLog("getTexCoords", texCoords, "textureInfo: topleft", texI.texCoordTopLeft, "botright", texI.texCoordBottomRight);
/* texCoords.x = texI.texCoordTopLeft.x + texCoords.x * (texI.texCoordBottomRight.x - texI.texCoordTopLeft.x); */
/* texCoords.y = texI.texCoordTopLeft.y + texCoords.y * (texI.texCoordBottomRight.y - texI.texCoordTopLeft.y); */
texCoords = texI.texCoordTopLeft + texCoords * (texI.texCoordBottomRight - texI.texCoordTopLeft);
}
void TextureManager::loadTextureFromFile(const std::string& textureName) {
int textureWidth, textureHeight, textureChannels;
stbi_uc* pixels = stbi_load(("textures/" + textureName).c_str(), &textureWidth, &textureHeight, &textureChannels, STBI_rgb_alpha);
if (!pixels) {
throw Exception("Failed to load texture: textures/" + textureName, "TextureManager::loadTexture");
}
TextureInfo& texI = textureInfos[textureName];
texI.atlas = 0;
auto texCoords = atlases.at(0).addTexture(pixels, textureWidth, textureHeight);
stbi_image_free(pixels);
VulkanInstance::vLog("TextureManager::loadTextureFromFile: TexCoords of new image:", texCoords.first, texCoords.second);
texI.texCoordTopLeft = std::move(texCoords.first);
texI.texCoordBottomRight = std::move(texCoords.second);
VulkanInstance::vLog("TextureManager::loadTextureFromFile: After loading:", atlases.at(0));
}
//
// DESCRIPTOR SET
//
void TextureManager::createDescriptorSetLayout() {
// combined image sampler
VkDescriptorSetLayoutBinding samplerLayoutBinding{};
samplerLayoutBinding.binding = 0;
samplerLayoutBinding.descriptorCount = 1;
samplerLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
samplerLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
/* samplerLayoutBinding.pImmutableSamplers = nullptr; */
VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCI{};
descriptorSetLayoutCI.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
descriptorSetLayoutCI.bindingCount = 1;
descriptorSetLayoutCI.pBindings = &samplerLayoutBinding;
VkResult result = vkCreateDescriptorSetLayout(vk.device, &descriptorSetLayoutCI, nullptr, &descriptorSetLayout);
if (result != VK_SUCCESS) {
throw getVkException(result, "Could not create descriptorSetLayout", "TextureManager::createDescriptorSetLayout");
}
/* vLog("createDescriptorSetLayout: Created descriptor set layout."); */
}
void TextureManager::createDescriptorPool() {
std::array<VkDescriptorPoolSize, 1> poolSizes;
/* poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; */
/* poolSizes[0].descriptorCount = static_cast<uint32_t>(MAX_FRAMES_IN_FLIGHT); */
poolSizes[0].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
poolSizes[0].descriptorCount = static_cast<uint32_t>(MAX_FRAMES_IN_FLIGHT);
VkDescriptorPoolCreateInfo poolCI{};
poolCI.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
poolCI.poolSizeCount = static_cast<uint32_t>(poolSizes.size());
poolCI.pPoolSizes = poolSizes.data();
poolCI.maxSets = 1;
VkResult result = vkCreateDescriptorPool(vk.device, &poolCI, nullptr, &descriptorPool);
if (result != VK_SUCCESS) {
throw getVkException(result, "Failed to create descriptor pool", "TextureManager::createDescriptorPool");
}
/* vLog("createDescriptorPool: Created descriptor pool."); */
}
void TextureManager::createDescriptorSet() {
/* std::vector<VkDescriptorSetLayout> layouts(MAX_FRAMES_IN_FLIGHT, descriptorSetLayout); */
VkDescriptorSetAllocateInfo setAI{};
setAI.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
setAI.descriptorPool = descriptorPool;
/* setAI.descriptorSetCount = static_cast<uint32_t>(MAX_FRAMES_IN_FLIGHT); */
/* setAI.pSetLayouts = layouts.data(); */
setAI.descriptorSetCount = 1;
setAI.pSetLayouts = &descriptorSetLayout;
/* descriptorSets.resize(MAX_FRAMES_IN_FLIGHT); */
/* VkResult result = vkAllocateDescriptorSets(vk.device, &setAI, descriptorSets.data()); */
VkResult result = vkAllocateDescriptorSets(vk.device, &setAI, &descriptorSet);
if (result != VK_SUCCESS) {
throw getVkException(result, "Failed to create descriptor set", "TextureManager::createDescriptorPool");
}
// configure sets
std::vector<VkDescriptorImageInfo> imageInfos;
for (auto it = atlases.cbegin(); it != atlases.cend(); it++) {
imageInfos.emplace_back();
imageInfos.back().imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
imageInfos.back().imageView = it->second.getTextureImageView();
imageInfos.back().sampler = it->second.getTextureSampler();
}
std::array<VkWriteDescriptorSet, 1> descriptorW{};
descriptorW[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorW[0].dstSet = descriptorSet;
descriptorW[0].dstBinding = 0;
descriptorW[0].dstArrayElement = 0;
descriptorW[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
descriptorW[0].descriptorCount = static_cast<uint32_t>(imageInfos.size());
descriptorW[0].pImageInfo = imageInfos.data();
uint32_t descriptorWriteCount = static_cast<uint32_t>(descriptorW.size());
uint32_t descriptorCopyCount = 0;
vkUpdateDescriptorSets(vk.device, descriptorWriteCount, descriptorW.data(), descriptorCopyCount, nullptr);
/* } */
/* vLog("createDescriptorSets: Created descriptor sets."); */
}
} // namespace gz::vk

65
texture_manager.hpp Normal file
View File

@ -0,0 +1,65 @@
#pragma once
#include "texture_atlas.hpp"
#include "stb_image.h"
#include <gz-util/util/string.hpp>
#include <glm/fwd.hpp>
#include <unordered_map>
namespace gz::vk {
struct TextureInfo {
uint32_t atlas;
glm::vec2 texCoordTopLeft;
glm::vec2 texCoordBottomRight;
};
/// Defined in vulkan_instance.hpp
class VulkanInstance;
class TextureManager {
public:
/**
* @brief Create a texture manager
* @details
* -# @ref VulkanInstance::registerCleanupCallback "register" @ref cleanup() "cleanup callback"
* -# @ref createDescriptor "create descriptor set layout, pool and set
*/
TextureManager(VulkanInstance& instance);
void getTexCoords(const std::string& textureName, glm::vec2& texCoords);
const VkDescriptorSet& getDescriptorSet() const { return descriptorSet; }
const VkDescriptorSetLayout& getDescriptorSetLayout() const { return descriptorSetLayout; }
private:
void cleanup();
void loadTextureFromFile(const std::string& textureName);
std::unordered_map<uint32_t, TextureAtlas> atlases;
util::unordered_string_map<TextureInfo> textureInfos;
VulkanInstance& vk;
/**
* @name Create desciptors
* @details These functions create a desciptor with bindings for
* -# Combined Image Sampler (DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER) at binding 0
* @{
*/
VkDescriptorSetLayout descriptorSetLayout;
VkDescriptorPool descriptorPool;
VkDescriptorSet descriptorSet;
/* std::vector<VkDescriptorSet> descriptorSets; */
/**
* @brief Create a descriptor set layout
* @details
* Bindings:
* -0 COMBINED_IMAGE_SAMPLER
*/
void createDescriptorSetLayout();
void createDescriptorPool();
void createDescriptorSet();
/* const uint32_t bindingCombinedImageSampler = 1; */
/**
* @}
*/
};
} // namespace gz::vk

View File

@ -24,7 +24,7 @@ VkFormat getVkFormat() {
//
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);
return "pos: <" + gz::toString(pos) + ", color: " + gz::toString(color) + ", texCoords: " + gz::toString(texCoord) + ">";
};

View File

@ -1,2 +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
# khronos_validation.enables = VK_VALIDATION_FEATURE_ENABLE_BEST_PRACTICES_EXT;VALIDATION_CHECK_ENABLE_VENDOR_SPECIFIC_AMD

File diff suppressed because it is too large Load Diff

View File

@ -23,6 +23,9 @@
#include <vector>
namespace gz::vk {
constexpr gz::Color VULKAN_MESSAGE_PREFIX_COLOR = gz::Color::BO_BLUE;
constexpr gz::Color VULKAN_MESSAGE_TIME_COLOR = gz::Color::BLUE;
const std::string CONFIG_FILE = "vulkan.conf";
const int MAX_FRAMES_IN_FLIGHT = 3;
#define SettingsTypes uint32_t, bool, float
@ -41,36 +44,79 @@ const gz::util::unordered_string_map<std::string> INITIAL_SETTINGS = {
};
constexpr VkClearColorValue missingTextureColor = { { 0.4f, 0.0f, 0.4f, 1.0f } };
const int BINDING = 0;
const uint32_t NO_FLAGS = 0;
const uint32_t NO_OFFSET = 0;
constexpr VkAllocationCallbacks* NO_ALLOC = nullptr;
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;
friend class TextureManager;
friend class TextureAtlas;
public:
VulkanInstance(gz::SettingsManagerCreateInfo<SettingsTypes>smCI) : settings(smCI) {};
/**
* @brief Initializes the vulkan instance
* @details
* -# @ref createWindow "create a window through glfw"
* -# @ref createInstance "create the vulkan instance"
* -# @ref setupDebugMessenger "sets up the debug messenger"
* -# @ref createSurface "create a the window surface"
* -# @ref selectPhysicalDevice "select a GPU"
* -# @ref setValidSettings "set the possible settings for the SettingsManager"
* -# @ref createLogicalDevice "create a logical device"
* -# @ref createSwapChain "create the swap chain"
* -# @ref createSwapChainImageViews "create the swap chain image views"
* -# @ref createCommandPools "create the command pools"
* @todo move depth image, texture, texture samples, model stuff to renderers
* -# @ref createCommandBuffers "create command buffers for swap chain image layout transitions"
* -# @ref createSyncObjects "create synchronization objects"
*/
void init();
/**
* @brief Acquires a new frame from the swap chain and sets the image layout
* @details
* How to draw stuff right now:
* -# Call this function
* -# Call the draw functions from the renderers.
* The renderers need to put the recorded command buffers into the commandBuffersToSubmitThisFrame vector using submitThisFrame()
* -# Call endFrameDraw
*/
uint32_t beginFrameDraw();
void submitThisFrame(VkCommandBuffer);
void submitThisFrame(VkCommandBuffer& cmdBuffer);
void endFrameDraw(uint32_t imageIndex);
void deInit();
GLFWwindow* window;
/**
* @brief Destroys everything that was initalized in init
* @details
* -# Calls every callback registered by registerCleanupCallback() (FILO order)
* -# calls cleanupSwapChain()
* -# destroys everything initalized in init()
* -# destroys the window
* -# calls glfwTerminate()
*/
void cleanup();
void registerCleanupCallback(std::function<void()> callbackF);
void registerSwapChainRecreateCallback(std::function<void()> callbackF);
/// 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();
std::vector<std::function<void()>> cleanupCallbacks;
std::vector<std::function<void()>> scRecreateCallbacks;
//
// INSTANCE
@ -79,9 +125,9 @@ const size_t INDEX_BUFFER_SIZE = 512;
/**
* @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
* -# check if validationLayers are available (if enabled)
* -# create instance with info
* -# check if all extensions required by glfw are available
*/
void createInstance();
@ -89,8 +135,17 @@ const size_t INDEX_BUFFER_SIZE = 512;
// PHYSICAL DEVICE
//
VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;
/// all the properties of the selected physcial device
VkPhysicalDeviceProperties phDevProperties;
VkPhysicalDeviceFeatures phDevFeatures;
/// all the features that the selected physical device supports
PhysicalDeviceFeatures phDevFeatures;
/**
* @brief Get rhe required physical device features
* @details
* The required features are:
* - synchronization2
*/
PhysicalDeviceFeatures getRequiredFeatures() const;
/**
* @brief Assign the physicalDevice handle to the @ref rateDevice "best rated" GPU
* @details
@ -110,10 +165,11 @@ const size_t INDEX_BUFFER_SIZE = 512;
/**
* @brief Set valid values for the SettingsManager according to phDevFeatures and phDevProperties
* @details
* Must be called after selectPhysicalDevice
* Must be called after selectPhysicalDevice and before createLogicalDevice
* Sets valid values for:
* - anisotropy_enable
* - max_anisotropy
*
*/
void setValidSettings();
/**
@ -152,6 +208,7 @@ const size_t INDEX_BUFFER_SIZE = 512;
//
VkSwapchainKHR swapChain;
std::vector<VkImage> scImages;
std::vector<VkImageView> scImageViews;
VkFormat scImageFormat;
VkExtent2D scExtent;
@ -166,13 +223,23 @@ const size_t INDEX_BUFFER_SIZE = 512;
VkPresentModeKHR selectSwapChainPresentMode(const std::vector<VkPresentModeKHR>& availableModes);
VkExtent2D selectSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities);
void createSwapChain();
void recreateSwapChain();
void createSwapChainImageViews();
void cleanupSwapChain();
/**
* @brief Recreate the swap chain for the current window size
* @details
* Does:
* -# get new window dimensions
* - blocks while dimensions == 0 (minimized)
* -# calls cleanupSwapChain
* -# calls createSwapChain
* -# other stuff
* -# calls all callbacks registered with registerSwapChainRecreateCallback
*/
void recreateSwapChain();
//
// IMAGE VIEW
//
std::vector<VkImageView> scImageViews;
void createImageViews();
/**
* @brief Create a 2D imageView with format for image.
*/
@ -180,8 +247,8 @@ const size_t INDEX_BUFFER_SIZE = 512;
//
// RENDER PASS
//
VkRenderPass renderPassBegin;
VkRenderPass renderPassEnd;
/* VkRenderPass renderPassBegin; */
/* VkRenderPass renderPassEnd; */
/**
* @brief Create the render pass
* @details
@ -189,40 +256,16 @@ const size_t INDEX_BUFFER_SIZE = 512;
* -# color attachment
* -# depth stencil attachment
*/
void createRenderPassBegin();
void createRenderPassEnd();
/* void createRenderPassBegin(); */
/* void createRenderPassEnd(); */
//
// DESCRIPTORS
//
/* VkDescriptorSetLayout descriptorSetLayout; */
/**
* @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)
* @{
* @brief Create a descriptor layout with bindings
*/
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; */
void createDescriptorSetLayout(std::vector<VkDescriptorSetLayoutBinding> bindings, VkDescriptorSetLayout& layout);
/**
* @}
*/
@ -230,7 +273,6 @@ const size_t INDEX_BUFFER_SIZE = 512;
// PIPELINE
//
PipelineContainer pipelines;
/* void createGraphicsPipeline(); */
VkShaderModule createShaderModule(const std::vector<char>& code);
/**
@ -254,7 +296,7 @@ const size_t INDEX_BUFFER_SIZE = 512;
*
*/
template<VertexType T>
void createGraphicsPipeline(const std::string& vertexShader, const std::string& fragmentShader, bool useDepthStencil, VkRenderPass& renderPass, Pipeline& pipeline);
void createGraphicsPipeline(const std::string& vertexShader, const std::string& fragmentShader, std::vector<VkDescriptorSetLayout>& descriptorSetLayouts, bool useDepthStencil, VkRenderPass& renderPass, Pipeline& pipeline);
//
// FONT
//
@ -306,28 +348,11 @@ const size_t INDEX_BUFFER_SIZE = 512;
//
// 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
* @brief Destroy the framebuffers
*/
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);
void destroyFramebuffers(std::vector<VkFramebuffer>& framebuffers);
void createFramebuffers(std::vector<VkFramebuffer>& framebuffers, std::vector<VkImageView>& imageViews, VkRenderPass& renderPass);
bool frameBufferResized = false;
static void frameBufferResizedCallback(GLFWwindow* window, int width, int height);
//
@ -339,15 +364,24 @@ const size_t INDEX_BUFFER_SIZE = 512;
//
// COMMAND BUFFER
//
/**
* @brief Create MAX_FRAMES_IN_FLIGHT command buffers from the commandPoolGraphics
*/
void createCommandBuffers(std::vector<VkCommandBuffer>& commandBuffers);
/**
* @brief Destroy all command buffers (must be from commandPoolGraphics)
*/
void destroyCommandBuffers(std::vector<VkCommandBuffer>& commandBuffers);
std::vector<VkCommandBuffer> commandBuffersBegin;
std::vector<VkCommandBuffer> commandBuffersEnd;
//
// IMAGE UTILITY
//
/**
* @brief Create the image, allocate the imageMemory and bind the image to it
*/
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);
void copyBufferToImage(VkBuffer buffer, VkImage image, int32_t offsetX, int32_t offsetY, uint32_t width, uint32_t height);
/**
* @todo make a version using vkCmdResolveImage for multisampled images
* @brief Copy srcImage to dstImage
@ -362,18 +396,22 @@ const size_t INDEX_BUFFER_SIZE = 512;
* @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);
void copyImageToImage(VkCommandBuffer& cmdBuffer, VkImage srcImage, VkImage dstImage, VkExtent2D extent); //, 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)
* - UNDEFINED -> DEPTH_STENCIL_ATTACHMENT_OPTIMAL (graphics q)
* - UNDEFINED -> TRANSFER_DST_OPTIMAL (transfer q)
* - SHADER_READ_ONLY_OPTIMAL -> TRANSFER_DST_OPTIMAL (graphics q)
* - TRANSFER_DST_OPTIMAL -> SHADER_READ_ONLY_OPTIMAL (graphics q)
* - TRANSFER_DST_OPTIMAL -> PRESENT_SRC_KHR (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.
*
* The transition is done by submitting a VkImageMemoryBarrier2
*
* @param cmdBuffer [Optional] The command buffer where the command will be recorded to
* @throws VkUserError if the transition is not supported.
*/
@ -382,21 +420,21 @@ const size_t INDEX_BUFFER_SIZE = 512;
//
// DEPTH
//
VkImage depthImage;
VkDeviceMemory depthImageMemory;
VkImageView depthImageView;
/* VkImage depthImage; */
/* VkDeviceMemory depthImageMemory; */
/* VkImageView depthImageView; */
VkFormat findDepthFormat();
void createDepthImageAndView();
/* void createDepthImageAndView(); */
//
// TEXTURES
//
VkImage textureImage;
VkDeviceMemory textureImageMemory;
VkImageView textureImageView;
VkSampler textureSampler;
/* VkImage textureImage; */
/* VkDeviceMemory textureImageMemory; */
/* VkImageView textureImageView; */
/* VkSampler textureSampler; */
/// Load textures/image.png
void createTextureImageAndView();
void createTextureSampler();
/* void createTextureImageAndView(); */
void createTextureSampler(VkSampler& sampler);
//
// SYNCHRONIZATION
@ -425,10 +463,27 @@ const size_t INDEX_BUFFER_SIZE = 512;
//
// UTILITY
//
/**
* @name Handle ownership
* @brief Retrieve the owner of a vulkan handle
* @see ObjectUsingVulkan
* @{
*/
static std::vector<ObjectUsingVulkan> objectsUsingVulkan;
static void registerObjectUsingVulkan(const ObjectUsingVulkan& obj);
static void getHandleOwnerString(std::string_view message);
static std::string handleOwnerString;
static bool lastColorWhite;
/// @}
/**
* @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.
* - Validation and performace messages usually have a bracket containing a VUID,
* these brackets are printed in yellow/red when its a warning/error.
* - Handles printed by the validation layer are checked with objectsUsingVulkan, if found
* the owner of the handle is added to the message @see registerObjectUsingVulkan, ObjectUsingVulkan
* - Different message sources (layer, shader, general...) get a prefix with a different color
*/
static VKAPI_ATTR VkBool32 VKAPI_CALL debugLog(
VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverety,

View File

@ -1,8 +1,73 @@
#include "vulkan_util.hpp"
#include <iostream>
#include <vulkan/vulkan_core.h>
namespace gz::vk {
//
// CONVENIENCE
//
PhysicalDeviceFeatures::PhysicalDeviceFeatures() {
f.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
f11.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES;
f12.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES;
f13.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES;
f.pNext = &f11;
f11.pNext = &f12;
f12.pNext = &f13;
}
bool PhysicalDeviceFeatures::requiredFeaturesAvailable(const PhysicalDeviceFeatures& requiredFeatures) const {
// iterate over all the features in the structs
// VkPhysicalDeviceFeatures2
size_t featureCount = sizeof(VkPhysicalDeviceFeatures) / sizeof(VkBool32);
// pointer to first feature
const VkBool32* pFeature = &f.features.robustBufferAccess;
const VkBool32* pRequiredFeature = &requiredFeatures.f.features.robustBufferAccess;
for (size_t i = 0; i < featureCount; i++) {
if (*(pRequiredFeature + i) == VK_TRUE and *(pFeature + i) == VK_FALSE) {
/* std::cout << "core feature not supported: i = " << i << "\n"; */
return false;
}
}
// VkPhysicalDeviceVulkan11Features
// pointer to first feature: after sType and pNext
featureCount = (sizeof(VkPhysicalDeviceVulkan11Features) - sizeof(VkStructureType) - sizeof(void*)) / sizeof(VkBool32);
pFeature = &f11.storageBuffer16BitAccess;
pRequiredFeature = &requiredFeatures.f11.storageBuffer16BitAccess;
for (size_t i = 0; i < featureCount; i++) {
if (*(pRequiredFeature + i) == VK_TRUE and *(pFeature + i) == VK_FALSE) {
/* std::cout << "vulkan11 feature not supported: i = " << i << "\n"; */
return false;
}
}
// VkPhysicalDeviceVulkan12Features
// pointer to first feature: after sType and pNext
featureCount = (sizeof(VkPhysicalDeviceVulkan12Features) - sizeof(VkStructureType) - sizeof(void*)) / sizeof(VkBool32);
pFeature = &f12.samplerMirrorClampToEdge;
pRequiredFeature = &requiredFeatures.f12.samplerMirrorClampToEdge;
for (size_t i = 0; i < featureCount; i++) {
if (*(pRequiredFeature + i) == VK_TRUE and *(pFeature + i) == VK_FALSE) {
/* std::cout << "vulkan12 feature not supported: i = " << i << "\n"; */
return false;
}
}
// VkPhysicalDeviceVulkan13Features
// pointer to first feature: after sType and pNext
featureCount = (sizeof(VkPhysicalDeviceVulkan13Features) - sizeof(VkStructureType) - sizeof(void*)) / sizeof(VkBool32);
pFeature = &f13.robustImageAccess;
pRequiredFeature = &requiredFeatures.f13.robustImageAccess;
for (size_t i = 0; i < featureCount; i++) {
if (*(pRequiredFeature + i) == VK_TRUE and *(pFeature + i) == VK_FALSE) {
/* std::cout << "vulkan13 feature not supported: i = " << i << "\n"; */
return false;
}
}
return true;
}
void PipelineContainer::erase(const PipelineT& key, VkDevice& device, const VkAllocationCallbacks* pAllocator) {
vkDestroyPipeline(device, pipelines[key].pipeline, pAllocator);
vkDestroyPipelineLayout(device, pipelines[key].layout, pAllocator);
@ -14,6 +79,46 @@ namespace gz::vk {
return pipelines.erase(it);
}
void PipelineContainer::destroy(VkDevice& device, const VkAllocationCallbacks* pAllocator) {
auto it = pipelines.begin(); while (it != pipelines.end()) {
it = erase(it, device);
}
}
VkDependencyInfo getDepInfo(const VkImageMemoryBarrier2& barrier) {
VkDependencyInfo depI{};
depI.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
depI.imageMemoryBarrierCount = 1;
depI.pImageMemoryBarriers = &barrier;
return depI;
}
//
//
//
ObjectUsingVulkan::ObjectUsingVulkan(std::string&& name, std::vector<void*>&& ptrsToHandles, std::vector<void*>&& vectorWithPtrsToHandles) {
objectName = std::move(name);
for (auto it = vectorWithPtrsToHandles.begin(); it != vectorWithPtrsToHandles.end(); it++) {
vectorWithPtrToHandle.emplace_back(reinterpret_cast<std::vector<void*>*>(*it));
}
ptrToHandle = std::move(ptrsToHandles);
updateHandles();
}
void ObjectUsingVulkan::updateHandles() {
handles.clear();
for (auto it = ptrToHandle.begin(); it != ptrToHandle.end(); it++) {
handles.insert(reinterpret_cast<uint64_t>(reinterpret_cast<void*>(*it)));
}
for (auto it = vectorWithPtrToHandle.begin(); it != vectorWithPtrToHandle.end(); it++) {
for (auto it2 = (*it)->begin(); it2 != (*it)->end(); it2++) {
handles.insert(reinterpret_cast<uint64_t>(reinterpret_cast<void*>(*it2)));
}
}
}
} // namespace gz::vk

View File

@ -1,10 +1,39 @@
#pragma once
#include "vertex.hpp"
#include <optional>
#include <set>
#include <unordered_map>
#include <vulkan/vulkan_core.h>
namespace gz::vk {
/**
* @name Convenience structs
* @details
* Structures and functionas making usiing the vulkan api more convenient
* @{
*/
/**
* @brief Struct containing all physical device features structs
* @details
* Make sure to call vkGetPhysicalDeviceFeatures2 with the f member, not this struct itself
*/
struct PhysicalDeviceFeatures {
/**
* @brief Initialize the structs with sType and pNext chain f -> f11 -> f12 -> f13
*/
PhysicalDeviceFeatures();
VkPhysicalDeviceFeatures2 f{};
VkPhysicalDeviceVulkan11Features f11{};
VkPhysicalDeviceVulkan12Features f12{};
VkPhysicalDeviceVulkan13Features f13{};
/**
* @brief Check if all features enabled in requiredFeatures are enabled in this
*/
bool requiredFeaturesAvailable(const PhysicalDeviceFeatures& requiredFeatures) const;
};
enum PipelineT {
PL_3D, PL_2D
};
@ -22,10 +51,17 @@ namespace gz::vk {
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
* @brief Destroy the pipeline+layout and then remove the handles from the underlying map
*/
void erase(const PipelineT& key, VkDevice& device, const VkAllocationCallbacks* pAllocator=nullptr);
/**
* @brief Destroy the pipeline+layout and then remove the handles from the underlying map
*/
iterator erase(const iterator& it, VkDevice& device, const VkAllocationCallbacks* pAllocator=nullptr);
/**
* @brief Destroy all pipelines#layouts and then remove the handles from the underlying map
*/
void destroy(VkDevice& device, const VkAllocationCallbacks* pAllocator=nullptr);
iterator begin() { return pipelines.begin(); }
iterator end() { return pipelines.end(); }
size_t size() const { return pipelines.size(); }
@ -34,6 +70,12 @@ namespace gz::vk {
};
struct SwapChainSupport {
VkSurfaceCapabilitiesKHR capabilities;
std::vector<VkSurfaceFormatKHR> formats;
std::vector<VkPresentModeKHR> presentModes;
};
template<typename T>
concept SupportedIndexType = std::same_as<T, uint16_t> or std::same_as<T, uint32_t>;
@ -81,9 +123,36 @@ namespace gz::vk {
};
struct SwapChainSupport {
VkSurfaceCapabilitiesKHR capabilities;
std::vector<VkSurfaceFormatKHR> formats;
std::vector<VkPresentModeKHR> presentModes;
/**
* @brief Get a VkDependencyInfo struct for a single image memory barrier
*/
VkDependencyInfo getDepInfo(const VkImageMemoryBarrier2& barrier);
/// @}
/**
* @brief Used for debugging
* @see VulkanInstance::debugLog
* @details
* Handles like VkDevice are pointers to the actual handle.
* Register the handle to a vulkan object with this struct and call updateHandles().
* The handles set will then contain the actual address of the handle, as printed by the validation layers.
* That means you can check a handle returned by a validation layer
* with this struct and see if this handle belongs to object that created this struct.
* @warning
* Every pointer you submit to this must stay valid!
* Use this struct only when debugging!
*/
struct ObjectUsingVulkan {
ObjectUsingVulkan(std::string&& name, std::vector<void*>&& ptrsToHandles, std::vector<void*>&& vectorWithPtrsToHandles);
std::string objectName;
std::vector<void*> ptrToHandle;
std::vector<std::vector<void*>*> vectorWithPtrToHandle;
std::set<uint64_t> handles;
/**
* @brief Update the handles map with the current handle (aka segfault generator)
*/
void updateHandles();
};
} // namespace gz::vk