Compare commits

...

10 Commits

21 changed files with 9027 additions and 146 deletions

View File

@ -1,11 +1,11 @@
# NF library CMakeLists.txt # NF library CMakeLists.txt
add_library(NothinFancy STATIC "src/Engine.cpp" "src/include/nf.h" "src/pch.h" "src/util.h" "src/util/log.h" "src/util/log.cpp" "src/include/nf/config.h" "src/util/util.cpp" "src/util/file.h" "src/util/file.cpp" "src/client/Client.h" "src/client/Client.cpp" "src/client/Window.h" "src/client/Window.cpp" "src/client/render/RenderEngine.h" "src/client/render/RenderEngine.cpp" "src/client/render/ShaderModule.h" "src/client/render/ShaderModule.cpp") add_library(NothinFancy STATIC "src/Engine.cpp" "src/include/nf.h" "src/pch.h" "src/util.h" "src/util/log.h" "src/util/log.cpp" "src/include/nf/config.h" "src/util/util.cpp" "src/util/file.h" "src/util/file.cpp" "src/client/Client.h" "src/client/Client.cpp" "src/client/Window.h" "src/client/Window.cpp" "src/client/render/RenderEngine.h" "src/client/render/RenderEngine.cpp" "src/client/render/ShaderModule.h" "src/client/render/ShaderModule.cpp" "src/client/render/GraphicsResource.h" "src/client/render/Buffer.h" "src/client/render/Buffer.cpp" "src/client/render/VideoMemoryAllocator.h" "src/client/render/VideoMemoryAllocator.cpp" "src/client/render/Image.h" "src/client/render/Image.cpp" "src/client/render/CommandPool.h" "src/client/render/CommandPool.cpp")
# Use C++20 # Use C++20
set_property(TARGET NothinFancy PROPERTY CXX_STANDARD 20) set_property(TARGET NothinFancy PROPERTY CXX_STANDARD 20)
# Additional include directories # Additional include directories
target_include_directories(NothinFancy PUBLIC "src" "src/include") target_include_directories(NothinFancy PUBLIC "src" "src/include" "dep/include")
# Use precompiled header # Use precompiled header
target_precompile_headers(NothinFancy PUBLIC "src/pch.h") target_precompile_headers(NothinFancy PUBLIC "src/pch.h")

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,14 @@
#version 450 #version 450
layout(location = 0) in vec3 fragColor; // Inputs
layout(location = 0) in vec2 inTextureCoordinates;
// Outputs
layout(location = 0) out vec4 outColor; layout(location = 0) out vec4 outColor;
// Uniforms
layout(binding = 1) uniform sampler2D image;
void main() { void main() {
outColor = vec4(fragColor, 1.0); outColor = texture(image, inTextureCoordinates);
} }

View File

@ -1,20 +1,18 @@
#version 450 #version 450
vec2 trianglePositions[3] = vec2[] ( // Inputs
vec2(0.0, -0.5), layout(location = 0) in vec3 inPosition;
vec2(0.5, 0.5), layout(location = 1) in vec2 inTextureCoordinates;
vec2(-0.5, 0.5)
);
vec3 triangleColors[3] = vec3[] ( // Outputs
vec3(1.0, 0.0, 0.0), layout(location = 0) out vec2 outTextureCoordinates;
vec3(0.0, 1.0, 0.0),
vec3(0.0, 0.0, 1.0)
);
layout(location = 0) out vec3 fragColor; // Uniforms
layout(binding = 0) uniform MVPMatrixUniformBufferObject {
mat4 mvp;
} mvpUBO;
void main() { void main() {
fragColor = triangleColors[gl_VertexIndex]; outTextureCoordinates = inTextureCoordinates;
gl_Position = vec4(trianglePositions[gl_VertexIndex], 0.0, 1.0); gl_Position = mvpUBO.mvp * vec4(inPosition, 1.0);
} }

View File

@ -18,8 +18,18 @@ namespace nf::client {
std::thread inputThread(&Client::runInputThread, this, std::move(windowPromise)); std::thread inputThread(&Client::runInputThread, this, std::move(windowPromise));
m_renderEngine = std::make_unique<render::RenderEngine>(std::move(windowFuture.get()), m_config.display); m_renderEngine = std::make_unique<render::RenderEngine>(std::move(windowFuture.get()), m_config.display);
auto fpsClock1 = std::chrono::high_resolution_clock::now(), fpsClock2 = fpsClock1;
unsigned int frame = 0;
while (m_running) { while (m_running) {
m_renderEngine->doFrame(); m_renderEngine->doFrame();
frame++;
fpsClock2 = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> duration = fpsClock2 - fpsClock1;
if (duration.count() >= 1.0) {
NFLog(std::format("FPS: {}", frame));
frame = 0;
fpsClock1 = std::chrono::high_resolution_clock::now();
}
} }
inputThread.join(); inputThread.join();

View File

@ -12,6 +12,7 @@ namespace nf::client {
, m_styleFullscreen(WS_POPUP) , m_styleFullscreen(WS_POPUP)
, m_currentWidth() , m_currentWidth()
, m_currentHeight() , m_currentHeight()
, m_active(true)
{ {
NFLog("Creating window"); NFLog("Creating window");
@ -29,6 +30,8 @@ namespace nf::client {
void Window::setDisplay(DisplayConfig& config) { void Window::setDisplay(DisplayConfig& config) {
NFLog("Setting window display"); NFLog("Setting window display");
bool wasShown = IsWindowVisible(m_handle);
show(false);
// TODO: Only use "active" monitor when starting windowed // TODO: Only use "active" monitor when starting windowed
POINT cursor = {}; POINT cursor = {};
@ -55,12 +58,15 @@ namespace nf::client {
SetWindowLongPtr(m_handle, GWL_STYLE, m_styleFullscreen); SetWindowLongPtr(m_handle, GWL_STYLE, m_styleFullscreen);
windowX = monitorX, windowY = monitorY; windowX = monitorX, windowY = monitorY;
windowWidth = monitorWidth, windowHeight = monitorHeight; windowWidth = monitorWidth, windowHeight = monitorHeight;
m_currentWidth = windowWidth, m_currentHeight = windowHeight;
config.width = windowWidth, config.height = windowHeight; config.width = windowWidth, config.height = windowHeight;
break; break;
} }
} }
SetWindowPos(m_handle, nullptr, windowX, windowY, windowWidth, windowHeight, SWP_NOZORDER | SWP_FRAMECHANGED); SetWindowPos(m_handle, nullptr, windowX, windowY, windowWidth, windowHeight, SWP_NOZORDER | SWP_FRAMECHANGED);
show(wasShown);
} }
void Window::runLoop() { void Window::runLoop() {
@ -71,12 +77,18 @@ namespace nf::client {
TranslateMessage(&msg); TranslateMessage(&msg);
DispatchMessage(&msg); DispatchMessage(&msg);
} }
m_active = false;
} }
void Window::show(bool show) { void Window::show(bool show) {
ShowWindow(m_handle, show ? SW_SHOW : SW_HIDE); ShowWindow(m_handle, show ? SW_SHOW : SW_HIDE);
} }
bool Window::isActive() const {
return m_active;
}
void Window::registerWindowClass() { void Window::registerWindowClass() {
WNDCLASS wc = {}; WNDCLASS wc = {};
wc.lpszClassName = m_wndClassName; wc.lpszClassName = m_wndClassName;

View File

@ -14,6 +14,7 @@ namespace nf::client {
void setDisplay(DisplayConfig& config); void setDisplay(DisplayConfig& config);
void runLoop(); void runLoop();
void show(bool show = true); void show(bool show = true);
bool isActive() const;
~Window(); ~Window();
private: private:
@ -28,5 +29,7 @@ namespace nf::client {
const DWORD m_styleFullscreen; const DWORD m_styleFullscreen;
unsigned int m_currentWidth, m_currentHeight; unsigned int m_currentWidth, m_currentHeight;
bool m_active;
}; };
} }

View File

@ -0,0 +1,91 @@
// Buffer class implementation
#include "pch.h"
#include "Buffer.h"
#include "util.h"
namespace nf::client::render {
Buffer::Buffer(BufferType type, const VkDevice& device, VideoMemoryAllocator& allocator, const CommandPool& commandPool, void* bufferData, size_t bufferSize)
: GraphicsResource(device)
, m_allocator(allocator)
, m_handle()
, m_allocation()
, m_indexCount()
{
if (type == BufferType::Staging) {
createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, m_handle);
m_allocator.allocateForBuffer(m_handle, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, m_allocation);
if (m_allocation.mappedMemoryPointer)
memcpy(m_allocation.mappedMemoryPointer, bufferData, bufferSize);
return;
}
if (type == BufferType::Uniform) {
createBuffer(bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, m_handle);
m_allocator.allocateForBuffer(m_handle, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, m_allocation);
return;
}
Buffer stagingBuffer(BufferType::Staging, m_device, m_allocator, commandPool, bufferData, bufferSize);
VkBufferUsageFlags mainUsage = NULL;
switch (type) {
case BufferType::Vertex:
mainUsage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
break;
case BufferType::Index:
mainUsage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT;
m_indexCount = bufferSize / sizeof(uint32_t);
break;
}
createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | mainUsage, m_handle);
m_allocator.allocateForBuffer(m_handle, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, m_allocation);
copyBuffer(stagingBuffer.getHandle(), m_handle, bufferSize, commandPool);
}
const VkBuffer& Buffer::getHandle() const {
return m_handle;
}
void* Buffer::getPointer() const {
return m_allocation.mappedMemoryPointer;
}
uint32_t Buffer::getIndicesCount() const {
return m_indexCount;
}
void Buffer::createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkBuffer& buffer) {
VkBufferCreateInfo bufferCI = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
bufferCI.size = size;
bufferCI.usage = usage;
if (vkCreateBuffer(m_device, &bufferCI, nullptr, &buffer) != VK_SUCCESS)
NFError("Could not create vertex buffer.");
}
void Buffer::copyBuffer(VkBuffer bufferSource, VkBuffer bufferDestination, VkDeviceSize size, const CommandPool& commandPool) {
VkCommandBuffer commandBuffer = commandPool.beginOneTimeExecution();
VkBufferCopy bufferCopyRegion = {};
bufferCopyRegion.size = size;
vkCmdCopyBuffer(commandBuffer, bufferSource, bufferDestination, 1, &bufferCopyRegion);
commandPool.endOneTimeExecution(commandBuffer);
}
void Buffer::destroyBuffer(VkBuffer buffer, VideoMemoryAllocation& allocation) {
m_allocator.deallocate(allocation);
vkDestroyBuffer(m_device, buffer, nullptr);
}
Buffer::~Buffer() {
destroyBuffer(m_handle, m_allocation);
}
}

View File

@ -0,0 +1,37 @@
// Buffer class header
#pragma once
#include "GraphicsResource.h"
#include "VideoMemoryAllocator.h"
#include "CommandPool.h"
namespace nf::client::render {
enum class BufferType {
Staging,
Vertex,
Index,
Uniform
};
class Buffer : GraphicsResource {
public:
Buffer(BufferType type, const VkDevice& device, VideoMemoryAllocator& allocator, const CommandPool& commandPool, void* bufferData, size_t bufferSize);
const VkBuffer& getHandle() const;
void* getPointer() const;
uint32_t getIndicesCount() const;
~Buffer();
private:
void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkBuffer& buffer);
void copyBuffer(VkBuffer sourceBuffer, VkBuffer destinationBuffer, VkDeviceSize size, const CommandPool& commandPool);
void destroyBuffer(VkBuffer buffer, VideoMemoryAllocation& allocation);
VideoMemoryAllocator& m_allocator;
VkBuffer m_handle;
VideoMemoryAllocation m_allocation;
uint32_t m_indexCount;
};
}

View File

@ -0,0 +1,61 @@
// CommandPool class implementation
#include "pch.h"
#include "CommandPool.h"
#include "util.h"
namespace nf::client::render {
CommandPool::CommandPool(const VkDevice& device, const VkQueue& queue, uint32_t queueFamilyIndex)
: GraphicsResource(device)
, m_queue(queue)
, m_handle()
{
VkCommandPoolCreateInfo commandPoolCI = { VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO };
commandPoolCI.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
commandPoolCI.queueFamilyIndex = queueFamilyIndex;
if (vkCreateCommandPool(m_device, &commandPoolCI, nullptr, &m_handle) != VK_SUCCESS)
NFError("Could not create command pool.");
}
VkCommandBuffer CommandPool::allocateCommandBuffer() const {
VkCommandBuffer commandBuffer = nullptr;
VkCommandBufferAllocateInfo commandBufferAI = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO };
commandBufferAI.commandPool = m_handle;
commandBufferAI.commandBufferCount = 1;
if (vkAllocateCommandBuffers(m_device, &commandBufferAI, &commandBuffer) != VK_SUCCESS)
NFError("Could not create command buffer.");
return commandBuffer;
}
VkCommandBuffer CommandPool::beginOneTimeExecution() const {
VkCommandBuffer commandBuffer = allocateCommandBuffer();
VkCommandBufferBeginInfo beginInfo = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO };
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
vkBeginCommandBuffer(commandBuffer, &beginInfo);
return commandBuffer;
}
void CommandPool::endOneTimeExecution(VkCommandBuffer commandBuffer) const {
vkEndCommandBuffer(commandBuffer);
VkSubmitInfo submitInfo = { VK_STRUCTURE_TYPE_SUBMIT_INFO };
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &commandBuffer;
vkQueueSubmit(m_queue, 1, &submitInfo, nullptr);
vkQueueWaitIdle(m_queue);
vkFreeCommandBuffers(m_device, m_handle, 1, &commandBuffer);
}
CommandPool::~CommandPool() {
vkDestroyCommandPool(m_device, m_handle, nullptr);
}
}

View File

@ -0,0 +1,22 @@
// CommandPool class header
#pragma once
#include "GraphicsResource.h"
namespace nf::client::render {
class CommandPool : GraphicsResource {
public:
CommandPool(const VkDevice& device, const VkQueue& queue, uint32_t queueFamilyIndex);
VkCommandBuffer allocateCommandBuffer() const;
VkCommandBuffer beginOneTimeExecution() const;
void endOneTimeExecution(VkCommandBuffer commandBuffer) const;
~CommandPool();
private:
const VkQueue& m_queue;
VkCommandPool m_handle;
};
}

View File

@ -0,0 +1,14 @@
// Vulkan resource base class header
#pragma once
namespace nf::client::render {
class GraphicsResource {
public:
GraphicsResource(const VkDevice& device)
: m_device(device)
{}
protected:
const VkDevice& m_device;
};
}

View File

@ -0,0 +1,131 @@
// Image class implementation
#include "pch.h"
#include "Image.h"
#include "util.h"
#include "Buffer.h"
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
namespace nf::client::render {
Image::Image(ImageType type, const VkDevice& device, VideoMemoryAllocator& allocator, const CommandPool& commandPool, const std::string& imageData, VkExtent2D attachmentExtent)
: GraphicsResource(device)
, m_allocator(allocator)
, m_handle()
, m_allocation()
, m_view()
{
VkFormat imageFormat = VK_FORMAT_UNDEFINED;
switch (type) {
case ImageType::Texture:
imageFormat = VK_FORMAT_R8G8B8A8_SRGB;
createTextureImage(imageFormat, commandPool, imageData);
break;
case ImageType::DepthAttachment:
imageFormat = VK_FORMAT_D32_SFLOAT;
createImage(imageFormat, attachmentExtent, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT);
break;
}
createImageView(imageFormat);
}
const VkImageView& Image::getView() const {
return m_view;
}
void Image::createTextureImage(VkFormat imageFormat, const CommandPool& commandPool, const std::string& imageData) {
int imageWidth = 0, imageHeight = 0, numChannels = 0;
stbi_uc* rawImageData = stbi_load_from_memory(reinterpret_cast<const stbi_uc*>(imageData.data()), imageData.size(), &imageWidth, &imageHeight, &numChannels, STBI_rgb_alpha);
VkDeviceSize imageSize = static_cast<VkDeviceSize>(imageWidth) * imageHeight * 4;
Buffer stagingBuffer(BufferType::Staging, m_device, m_allocator, commandPool, rawImageData, imageSize);
stbi_image_free(rawImageData);
VkExtent2D imageExtent = { static_cast<uint32_t>(imageWidth), static_cast<uint32_t>(imageHeight) };
createImage(imageFormat, imageExtent, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT);
VkCommandBuffer commandBuffer = commandPool.beginOneTimeExecution();
// Image transition undefined -> transfer destination
VkImageMemoryBarrier imageMemoryBarrier = { {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER} };
imageMemoryBarrier.image = m_handle;
imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
imageMemoryBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
imageMemoryBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
imageMemoryBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
imageMemoryBarrier.subresourceRange.levelCount = 1;
imageMemoryBarrier.subresourceRange.layerCount = 1;
imageMemoryBarrier.srcAccessMask = 0;
imageMemoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &imageMemoryBarrier);
commandPool.endOneTimeExecution(commandBuffer);
commandBuffer = commandPool.beginOneTimeExecution();
VkBufferImageCopy bufferImageCopyRegion = {};
bufferImageCopyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
bufferImageCopyRegion.imageSubresource.layerCount = 1;
bufferImageCopyRegion.imageExtent = { static_cast<uint32_t>(imageWidth), static_cast<uint32_t>(imageHeight), 1 };
vkCmdCopyBufferToImage(commandBuffer, stagingBuffer.getHandle(), m_handle, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &bufferImageCopyRegion);
commandPool.endOneTimeExecution(commandBuffer);
commandBuffer = commandPool.beginOneTimeExecution();
// Image transition transfer destination -> shader read only
imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
imageMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
imageMemoryBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &imageMemoryBarrier);
commandPool.endOneTimeExecution(commandBuffer);
}
void Image::createImage(VkFormat format, VkExtent2D& size, VkImageUsageFlags usage) {
VkImageCreateInfo imageCI = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
imageCI.imageType = VK_IMAGE_TYPE_2D;
imageCI.extent = { size.width, size.height, 1 };
imageCI.mipLevels = 1;
imageCI.arrayLayers = 1;
imageCI.samples = VK_SAMPLE_COUNT_1_BIT;
imageCI.format = format;
imageCI.usage = usage;
imageCI.tiling = VK_IMAGE_TILING_OPTIMAL;
if (vkCreateImage(m_device, &imageCI, nullptr, &m_handle) != VK_SUCCESS)
NFError("Could not create image.");
m_allocator.allocateForImage(m_handle, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, m_allocation);
}
void Image::createImageView(VkFormat format) {
VkImageAspectFlags aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
if (format == VK_FORMAT_D32_SFLOAT)
aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
VkImageViewCreateInfo imageViewCI = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO };
imageViewCI.image = m_handle;
imageViewCI.viewType = VK_IMAGE_VIEW_TYPE_2D;
imageViewCI.format = format;
imageViewCI.subresourceRange.aspectMask = aspectMask;
imageViewCI.subresourceRange.levelCount = 1;
imageViewCI.subresourceRange.layerCount = 1;
if (vkCreateImageView(m_device, &imageViewCI, nullptr, &m_view) != VK_SUCCESS)
NFError("Could not create image view.");
}
Image::~Image() {
vkDestroyImageView(m_device, m_view, nullptr);
m_allocator.deallocate(m_allocation);
vkDestroyImage(m_device, m_handle, nullptr);
}
}

View File

@ -0,0 +1,32 @@
// Image class header
#pragma once
#include "GraphicsResource.h"
#include "VideoMemoryAllocator.h"
#include "CommandPool.h"
namespace nf::client::render {
enum class ImageType {
Texture,
DepthAttachment
};
class Image : GraphicsResource {
public:
Image(ImageType type, const VkDevice& device, VideoMemoryAllocator& allocator, const CommandPool& commandPool, const std::string& imageData, VkExtent2D attachmentExtent = {});
const VkImageView& getView() const;
~Image();
private:
void createTextureImage(VkFormat imageFormat, const CommandPool& commandPool, const std::string& imageData);
void createImage(VkFormat imageFormat, VkExtent2D& size, VkImageUsageFlags usage);
void createImageView(VkFormat format);
VideoMemoryAllocator& m_allocator;
VkImage m_handle;
VideoMemoryAllocation m_allocation;
VkImageView m_view;
};
}

View File

@ -6,6 +6,12 @@
#include "ShaderModule.h" #include "ShaderModule.h"
namespace nf::client::render { namespace nf::client::render {
// Vertex layout definition
struct Vertex {
glm::vec3 position;
glm::vec2 textureCoordinates;
};
RenderEngine::RenderEngine(std::shared_ptr<Window> window, DisplayConfig display) RenderEngine::RenderEngine(std::shared_ptr<Window> window, DisplayConfig display)
: m_window(window) : m_window(window)
, m_display(display) , m_display(display)
@ -17,18 +23,31 @@ namespace nf::client::render {
, m_device() , m_device()
, m_queueGraphics() , m_queueGraphics()
, m_queuePresent() , m_queuePresent()
, m_renderPassOutput() , m_commandPool()
, m_commandBuffer()
, m_semaphoreImageAvailable()
, m_semaphoreRenderingDone()
, m_fenceFrameInFlight()
, m_swapchain() , m_swapchain()
, m_swapchainImageFormat() , m_swapchainImageFormat()
, m_swapchainExtent()
, m_swapchainImages() , m_swapchainImages()
, m_swapchainImageViews() , m_swapchainImageViews()
, m_swapchainFramebuffers() , m_renderPassOutput()
, m_pipelineLayoutOutput() , m_pipelineOutputDescriptorSetLayout()
, m_pipelineOutputLayout()
, m_pipelineOutputDescriptorPool()
, m_pipelineOutput() , m_pipelineOutput()
, m_commandPool() , m_allocator()
, m_numFramesInFlight(2) , m_imageDepth()
, m_frameExecutors() , m_swapchainFramebuffers()
, m_currentFrameExecutor() , m_bufferVertex()
, m_bufferIndex()
, m_bufferUniformMVP()
, m_imageTest()
, m_sampler()
, m_pipelineOutputDescriptorSet()
{ {
NFLog("Initializing render engine"); NFLog("Initializing render engine");
m_window->setDisplay(m_display); m_window->setDisplay(m_display);
@ -37,10 +56,18 @@ namespace nf::client::render {
createSurface(); createSurface();
pickPhysicalDevice(); pickPhysicalDevice();
createDevice(); createDevice();
createExecutionObjects();
createSwapchain(); createSwapchain();
createOutputRenderPass(); createOutputRenderPass();
createOutputPipeline(); createOutputPipeline();
createFrameExecutors();
m_allocator = std::make_unique<VideoMemoryAllocator>(m_device, m_physicalDevice);
createSwapchainFramebuffers();
createBuffers();
createImage();
createDescriptorSet();
m_window->show(); m_window->show();
} }
@ -86,6 +113,7 @@ namespace nf::client::render {
// Then gather information on them and save first dedicated // Then gather information on them and save first dedicated
std::optional<int> firstDedicatedPhysicalDeviceIndex; std::optional<int> firstDedicatedPhysicalDeviceIndex;
std::vector<VkPhysicalDeviceProperties> physicalDeviceProperties; std::vector<VkPhysicalDeviceProperties> physicalDeviceProperties;
physicalDeviceProperties.reserve(numPhysicalDevices);
for (int i = 0; i < numPhysicalDevices; i++) { for (int i = 0; i < numPhysicalDevices; i++) {
VkPhysicalDeviceProperties currentPhysicalDeviceProperties = {}; VkPhysicalDeviceProperties currentPhysicalDeviceProperties = {};
vkGetPhysicalDeviceProperties(physicalDevices[i], &currentPhysicalDeviceProperties); vkGetPhysicalDeviceProperties(physicalDevices[i], &currentPhysicalDeviceProperties);
@ -154,6 +182,7 @@ namespace nf::client::render {
float queuePriority = 1.0f; float queuePriority = 1.0f;
std::vector<VkDeviceQueueCreateInfo> queueCIs; std::vector<VkDeviceQueueCreateInfo> queueCIs;
queueCIs.reserve(uniqueQueueFamilyIndices.size());
for (uint32_t currentQueueFamilyIndex : uniqueQueueFamilyIndices) { for (uint32_t currentQueueFamilyIndex : uniqueQueueFamilyIndices) {
VkDeviceQueueCreateInfo queueCI = { VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO }; VkDeviceQueueCreateInfo queueCI = { VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO };
queueCI.queueFamilyIndex = currentQueueFamilyIndex; queueCI.queueFamilyIndex = currentQueueFamilyIndex;
@ -205,13 +234,13 @@ namespace nf::client::render {
vkGetPhysicalDeviceSurfacePresentModesKHR(m_physicalDevice, m_surface, &numPresentModes, presentModes.data()); vkGetPhysicalDeviceSurfacePresentModesKHR(m_physicalDevice, m_surface, &numPresentModes, presentModes.data());
VkPresentModeKHR chosenPresentMode = VK_PRESENT_MODE_FIFO_KHR; VkPresentModeKHR chosenPresentMode = VK_PRESENT_MODE_FIFO_KHR;
// TODO: IMMEDIATE isn't always available, so do something about that // TODO: IMMEDIATE isn't always available, so do something about that
for (int i = 0; i < presentModes.size(); i++) /*for (int i = 0; i < presentModes.size(); i++)
if (presentModes[i] == VK_PRESENT_MODE_IMMEDIATE_KHR) if (presentModes[i] == VK_PRESENT_MODE_IMMEDIATE_KHR)
chosenPresentMode = presentModes[i]; chosenPresentMode = presentModes[i];*/
VkExtent2D swapchainExtent = surfaceCapabilities.currentExtent; m_swapchainExtent = surfaceCapabilities.currentExtent;
if (swapchainExtent.width == std::numeric_limits<uint32_t>::max()) if (m_swapchainExtent.width == std::numeric_limits<uint32_t>::max())
swapchainExtent.width = m_display.width, swapchainExtent.height = m_display.height; m_swapchainExtent = { m_display.width, m_display.height };
uint32_t numRequestedImages = surfaceCapabilities.minImageCount + (surfaceCapabilities.minImageCount != surfaceCapabilities.maxImageCount ? 1 : 0); uint32_t numRequestedImages = surfaceCapabilities.minImageCount + (surfaceCapabilities.minImageCount != surfaceCapabilities.maxImageCount ? 1 : 0);
@ -220,7 +249,7 @@ namespace nf::client::render {
swapchainCI.minImageCount = numRequestedImages; swapchainCI.minImageCount = numRequestedImages;
swapchainCI.imageFormat = m_swapchainImageFormat = chosenSurfaceFormat.format; swapchainCI.imageFormat = m_swapchainImageFormat = chosenSurfaceFormat.format;
swapchainCI.imageColorSpace = chosenSurfaceFormat.colorSpace; swapchainCI.imageColorSpace = chosenSurfaceFormat.colorSpace;
swapchainCI.imageExtent = swapchainExtent; swapchainCI.imageExtent = m_swapchainExtent;
swapchainCI.imageArrayLayers = 1; swapchainCI.imageArrayLayers = 1;
swapchainCI.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; swapchainCI.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
@ -259,35 +288,62 @@ namespace nf::client::render {
} }
} }
void RenderEngine::createOutputRenderPass() { void RenderEngine::createExecutionObjects() {
VkAttachmentDescription outputColorAttachment = {}; m_commandPool = std::make_unique<CommandPool>(m_device, m_queueGraphics, m_queueFIGraphics);
outputColorAttachment.format = m_swapchainImageFormat; m_commandBuffer = m_commandPool->allocateCommandBuffer();
outputColorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
outputColorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
outputColorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
outputColorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
outputColorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
outputColorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
outputColorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
VkAttachmentReference outputColorAttachmentReference = {}; VkSemaphoreCreateInfo semaphoreCI = { VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO };
outputColorAttachmentReference.attachment = 0; if (vkCreateSemaphore(m_device, &semaphoreCI, nullptr, &m_semaphoreImageAvailable) != VK_SUCCESS ||
outputColorAttachmentReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; vkCreateSemaphore(m_device, &semaphoreCI, nullptr, &m_semaphoreRenderingDone) != VK_SUCCESS)
NFError("Could not create semaphore.");
VkFenceCreateInfo fenceCI = { VK_STRUCTURE_TYPE_FENCE_CREATE_INFO };
fenceCI.flags = VK_FENCE_CREATE_SIGNALED_BIT;
if (vkCreateFence(m_device, &fenceCI, nullptr, &m_fenceFrameInFlight) != VK_SUCCESS)
NFError("Could not create fence.");
}
void RenderEngine::createOutputRenderPass() {
VkAttachmentDescription attachmentDescriptions[2] = {};
// Color attachment
attachmentDescriptions[0].format = m_swapchainImageFormat;
attachmentDescriptions[0].samples = VK_SAMPLE_COUNT_1_BIT;
attachmentDescriptions[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
attachmentDescriptions[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
attachmentDescriptions[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
attachmentDescriptions[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
attachmentDescriptions[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
attachmentDescriptions[0].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
// Depth attachment
attachmentDescriptions[1] = attachmentDescriptions[0];
attachmentDescriptions[1].format = VK_FORMAT_D32_SFLOAT;
attachmentDescriptions[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
VkAttachmentReference attachmentReferences[2] = {};
attachmentReferences[0].attachment = 0;
attachmentReferences[0].layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
attachmentReferences[1].attachment = 1;
attachmentReferences[1].layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
VkSubpassDescription subpassDescription = {}; VkSubpassDescription subpassDescription = {};
subpassDescription.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpassDescription.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpassDescription.colorAttachmentCount = 1; subpassDescription.colorAttachmentCount = 1;
subpassDescription.pColorAttachments = &outputColorAttachmentReference; subpassDescription.pColorAttachments = &attachmentReferences[0];
subpassDescription.pDepthStencilAttachment = &attachmentReferences[1];
VkSubpassDependency dependency = {}; VkSubpassDependency dependency = {};
dependency.srcSubpass = VK_SUBPASS_EXTERNAL; dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; dependency.dstSubpass = 0;
dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; dependency.srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
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;
VkRenderPassCreateInfo renderPassCI = { VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO }; VkRenderPassCreateInfo renderPassCI = { VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO };
renderPassCI.attachmentCount = 1; renderPassCI.attachmentCount = 2;
renderPassCI.pAttachments = &outputColorAttachment; renderPassCI.pAttachments = attachmentDescriptions;
renderPassCI.subpassCount = 1; renderPassCI.subpassCount = 1;
renderPassCI.pSubpasses = &subpassDescription; renderPassCI.pSubpasses = &subpassDescription;
renderPassCI.dependencyCount = 1; renderPassCI.dependencyCount = 1;
@ -315,35 +371,52 @@ namespace nf::client::render {
outputShaderStages[1].module = outputShaderFragmentModule.getHandle(); outputShaderStages[1].module = outputShaderFragmentModule.getHandle();
// Specify dynamic state // Specify dynamic state
VkDynamicState dynamicState = VK_DYNAMIC_STATE_VIEWPORT;
VkPipelineDynamicStateCreateInfo dynamicStateCI = { VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO }; VkPipelineDynamicStateCreateInfo dynamicStateCI = { VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO };
dynamicStateCI.dynamicStateCount = 1; std::vector<VkDynamicState> dynamicStates = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
dynamicStateCI.pDynamicStates = &dynamicState; dynamicStateCI.dynamicStateCount = dynamicStates.size();
dynamicStateCI.pDynamicStates = dynamicStates.data();
// Specify vertex input state // Specify vertex input state
VkPipelineVertexInputStateCreateInfo vertexInputStateCI = { VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO }; VkPipelineVertexInputStateCreateInfo vertexInputStateCI = { VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO };
VkVertexInputBindingDescription vertexBindingDescription = {};
vertexBindingDescription.stride = sizeof(Vertex);
VkVertexInputAttributeDescription vertexAttributeDescriptions[2] = {};
vertexAttributeDescriptions[0].location = 0;
vertexAttributeDescriptions[0].format = VK_FORMAT_R32G32B32_SFLOAT;
vertexAttributeDescriptions[0].offset = offsetof(Vertex, position);
vertexAttributeDescriptions[1].location = 1;
vertexAttributeDescriptions[1].format = VK_FORMAT_R32G32_SFLOAT;
vertexAttributeDescriptions[1].offset = offsetof(Vertex, textureCoordinates);
vertexInputStateCI.vertexBindingDescriptionCount = 1;
vertexInputStateCI.pVertexBindingDescriptions = &vertexBindingDescription;
vertexInputStateCI.vertexAttributeDescriptionCount = 2;
vertexInputStateCI.pVertexAttributeDescriptions = vertexAttributeDescriptions;
// Specify input assembly state // Specify input assembly state
VkPipelineInputAssemblyStateCreateInfo inputAssemblyStateCI = { VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO }; VkPipelineInputAssemblyStateCreateInfo inputAssemblyStateCI = { VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO };
inputAssemblyStateCI.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; inputAssemblyStateCI.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
// Specify viewport state // Specify viewport state
VkRect2D scissor = { {0, 0}, {m_display.width, m_display.height} };
VkPipelineViewportStateCreateInfo viewportStateCI = { VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO }; VkPipelineViewportStateCreateInfo viewportStateCI = { VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO };
viewportStateCI.viewportCount = 1; viewportStateCI.viewportCount = 1;
viewportStateCI.scissorCount = 1; viewportStateCI.scissorCount = 1;
viewportStateCI.pScissors = &scissor;
// Specify rasterization state // Specify rasterization state
VkPipelineRasterizationStateCreateInfo rasterizationCI = { VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO }; VkPipelineRasterizationStateCreateInfo rasterizationCI = { VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO };
rasterizationCI.lineWidth = 1.0; rasterizationCI.lineWidth = 1.0;
rasterizationCI.cullMode = VK_CULL_MODE_BACK_BIT; rasterizationCI.cullMode = VK_CULL_MODE_BACK_BIT;
rasterizationCI.frontFace = VK_FRONT_FACE_CLOCKWISE; rasterizationCI.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
// Specify multisample state // Specify multisample state
VkPipelineMultisampleStateCreateInfo multisampleStateCI = { VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO }; VkPipelineMultisampleStateCreateInfo multisampleStateCI = { VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO };
multisampleStateCI.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; multisampleStateCI.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
// Specify depth stencil state
VkPipelineDepthStencilStateCreateInfo depthStencilStateCI = { VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO };
depthStencilStateCI.depthTestEnable = VK_TRUE;
depthStencilStateCI.depthWriteEnable = VK_TRUE;
depthStencilStateCI.depthCompareOp = VK_COMPARE_OP_LESS;
// Specify color blend state // Specify color blend state
VkPipelineColorBlendAttachmentState colorBlendAttachmentState = {}; VkPipelineColorBlendAttachmentState colorBlendAttachmentState = {};
colorBlendAttachmentState.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; colorBlendAttachmentState.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
@ -351,12 +424,46 @@ namespace nf::client::render {
colorBlendStateCI.attachmentCount = 1; colorBlendStateCI.attachmentCount = 1;
colorBlendStateCI.pAttachments = &colorBlendAttachmentState; colorBlendStateCI.pAttachments = &colorBlendAttachmentState;
// Create descriptor set layout
VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCI = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO };
VkDescriptorSetLayoutBinding layoutBindings[] = { {}, {} };
layoutBindings[0].binding = 0;
layoutBindings[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
layoutBindings[0].descriptorCount = 1;
layoutBindings[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
layoutBindings[1].binding = 1;
layoutBindings[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
layoutBindings[1].descriptorCount = 1;
layoutBindings[1].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
descriptorSetLayoutCI.bindingCount = 2;
descriptorSetLayoutCI.bindingCount = 2;
descriptorSetLayoutCI.pBindings = layoutBindings;
if (vkCreateDescriptorSetLayout(m_device, &descriptorSetLayoutCI, nullptr, &m_pipelineOutputDescriptorSetLayout) != VK_SUCCESS)
NFError("Could not create descriptor set layout.");
// Create pipeline layout // Create pipeline layout
VkPipelineLayoutCreateInfo layoutCI = { VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO }; VkPipelineLayoutCreateInfo layoutCI = { VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO };
layoutCI.setLayoutCount = 1;
layoutCI.pSetLayouts = &m_pipelineOutputDescriptorSetLayout;
if (vkCreatePipelineLayout(m_device, &layoutCI, nullptr, &m_pipelineLayoutOutput) != VK_SUCCESS) if (vkCreatePipelineLayout(m_device, &layoutCI, nullptr, &m_pipelineOutputLayout) != VK_SUCCESS)
NFError("Could not create pipeline layout."); NFError("Could not create pipeline layout.");
// Create descriptor pool
VkDescriptorPoolCreateInfo descriptorPoolCI = { VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO };
VkDescriptorPoolSize descriptorPoolSizes[] = { {}, {} };
descriptorPoolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
descriptorPoolSizes[0].descriptorCount = 1;
descriptorPoolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
descriptorPoolSizes[1].descriptorCount = 1;
descriptorPoolCI.poolSizeCount = 2;
descriptorPoolCI.pPoolSizes = descriptorPoolSizes;
descriptorPoolCI.maxSets = 1;
if (vkCreateDescriptorPool(m_device, &descriptorPoolCI, nullptr, &m_pipelineOutputDescriptorPool) != VK_SUCCESS)
NFError("Could not create descriptor pool.");
// And finally put it all together // And finally put it all together
VkGraphicsPipelineCreateInfo pipelineCI = { VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO }; VkGraphicsPipelineCreateInfo pipelineCI = { VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO };
pipelineCI.stageCount = 2; pipelineCI.stageCount = 2;
@ -366,22 +473,30 @@ namespace nf::client::render {
pipelineCI.pViewportState = &viewportStateCI; pipelineCI.pViewportState = &viewportStateCI;
pipelineCI.pRasterizationState = &rasterizationCI; pipelineCI.pRasterizationState = &rasterizationCI;
pipelineCI.pMultisampleState = &multisampleStateCI; pipelineCI.pMultisampleState = &multisampleStateCI;
pipelineCI.pDepthStencilState = &depthStencilStateCI;
pipelineCI.pColorBlendState = &colorBlendStateCI; pipelineCI.pColorBlendState = &colorBlendStateCI;
pipelineCI.pDynamicState = &dynamicStateCI; pipelineCI.pDynamicState = &dynamicStateCI;
pipelineCI.layout = m_pipelineLayoutOutput; pipelineCI.layout = m_pipelineOutputLayout;
pipelineCI.renderPass = m_renderPassOutput; pipelineCI.renderPass = m_renderPassOutput;
if (vkCreateGraphicsPipelines(m_device, nullptr, 1, &pipelineCI, nullptr, &m_pipelineOutput) != VK_SUCCESS) if (vkCreateGraphicsPipelines(m_device, nullptr, 1, &pipelineCI, nullptr, &m_pipelineOutput) != VK_SUCCESS)
NFError("Could not create graphics pipeline."); NFError("Could not create graphics pipeline.");
}
void RenderEngine::createSwapchainFramebuffers() {
// TODO: Won't need these forever
// Create depth buffer first
m_imageDepth = std::make_unique<Image>(ImageType::DepthAttachment, m_device, *m_allocator, *m_commandPool, std::string(), m_swapchainExtent);
// TODO: For now, create swapchain framebuffers
m_swapchainFramebuffers.resize(m_swapchainImageViews.size()); m_swapchainFramebuffers.resize(m_swapchainImageViews.size());
for (int i = 0; i < m_swapchainImageViews.size(); i++) { for (int i = 0; i < m_swapchainImageViews.size(); i++) {
VkFramebufferCreateInfo framebufferCI = { VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO }; VkFramebufferCreateInfo framebufferCI = { VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO };
framebufferCI.renderPass = m_renderPassOutput; framebufferCI.renderPass = m_renderPassOutput;
framebufferCI.attachmentCount = 1; VkImageView views[] = { m_swapchainImageViews[i], m_imageDepth->getView() };
framebufferCI.pAttachments = &m_swapchainImageViews[i]; framebufferCI.attachmentCount = 2;
framebufferCI.width = m_display.width, framebufferCI.height = m_display.height; framebufferCI.pAttachments = views;
framebufferCI.width = m_swapchainExtent.width, framebufferCI.height = m_swapchainExtent.height;
framebufferCI.layers = 1; framebufferCI.layers = 1;
if (vkCreateFramebuffer(m_device, &framebufferCI, nullptr, &m_swapchainFramebuffers[i]) != VK_SUCCESS) if (vkCreateFramebuffer(m_device, &framebufferCI, nullptr, &m_swapchainFramebuffers[i]) != VK_SUCCESS)
@ -389,132 +504,278 @@ namespace nf::client::render {
} }
} }
void RenderEngine::createFrameExecutors() { void RenderEngine::createBuffers() {
VkCommandPoolCreateInfo commandPoolCI = { VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO }; std::vector<Vertex> cubeVertices = {
commandPoolCI.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; {{-0.5, -0.5, 0.5}, {0.0, 1.0}},
commandPoolCI.queueFamilyIndex = m_queueFIGraphics; {{ 0.5, -0.5, 0.5}, {1.0, 1.0}},
{{ 0.5, 0.5, 0.5}, {1.0, 0.0}},
{{-0.5, 0.5, 0.5}, {0.0, 0.0}},
if (vkCreateCommandPool(m_device, &commandPoolCI, nullptr, &m_commandPool) != VK_SUCCESS) {{ 0.5, 0.5, 0.5}, {0.0, 0.0}},
NFError("Could not create command pool."); {{ 0.5, -0.5, -0.5}, {1.0, 1.0}},
{{ 0.5, 0.5, -0.5}, {1.0, 0.0}},
{{ 0.5, -0.5, 0.5}, {0.0, 1.0}},
m_frameExecutors.resize(m_numFramesInFlight); {{-0.5, 0.5, -0.5}, {0.0, 0.0}},
for (int i = 0; i < m_frameExecutors.size(); i++) { {{-0.5, 0.5, 0.5}, {0.0, 1.0}},
VkCommandBufferAllocateInfo commandBufferAI = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO }; {{ 0.5, 0.5, -0.5}, {1.0, 0.0}},
commandBufferAI.commandPool = m_commandPool; {{ 0.5, 0.5, 0.5}, {1.0, 1.0}},
commandBufferAI.commandBufferCount = 1;
if (vkAllocateCommandBuffers(m_device, &commandBufferAI, &m_frameExecutors[i].commandBuffer) != VK_SUCCESS) {{-0.5, -0.5, -0.5}, {1.0, 0.0}},
NFError("Could not create command buffer."); {{-0.5, -0.5, 0.5}, {1.0, 1.0}},
{{ 0.5, -0.5, -0.5}, {0.0, 0.0}},
{{ 0.5, -0.5, 0.5}, {0.0, 1.0}},
VkSemaphoreCreateInfo semaphoreCI = { VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO }; {{-0.5, 0.5, -0.5}, {0.0, 0.0}},
if (vkCreateSemaphore(m_device, &semaphoreCI, nullptr, &m_frameExecutors[i].semaphoreImageAvailable) != VK_SUCCESS || {{-0.5, 0.5, 0.5}, {1.0, 0.0}},
vkCreateSemaphore(m_device, &semaphoreCI, nullptr, &m_frameExecutors[i].semaphoreRenderFinished) != VK_SUCCESS) {{-0.5, -0.5, 0.5}, {1.0, 1.0}},
NFError("Could not create semaphore."); {{-0.5, -0.5, -0.5}, {0.0, 1.0}},
VkFenceCreateInfo fenceCI = { VK_STRUCTURE_TYPE_FENCE_CREATE_INFO }; {{ 0.5, 0.5, -0.5}, {1.0, 0.0}},
fenceCI.flags = VK_FENCE_CREATE_SIGNALED_BIT; {{-0.5, 0.5, -0.5}, {0.0, 0.0}},
if (vkCreateFence(m_device, &fenceCI, nullptr, &m_frameExecutors[i].fenceFrameInFlight) != VK_SUCCESS) {{-0.5, -0.5, -0.5}, {0.0, 1.0}},
NFError("Could not create fence."); {{ 0.5, -0.5, -0.5}, {1.0, 1.0}},
}
{{ 0.5, -0.6, 0.5}, {1.0, 1.0}},
{{ 0.5, -0.6, -0.5}, {1.0, 0.0}},
{{-0.5, -0.6, 0.5}, {0.0, 1.0}}
};
size_t cubeVerticesSize = sizeof(cubeVertices[0]) * cubeVertices.size();
m_bufferVertex = std::make_unique<Buffer>(BufferType::Vertex, m_device, *m_allocator, *m_commandPool, cubeVertices.data(), cubeVerticesSize);
std::vector<uint32_t> cubeIndices = {
0, 1, 2,
2, 3, 0,
4, 5, 6,
4, 7, 5,
8, 9, 10,
10, 9, 11,
12, 14, 13,
14, 15, 13,
16, 19, 17,
17, 19, 18,
20, 23, 21,
23, 22, 21,
24, 25, 26
};
size_t cubeIndicesSize = sizeof(cubeIndices[0]) * cubeIndices.size();
m_bufferIndex = std::make_unique<Buffer>(BufferType::Index, m_device, *m_allocator, *m_commandPool, cubeIndices.data(), cubeIndicesSize);
size_t mvpUniformBufferSize = sizeof(glm::mat4);
m_bufferUniformMVP = std::make_unique<Buffer>(BufferType::Uniform, m_device, *m_allocator, *m_commandPool, nullptr, mvpUniformBufferSize);
}
void RenderEngine::createImage() {
std::string imageData;
util::readFile("grayson.jpg", imageData);
m_imageTest = std::make_unique<Image>(ImageType::Texture, m_device, *m_allocator, *m_commandPool, imageData);
VkSamplerCreateInfo samplerCI = { VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO };
samplerCI.magFilter = samplerCI.minFilter = VK_FILTER_LINEAR;
/*samplerCI.anisotropyEnable = VK_TRUE;
samplerCI.maxAnisotropy = 16;*/
if (vkCreateSampler(m_device, &samplerCI, nullptr, &m_sampler) != VK_SUCCESS)
NFError("Could not create sampler.");
}
void RenderEngine::createDescriptorSet() {
VkDescriptorSetAllocateInfo descriptorSetAI = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO };
descriptorSetAI.descriptorPool = m_pipelineOutputDescriptorPool;
descriptorSetAI.descriptorSetCount = 1;
descriptorSetAI.pSetLayouts = &m_pipelineOutputDescriptorSetLayout;
if (vkAllocateDescriptorSets(m_device, &descriptorSetAI, &m_pipelineOutputDescriptorSet) != VK_SUCCESS)
NFError("Could not allocate descriptor set.");
VkWriteDescriptorSet descriptorSetWrites[] = { {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET}, {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET} };
VkDescriptorBufferInfo uniformDescriptorBI = {};
uniformDescriptorBI.buffer = m_bufferUniformMVP->getHandle();
uniformDescriptorBI.range = VK_WHOLE_SIZE;
VkDescriptorImageInfo imageDescriptorII = {};
imageDescriptorII.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
imageDescriptorII.imageView = m_imageTest->getView();
imageDescriptorII.sampler = m_sampler;
descriptorSetWrites[0].dstSet = m_pipelineOutputDescriptorSet;
descriptorSetWrites[0].dstBinding = 0;
descriptorSetWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
descriptorSetWrites[0].descriptorCount = 1;
descriptorSetWrites[0].pBufferInfo = &uniformDescriptorBI;
descriptorSetWrites[1].dstSet = m_pipelineOutputDescriptorSet;
descriptorSetWrites[1].dstBinding = 1;
descriptorSetWrites[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
descriptorSetWrites[1].descriptorCount = 1;
descriptorSetWrites[1].pImageInfo = &imageDescriptorII;
vkUpdateDescriptorSets(m_device, 2, descriptorSetWrites, 0, nullptr);
} }
void RenderEngine::doFrame() { void RenderEngine::doFrame() {
// Get current frame executor
auto& [commandBuffer, semaphoreImageAvailable, semaphoreRenderFinished, fenceFrameInFlight] = m_frameExecutors[m_currentFrameExecutor];
// First, wait for previous frame to complete // First, wait for previous frame to complete
vkWaitForFences(m_device, 1, &fenceFrameInFlight, VK_TRUE, UINT64_MAX); vkWaitForFences(m_device, 1, &m_fenceFrameInFlight, VK_TRUE, UINT64_MAX);
vkResetFences(m_device, 1, &fenceFrameInFlight);
// Get next swapchain image // Get next swapchain image and recreate if necessary
uint32_t nextImageIndex = 0; uint32_t nextImageIndex = 0;
vkAcquireNextImageKHR(m_device, m_swapchain, UINT64_MAX, semaphoreImageAvailable, nullptr, &nextImageIndex); VkResult result = vkAcquireNextImageKHR(m_device, m_swapchain, UINT64_MAX, m_semaphoreImageAvailable, nullptr, &nextImageIndex);
if (result == VK_ERROR_OUT_OF_DATE_KHR) {
recreateSwapchain();
return;
}
else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR)
NFError("Could not aquire next swapchain image.");
// Reset fence for this frame
vkResetFences(m_device, 1, &m_fenceFrameInFlight);
// Begin recording // Begin recording
VkCommandBufferBeginInfo commandBufferBI = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; VkCommandBufferBeginInfo commandBufferBI = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO };
if (vkBeginCommandBuffer(commandBuffer, &commandBufferBI) != VK_SUCCESS) vkBeginCommandBuffer(m_commandBuffer, &commandBufferBI);
NFError("Could not begin recording command buffer.");
// Start the render pass // Start the render pass
VkClearValue black = { {{0.0, 0.0, 0.0, 1.0}} }; VkClearValue clearValues[] = { {{0.0, 0.0, 0.0, 1.0}}, {1.0, 0} };
VkRenderPassBeginInfo renderPassBI = { VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO }; VkRenderPassBeginInfo renderPassBI = { VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO };
renderPassBI.renderPass = m_renderPassOutput; renderPassBI.renderPass = m_renderPassOutput;
renderPassBI.framebuffer = m_swapchainFramebuffers[nextImageIndex]; renderPassBI.framebuffer = m_swapchainFramebuffers[nextImageIndex];
renderPassBI.renderArea.extent = { m_display.width, m_display.height }; renderPassBI.renderArea.extent = m_swapchainExtent;
renderPassBI.clearValueCount = 1; renderPassBI.clearValueCount = 2;
renderPassBI.pClearValues = &black; renderPassBI.pClearValues = clearValues;
vkCmdBeginRenderPass(commandBuffer, &renderPassBI, VK_SUBPASS_CONTENTS_INLINE); vkCmdBeginRenderPass(m_commandBuffer, &renderPassBI, VK_SUBPASS_CONTENTS_INLINE);
// Bind output pipeline // Bind output pipeline
vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelineOutput); vkCmdBindPipeline(m_commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelineOutput);
// Set viewport // Set viewport and scissor
VkViewport viewport = {}; VkViewport viewport = {};
viewport.width = static_cast<float>(m_display.width), viewport.height = static_cast<float>(m_display.height); viewport.width = static_cast<float>(m_swapchainExtent.width), viewport.height = static_cast<float>(m_swapchainExtent.height);
viewport.maxDepth = 1.0; viewport.maxDepth = 1.0;
vkCmdSetViewport(commandBuffer, 0, 1, &viewport); vkCmdSetViewport(m_commandBuffer, 0, 1, &viewport);
VkRect2D scissor = { {}, m_swapchainExtent };
vkCmdSetScissor(m_commandBuffer, 0, 1, &scissor);
// Bind buffers
VkDeviceSize bufferOffset = 0;
vkCmdBindVertexBuffers(m_commandBuffer, 0, 1, &m_bufferVertex->getHandle(), &bufferOffset);
vkCmdBindIndexBuffer(m_commandBuffer, m_bufferIndex->getHandle(), 0, VK_INDEX_TYPE_UINT32);
// Update uniform buffer
static auto startTime = std::chrono::high_resolution_clock::now();
auto currentTime = std::chrono::high_resolution_clock::now();
float time = std::chrono::duration<float>(currentTime - startTime).count();
glm::mat4 modelMatrix = glm::rotate(glm::mat4(1.0), time * glm::radians(20.0f), glm::vec3(0.0, 1.0, 0.0)),
viewMatrix = glm::lookAt(glm::vec3(1.0, 1.0, 2.0), glm::vec3(0.0), glm::vec3(0.0, 1.0, 0.0)),
projectionMatrix = glm::perspective(glm::radians(45.0f), static_cast<float>(m_swapchainExtent.width) / m_swapchainExtent.height, 0.1f, 10.0f);
projectionMatrix[1][1] *= -1;
glm::mat4 mvpMatrix = projectionMatrix * viewMatrix * modelMatrix;
memcpy(m_bufferUniformMVP->getPointer(), &mvpMatrix, sizeof(mvpMatrix));
// Bind descriptors
vkCmdBindDescriptorSets(m_commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelineOutputLayout, 0, 1, &m_pipelineOutputDescriptorSet, 0, nullptr);
// Draw // Draw
vkCmdDraw(commandBuffer, 3, 1, 0, 0); vkCmdDrawIndexed(m_commandBuffer, m_bufferIndex->getIndicesCount(), 1, 0, 0, 0);
// End the render pass // End the render pass
vkCmdEndRenderPass(commandBuffer); vkCmdEndRenderPass(m_commandBuffer);
// Finish recording // Finish recording
if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) vkEndCommandBuffer(m_commandBuffer);
NFError("Could not end command buffer.");
// Submit to graphics queue // Submit to graphics queue
VkPipelineStageFlags waitStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; VkPipelineStageFlags waitStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
VkSubmitInfo submitInfo = { VK_STRUCTURE_TYPE_SUBMIT_INFO }; VkSubmitInfo submitInfo = { VK_STRUCTURE_TYPE_SUBMIT_INFO };
submitInfo.waitSemaphoreCount = 1; submitInfo.waitSemaphoreCount = 1;
submitInfo.pWaitSemaphores = &semaphoreImageAvailable; submitInfo.pWaitSemaphores = &m_semaphoreImageAvailable;
submitInfo.pWaitDstStageMask = &waitStage; submitInfo.pWaitDstStageMask = &waitStage;
submitInfo.commandBufferCount = 1; submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &commandBuffer; submitInfo.pCommandBuffers = &m_commandBuffer;
submitInfo.signalSemaphoreCount = 1; submitInfo.signalSemaphoreCount = 1;
submitInfo.pSignalSemaphores = &semaphoreRenderFinished; submitInfo.pSignalSemaphores = &m_semaphoreRenderingDone;
if (vkQueueSubmit(m_queueGraphics, 1, &submitInfo, fenceFrameInFlight) != VK_SUCCESS) if (vkQueueSubmit(m_queueGraphics, 1, &submitInfo, m_fenceFrameInFlight) != VK_SUCCESS)
NFError("Could not submit to Vulkan queue."); NFError("Could not submit to Vulkan queue.");
// And present! // And present!
VkPresentInfoKHR presentInfo = { VK_STRUCTURE_TYPE_PRESENT_INFO_KHR }; VkPresentInfoKHR presentInfo = { VK_STRUCTURE_TYPE_PRESENT_INFO_KHR };
presentInfo.waitSemaphoreCount = 1; presentInfo.waitSemaphoreCount = 1;
presentInfo.pWaitSemaphores = &semaphoreRenderFinished; presentInfo.pWaitSemaphores = &m_semaphoreRenderingDone;
presentInfo.swapchainCount = 1; presentInfo.swapchainCount = 1;
presentInfo.pSwapchains = &m_swapchain; presentInfo.pSwapchains = &m_swapchain;
presentInfo.pImageIndices = &nextImageIndex; presentInfo.pImageIndices = &nextImageIndex;
vkQueuePresentKHR(m_queuePresent, &presentInfo); result = vkQueuePresentKHR(m_queuePresent, &presentInfo);
if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR)
// Advance to next frame executor recreateSwapchain();
m_currentFrameExecutor = (m_currentFrameExecutor + 1) % m_numFramesInFlight; else if (result != VK_SUCCESS)
NFError("Could not present image.");
} }
RenderEngine::~RenderEngine() { void RenderEngine::setDisplay(DisplayConfig config) {
vkDeviceWaitIdle(m_device); m_display = config;
m_window->setDisplay(m_display);
}
for (int i = 0; i < m_frameExecutors.size(); i++) { void RenderEngine::recreateSwapchain() {
vkDestroyFence(m_device, m_frameExecutors[i].fenceFrameInFlight, nullptr); // Might be here due to window closing
vkDestroySemaphore(m_device, m_frameExecutors[i].semaphoreRenderFinished, nullptr); if (!m_window->isActive())
vkDestroySemaphore(m_device, m_frameExecutors[i].semaphoreImageAvailable, nullptr); return;
}
vkDestroyCommandPool(m_device, m_commandPool, nullptr); waitIdle();
destroySwapchain();
createSwapchain();
createSwapchainFramebuffers();
}
void RenderEngine::destroySwapchain() {
for (int i = 0; i < m_swapchainFramebuffers.size(); i++) for (int i = 0; i < m_swapchainFramebuffers.size(); i++)
vkDestroyFramebuffer(m_device, m_swapchainFramebuffers[i], nullptr); vkDestroyFramebuffer(m_device, m_swapchainFramebuffers[i], nullptr);
vkDestroyPipeline(m_device, m_pipelineOutput, nullptr); m_imageDepth.reset();
vkDestroyPipelineLayout(m_device, m_pipelineLayoutOutput, nullptr);
vkDestroyRenderPass(m_device, m_renderPassOutput, nullptr);
for (int i = 0; i < m_swapchainImageViews.size(); i++) for (int i = 0; i < m_swapchainImageViews.size(); i++)
vkDestroyImageView(m_device, m_swapchainImageViews[i], nullptr); vkDestroyImageView(m_device, m_swapchainImageViews[i], nullptr);
vkDestroySwapchainKHR(m_device, m_swapchain, nullptr); vkDestroySwapchainKHR(m_device, m_swapchain, nullptr);
}
void RenderEngine::waitIdle() {
vkDeviceWaitIdle(m_device);
}
RenderEngine::~RenderEngine() {
waitIdle();
vkDestroySampler(m_device, m_sampler, nullptr);
m_imageTest.reset();
m_bufferUniformMVP.reset();
m_bufferIndex.reset();
m_bufferVertex.reset();
vkDestroyPipeline(m_device, m_pipelineOutput, nullptr);
vkDestroyDescriptorPool(m_device, m_pipelineOutputDescriptorPool, nullptr);
vkDestroyPipelineLayout(m_device, m_pipelineOutputLayout, nullptr);
vkDestroyDescriptorSetLayout(m_device, m_pipelineOutputDescriptorSetLayout, nullptr);
vkDestroyRenderPass(m_device, m_renderPassOutput, nullptr);
destroySwapchain();
vkDestroyFence(m_device, m_fenceFrameInFlight, nullptr);
vkDestroySemaphore(m_device, m_semaphoreRenderingDone, nullptr);
vkDestroySemaphore(m_device, m_semaphoreImageAvailable, nullptr);
m_commandPool.reset();
vkDestroyDevice(m_device, nullptr); vkDestroyDevice(m_device, nullptr);
vkDestroySurfaceKHR(m_instance, m_surface, nullptr); vkDestroySurfaceKHR(m_instance, m_surface, nullptr);
vkDestroyInstance(m_instance, nullptr); vkDestroyInstance(m_instance, nullptr);

View File

@ -3,19 +3,18 @@
#include "client/Window.h" #include "client/Window.h"
#include "nf/config.h" #include "nf/config.h"
#include "CommandPool.h"
#include "VideoMemoryAllocator.h"
#include "Buffer.h"
#include "Image.h"
namespace nf::client::render { namespace nf::client::render {
struct FrameExecutor {
VkCommandBuffer commandBuffer;
VkSemaphore semaphoreImageAvailable, semaphoreRenderFinished;
VkFence fenceFrameInFlight;
};
class RenderEngine { class RenderEngine {
public: public:
RenderEngine(std::shared_ptr<Window> window, DisplayConfig display); RenderEngine(std::shared_ptr<Window> window, DisplayConfig display);
void doFrame(); void doFrame();
void setDisplay(DisplayConfig config);
~RenderEngine(); ~RenderEngine();
private: private:
@ -23,10 +22,22 @@ namespace nf::client::render {
void createSurface(); void createSurface();
void pickPhysicalDevice(); void pickPhysicalDevice();
void createDevice(); void createDevice();
void createExecutionObjects();
void createSwapchain(); void createSwapchain();
void createOutputRenderPass(); void createOutputRenderPass();
void createOutputPipeline(); void createOutputPipeline();
void createFrameExecutors();
void createSwapchainFramebuffers();
void createBuffers();
void createImage();
void createDescriptorSet();
void recreateSwapchain();
void destroySwapchain();
void waitIdle();
std::shared_ptr<Window> m_window; std::shared_ptr<Window> m_window;
DisplayConfig m_display; DisplayConfig m_display;
@ -38,23 +49,37 @@ namespace nf::client::render {
uint32_t m_queueFIGraphics, m_queueFIPresent; uint32_t m_queueFIGraphics, m_queueFIPresent;
VkDevice m_device; VkDevice m_device;
VkQueue m_queueGraphics, m_queuePresent; VkQueue m_queueGraphics, m_queuePresent;
VkRenderPass m_renderPassOutput;
// Execution objects
std::unique_ptr<CommandPool> m_commandPool;
VkCommandBuffer m_commandBuffer;
VkSemaphore m_semaphoreImageAvailable;
VkSemaphore m_semaphoreRenderingDone;
VkFence m_fenceFrameInFlight;
// Swapchain // Swapchain
VkSwapchainKHR m_swapchain; VkSwapchainKHR m_swapchain;
VkFormat m_swapchainImageFormat; VkFormat m_swapchainImageFormat;
VkExtent2D m_swapchainExtent;
std::vector<VkImage> m_swapchainImages; std::vector<VkImage> m_swapchainImages;
std::vector<VkImageView> m_swapchainImageViews; std::vector<VkImageView> m_swapchainImageViews;
std::vector<VkFramebuffer> m_swapchainFramebuffers;
// Pipelines // RenderPass and Pipeline
VkPipelineLayout m_pipelineLayoutOutput; VkRenderPass m_renderPassOutput;
VkDescriptorSetLayout m_pipelineOutputDescriptorSetLayout;
VkPipelineLayout m_pipelineOutputLayout;
VkDescriptorPool m_pipelineOutputDescriptorPool;
VkPipeline m_pipelineOutput; VkPipeline m_pipelineOutput;
// Execution objects // Temporary objects
VkCommandPool m_commandPool; std::unique_ptr<VideoMemoryAllocator> m_allocator;
const uint32_t m_numFramesInFlight; std::unique_ptr<Image> m_imageDepth;
std::vector<FrameExecutor> m_frameExecutors; std::vector<VkFramebuffer> m_swapchainFramebuffers;
uint32_t m_currentFrameExecutor; std::unique_ptr<Buffer> m_bufferVertex;
std::unique_ptr<Buffer> m_bufferIndex;
std::unique_ptr<Buffer> m_bufferUniformMVP;
std::unique_ptr<Image> m_imageTest;
VkSampler m_sampler;
VkDescriptorSet m_pipelineOutputDescriptorSet;
}; };
} }

View File

@ -6,7 +6,7 @@
namespace nf::client::render { namespace nf::client::render {
ShaderModule::ShaderModule(const VkDevice& device, const std::string& shaderBinary) ShaderModule::ShaderModule(const VkDevice& device, const std::string& shaderBinary)
: m_device(device) : GraphicsResource(device)
, m_shaderModule() , m_shaderModule()
{ {
VkShaderModuleCreateInfo shaderModuleCI = { VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO }; VkShaderModuleCreateInfo shaderModuleCI = { VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO };

View File

@ -1,8 +1,10 @@
// ShaderModule class header // ShaderModule class header
#pragma once #pragma once
#include "GraphicsResource.h"
namespace nf::client::render { namespace nf::client::render {
class ShaderModule { class ShaderModule : GraphicsResource {
public: public:
ShaderModule(const VkDevice& device, const std::string& shaderBinary); ShaderModule(const VkDevice& device, const std::string& shaderBinary);
@ -10,8 +12,6 @@ namespace nf::client::render {
~ShaderModule(); ~ShaderModule();
private: private:
const VkDevice& m_device;
VkShaderModule m_shaderModule; VkShaderModule m_shaderModule;
}; };
} }

View File

@ -0,0 +1,145 @@
// VideoMemoryAllocator class implementation
#include "pch.h"
#include "VideoMemoryAllocator.h"
#include "util.h"
// 1 GiB
#define HEAP_SIZE_LARGE 1024ull * 1024 * 1024
// 50 MiB
#define DEFAULT_ALLOCATION_AMOUNT 50ull * 1024 * 1024
namespace nf::client::render {
VideoMemoryAllocator::VideoMemoryAllocator(const VkDevice& device, const VkPhysicalDevice& physicalDevice)
: m_device(device)
, m_physicalDevice(physicalDevice)
, m_blocks()
{}
void VideoMemoryAllocator::allocateForBuffer(VkBuffer buffer, VkMemoryPropertyFlags memoryPropertyFlags, VideoMemoryAllocation& outAllocation) {
VkMemoryRequirements memoryRequirements = {};
vkGetBufferMemoryRequirements(m_device, buffer, &memoryRequirements);
allocate(memoryRequirements, memoryPropertyFlags, outAllocation);
vkBindBufferMemory(m_device, buffer, outAllocation.memory, outAllocation.offset);
}
void VideoMemoryAllocator::allocateForImage(VkImage image, VkMemoryPropertyFlags memoryPropertyFlags, VideoMemoryAllocation& outAllocation) {
VkMemoryRequirements memoryRequirements = {};
vkGetImageMemoryRequirements(m_device, image, &memoryRequirements);
allocate(memoryRequirements, memoryPropertyFlags, outAllocation);
vkBindImageMemory(m_device, image, outAllocation.memory, outAllocation.offset);
}
void VideoMemoryAllocator::deallocate(const VideoMemoryAllocation& allocation) {
const auto blockIterator = std::find_if(m_blocks.begin(), m_blocks.end(), [&](const auto& currentBlock) {
if (currentBlock.memory == allocation.memory)
return true;
else return false;
});
if (blockIterator == m_blocks.end())
NFError("Could not find video memory block to deallocate from.");
MemoryBlock& block = *blockIterator;
const auto allocationIterator = block.allocations.find(allocation.offset);
if (allocationIterator == block.allocations.end())
NFError("Could not find video memory allocation to deallocate.");
block.allocations.erase(allocationIterator);
// If block is now empty, free it
if (block.allocations.empty()) {
vkFreeMemory(m_device, block.memory, nullptr);
m_blocks.erase(blockIterator);
}
}
void VideoMemoryAllocator::allocate(VkMemoryRequirements memoryRequirements, VkMemoryPropertyFlags memoryPropertyFlags, VideoMemoryAllocation& outAllocation) {
uint32_t memoryTypeIndex = findMemoryType(memoryRequirements.memoryTypeBits, memoryPropertyFlags);
if (memoryTypeIndex == -1)
NFError("Could not find suitable memory type.");
while (true) {
for (auto& block : m_blocks) {
if (block.memoryTypeIndex == memoryTypeIndex) {
VkDeviceSize lastOffset = 0, lastSize = 0;
// Check between existing allocations
for (const auto& [currentOffset, currentSize] : block.allocations) {
// Check for alignment
VkDeviceSize checkOffset = lastOffset + lastSize, remainder = checkOffset % memoryRequirements.alignment;
checkOffset = remainder ? checkOffset + (memoryRequirements.alignment - remainder) : checkOffset;
// Check for enough space
if (currentOffset - lastOffset - lastSize >= memoryRequirements.size) {
outAllocation.memory = block.memory;
outAllocation.offset = checkOffset;
if (block.mappedMemoryPointer)
outAllocation.mappedMemoryPointer = reinterpret_cast<char*>(block.mappedMemoryPointer) + outAllocation.offset;
block.allocations[checkOffset] = memoryRequirements.size;
return;
}
lastOffset = currentOffset, lastSize = currentSize;
}
// Check end
VkDeviceSize checkOffset = lastOffset + lastSize, remainder = checkOffset % memoryRequirements.alignment;
checkOffset = remainder ? checkOffset + (memoryRequirements.alignment - remainder) : checkOffset;
if ((block.size - 1) - lastOffset - lastSize >= memoryRequirements.size) {
outAllocation.memory = block.memory;
outAllocation.offset = checkOffset;
if (block.mappedMemoryPointer)
outAllocation.mappedMemoryPointer = reinterpret_cast<char*>(block.mappedMemoryPointer) + outAllocation.offset;
block.allocations[checkOffset] = memoryRequirements.size;
return;
}
// If not enough space, continue
continue;
}
}
// No suitable block found, allocate one
VkMemoryAllocateInfo newBlockAllocateInfo = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO };
newBlockAllocateInfo.memoryTypeIndex = memoryTypeIndex;
newBlockAllocateInfo.allocationSize = calculateBlockSize(memoryTypeIndex);
allocateBlock(newBlockAllocateInfo);
// If host visible, map
if (memoryPropertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)
vkMapMemory(m_device, m_blocks.back().memory, 0, VK_WHOLE_SIZE, 0, &m_blocks.back().mappedMemoryPointer);
}
}
uint32_t VideoMemoryAllocator::findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags desiredPropertyFlags) {
VkPhysicalDeviceMemoryProperties memoryProperties = {};
vkGetPhysicalDeviceMemoryProperties(m_physicalDevice, &memoryProperties);
for (int i = 0; i < memoryProperties.memoryTypeCount; i++)
if (typeFilter & (1 << i) && (memoryProperties.memoryTypes[i].propertyFlags & desiredPropertyFlags) == desiredPropertyFlags)
return i;
return -1;
}
size_t VideoMemoryAllocator::calculateBlockSize(uint32_t memoryTypeIndex) const {
VkPhysicalDeviceMemoryProperties memoryProperties = {};
vkGetPhysicalDeviceMemoryProperties(m_physicalDevice, &memoryProperties);
size_t heapSize = memoryProperties.memoryHeaps[memoryProperties.memoryTypes[memoryTypeIndex].heapIndex].size;
return heapSize > HEAP_SIZE_LARGE ? DEFAULT_ALLOCATION_AMOUNT : (heapSize / 8);
}
void VideoMemoryAllocator::allocateBlock(VkMemoryAllocateInfo& allocateInfo) {
VkDeviceMemory memoryBlock = nullptr;
if (vkAllocateMemory(m_device, &allocateInfo, nullptr, &memoryBlock) != VK_SUCCESS)
NFError("Could not allocate video memory.");
m_blocks.emplace_back(memoryBlock, allocateInfo.memoryTypeIndex, allocateInfo.allocationSize, nullptr);
}
}

View File

@ -0,0 +1,41 @@
// VideoMemoryAllocator class header
#pragma once
namespace nf::client::render {
struct VideoMemoryAllocation {
VkDeviceMemory memory;
VkDeviceSize offset;
void* mappedMemoryPointer;
};
struct MemoryBlock {
VkDeviceMemory memory;
uint32_t memoryTypeIndex;
size_t size;
void* mappedMemoryPointer;
std::map<VkDeviceSize, VkDeviceSize> allocations;
// Offset Size
};
class VideoMemoryAllocator {
public:
VideoMemoryAllocator(const VkDevice& device, const VkPhysicalDevice& physicalDevice);
void allocateForBuffer(VkBuffer buffer, VkMemoryPropertyFlags memoryPropertyFlags, VideoMemoryAllocation& outAllocation);
void allocateForImage(VkImage image, VkMemoryPropertyFlags memoryPropertyFlags, VideoMemoryAllocation& outAllocation);
void deallocate(const VideoMemoryAllocation& allocation);
private:
void allocate(VkMemoryRequirements memoryRequirements, VkMemoryPropertyFlags memoryPropertyFlags, VideoMemoryAllocation& outAllocation);
uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags desiredPropertyFlags);
size_t calculateBlockSize(uint32_t memoryTypeIndex) const;
void allocateBlock(VkMemoryAllocateInfo& allocateInfo);
const VkDevice& m_device;
const VkPhysicalDevice& m_physicalDevice;
std::vector<MemoryBlock> m_blocks;
};
}

View File

@ -42,3 +42,8 @@
// Vulkan // Vulkan
#define VK_USE_PLATFORM_WIN32_KHR #define VK_USE_PLATFORM_WIN32_KHR
#include <vulkan/vulkan.h> #include <vulkan/vulkan.h>
// GLM
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>