364 lines
10 KiB
C++
364 lines
10 KiB
C++
/* -*- tab-width: 4; -*- */
|
|
/* vi: set sw=2 ts=4 expandtab: */
|
|
|
|
/*
|
|
* Copyright 2017-2020 Mark Callow.
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
|
|
/**
|
|
* @internal
|
|
* @class VulkanContext
|
|
* @~English
|
|
*
|
|
* @brief Class for holding and passing Vulkan context info to applications.
|
|
*
|
|
* @author Mark Callow, github.com/MarkCallow.
|
|
*/
|
|
|
|
#include <exception>
|
|
#include <iomanip>
|
|
#include <sstream>
|
|
#include <SDL3/SDL_iostream.h>
|
|
#include "VulkanContext.h"
|
|
// Until exceptions are used everywhere...
|
|
#include "vulkancheckres.h"
|
|
#include "ltexceptions.h"
|
|
#include "unused.h"
|
|
|
|
vk::CommandBuffer
|
|
VulkanContext::createCommandBuffer(vk::CommandBufferLevel level, bool begin)
|
|
{
|
|
vk::CommandBuffer cmdBuffer;
|
|
|
|
vk::CommandBufferAllocateInfo cmdBufAllocateInfo(
|
|
commandPool,
|
|
level,
|
|
1);
|
|
|
|
vk::Result res
|
|
= device.allocateCommandBuffers(&cmdBufAllocateInfo, &cmdBuffer);
|
|
|
|
if (res != vk::Result::eSuccess) {
|
|
throw bad_vulkan_alloc((int)res, "device.allocateCommandBuffers");
|
|
}
|
|
|
|
// If requested, also start the new command buffer
|
|
if (begin)
|
|
{
|
|
vk::CommandBufferBeginInfo cmdBufInfo;
|
|
res = cmdBuffer.begin(&cmdBufInfo);
|
|
if (res != vk::Result::eSuccess) {
|
|
throw bad_vulkan_alloc((int)res, "cmdBuffer.begin");
|
|
}
|
|
}
|
|
|
|
return cmdBuffer;
|
|
}
|
|
|
|
|
|
void
|
|
VulkanContext::flushCommandBuffer(vk::CommandBuffer& cmdBuffer,
|
|
bool free)
|
|
{
|
|
if (!cmdBuffer) {
|
|
return;
|
|
}
|
|
|
|
cmdBuffer.end();
|
|
|
|
vk::SubmitInfo submitInfo;
|
|
submitInfo.commandBufferCount = 1;
|
|
submitInfo.pCommandBuffers = &cmdBuffer;
|
|
|
|
vk::Result res = queue.submit(1, &submitInfo, nullptr);
|
|
if (res != vk::Result::eSuccess) {
|
|
if (res == vk::Result::eErrorDeviceLost) {
|
|
throw std::runtime_error("Vulkan device lost.");
|
|
} else {
|
|
throw bad_vulkan_alloc((int)res, "queue.submit");
|
|
}
|
|
}
|
|
|
|
queue.waitIdle();
|
|
|
|
if (free) {
|
|
device.freeCommandBuffers(commandPool, 1, &cmdBuffer);
|
|
}
|
|
}
|
|
|
|
bool
|
|
VulkanContext::createDrawCommandBuffers()
|
|
{
|
|
VkCommandBufferAllocateInfo aInfo;
|
|
aInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
|
aInfo.pNext = NULL;
|
|
aInfo.commandPool = commandPool;
|
|
aInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
|
aInfo.commandBufferCount = 1;
|
|
|
|
drawCmdBuffers.resize(swapchain.imageCount);
|
|
for (uint32_t i = 0; i < swapchain.imageCount; i++) {
|
|
VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &aInfo,
|
|
&drawCmdBuffers[i]));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
VulkanContext::createPresentCommandBuffers()
|
|
{
|
|
VkCommandBufferAllocateInfo aInfo;
|
|
aInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
|
aInfo.pNext = NULL;
|
|
aInfo.commandPool = commandPool;
|
|
aInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
|
aInfo.commandBufferCount = 1;
|
|
|
|
prePresentCmdBuffers.resize(swapchain.imageCount);
|
|
postPresentCmdBuffers.resize(swapchain.imageCount);
|
|
for (uint32_t i = 0; i < swapchain.imageCount; i++) {
|
|
VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &aInfo,
|
|
&prePresentCmdBuffers[i]));
|
|
VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &aInfo,
|
|
&postPresentCmdBuffers[i]));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void
|
|
VulkanContext::destroyDrawCommandBuffers()
|
|
{
|
|
if (drawCmdBuffers.size() > 0) {
|
|
vkFreeCommandBuffers(device, commandPool,
|
|
static_cast<uint32_t>(drawCmdBuffers.size()),
|
|
drawCmdBuffers.data());
|
|
}
|
|
for (uint32_t i = 0; i < drawCmdBuffers.size(); ++i) {
|
|
drawCmdBuffers[i] = nullptr;
|
|
}
|
|
|
|
}
|
|
|
|
void
|
|
VulkanContext::destroyPresentCommandBuffers()
|
|
{
|
|
vkFreeCommandBuffers(device, commandPool,
|
|
static_cast<uint32_t>(drawCmdBuffers.size()),
|
|
prePresentCmdBuffers.data());
|
|
vkFreeCommandBuffers(device, commandPool,
|
|
static_cast<uint32_t>(drawCmdBuffers.size()),
|
|
postPresentCmdBuffers.data());
|
|
}
|
|
|
|
bool
|
|
VulkanContext::checkDrawCommandBuffers()
|
|
{
|
|
for (auto& cmdBuffer : drawCmdBuffers)
|
|
{
|
|
if (cmdBuffer == VK_NULL_HANDLE)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
VulkanContext::createBuffer(vk::BufferUsageFlags usageFlags,
|
|
vk::MemoryPropertyFlags memoryPropertyFlags,
|
|
vk::DeviceSize size,
|
|
void * data,
|
|
vk::Buffer* buffer,
|
|
vk::DeviceMemory* memory)
|
|
{
|
|
vk::MemoryRequirements memReqs;
|
|
vk::MemoryAllocateInfo memAlloc(0, 0);
|
|
vk::BufferCreateInfo bufferCreateInfo({}, size, usageFlags);
|
|
|
|
vk::Result res = device.createBuffer(&bufferCreateInfo, nullptr, buffer);
|
|
if (res != vk::Result::eSuccess) {
|
|
return false;
|
|
}
|
|
|
|
device.getBufferMemoryRequirements(*buffer, &memReqs);
|
|
memAlloc.allocationSize = memReqs.size;
|
|
memAlloc.memoryTypeIndex = getMemoryType(memReqs.memoryTypeBits,
|
|
memoryPropertyFlags);
|
|
res = device.allocateMemory(&memAlloc, nullptr, memory);
|
|
if (res == vk::Result::eSuccess) {
|
|
if (data != nullptr)
|
|
{
|
|
void *mapped;
|
|
mapped = device.mapMemory(*memory, 0, size, {});
|
|
memcpy(mapped, data, (size_t)size);
|
|
device.unmapMemory(*memory);
|
|
}
|
|
device.bindBufferMemory(*buffer, *memory, 0);
|
|
|
|
return true;
|
|
} else
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
VulkanContext::createBuffer(vk::BufferUsageFlags usage,
|
|
vk::DeviceSize size,
|
|
void* data,
|
|
vk::Buffer* buffer,
|
|
vk::DeviceMemory* memory)
|
|
{
|
|
return createBuffer(usage, vk::MemoryPropertyFlagBits::eHostVisible,
|
|
size, data, buffer, memory);
|
|
}
|
|
|
|
bool
|
|
VulkanContext::createBuffer(vk::BufferUsageFlags usage,
|
|
vk::DeviceSize size,
|
|
void* data,
|
|
vk::Buffer* buffer,
|
|
vk::DeviceMemory* memory,
|
|
vk::DescriptorBufferInfo* descriptor)
|
|
{
|
|
bool res = createBuffer(usage, size, data, buffer, memory);
|
|
if (res)
|
|
{
|
|
descriptor->offset = 0;
|
|
descriptor->buffer = *buffer;
|
|
descriptor->range = size;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool
|
|
VulkanContext::createBuffer(vk::BufferUsageFlags usage,
|
|
vk::MemoryPropertyFlags memoryPropertyFlags,
|
|
vk::DeviceSize size,
|
|
void* data,
|
|
vk::Buffer* buffer,
|
|
vk::DeviceMemory* memory,
|
|
vk::DescriptorBufferInfo* descriptor)
|
|
{
|
|
bool res = createBuffer(usage, memoryPropertyFlags, size, data, buffer, memory);
|
|
if (res)
|
|
{
|
|
descriptor->offset = 0;
|
|
descriptor->buffer = *buffer;
|
|
descriptor->range = size;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool
|
|
VulkanContext::getMemoryType(uint32_t typeBits,
|
|
vk::MemoryPropertyFlags requirementsMask,
|
|
uint32_t *typeIndex) const
|
|
{
|
|
// Search memtypes to find first index with desired properties
|
|
for (uint32_t i = 0; i < VK_MAX_MEMORY_TYPES; i++) {
|
|
if ((typeBits & 1) == 1) {
|
|
// Type is available, does it match user properties?
|
|
if ((memoryProperties.memoryTypes[i].propertyFlags &
|
|
requirementsMask) == requirementsMask) {
|
|
*typeIndex = i;
|
|
return true;
|
|
}
|
|
}
|
|
typeBits >>= 1;
|
|
}
|
|
// No memory types matched, return failure
|
|
return false;
|
|
}
|
|
|
|
uint32_t
|
|
VulkanContext::getMemoryType(uint32_t typeBits,
|
|
vk::MemoryPropertyFlags requirementsMask) const
|
|
{
|
|
// Search memtypes to find first index with desired properties
|
|
for (uint32_t i = 0; i < VK_MAX_MEMORY_TYPES; i++) {
|
|
if ((typeBits & 1) == 1) {
|
|
// Type is available, does it match user properties?
|
|
if ((memoryProperties.memoryTypes[i].propertyFlags &
|
|
requirementsMask) == requirementsMask) {
|
|
return i;
|
|
}
|
|
}
|
|
typeBits >>= 1;
|
|
}
|
|
// No memory types matched, return failure
|
|
// TODO throw error
|
|
return 0;
|
|
}
|
|
|
|
vk::PipelineShaderStageCreateInfo
|
|
VulkanContext::loadShader(std::string filename,
|
|
vk::ShaderStageFlagBits stage,
|
|
const char* const modname)
|
|
{
|
|
vk::PipelineShaderStageCreateInfo shaderStage({}, stage);
|
|
shaderStage.module = loadShader(filename);
|
|
shaderStage.pName = modname;
|
|
return shaderStage;
|
|
}
|
|
|
|
vk::ShaderModule
|
|
VulkanContext::loadShader(std::string filename)
|
|
{
|
|
size_t codeSize;
|
|
uint32_t* shaderCode;
|
|
|
|
shaderCode = readSpv(filename.c_str(), &codeSize);
|
|
|
|
vk::ShaderModule shaderModule;
|
|
vk::ShaderModuleCreateInfo moduleCreateInfo({}, codeSize, shaderCode);
|
|
|
|
vk::Result res
|
|
= device.createShaderModule(&moduleCreateInfo, NULL, &shaderModule);
|
|
if (res != vk::Result::eSuccess) {
|
|
throw bad_vulkan_alloc((int)res, "device.createShaderModule");
|
|
}
|
|
|
|
delete[] shaderCode;
|
|
|
|
assert(shaderModule);
|
|
return shaderModule;
|
|
}
|
|
|
|
uint32_t*
|
|
VulkanContext::readSpv(const char *filename, size_t *pSize) {
|
|
size_t size;
|
|
U_ASSERT_ONLY size_t retval;
|
|
uint32_t* shader_code;
|
|
|
|
SDL_IOStream* io = SDL_IOFromFile(filename, "rb");
|
|
if (!io) {
|
|
std::stringstream message;
|
|
|
|
// String returned by SDL_GetError() includes file name.
|
|
message << "Open of shader failed: " << SDL_GetError();
|
|
throw std::runtime_error(message.str());
|
|
}
|
|
|
|
size = (size_t)SDL_GetIOSize(io);
|
|
|
|
// Round-up to next 4-byte size.
|
|
shader_code = new uint32_t[(size + 3)/4];
|
|
retval = SDL_ReadIO(io, shader_code, size);
|
|
assert(retval == size);
|
|
|
|
*pSize = size;
|
|
|
|
SDL_CloseIO(io);
|
|
return shader_code;
|
|
}
|
|
|