We be kinda rendering

This commit is contained in:
2026-01-05 06:20:49 +01:00
parent 1168f9e5d1
commit c83c423b42
48 changed files with 2789 additions and 382 deletions
+565
View File
@@ -0,0 +1,565 @@
#include <destrum/Graphics/GfxDevice.h>
#include "destrum/Graphics/Util.h"
#define VOLK_IMPLEMENTATION
#include <volk.h>
#define VMA_IMPLEMENTATION
#include <filesystem>
#include <vk_mem_alloc.h>
#include <SDL2/SDL.h>
#include <SDL2/SDL_vulkan.h>
#include <destrum/Graphics/Init.h>
#include "destrum/Graphics/imageLoader.h"
#include "spdlog/spdlog.h"
GfxDevice::GfxDevice(): imageCache(*this) {
}
void GfxDevice::init(SDL_Window* window, const std::string& appName, bool vSync) {
VK_CHECK(volkInitialize());
instance = vkb::InstanceBuilder{}
.set_app_name(appName.c_str())
.set_app_version(1, 0, 0)
.request_validation_layers()
.use_default_debug_messenger()
.require_api_version(1, 3, 0)
.build()
.value();
volkLoadInstance(instance);
const auto res = SDL_Vulkan_CreateSurface(window, instance, &surface);
if (res != SDL_TRUE) {
spdlog::error("Failed to create Vulkan surface: {}", SDL_GetError());
std::exit(1);
}
constexpr auto deviceFeatures = VkPhysicalDeviceFeatures{
.imageCubeArray = VK_TRUE,
.geometryShader = VK_TRUE, // for im3d
.depthClamp = VK_TRUE,
.samplerAnisotropy = VK_TRUE,
};
constexpr auto features12 = VkPhysicalDeviceVulkan12Features{
.descriptorIndexing = true,
.descriptorBindingSampledImageUpdateAfterBind = true,
.descriptorBindingStorageImageUpdateAfterBind = true,
.descriptorBindingPartiallyBound = true,
.descriptorBindingVariableDescriptorCount = true,
.runtimeDescriptorArray = true,
.scalarBlockLayout = true,
.bufferDeviceAddress = true,
};
constexpr auto features13 = VkPhysicalDeviceVulkan13Features{
.synchronization2 = true,
.dynamicRendering = true,
};
physicalDevice = vkb::PhysicalDeviceSelector{instance}
.set_minimum_version(1, 3)
.set_required_features(deviceFeatures)
.set_required_features_12(features12)
.set_required_features_13(features13)
.set_surface(surface)
.select()
.value();
device = vkb::DeviceBuilder{physicalDevice}.build().value();
graphicsQueueFamily = device.get_queue_index(vkb::QueueType::graphics).value();
graphicsQueue = device.get_queue(vkb::QueueType::graphics).value();
//Vma
const auto vulkanFunctions = VmaVulkanFunctions{
.vkGetInstanceProcAddr = vkGetInstanceProcAddr,
.vkGetDeviceProcAddr = vkGetDeviceProcAddr,
};
const auto allocatorInfo = VmaAllocatorCreateInfo{
.flags = VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT,
.physicalDevice = physicalDevice,
.device = device,
.pVulkanFunctions = &vulkanFunctions,
.instance = instance,
};
vmaCreateAllocator(&allocatorInfo, &allocator);
executor.init(device, graphicsQueueFamily, graphicsQueue);
int w, h;
SDL_GetWindowSize(window, &w, &h);
swapchainFormat = VK_FORMAT_B8G8R8A8_SRGB;
swapchain.createSwapchain(this, swapchainFormat, w, h, vSync);
swapchain.initSync(device);
const auto poolCreateInfo = vkinit::commandPoolCreateInfo(VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, graphicsQueueFamily);
for (std::uint32_t i = 0; i < FRAMES_IN_FLIGHT; ++i) {
auto& commandPool = frames[i].commandPool;
VK_CHECK(vkCreateCommandPool(device, &poolCreateInfo, nullptr, &commandPool));
const auto cmdAllocInfo = vkinit::commandBufferAllocateInfo(commandPool, 1);
auto& mainCommandBuffer = frames[i].commandBuffer;
VK_CHECK(vkAllocateCommandBuffers(device, &cmdAllocInfo, &mainCommandBuffer));
}
//
// { // create white texture
// std::uint32_t pixel = 0xFFFFFFFF;
// whiteImageId = createImage(
// {
// .format = VK_FORMAT_R8G8B8A8_UNORM,
// .usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT,
// .extent = VkExtent3D{1, 1, 1},
// },
// "white texture",
// &pixel);
// }
}
void GfxDevice::recreateSwapchain(int width, int height) {
assert(width != 0 && height != 0);
waitIdle();
swapchain.recreateSwapchain(*this, swapchainFormat, width, height, false);
}
VkCommandBuffer GfxDevice::beginFrame() {
swapchain.beginFrame(getCurrentFrameIndex());
const auto& frame = getCurrentFrame();
const auto& cmd = frame.commandBuffer;
const auto cmdBeginInfo = VkCommandBufferBeginInfo{
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
};
VK_CHECK(vkBeginCommandBuffer(cmd, &cmdBeginInfo));
return cmd;
}
void GfxDevice::endFrame(VkCommandBuffer cmd, const GPUImage& drawImage, const EndFrameProps& props) {
// get swapchain image
const auto [swapchainImage, swapchainImageIndex] = swapchain.acquireNextImage(getCurrentFrameIndex());
if (swapchainImage == VK_NULL_HANDLE) {
spdlog::info("Swapchain is freaky, skipping frame...");
return;
}
// Fences are reset here to prevent the deadlock in case swapchain becomes dirty
swapchain.resetFences(getCurrentFrameIndex());
auto swapchainLayout = VK_IMAGE_LAYOUT_UNDEFINED;
{
// clear swapchain image
VkImageSubresourceRange clearRange =
vkinit::imageSubresourceRange(VK_IMAGE_ASPECT_COLOR_BIT);
vkutil::transitionImage(cmd, swapchainImage, swapchainLayout, VK_IMAGE_LAYOUT_GENERAL);
swapchainLayout = VK_IMAGE_LAYOUT_GENERAL;
}
// if (props.copyImageIntoSwapchain) {
// copy from draw image into swapchain
// vkutil::transitionImage(
// cmd,
// drawImage.getImage(),
// VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL,
// VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
vkutil::transitionImage(
cmd, swapchainImage, swapchainLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
swapchainLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
// const auto filter = props.drawImageLinearBlit ? VK_FILTER_LINEAR : VK_FILTER_NEAREST;
const auto filter = VK_FILTER_LINEAR;
// // if (props.drawImageBlitRect != glm::ivec4{}) {
// vkutil::copyImageToImage(
// cmd,
// drawImage.getImage(),
// swapchainImage,
// drawImage.getExtent2D(),
// props.drawImageBlitRect.x,
// props.drawImageBlitRect.y,
// props.drawImageBlitRect.z,
// props.drawImageBlitRect.w,
// filter);
// } else {
// // will stretch image to swapchain
// vkutil::copyImageToImage(
// cmd,
// drawImage.getImage(),
// swapchainImage,
// drawImage.getExtent2D(),
// getSwapchainExtent(),
// filter);
// }
// prepare for present
vkutil::transitionImage(cmd, swapchainImage, swapchainLayout, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
swapchainLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
VK_CHECK(vkEndCommandBuffer(cmd));
// swapchain.submitAndPresent(cmd, graphicsQueue, getCurrentFrameIndex(), swapchainImageIndex);
swapchain.submitAndPresent(cmd, graphicsQueue, swapchainImageIndex, getCurrentFrameIndex());
frameNumber++;
}
void GfxDevice::cleanup() {
}
void GfxDevice::waitIdle() {
VK_CHECK(vkDeviceWaitIdle(device));
}
void GfxDevice::immediateSubmit(ImmediateExecuteFunction&& f) const {
executor.immediateSubmit(std::move(f));
}
GPUBuffer GfxDevice::createBuffer(
std::size_t allocSize,
VkBufferUsageFlags usage,
VmaMemoryUsage memoryUsage) const
{
const auto bufferInfo = VkBufferCreateInfo{
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.size = allocSize,
.usage = usage,
};
const auto allocInfo = VmaAllocationCreateInfo{
.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT |
// TODO: allow to set VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT when needed
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT,
.usage = memoryUsage,
};
GPUBuffer buffer{};
VK_CHECK(vmaCreateBuffer(allocator, &bufferInfo, &allocInfo, &buffer.buffer, &buffer.allocation, &buffer.info));
if ((usage & VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT) != 0) {
const auto deviceAdressInfo = VkBufferDeviceAddressInfo{
.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO,
.buffer = buffer.buffer,
};
buffer.address = vkGetBufferDeviceAddress(device, &deviceAdressInfo);
}
return buffer;
}
VkDeviceAddress GfxDevice::getBufferAddress(const GPUBuffer& buffer) const
{
const auto deviceAdressInfo = VkBufferDeviceAddressInfo{
.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO,
.buffer = buffer.buffer,
};
return vkGetBufferDeviceAddress(device, &deviceAdressInfo);
}
void GfxDevice::destroyBuffer(const GPUBuffer& buffer) const
{
vmaDestroyBuffer(allocator, buffer.buffer, buffer.allocation);
}
//
// GPUImage GfxDevice::loadImageFromFileRaw(
// const std::filesystem::path& path,
// VkFormat format,
// VkImageUsageFlags usage,
// bool mipMap) const
// {
// auto data = util::loadImage(path);
// if (!data.pixels) {
// fmt::println("[error] failed to load image from '{}'", path.string());
// return getImage(errorImageId);
// }
//
// auto image = createImageRaw({
// .format = format,
// .usage = usage | //
// VK_IMAGE_USAGE_TRANSFER_DST_BIT | // for uploading pixel data to image
// VK_IMAGE_USAGE_TRANSFER_SRC_BIT, // for generating mips
// .extent =
// VkExtent3D{
// .width = (std::uint32_t)data.width,
// .height = (std::uint32_t)data.height,
// .depth = 1,
// },
// .mipMap = mipMap,
// });
// uploadImageData(image, data.pixels);
//
// image.debugName = path.string();
// vkutil::addDebugLabel(device, image.image, path.string().c_str());
//
// return image;
// }
ImageID GfxDevice::createImage(
const vkutil::CreateImageInfo& createInfo,
const std::string& debugName,
void* pixelData,
ImageID imageId)
{
auto image = createImageRaw(createInfo);
if (!debugName.empty()) {
vkutil::addDebugLabel(device, image.image, debugName.c_str());
image.debugName = debugName;
}
if (pixelData) {
uploadImageData(image, pixelData);
}
if (imageId != NULL_IMAGE_ID) {
return imageCache.addImage(imageId, std::move(image));
} else {
return addImageToCache(std::move(image));
}
}
ImageID GfxDevice::createDrawImage(
VkFormat format,
glm::ivec2 size,
const std::string& debugName,
ImageID imageId)
{
assert(size.x > 0 && size.y > 0);
const auto extent = VkExtent3D{
.width = (std::uint32_t)size.x,
.height = (std::uint32_t)size.y,
.depth = 1,
};
VkImageUsageFlags usages{};
usages |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
usages |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
usages |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
usages |= VK_IMAGE_USAGE_SAMPLED_BIT;
const auto createImageInfo = vkutil::CreateImageInfo{
.format = format,
.usage = usages,
.extent = extent,
};
return createImage(createImageInfo, debugName, nullptr, imageId);
}
ImageID GfxDevice::loadImageFromFile(
const std::filesystem::path& path,
VkFormat format,
VkImageUsageFlags usage,
bool mipMap)
{
return imageCache.loadImageFromFile(path, format, usage, mipMap);
}
const GPUImage& GfxDevice::getImage(ImageID id) const
{
return imageCache.getImage(id);
}
ImageID GfxDevice::addImageToCache(GPUImage img)
{
return imageCache.addImage(std::move(img));
}
GPUImage GfxDevice::createImageRaw(
const vkutil::CreateImageInfo& createInfo,
std::optional<VmaAllocationCreateInfo> customAllocationCreateInfo) const
{
std::uint32_t mipLevels = 1;
if (createInfo.mipMap) {
const auto maxExtent = std::max(createInfo.extent.width, createInfo.extent.height);
mipLevels = (std::uint32_t)std::floor(std::log2(maxExtent)) + 1;
}
if (createInfo.isCubemap) {
assert(createInfo.numLayers % 6 == 0);
assert(!createInfo.mipMap);
assert((createInfo.flags & VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT) != 0);
}
auto imgInfo = VkImageCreateInfo{
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.flags = createInfo.flags,
.imageType = VK_IMAGE_TYPE_2D,
.format = createInfo.format,
.extent = createInfo.extent,
.mipLevels = mipLevels,
.arrayLayers = createInfo.numLayers,
.samples = createInfo.samples,
.tiling = createInfo.tiling,
.usage = createInfo.usage,
};
static const auto defaultAllocInfo = VmaAllocationCreateInfo{
.usage = VMA_MEMORY_USAGE_AUTO,
.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
};
const auto allocInfo = customAllocationCreateInfo.has_value() ?
customAllocationCreateInfo.value() :
defaultAllocInfo;
GPUImage image{};
image.format = createInfo.format;
image.usage = createInfo.usage;
image.extent = createInfo.extent;
image.mipLevels = mipLevels;
image.numLayers = createInfo.numLayers;
image.isCubemap = createInfo.isCubemap;
VK_CHECK(
vmaCreateImage(allocator, &imgInfo, &allocInfo, &image.image, &image.allocation, nullptr));
// create view only when usage flags allow it
bool shouldCreateView = ((createInfo.usage & VK_IMAGE_USAGE_SAMPLED_BIT) != 0) ||
((createInfo.usage & VK_IMAGE_USAGE_STORAGE_BIT) != 0) ||
((createInfo.usage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) != 0) ||
((createInfo.usage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) != 0);
if (shouldCreateView) {
VkImageAspectFlags aspectFlag = VK_IMAGE_ASPECT_COLOR_BIT;
if (createInfo.format == VK_FORMAT_D32_SFLOAT) { // TODO: support other depth formats
aspectFlag = VK_IMAGE_ASPECT_DEPTH_BIT;
}
auto viewType =
createInfo.numLayers == 1 ? VK_IMAGE_VIEW_TYPE_2D : VK_IMAGE_VIEW_TYPE_2D_ARRAY;
if (createInfo.isCubemap && createInfo.numLayers == 6) {
viewType = VK_IMAGE_VIEW_TYPE_CUBE;
}
const auto viewCreateInfo = VkImageViewCreateInfo{
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.image = image.image,
.viewType = viewType,
.format = createInfo.format,
.subresourceRange =
VkImageSubresourceRange{
.aspectMask = aspectFlag,
.baseMipLevel = 0,
.levelCount = mipLevels,
.baseArrayLayer = 0,
.layerCount = createInfo.numLayers,
},
};
VK_CHECK(vkCreateImageView(device, &viewCreateInfo, nullptr, &image.imageView));
}
return image;
}
void GfxDevice::uploadImageData(const GPUImage& image, void* pixelData, std::uint32_t layer) const
{
int numChannels = 4;
if (image.format == VK_FORMAT_R8_UNORM) {
// FIXME: support more types
numChannels = 1;
}
const auto dataSize =
image.extent.depth * image.extent.width * image.extent.height * numChannels;
const auto uploadBuffer = createBuffer(dataSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
memcpy(uploadBuffer.info.pMappedData, pixelData, dataSize);
executor.immediateSubmit([&](VkCommandBuffer cmd) {
assert(
(image.usage & VK_IMAGE_USAGE_TRANSFER_DST_BIT) != 0 &&
"Image needs to have VK_IMAGE_USAGE_TRANSFER_DST_BIT to upload data to it");
vkutil::transitionImage(
cmd, image.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
const auto copyRegion = VkBufferImageCopy{
.bufferOffset = 0,
.bufferRowLength = 0,
.bufferImageHeight = 0,
.imageSubresource =
{
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.mipLevel = 0,
.baseArrayLayer = layer,
.layerCount = 1,
},
.imageExtent = image.extent,
};
vkCmdCopyBufferToImage(
cmd,
uploadBuffer.buffer,
image.image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
1,
&copyRegion);
if (image.mipLevels > 1) {
assert(
(image.usage & VK_IMAGE_USAGE_TRANSFER_DST_BIT) != 0 &&
(image.usage & VK_IMAGE_USAGE_TRANSFER_SRC_BIT) != 0 &&
"Image needs to have VK_IMAGE_USAGE_TRANSFER_{DST,SRC}_BIT to generate mip maps");
// graphics::generateMipmaps(
// cmd,
// image.image,
// VkExtent2D{image.extent.width, image.extent.height},
// image.mipLevels);
spdlog::warn("Yea dawg, i ain't written this yet :pray:");
} else {
vkutil::transitionImage(
cmd,
image.image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
}
});
destroyBuffer(uploadBuffer);
}
GPUImage GfxDevice::loadImageFromFileRaw(
const std::filesystem::path& path,
VkFormat format,
VkImageUsageFlags usage,
bool mipMap) const
{
auto data = util::loadImage(path);
if (!data.pixels) {
fmt::println("[error] failed to load image from '{}'", path.string());
return getImage(errorImageId);
}
auto image = createImageRaw({
.format = format,
.usage = usage | //
VK_IMAGE_USAGE_TRANSFER_DST_BIT | // for uploading pixel data to image
VK_IMAGE_USAGE_TRANSFER_SRC_BIT, // for generating mips
.extent =
VkExtent3D{
.width = (std::uint32_t)data.width,
.height = (std::uint32_t)data.height,
.depth = 1,
},
.mipMap = mipMap,
});
uploadImageData(image, data.pixels);
image.debugName = path.string();
vkutil::addDebugLabel(device, image.image, path.string().c_str());
return image;
}
void GfxDevice::destroyImage(const GPUImage& image) const
{
vkDestroyImageView(device, image.imageView, nullptr);
vmaDestroyImage(allocator, image.image, image.allocation);
// TODO: if image has bindless id, update the set
}