684 lines
24 KiB
C++
684 lines
24 KiB
C++
#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/Pipelines/ImguiPass.h>
|
|
|
|
|
|
#include "destrum/Graphics/imageLoader.h"
|
|
#include "destrum/Util/GameState.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,
|
|
.fillModeNonSolid = 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)
|
|
.add_required_extension(VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME)
|
|
.set_surface(surface)
|
|
.prefer_gpu_device_type(vkb::PreferredDeviceType::discrete)
|
|
.select()
|
|
.value();
|
|
|
|
device = vkb::DeviceBuilder{physicalDevice}.build().value();
|
|
volkLoadDevice(device);
|
|
|
|
|
|
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);
|
|
|
|
VkPhysicalDeviceProperties props{};
|
|
vkGetPhysicalDeviceProperties(physicalDevice, &props);
|
|
|
|
imageCache.bindlessSetManager.init(device, props.limits.maxSamplerAnisotropy);
|
|
|
|
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);
|
|
}
|
|
|
|
{ // create error texture (black/magenta checker)
|
|
constexpr auto black = 0xFF000000;
|
|
constexpr auto magenta = 0xFFFF00FF;
|
|
|
|
std::array<std::uint32_t, 4> pixels{black, magenta, magenta, black};
|
|
errorImageId = createImage(
|
|
{
|
|
.format = VK_FORMAT_R8G8B8A8_UNORM,
|
|
.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT |
|
|
VK_IMAGE_USAGE_TRANSFER_SRC_BIT,
|
|
.extent = VkExtent3D{2, 2, 1},
|
|
},
|
|
"error texture",
|
|
pixels.data());
|
|
imageCache.setErrorImageId(errorImageId);
|
|
}
|
|
|
|
GameState::GetInstance().SetGfxDevice(this);
|
|
}
|
|
|
|
void GfxDevice::recreateSwapchain(int width, int height) {
|
|
assert(width != 0 && height != 0);
|
|
waitIdle();
|
|
swapchain.recreateSwapchain(*this, swapchainFormat, width, height, true);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
VulkanImmediateExecutor& GfxDevice::GetImmediateExecuter() {
|
|
return executor;
|
|
}
|
|
|
|
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; {
|
|
const VkImageSubresourceRange clearRange = vkinit::imageSubresourceRange(VK_IMAGE_ASPECT_COLOR_BIT);
|
|
vkutil::transitionImage(cmd, swapchainImage, swapchainLayout, VK_IMAGE_LAYOUT_GENERAL);
|
|
swapchainLayout = VK_IMAGE_LAYOUT_GENERAL;
|
|
|
|
const auto clearValue = props.clearColor;
|
|
vkCmdClearColorImage(cmd, swapchainImage, VK_IMAGE_LAYOUT_GENERAL, &clearValue, 1, &clearRange);
|
|
}
|
|
|
|
if (true) {
|
|
// copy from draw image into swapchain
|
|
vkutil::transitionImage(
|
|
cmd,
|
|
drawImage.image,
|
|
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;
|
|
|
|
auto filter = false ? VK_FILTER_LINEAR : VK_FILTER_NEAREST;
|
|
filter = VK_FILTER_NEAREST;
|
|
if (false) {
|
|
vkutil::copyImageToImage(
|
|
cmd,
|
|
drawImage.image,
|
|
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.image,
|
|
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;
|
|
|
|
if (props.imguiPass) {
|
|
props.imguiPass->render(
|
|
cmd,
|
|
swapchainImage,
|
|
getSwapchainImageView(static_cast<std::uint32_t>(swapchainImageIndex)),
|
|
swapchainLayout,
|
|
getSwapchainExtent()
|
|
);
|
|
}
|
|
|
|
// 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));
|
|
}
|
|
|
|
BindlessSetManager& GfxDevice::getBindlessSetManager() {
|
|
return imageCache.bindlessSetManager;
|
|
}
|
|
|
|
VkDescriptorSetLayout GfxDevice::getBindlessDescSetLayout() const {
|
|
return imageCache.bindlessSetManager.getDescSetLayout();
|
|
}
|
|
|
|
const VkDescriptorSet& GfxDevice::getBindlessDescSet() const {
|
|
return imageCache.bindlessSetManager.getDescSet();
|
|
}
|
|
|
|
void GfxDevice::bindBindlessDescSet(VkCommandBuffer cmd, VkPipelineLayout layout) const {
|
|
vkCmdBindDescriptorSets(
|
|
cmd,
|
|
VK_PIPELINE_BIND_POINT_GRAPHICS,
|
|
layout,
|
|
0,
|
|
1,
|
|
&imageCache.bindlessSetManager.getDescSet(),
|
|
0,
|
|
nullptr);
|
|
}
|
|
|
|
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) {
|
|
const std::size_t bytes =
|
|
std::size_t(image.extent.width) *
|
|
std::size_t(image.extent.height) *
|
|
std::size_t(image.extent.depth) *
|
|
BytesPerTexel(image.format);
|
|
|
|
uploadImageDataSized(image, pixelData, bytes, 0);
|
|
}
|
|
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, VkImageUsageFlags usage, bool mipMap, TextureIntent intent) {
|
|
return imageCache.loadImageFromFile(path, usage, mipMap, intent);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
GPUImage GfxDevice::loadImageFromFileRaw(const std::filesystem::path& path, VkImageUsageFlags usage, bool mipMap, TextureIntent intent) const {
|
|
const auto data = util::loadImage(path, intent);
|
|
|
|
if (data.vkFormat == VK_FORMAT_UNDEFINED || data.byteSize == 0 || (data.hdr ? (data.hdrPixels == nullptr) : (data.pixels == nullptr))) {
|
|
spdlog::error("Failed to load image '{}'", path.string());
|
|
return getImage(errorImageId);
|
|
}
|
|
|
|
auto image = createImageRaw({
|
|
.format = data.vkFormat,
|
|
.usage = usage |
|
|
VK_IMAGE_USAGE_TRANSFER_DST_BIT |
|
|
(mipMap ? VK_IMAGE_USAGE_TRANSFER_SRC_BIT : 0),
|
|
.extent = VkExtent3D{
|
|
.width = static_cast<std::uint32_t>(data.width),
|
|
.height = static_cast<std::uint32_t>(data.height),
|
|
.depth = 1,
|
|
},
|
|
.mipMap = mipMap,
|
|
});
|
|
|
|
const void* src = data.hdr ? static_cast<const void*>(data.hdrPixels) : static_cast<const void*>(data.pixels);
|
|
|
|
uploadImageDataSized(image, src, data.byteSize, 0);
|
|
|
|
image.debugName = path.string();
|
|
vkutil::addDebugLabel(device, image.image, path.string().c_str());
|
|
|
|
return image;
|
|
}
|
|
|
|
// void GfxDevice::uploadImageData(const GPUImage& image, void* pixelData, std::uint32_t layer) const {
|
|
// VkDeviceSize dataSize =
|
|
// VkDeviceSize(image.extent.depth) *
|
|
// image.extent.width *
|
|
// image.extent.height *
|
|
// BytesPerTexel(image.format);
|
|
//
|
|
// auto uploadBuffer = createBuffer(dataSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
|
|
// memcpy(uploadBuffer.info.pMappedData, pixelData, size_t(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,
|
|
// ©Region);
|
|
//
|
|
// 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);
|
|
// }
|
|
|
|
void GfxDevice::uploadImageDataSized(const GPUImage& image, const void* pixelData, std::size_t byteSize, std::uint32_t layer) const {
|
|
auto uploadBuffer = createBuffer(byteSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU);
|
|
|
|
// safety checks
|
|
assert(uploadBuffer.info.pMappedData);
|
|
assert(pixelData);
|
|
assert(byteSize > 0);
|
|
|
|
std::memcpy(uploadBuffer.info.pMappedData, pixelData, byteSize);
|
|
|
|
executor.immediateSubmit([&] (VkCommandBuffer cmd) {
|
|
vkutil::transitionImage(cmd, image.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
|
|
|
|
VkBufferImageCopy copyRegion{};
|
|
copyRegion.bufferOffset = 0;
|
|
copyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
copyRegion.imageSubresource.mipLevel = 0;
|
|
copyRegion.imageSubresource.baseArrayLayer = layer;
|
|
copyRegion.imageSubresource.layerCount = 1;
|
|
copyRegion.imageExtent = image.extent;
|
|
|
|
vkCmdCopyBufferToImage(cmd, uploadBuffer.buffer, image.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©Region);
|
|
|
|
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, VkImageUsageFlags usage, bool mipMap) const {
|
|
// const auto data = util::loadImage(path);
|
|
// const bool isHdr = data.hdr && data.hdrPixels;
|
|
// const bool isLdr = !data.hdr && data.pixels;
|
|
//
|
|
// if (!isHdr && !isLdr || data.vkFormat == VK_FORMAT_UNDEFINED || data.byteSize == 0) {
|
|
// spdlog::error("failed to load image from '{}'", path.string());
|
|
// return getImage(errorImageId);
|
|
// }
|
|
//
|
|
// auto image = createImageRaw({
|
|
// .format = data.vkFormat,
|
|
// .usage = usage |
|
|
// VK_IMAGE_USAGE_TRANSFER_DST_BIT |
|
|
// (mipMap ? VK_IMAGE_USAGE_TRANSFER_SRC_BIT : 0),
|
|
// .extent = VkExtent3D{
|
|
// .width = (std::uint32_t)data.width,
|
|
// .height = (std::uint32_t)data.height,
|
|
// .depth = 1,
|
|
// },
|
|
// .mipMap = mipMap,
|
|
// });
|
|
//
|
|
// const void* src = isHdr ? (const void*)data.hdrPixels : (const void*)data.pixels;
|
|
// uploadImageDataSized(image, src, data.byteSize, 0);
|
|
//
|
|
// 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
|
|
}
|