This commit is contained in:
2026-06-14 19:09:18 +01:00
parent 14bd1a9271
commit 13fa90a0e9
3958 changed files with 999286 additions and 4 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,172 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
#ifndef VULKAN_APP_SDL_H_1456211188
#define VULKAN_APP_SDL_H_1456211188
/*
* Copyright 2017-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
#include <array>
#include <cstring>
#include <new>
#include <vector>
#define VK_ENABLE_BETA_EXTENSIONS 1
#include <vulkan/vulkan.hpp>
#include <SDL3/SDL_vulkan.h> // Must be after vulkan.hpp.
#include "AppBaseSDL.h"
#include "VulkanContext.h"
#include "VulkanSwapchain.h"
#include "vulkantextoverlay.hpp"
#include "unused.h"
class VulkanAppSDL : public AppBaseSDL {
public:
VulkanAppSDL(const char* const name,
int width, int height,
const uint32_t version,
bool enableTextOverlay)
: AppBaseSDL(name), w_width(width), w_height(height),
subOptimalPresentWarned(false), validate(false),
vkVersion(version),
enableTextOverlay(enableTextOverlay),
textOverlay(nullptr)
{
// The overridden new below will zero the storage. Thus
// we avoid a long list of initializers.
appTitle = name;
};
virtual ~VulkanAppSDL();
virtual bool doEvent(SDL_Event* event);
virtual void drawFrame(uint32_t msTicks);
virtual void finalize();
virtual bool initialize(Args& args);
virtual void onFPSUpdate();
virtual void resizeWindow(int width, int height);
virtual void windowResized();
static void* operator new(size_t size) {
void* storage = ::operator new(size);
memset(storage, 0, size);
return storage;
}
static void operator delete(void* storage, size_t) {
::operator delete(storage);
}
void updateTextOverlay();
// Called when the text overlay is updating
// Can be overridden in derived class to add custom text to the overlay
virtual void getOverlayText(float yOffset);
protected:
bool createDevice();
bool createInstance();
bool createPipelineCache();
bool createSemaphores();
bool createSurface();
bool createSwapchain();
bool findGpu();
void flushInitialCommands();
bool initializeVulkan();
bool prepareColorBuffers();
bool prepareCommandBuffers();
bool prepareDepthBuffer();
bool prepareDescriptorLayout();
void prepareFrame();
bool preparePresentCommandBuffers();
bool prepareRenderPass();
bool preparePipeline();
bool prepareDescriptorSet();
bool prepareFramebuffers();
void prepareTextOverlay();
void submitFrame();
bool setupDebugReporting();
enum stencilRequirement { eNoStencil = 0, eStencil = 1 };
enum depthRequirement { e16bits = 0, e24bits = 1, e32bits = 2 };
bool getSupportedDepthFormat(vk::PhysicalDevice gpu,
stencilRequirement requiredStencil,
depthRequirement requiredDepth,
vk::ImageTiling tiling,
vk::Format& pFormat,
vk::ImageAspectFlags& pAspectMask);
// Sets text on window title bar.
void setWindowTitle();
void setImageLayout(VkImage image, VkImageAspectFlags aspectMask,
VkImageLayout old_image_layout,
VkImageLayout new_image_layout,
VkAccessFlags srcAccessMask);
VKAPI_ATTR VkBool32 VKAPI_CALL
debugFunc(VkFlags msgFlags, VkDebugReportObjectTypeEXT objType,
uint64_t srcObject, size_t location, int32_t msgCode,
const char *pLayerPrefix, const char *pMsg);
std::string& wrapText(std::string& source, size_t width = 70,
const std::string& whitespace = " \t\r");
uint32_t showDebugReport(uint32_t mbFlags, const std::string title,
std::string message, bool enableAbort);
static bool checkLayers(uint32_t nameCount, const char **names,
uint32_t layerCount, VkLayerProperties *layers);
static VKAPI_ATTR VkBool32 VKAPI_CALL
debugFunc(VkFlags msgFlags, VkDebugReportObjectTypeEXT objType,
uint64_t srcObject, size_t location, int32_t msgCode,
const char *pLayerPrefix, const char *pMsg, void *pUserData);
bool prepared = false;
// Set true if want presents v-sync'ed.
bool enableVSync = false;
uint32_t w_width;
uint32_t w_height;
bool subOptimalPresentWarned;
bool validate;
std::vector<const char*> extensionNames;
std::vector<const char*> deviceValidationLayers;
VkCommandBuffer setupCmdBuffer;
VkSurfaceKHR vsSurface;
VulkanContext vkctx;
// Index of active framebuffer.
uint32_t currentBuffer;
// Synchronization semaphores
struct {
// Swap chain image presentation
VkSemaphore presentComplete;
// Command buffer submission and execution
VkSemaphore renderComplete;
// Text overlay submission and execution
VkSemaphore textOverlayComplete;
} semaphores;
const uint32_t vkVersion;
// Saved for clean-up
std::vector<vk::ShaderModule> shaderModules;
bool enableTextOverlay = false;
VulkanTextOverlay *textOverlay;
// List of shader modules created (stored for cleanup)
VkDebugReportCallbackEXT msgCallback;
PFN_vkCreateDebugReportCallbackEXT pfnCreateDebugReportCallbackEXT;
PFN_vkDestroyDebugReportCallbackEXT pfnDestroyDebugReportCallbackEXT;
PFN_vkDebugReportMessageEXT pfnDebugReportMessageEXT;
};
#endif /* VULKAN_APP_SDL_H_1456211188 */
@@ -0,0 +1,363 @@
/* -*- 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;
}
@@ -0,0 +1,134 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
#ifndef VULKAN_TEXTURE_H_229895365400979164311947449304284143508
#define VULKAN_TEXTURE_H_229895365400979164311947449304284143508
/*
* Copyright 2017-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
#include <vector>
#define VK_ENABLE_BETA_EXTENSIONS 1
#include <vulkan/vulkan.hpp>
#include "VulkanSwapchain.h"
struct VulkanDepthBuffer {
VkFormat format;
VkImage image;
VkMemoryAllocateInfo memAlloc;
VkDeviceMemory mem;
VkImageView view;
};
struct VulkanContext {
vk::Instance instance;
vk::PhysicalDevice gpu;
vk::PhysicalDeviceFeatures gpuFeatures;
#if VK_KHR_portability_subset
vk::PhysicalDevicePortabilitySubsetFeaturesKHR gpuPortabilityFeatures;
#endif
vk::PhysicalDeviceProperties gpuProperties;
vk::PhysicalDeviceMemoryProperties memoryProperties;
vk::Device device;
vk::CommandPool commandPool;
vk::Queue queue;
bool gpuIsPortabilitySubsetDevice = false;
struct {
bool pvrtc = false;
bool astc_hdr = false;
bool astc_3d = false;
} enabledDeviceExtensions;
std::vector<VkCommandBuffer> drawCmdBuffers;
std::vector<VkCommandBuffer> postPresentCmdBuffers;
std::vector<VkCommandBuffer> prePresentCmdBuffers;
VkRenderPass renderPass;
// Pipeline stage flags for the submit info structure
const VkPipelineStageFlags submitPipelineStages =
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
// Contains command buffers and semaphores to be presented to the queue
VkSubmitInfo drawCmdSubmitInfo;
vk::DescriptorPool descriptorPool;
VkPipelineCache pipelineCache;
VulkanSwapchain swapchain;
// List of frame buffers (same as number of swap chain images)
std::vector<VkFramebuffer>framebuffers;
VulkanDepthBuffer depthBuffer;
// Create a new command buffer, opening it for command entry,
// if requested.
vk::CommandBuffer createCommandBuffer(vk::CommandBufferLevel level,
bool begin);
// End a command buffer, submit it to the queue and free, if requested.
// Note : Waits for the queue to become idle
void flushCommandBuffer(vk::CommandBuffer& commandBuffer, bool free);
// Create a command buffer for each image in the swap chain.
bool createDrawCommandBuffers();
bool createPresentCommandBuffers();
bool checkDrawCommandBuffers();
void destroyDrawCommandBuffers();
void destroyPresentCommandBuffers();
bool getMemoryType(uint32_t typeBits,
vk::MemoryPropertyFlags requirementsMask,
uint32_t *typeIndex) const;
uint32_t getMemoryType(uint32_t typeBits,
vk::MemoryPropertyFlags requirementsMask) const;
// Create a buffer, fill it with data (if != NULL) and bind buffer memory
bool createBuffer(
vk::BufferUsageFlags usageFlags,
vk::MemoryPropertyFlags memoryPropertyFlags,
vk::DeviceSize size,
void *data,
vk::Buffer *buffer,
vk::DeviceMemory *memory);
// This version always uses HOST_VISIBLE memory
bool createBuffer(
vk::BufferUsageFlags usage,
vk::DeviceSize size,
void *data,
vk::Buffer *buffer,
vk::DeviceMemory *memory);
// Overload that assigns buffer info to descriptor
bool createBuffer(
vk::BufferUsageFlags usage,
vk::DeviceSize size,
void* data,
vk::Buffer* buffer,
vk::DeviceMemory* memory,
vk::DescriptorBufferInfo* descriptor);
// Overload to pass memory property flags
bool createBuffer(
vk::BufferUsageFlags usage,
vk::MemoryPropertyFlags memoryPropertyFlags,
vk::DeviceSize size,
void* data,
vk::Buffer* buffer,
vk::DeviceMemory* memory,
vk::DescriptorBufferInfo* descriptor);
vk::PipelineShaderStageCreateInfo loadShader(std::string filename,
vk::ShaderStageFlagBits stage,
const char* const modname = "main");
vk::ShaderModule loadShader(std::string filename);
uint32_t* readSpv(const char *filename, size_t *pSize);
bool gpuSupportsSwizzle() {
#if VK_KHR_portability_subset
return !gpuIsPortabilitySubsetDevice
|| gpuPortabilityFeatures.imageViewFormatSwizzle;
#else
return true;
#endif
}
};
#endif /* VULKAN_TEXTURE_H_229895365400979164311947449304284143508 */
@@ -0,0 +1,434 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2017-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @internal
* @class VulkanSwapchain
* @~English
*
* @brief Manage the swapchain for a Vulkan app.
*
* A swap chain is a collection of image buffers used for rendering
* The images can then be presented to the windowing system for display.
*
* @author Mark Callow, github.com/MarkCallow.
*/
#include <stdlib.h>
#include <string>
#include <fstream>
#include <assert.h>
#include <stdio.h>
#include <vector>
#ifdef _WIN32
#include <windows.h>
#include <fcntl.h>
#include <io.h>
#else
#endif
#include "VulkanSwapchain.h"
#include <SDL3/SDL.h>
#include <SDL3/SDL_vulkan.h>
#include "AppBaseSDL.h"
#include "unused.h"
#define ERROR_RETURN(msg) \
(void)SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, theApp->name(), \
msg, NULL); \
return false;
#define GET_INSTANCE_PROC_ADDR(inst, entrypoint) \
{ \
pfn##entrypoint = \
(PFN_vk##entrypoint)vkGetInstanceProcAddr(inst, "vk"#entrypoint); \
if (pfn##entrypoint == NULL) { \
ERROR_RETURN("vkGetInstanceProcAddr: unable to find vk"#entrypoint); \
} \
}
#define GET_DEVICE_PROC_ADDR(device, entrypoint) \
{ \
pfn##entrypoint = \
(PFN_vk##entrypoint)vkGetDeviceProcAddr(device, "vk"#entrypoint); \
if (pfn##entrypoint == NULL) { \
ERROR_RETURN("vkGetDeviceProcAddr: unable to find vk"#entrypoint); \
} \
}
// Creates an os specific surface
// Tries to find a graphics and a present queue
bool
VulkanSwapchain::initSurface(SDL_Window* window)
{
U_ASSERT_ONLY VkResult err;
if (!SDL_Vulkan_CreateSurface(window, instance, nullptr, &surface)) {
std::string msg = "SDL_CreateVulkanSurface failed: ";
msg += SDL_GetError();
ERROR_RETURN(msg.c_str());
}
// Get available queue family properties
uint32_t queueCount;
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueCount, NULL);
assert(queueCount >= 1);
std::vector<VkQueueFamilyProperties> queueProps(queueCount);
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueCount,
queueProps.data());
// Iterate over the queues looking for ones which support presenting.
std::vector<VkBool32> supportsPresent(queueCount);
for (uint32_t i = 0; i < queueCount; i++) {
vkGetPhysicalDeviceSurfaceSupportKHR(physicalDevice, i, surface,
&supportsPresent[i]);
}
// Search for a graphics- and present-capable queue.
uint32_t graphicsQueueIndex = UINT32_MAX;
uint32_t presentQueueIndex = UINT32_MAX;
for (uint32_t i = 0; i < queueCount; i++) {
if ((queueProps[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0)
{
if (graphicsQueueIndex == UINT32_MAX)
{
graphicsQueueIndex = i;
}
if (supportsPresent[i] == VK_TRUE)
{
graphicsQueueIndex = i;
presentQueueIndex = i;
break;
}
}
}
if (presentQueueIndex == UINT32_MAX) {
// If there's no queue that supports both present and graphics
// try to find a separate present queue
for (uint32_t i = 0; i < queueCount; ++i)
{
if (supportsPresent[i] == VK_TRUE)
{
presentQueueIndex = i;
break;
}
}
}
// Exit if either a graphics or a presenting queue hasn't been found
if (graphicsQueueIndex == UINT32_MAX
|| presentQueueIndex == UINT32_MAX)
{
ERROR_RETURN("Could not find a graphics or presenting queue!");
}
// TODO: Add support for separate graphics and presenting queue
if (graphicsQueueIndex != presentQueueIndex)
{
ERROR_RETURN("Separate graphics and present queues not yet supported!");
}
queueIndex = graphicsQueueIndex;
// Get list of supported surface formats
uint32_t formatCount;
err = vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface,
&formatCount, NULL);
assert(err == VK_SUCCESS);
assert(formatCount > 0);
std::vector<VkSurfaceFormatKHR> surfaceFormats(formatCount);
err = vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface,
&formatCount,
surfaceFormats.data());
assert(err == VK_SUCCESS);
// If the surface format list only includes one entry with
// VK_FORMAT_UNDEFINED, there is no preferred format.
// Assume VK_FORMAT_B8G8R8A8_RGB.
// TODO: Consider passing in desired format from app.
if ((formatCount == 1) && (surfaceFormats[0].format == VK_FORMAT_UNDEFINED))
{
colorFormat = VK_FORMAT_B8G8R8A8_SRGB;
}
else
{
assert(formatCount >= 1);
uint32_t i;
for (i = 0; i < formatCount; i++) {
if (surfaceFormats[i].format == VK_FORMAT_B8G8R8A8_SRGB) {
break;
}
}
if (i == formatCount) {
// Pick the first available, if no SRGB.
// FIXME probably should raise an error...
i = 0;
}
colorFormat = surfaceFormats[i].format;
}
colorSpace = surfaceFormats[0].colorSpace;
return true;
}
// Connect to the instance and device and get all required function pointers
bool
VulkanSwapchain::connectDevice(VkDevice targetDevice)
{
this->device = targetDevice;
#if USE_FUNCPTRS_FOR_KHR_EXTS
GET_DEVICE_PROC_ADDR(device, CreateSwapchainKHR);
GET_DEVICE_PROC_ADDR(device, DestroySwapchainKHR);
GET_DEVICE_PROC_ADDR(device, GetSwapchainImagesKHR);
GET_DEVICE_PROC_ADDR(device, AcquireNextImageKHR);
GET_DEVICE_PROC_ADDR(device, QueuePresentKHR);
#endif
return true;
}
// Connect to the instance and device and get all required function pointers
bool
VulkanSwapchain::connectInstance(VkInstance targetInstance,
VkPhysicalDevice targetPhysicalDevice)
{
this->instance = targetInstance;
this->physicalDevice = targetPhysicalDevice;
#if USE_FUNCPTRS_FOR_KHR_EXTS
GET_INSTANCE_PROC_ADDR(instance, GetPhysicalDeviceSurfaceSupportKHR);
GET_INSTANCE_PROC_ADDR(instance, GetPhysicalDeviceSurfaceCapabilitiesKHR);
GET_INSTANCE_PROC_ADDR(instance, GetPhysicalDeviceSurfaceFormatsKHR);
GET_INSTANCE_PROC_ADDR(instance, GetPhysicalDeviceSurfacePresentModesKHR);
#endif
return true;
}
// Create the swap chain and get images with given width and height
void
VulkanSwapchain::create(uint32_t *width, uint32_t *height,
bool vsync)
{
U_ASSERT_ONLY VkResult err;
VkSwapchainKHR oldSwapchain = swapchain;
// Get physical device surface properties and formats
VkSurfaceCapabilitiesKHR surfCaps;
err = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, surface,
&surfCaps);
assert(err == VK_SUCCESS);
// Get available present modes
uint32_t presentModeCount;
err = vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface,
&presentModeCount, NULL);
assert(err == VK_SUCCESS);
assert(presentModeCount > 0);
std::vector<VkPresentModeKHR> presentModes(presentModeCount);
err = vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface,
&presentModeCount,
presentModes.data());
assert(err == VK_SUCCESS);
VkExtent2D swapchainExtent = {};
// width and height are either both 0xFFFFFFFF, or both not 0xFFFFFFFF.
if (surfCaps.currentExtent.width == UINT32_MAX)
{
// If the surface size is undefined, the size is set to
// the size of the images requested.
swapchainExtent.width = *width;
swapchainExtent.height = *height;
}
else
{
swapchainExtent = surfCaps.currentExtent;
*width = surfCaps.currentExtent.width;
*height = surfCaps.currentExtent.height;
}
// Select a present mode for the swapchain
// The VK_PRESENT_MODE_FIFO_KHR mode must always be present as per spec
// This mode waits for the vertical blank ("v-sync")
VkPresentModeKHR swapchainPresentMode = VK_PRESENT_MODE_FIFO_KHR;
// If v-sync is not requested, try to find a mailbox mode if present
// It's the lowest latency non-tearing present mode available
if (!vsync)
{
for (size_t i = 0; i < presentModeCount; i++)
{
if (presentModes[i] == VK_PRESENT_MODE_MAILBOX_KHR)
{
swapchainPresentMode = VK_PRESENT_MODE_MAILBOX_KHR;
break;
}
if ((swapchainPresentMode != VK_PRESENT_MODE_MAILBOX_KHR)
&& (presentModes[i] == VK_PRESENT_MODE_IMMEDIATE_KHR))
{
swapchainPresentMode = VK_PRESENT_MODE_IMMEDIATE_KHR;
}
}
}
// Determine the number of images
uint32_t desiredNumberOfSwapchainImages = surfCaps.minImageCount + 1;
if ((surfCaps.maxImageCount > 0)
&& (desiredNumberOfSwapchainImages > surfCaps.maxImageCount))
{
desiredNumberOfSwapchainImages = surfCaps.maxImageCount;
}
VkSurfaceTransformFlagsKHR preTransform;
if (surfCaps.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR) {
preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
} else {
preTransform = surfCaps.currentTransform;
}
VkSwapchainCreateInfoKHR swapchainCI = {};
swapchainCI.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
swapchainCI.pNext = NULL;
swapchainCI.surface = surface;
swapchainCI.minImageCount = desiredNumberOfSwapchainImages;
swapchainCI.imageFormat = colorFormat;
swapchainCI.imageColorSpace = colorSpace;
swapchainCI.imageExtent = { swapchainExtent.width, swapchainExtent.height };
swapchainCI.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
swapchainCI.preTransform = (VkSurfaceTransformFlagBitsKHR)preTransform;
swapchainCI.imageArrayLayers = 1;
swapchainCI.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
swapchainCI.queueFamilyIndexCount = 0;
swapchainCI.pQueueFamilyIndices = NULL;
swapchainCI.presentMode = swapchainPresentMode;
swapchainCI.oldSwapchain = oldSwapchain;
swapchainCI.clipped = true;
swapchainCI.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
err = vkCreateSwapchainKHR(device, &swapchainCI, nullptr, &swapchain);
assert(err == VK_SUCCESS);
// If an existing swap chain is re-created, destroy the old swap chain
// This also cleans up all the presentable images
if (oldSwapchain != VK_NULL_HANDLE)
{
for (uint32_t i = 0; i < imageCount; i++)
{
vkDestroyImageView(device, buffers[i].view, nullptr);
}
vkDestroySwapchainKHR(device, oldSwapchain, nullptr);
}
err = vkGetSwapchainImagesKHR(device, swapchain, &imageCount, NULL);
assert(err == VK_SUCCESS);
// Get the swap chain images
images.resize(imageCount);
err = vkGetSwapchainImagesKHR(device, swapchain,
&imageCount, images.data());
assert(err == VK_SUCCESS);
// Get the swap chain buffers containing the image and imageview
buffers.resize(imageCount);
for (uint32_t i = 0; i < imageCount; i++)
{
VkImageViewCreateInfo colorAttachmentView = {};
colorAttachmentView.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
colorAttachmentView.pNext = NULL;
colorAttachmentView.format = colorFormat;
colorAttachmentView.components = {
VK_COMPONENT_SWIZZLE_IDENTITY,
VK_COMPONENT_SWIZZLE_IDENTITY,
VK_COMPONENT_SWIZZLE_IDENTITY,
VK_COMPONENT_SWIZZLE_IDENTITY
};
colorAttachmentView.subresourceRange.aspectMask
= VK_IMAGE_ASPECT_COLOR_BIT;
colorAttachmentView.subresourceRange.baseMipLevel = 0;
colorAttachmentView.subresourceRange.levelCount = 1;
colorAttachmentView.subresourceRange.baseArrayLayer = 0;
colorAttachmentView.subresourceRange.layerCount = 1;
colorAttachmentView.viewType = VK_IMAGE_VIEW_TYPE_2D;
colorAttachmentView.flags = 0;
buffers[i].image = images[i];
colorAttachmentView.image = buffers[i].image;
err = vkCreateImageView(device, &colorAttachmentView, nullptr,
&buffers[i].view);
assert(err == VK_SUCCESS);
}
}
// Acquires the next image in the swap chain
VkResult
VulkanSwapchain::acquireNextImage(VkSemaphore presentCompleteSemaphore,
uint32_t *currentBuffer)
{
return vkAcquireNextImageKHR(device, swapchain, UINT64_MAX,
presentCompleteSemaphore,
(VkFence)nullptr,
currentBuffer);
}
// Present the current image to the queue
VkResult
VulkanSwapchain::queuePresent(VkQueue queue, uint32_t currentBuffer)
{
VkPresentInfoKHR presentInfo = {};
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
presentInfo.pNext = NULL;
presentInfo.swapchainCount = 1;
presentInfo.pSwapchains = &swapchain;
presentInfo.pImageIndices = &currentBuffer;
return vkQueuePresentKHR(queue, &presentInfo);
}
// Present the current image to the queue when semaphore signaled.
VkResult
VulkanSwapchain::queuePresent(VkQueue queue, uint32_t currentBuffer,
VkSemaphore waitSemaphore)
{
VkPresentInfoKHR presentInfo = {};
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
presentInfo.pNext = NULL;
presentInfo.swapchainCount = 1;
presentInfo.pSwapchains = &swapchain;
presentInfo.pImageIndices = &currentBuffer;
if (waitSemaphore != VK_NULL_HANDLE)
{
presentInfo.pWaitSemaphores = &waitSemaphore;
presentInfo.waitSemaphoreCount = 1;
}
return vkQueuePresentKHR(queue, &presentInfo);
}
// Free all Vulkan resources used by the swap chain
void
VulkanSwapchain::cleanup()
{
for (uint32_t i = 0; i < imageCount; i++)
{
vkDestroyImageView(device, buffers[i].view, nullptr);
}
vkDestroySwapchainKHR(device, swapchain, nullptr);
vkDestroySurfaceKHR(instance, surface, nullptr);
}
@@ -0,0 +1,105 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2017-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <vector>
#include <vulkan/vulkan.h>
#if !defined(USE_FUNCPTRS_FOR_KHR_EXTS)
#define USE_FUNCPTRS_FOR_KHR_EXTS 0
#endif
typedef struct _SwapchainBuffers {
VkImage image;
VkImageView view;
} SwapchainBuffer;
class VulkanSwapchain
{
public:
VkFormat colorFormat;
VkColorSpaceKHR colorSpace;
VkSwapchainKHR swapchain = VK_NULL_HANDLE;
uint32_t imageCount;
std::vector<VkImage> images;
std::vector<SwapchainBuffer> buffers;
// Index of the detected graphics- and present-capable device queue.
uint32_t queueIndex = UINT32_MAX;
// Creates an OS specific surface.
// Looks for a graphics and a present queue
bool initSurface(struct SDL_Window* window);
// Connect to device and get required device function pointers.
bool connectDevice(VkDevice device);
// Connect to instance and get required instance function pointers.
bool connectInstance(VkInstance instance,
VkPhysicalDevice physicalDevice);
// Create the swap chain and get images with given width and height
void create(uint32_t *width, uint32_t *height,
bool vsync = false);
// Acquires the next image in the swap chain
VkResult acquireNextImage(VkSemaphore presentCompleteSemaphore,
uint32_t *currentBuffer);
// Present the current image to the queue
VkResult queuePresent(VkQueue queue, uint32_t currentBuffer);
// Present the current image to the queue
VkResult queuePresent(VkQueue queue, uint32_t currentBuffer,
VkSemaphore waitSemaphore);
// Free all Vulkan resources used by the swap chain
void cleanup();
private:
VkInstance instance;
VkDevice device;
VkPhysicalDevice physicalDevice;
VkSurfaceKHR surface;
#if USE_FUNCPTRS_FOR_KHR_EXTS
PFN_vkGetPhysicalDeviceSurfaceSupportKHR
pfnGetPhysicalDeviceSurfaceSupportKHR;
PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR
pfnGetPhysicalDeviceSurfaceCapabilitiesKHR;
PFN_vkGetPhysicalDeviceSurfaceFormatsKHR
pfnGetPhysicalDeviceSurfaceFormatsKHR;
PFN_vkGetPhysicalDeviceSurfacePresentModesKHR
pfnGetPhysicalDeviceSurfacePresentModesKHR;
PFN_vkCreateSwapchainKHR pfnCreateSwapchainKHR;
PFN_vkDestroySwapchainKHR pfnDestroySwapchainKHR;
PFN_vkGetSwapchainImagesKHR pfnGetSwapchainImagesKHR;
PFN_vkAcquireNextImageKHR pfnAcquireNextImageKHR;
PFN_vkQueuePresentKHR pfnQueuePresentKHR;
#define vkGetPhysicalDeviceSurfaceSupportKHR \
pfnGetPhysicalDeviceSurfaceSupportKHR
#define vkGetPhysicalDeviceSurfaceCapabilitiesKHR \
pfnGetPhysicalDeviceSurfaceCapabilitiesKHR
#define vkGetPhysicalDeviceSurfaceFormatsKHR \
pfnGetPhysicalDeviceSurfaceFormatsKHR
#define vkGetPhysicalDeviceSurfacePresentModesKHR \
pfnGetPhysicalDeviceSurfacePresentModesKHR
#define vkCreateSwapchainKHR pfnCreateSwapchainKHR
#define vkDestroySwapchainKHR pfnDestroySwapchainKHR
#define vkGetSwapchainImagesKHR pfnGetSwapchainImagesKHR
#define vkAcquireNextImageKHR pfnAcquireNextImageKHR
#define vkQueuePresentKHR pfnQueuePresentKHR
#endif
};
@@ -0,0 +1,8 @@
<!-- Copyright 2024 Mark Callow -->
<!-- SPDX-License-Identifier: Apache-2.0 -->
# Vulkan ICD and Explicit Layer Manifest Files
These were copied from `macOS/share/vulkan` in the Vulkan SDK and have been
modified for inclusion in a macOS application bundle to cause the Vulkan loader
to find the libraries within the bundle.
@@ -0,0 +1,8 @@
{
"file_format_version": "1.0.0",
"ICD": {
"library_path": "../../../Frameworks/libMoltenVK.dylib",
"api_version": "1.2.0",
"is_portability_driver": true
}
}
@@ -0,0 +1,16 @@
// Copyright 2017-2020 Mark Callow
// SPDX-License-Identifier: Apache-2.0
#version 450 core
layout (location = 0) in vec2 inUV;
layout (binding = 0) uniform sampler2D samplerFont;
layout (location = 0) out vec4 outFragColor;
void main(void)
{
float color = texture(samplerFont, inUV).r;
outFragColor = vec4(vec3(color), 1.0);
}
@@ -0,0 +1,20 @@
// Copyright 2017-2020 Mark Callow
// SPDX-License-Identifier: Apache-2.0
#version 450 core
layout (location = 0) in vec2 inPos;
layout (location = 1) in vec2 inUV;
layout (location = 0) out vec2 outUV;
out gl_PerVertex
{
vec4 gl_Position;
};
void main(void)
{
gl_Position = vec4(inPos, 0.0, 1.0);
outUV = inUV;
}
@@ -0,0 +1,48 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
#ifndef VULKAN_CHECK_RES_H_1456211188
#define VULKAN_CHECK_RES_H_1456211188
/*
* Copyright 2017-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include <iomanip>
#include <sstream>
#include <SDL3/SDL_messagebox.h>
/**
* @internal
* @file
* @~English
*
* @brief Check result of a Vulkan command.
*
* Use for commands that will always succeed unless usage is invalid.
*/
#if defined(DEBUG)
#define VK_CHECK_RESULT(f) \
{ \
VkResult res = (f); \
if (res != VK_SUCCESS) \
{ \
std::stringstream msg; \
msg << "Fatal : VkResult is \"" << res << "\" in " << __FILE__ \
<< " at line " << __LINE__ << std::endl; \
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, \
"VkSample_02_cube_textured", \
msg.str().c_str(), \
NULL); \
assert(res == VK_SUCCESS); \
} \
}
#else
#define VK_CHECK_RESULT(f) (void)f
#endif
#endif
@@ -0,0 +1,288 @@
/*
* Vulkan examples debug wrapper
*
* Appendix for VK_EXT_Debug_Report can be found at https://github.com/KhronosGroup/Vulkan-Docs/blob/1.0-VK_EXT_debug_report/doc/specs/vulkan/appendices/debug_report.txt
*
* Copyright 2016 Sascha Willems - www.saschawillems.de
* SPDX-License-Identifier: MIT
*/
#include "vulkandebug.h"
#include <iostream>
#include "unused.h"
namespace vkDebug
{
int validationLayerCount = 1;
const char *validationLayerNames[] =
{
// This is a meta layer that enables all of the standard
// validation layers in the correct order :
// threading, parameter_validation, device_limits, object_tracker, image, core_validation, swapchain, and unique_objects
"VK_LAYER_LUNARG_standard_validation"
};
PFN_vkCreateDebugReportCallbackEXT CreateDebugReportCallback = VK_NULL_HANDLE;
PFN_vkDestroyDebugReportCallbackEXT DestroyDebugReportCallback = VK_NULL_HANDLE;
PFN_vkDebugReportMessageEXT dbgBreakCallback = VK_NULL_HANDLE;
VkDebugReportCallbackEXT msgCallback;
VkBool32 messageCallback(
VkDebugReportFlagsEXT flags,
VkDebugReportObjectTypeEXT /*objType*/,
uint64_t /*srcObject*/,
size_t /*location*/,
int32_t msgCode,
const char* pLayerPrefix,
const char* pMsg,
void* /*pUserData*/)
{
// Message text passed in by validation layer
std::string text(pMsg);
// Select prefix depending on flags passed to the callback
// Note that multiple flags may be set for a single validation message
std::string prefix("");
// Error that may result in undefined behaviour
if (flags & VK_DEBUG_REPORT_ERROR_BIT_EXT)
{
prefix += "ERROR:";
};
// Warnings may hint at unexpected / non-spec API usage
if (flags & VK_DEBUG_REPORT_WARNING_BIT_EXT)
{
prefix += "WARNING:";
};
// May indicate sub-optimal usage of the API
if (flags & VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT)
{
prefix += "PERFORMANCE:";
};
// Informal messages that may become handy during debugging
if (flags & VK_DEBUG_REPORT_INFORMATION_BIT_EXT)
{
prefix += "INFO:";
}
// Diagnostic info from the Vulkan loader and layers
// Usually not helpful in terms of API usage, but may help to debug layer and loader problems
if (flags & VK_DEBUG_REPORT_DEBUG_BIT_EXT)
{
prefix += "DEBUG:";
}
// Display message to default output (console if activated)
std::cout << prefix << " [" << pLayerPrefix << "] Code " << msgCode << " : " << pMsg << "\n";
fflush(stdout);
// The return value of this callback controls wether the Vulkan call that caused
// the validation message will be aborted or not
// We return VK_FALSE as we DON'T want Vulkan calls that cause a validation message
// (and return a VkResult) to abort
// If you instead want to have calls abort, pass in VK_TRUE and the function will
// return VK_ERROR_VALIDATION_FAILED_EXT
return VK_FALSE;
}
void setupDebugging(VkInstance instance, VkDebugReportFlagsEXT flags, VkDebugReportCallbackEXT callBack)
{
CreateDebugReportCallback = (PFN_vkCreateDebugReportCallbackEXT)vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT");
DestroyDebugReportCallback = (PFN_vkDestroyDebugReportCallbackEXT)vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT");
dbgBreakCallback = (PFN_vkDebugReportMessageEXT)vkGetInstanceProcAddr(instance, "vkDebugReportMessageEXT");
VkDebugReportCallbackCreateInfoEXT dbgCreateInfo = {};
dbgCreateInfo.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CREATE_INFO_EXT;
dbgCreateInfo.pfnCallback = (PFN_vkDebugReportCallbackEXT)messageCallback;
dbgCreateInfo.flags = flags;
U_ASSERT_ONLY VkResult err = CreateDebugReportCallback(
instance,
&dbgCreateInfo,
nullptr,
(callBack != VK_NULL_HANDLE) ? &callBack : &msgCallback);
assert(!err);
(void)err; // Supress VC++ unused variable warning for Release config.
}
void freeDebugCallback(VkInstance instance)
{
if (msgCallback != VK_NULL_HANDLE)
{
DestroyDebugReportCallback(instance, msgCallback, nullptr);
}
}
namespace DebugMarker
{
bool active = false;
PFN_vkDebugMarkerSetObjectTagEXT pfnDebugMarkerSetObjectTag = VK_NULL_HANDLE;
PFN_vkDebugMarkerSetObjectNameEXT pfnDebugMarkerSetObjectName = VK_NULL_HANDLE;
PFN_vkCmdDebugMarkerBeginEXT pfnCmdDebugMarkerBegin = VK_NULL_HANDLE;
PFN_vkCmdDebugMarkerEndEXT pfnCmdDebugMarkerEnd = VK_NULL_HANDLE;
PFN_vkCmdDebugMarkerInsertEXT pfnCmdDebugMarkerInsert = VK_NULL_HANDLE;
void setup(VkDevice device)
{
pfnDebugMarkerSetObjectTag = (PFN_vkDebugMarkerSetObjectTagEXT)vkGetDeviceProcAddr(device, "vkDebugMarkerSetObjectTagEXT");
pfnDebugMarkerSetObjectName = (PFN_vkDebugMarkerSetObjectNameEXT)vkGetDeviceProcAddr(device, "vkDebugMarkerSetObjectNameEXT");
pfnCmdDebugMarkerBegin = (PFN_vkCmdDebugMarkerBeginEXT)vkGetDeviceProcAddr(device, "vkCmdDebugMarkerBeginEXT");
pfnCmdDebugMarkerEnd = (PFN_vkCmdDebugMarkerEndEXT)vkGetDeviceProcAddr(device, "vkCmdDebugMarkerEndEXT");
pfnCmdDebugMarkerInsert = (PFN_vkCmdDebugMarkerInsertEXT)vkGetDeviceProcAddr(device, "vkCmdDebugMarkerInsertEXT");
// Set flag if at least one function pointer is present
active = (pfnDebugMarkerSetObjectName != VK_NULL_HANDLE);
}
void setObjectName(VkDevice device, uint64_t object, VkDebugReportObjectTypeEXT objectType, const char *name)
{
// Check for valid function pointer (may not be present if not running in a debugging application)
if (pfnDebugMarkerSetObjectName)
{
VkDebugMarkerObjectNameInfoEXT nameInfo = {};
nameInfo.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_OBJECT_NAME_INFO_EXT;
nameInfo.objectType = objectType;
nameInfo.object = object;
nameInfo.pObjectName = name;
pfnDebugMarkerSetObjectName(device, &nameInfo);
}
}
void setObjectTag(VkDevice device, uint64_t object, VkDebugReportObjectTypeEXT objectType, uint64_t name, size_t tagSize, const void* tag)
{
// Check for valid function pointer (may not be present if not running in a debugging application)
if (pfnDebugMarkerSetObjectTag)
{
VkDebugMarkerObjectTagInfoEXT tagInfo = {};
tagInfo.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_OBJECT_TAG_INFO_EXT;
tagInfo.objectType = objectType;
tagInfo.object = object;
tagInfo.tagName = name;
tagInfo.tagSize = tagSize;
tagInfo.pTag = tag;
pfnDebugMarkerSetObjectTag(device, &tagInfo);
}
}
void beginRegion(VkCommandBuffer cmdbuffer, const char* pMarkerName, glm::vec4 color)
{
// Check for valid function pointer (may not be present if not running in a debugging application)
if (pfnCmdDebugMarkerBegin)
{
VkDebugMarkerMarkerInfoEXT markerInfo = {};
markerInfo.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT;
memcpy(markerInfo.color, &color[0], sizeof(float) * 4);
markerInfo.pMarkerName = pMarkerName;
pfnCmdDebugMarkerBegin(cmdbuffer, &markerInfo);
}
}
void insert(VkCommandBuffer cmdbuffer, std::string markerName, glm::vec4 color)
{
// Check for valid function pointer (may not be present if not running in a debugging application)
if (pfnCmdDebugMarkerInsert)
{
VkDebugMarkerMarkerInfoEXT markerInfo = {};
markerInfo.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT;
memcpy(markerInfo.color, &color[0], sizeof(float) * 4);
markerInfo.pMarkerName = markerName.c_str();
pfnCmdDebugMarkerInsert(cmdbuffer, &markerInfo);
}
}
void endRegion(VkCommandBuffer cmdBuffer)
{
// Check for valid function (may not be present if not runnin in a debugging application)
if (pfnCmdDebugMarkerEnd)
{
pfnCmdDebugMarkerEnd(cmdBuffer);
}
}
void setCommandBufferName(VkDevice device, VkCommandBuffer cmdBuffer, const char * name)
{
setObjectName(device, (uint64_t)cmdBuffer, VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT, name);
}
void setQueueName(VkDevice device, VkQueue queue, const char * name)
{
setObjectName(device, (uint64_t)queue, VK_DEBUG_REPORT_OBJECT_TYPE_QUEUE_EXT, name);
}
void setImageName(VkDevice device, VkImage image, const char * name)
{
setObjectName(device, (uint64_t)image, VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_EXT, name);
}
void setSamplerName(VkDevice device, VkSampler sampler, const char * name)
{
setObjectName(device, (uint64_t)sampler, VK_DEBUG_REPORT_OBJECT_TYPE_SAMPLER_EXT, name);
}
void setBufferName(VkDevice device, VkBuffer buffer, const char * name)
{
setObjectName(device, (uint64_t)buffer, VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_EXT, name);
}
void setDeviceMemoryName(VkDevice device, VkDeviceMemory memory, const char * name)
{
setObjectName(device, (uint64_t)memory, VK_DEBUG_REPORT_OBJECT_TYPE_DEVICE_MEMORY_EXT, name);
}
void setShaderModuleName(VkDevice device, VkShaderModule shaderModule, const char * name)
{
setObjectName(device, (uint64_t)shaderModule, VK_DEBUG_REPORT_OBJECT_TYPE_SHADER_MODULE_EXT, name);
}
void setPipelineName(VkDevice device, VkPipeline pipeline, const char * name)
{
setObjectName(device, (uint64_t)pipeline, VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_EXT, name);
}
void setPipelineLayoutName(VkDevice device, VkPipelineLayout pipelineLayout, const char * name)
{
setObjectName(device, (uint64_t)pipelineLayout, VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_LAYOUT_EXT, name);
}
void setRenderPassName(VkDevice device, VkRenderPass renderPass, const char * name)
{
setObjectName(device, (uint64_t)renderPass, VK_DEBUG_REPORT_OBJECT_TYPE_RENDER_PASS_EXT, name);
}
void setFramebufferName(VkDevice device, VkFramebuffer framebuffer, const char * name)
{
setObjectName(device, (uint64_t)framebuffer, VK_DEBUG_REPORT_OBJECT_TYPE_FRAMEBUFFER_EXT, name);
}
void setDescriptorSetLayoutName(VkDevice device, VkDescriptorSetLayout descriptorSetLayout, const char * name)
{
setObjectName(device, (uint64_t)descriptorSetLayout, VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT_EXT, name);
}
void setDescriptorSetName(VkDevice device, VkDescriptorSet descriptorSet, const char * name)
{
setObjectName(device, (uint64_t)descriptorSet, VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_SET_EXT, name);
}
void setSemaphoreName(VkDevice device, VkSemaphore semaphore, const char * name)
{
setObjectName(device, (uint64_t)semaphore, VK_DEBUG_REPORT_OBJECT_TYPE_SEMAPHORE_EXT, name);
}
void setFenceName(VkDevice device, VkFence fence, const char * name)
{
setObjectName(device, (uint64_t)fence, VK_DEBUG_REPORT_OBJECT_TYPE_FENCE_EXT, name);
}
void setEventName(VkDevice device, VkEvent _event, const char * name)
{
setObjectName(device, (uint64_t)_event, VK_DEBUG_REPORT_OBJECT_TYPE_EVENT_EXT, name);
}
}
}
// vi: set sw=2 ts=4 expandtab:
@@ -0,0 +1,106 @@
/*
* Copyright 2017-2020 Mark Callow
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "vulkan/vulkan.h"
#include <math.h>
#include <stdlib.h>
#include <string>
#include <cstring>
#include <fstream>
#include <assert.h>
#include <stdio.h>
#include <vector>
#ifdef _WIN32
#include <windows.h>
#include <fcntl.h>
#include <io.h>
#endif
#define GLM_FORCE_RADIANS
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
#include "disable_glm_warnings.h"
#include <glm/glm.hpp>
#include "reenable_warnings.h"
namespace vkDebug
{
// Default validation layers
extern int validationLayerCount;
extern const char *validationLayerNames[];
// Default debug callback
VkBool32 messageCallback(
VkDebugReportFlagsEXT flags,
VkDebugReportObjectTypeEXT objType,
uint64_t srcObject,
size_t location,
int32_t msgCode,
const char* pLayerPrefix,
const char* pMsg,
void* pUserData);
// Load debug function pointers and set debug callback
// if callBack is NULL, default message callback will be used
void setupDebugging(
VkInstance instance,
VkDebugReportFlagsEXT flags,
VkDebugReportCallbackEXT callBack);
// Clear debug callback
void freeDebugCallback(VkInstance instance);
// Setup and functions for the VK_EXT_debug_marker_extension
// Extension spec can be found at https://github.com/KhronosGroup/Vulkan-Docs/blob/1.0-VK_EXT_debug_marker/doc/specs/vulkan/appendices/VK_EXT_debug_marker.txt
// Note that the extension will only be present if run from an offline debugging application
// The actual check for extension presence and enabling it on the device is done in the example base class
// See VulkanExampleBase::createInstance and VulkanExampleBase::createDevice (base/vulkanexamplebase.cpp)
namespace DebugMarker
{
// Set to true if function pointer for the debug marker are available
extern bool active;
// Get function pointers for the debug report extensions from the device
void setup(VkDevice device);
// Sets the debug name of an object
// All Objects in Vulkan are represented by their 64-bit handles which are passed into this function
// along with the object type
void setObjectName(VkDevice device, uint64_t object, VkDebugReportObjectTypeEXT objectType, const char *name);
// Set the tag for an object
void setObjectTag(VkDevice device, uint64_t object, VkDebugReportObjectTypeEXT objectType, uint64_t name, size_t tagSize, const void* tag);
// Start a new debug marker region
void beginRegion(VkCommandBuffer cmdbuffer, const char* pMarkerName, glm::vec4 color);
// Insert a new debug marker into the command buffer
void insert(VkCommandBuffer cmdbuffer, std::string markerName, glm::vec4 color);
// End the current debug marker region
void endRegion(VkCommandBuffer cmdBuffer);
// Object specific naming functions
void setCommandBufferName(VkDevice device, VkCommandBuffer cmdBuffer, const char * name);
void setQueueName(VkDevice device, VkQueue queue, const char * name);
void setImageName(VkDevice device, VkImage image, const char * name);
void setSamplerName(VkDevice device, VkSampler sampler, const char * name);
void setBufferName(VkDevice device, VkBuffer buffer, const char * name);
void setDeviceMemoryName(VkDevice device, VkDeviceMemory memory, const char * name);
void setShaderModuleName(VkDevice device, VkShaderModule shaderModule, const char * name);
void setPipelineName(VkDevice device, VkPipeline pipeline, const char * name);
void setPipelineLayoutName(VkDevice device, VkPipelineLayout pipelineLayout, const char * name);
void setRenderPassName(VkDevice device, VkRenderPass renderPass, const char * name);
void setFramebufferName(VkDevice device, VkFramebuffer framebuffer, const char * name);
void setDescriptorSetLayoutName(VkDevice device, VkDescriptorSetLayout descriptorSetLayout, const char * name);
void setDescriptorSetName(VkDevice device, VkDescriptorSet descriptorSet, const char * name);
void setSemaphoreName(VkDevice device, VkSemaphore semaphore, const char * name);
void setFenceName(VkDevice device, VkFence fence, const char * name);
void setEventName(VkDevice device, VkEvent _event, const char * name);
}
}
// vi: set sw=2 ts=4 expandtab:
@@ -0,0 +1,839 @@
/*
* Copyright 2016 Sascha Willems - www.saschawillems.de
* SPDX-License-Identifier: MIT
*
* Text overlay class for displaying debug information
*
*/
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <vector>
#include <sstream>
#include <iomanip>
#include <vulkan/vulkan.h>
#include "vulkantools.h"
#include "vulkandebug.h"
#include "stb/stb_font_consolas_24_latin1.inl"
// Defines for the STB font used
// STB font files can be found at http://nothings.org/stb/font/
#define STB_FONT_NAME stb_font_consolas_24_latin1
#define STB_FONT_WIDTH STB_FONT_consolas_24_latin1_BITMAP_WIDTH
#define STB_FONT_HEIGHT STB_FONT_consolas_24_latin1_BITMAP_HEIGHT
#define STB_FIRST_CHAR STB_FONT_consolas_24_latin1_FIRST_CHAR
#define STB_NUM_CHARS STB_FONT_consolas_24_latin1_NUM_CHARS
#define STB_MISSING_GLPYH 0x80 // Actually a control character.
// Max. number of chars the text overlay buffer can hold
#define MAX_CHAR_COUNT 1024
/**
* @internal
* @brief Given the lead byte of a UTF-8 sequence returns the expected length of the codepoint
* @param[in] leadByte The lead byte of a UTF-8 sequence
* @return The expected length of the codepoint */
[[nodiscard]] constexpr inline int sequenceLength(uint8_t leadByte) noexcept {
if ((leadByte & 0b1000'0000u) == 0b0000'0000u)
return 1;
if ((leadByte & 0b1110'0000u) == 0b1100'0000u)
return 2;
if ((leadByte & 0b1111'0000u) == 0b1110'0000u)
return 3;
if ((leadByte & 0b1111'1000u) == 0b1111'0000u)
return 4;
return 0;
}
/**
* @internal
* @brief Checks if the codepoint was coded as a longer than required sequence
* @param[in] codepoint The unicode codepoint
* @param[in] length The UTF-8 sequence length
* @return True if the sequence length was inappropriate for the given codepoint */
[[nodiscard]] constexpr inline bool isOverlongSequence(uint32_t codepoint, int length) noexcept {
if (codepoint < 0x80)
return length != 1;
else if (codepoint < 0x800)
return length != 2;
else if (codepoint < 0x10000)
return length != 3;
else
return false;
}
/**
* @internal
* @brief Checks if the codepoint is valid
* @param[in] codepoint The unicode codepoint
* @return True if the codepoint is a valid unicode codepoint */
[[nodiscard]] constexpr inline bool isCodepointValid(uint32_t codepoint) noexcept {
return codepoint <= 0x0010FFFFu
&& !(0xD800u <= codepoint && codepoint <= 0xDBFFu);
}
/**
* @internal
* @brief Safely checks and advances a UTF-8 sequence iterator to the start of the next unicode codepoint
* @param[in] it iterator to be advanced
* @param[in] end iterator pointing to the end of the range
* @return True if the advance operation was successful and the advanced codepoint was a valid UTF-8 sequence */
template <typename Iterator>
[[nodiscard]] constexpr bool advanceUTF8(Iterator& it, Iterator end,
uint32_t& codepoint) noexcept {
if (it == end)
return false;
const auto length = sequenceLength(*it);
if (length == 0)
return false;
if (std::distance(it, end) < length)
return false;
for (int i = 1; i < length; ++i) {
const auto trailByte = *(it + i);
if ((static_cast<uint8_t>(trailByte) & 0b1100'0000u) != 0b1000'0000u)
return false;
}
codepoint = 0;
switch (length) {
case 1:
codepoint |= *it++;
break;
case 2:
codepoint |= (*it++ & 0b0001'1111u) << 6u;
codepoint |= (*it++ & 0b0011'1111u);
break;
case 3:
codepoint |= (*it++ & 0b0000'1111u) << 12u;
codepoint |= (*it++ & 0b0011'1111u) << 6u;
codepoint |= (*it++ & 0b0011'1111u);
break;
case 4:
codepoint |= (*it++ & 0b0000'0111u) << 18u;
codepoint |= (*it++ & 0b0011'1111u) << 12u;
codepoint |= (*it++ & 0b0011'1111u) << 6u;
codepoint |= (*it++ & 0b0011'1111u);
break;
}
if (!isCodepointValid(codepoint))
return false;
if (isOverlongSequence(codepoint, length))
return false;
return true;
}
// Mostly self-contained text overlay class
// todo : comment
class VulkanTextOverlay
{
private:
VkPhysicalDevice physicalDevice;
VkDevice device;
VkPhysicalDeviceMemoryProperties deviceMemoryProperties;
VkQueue queue;
VkFormat colorFormat;
VkFormat depthFormat;
uint32_t *frameBufferWidth;
uint32_t *frameBufferHeight;
VkSampler sampler;
VkImage image;
VkImageView view;
VkBuffer buffer;
VkDeviceMemory memory;
VkDeviceMemory imageMemory;
VkDescriptorPool descriptorPool;
VkDescriptorSetLayout descriptorSetLayout;
VkDescriptorSet descriptorSet;
VkPipelineLayout pipelineLayout;
VkPipelineCache pipelineCache;
VkPipeline pipeline;
VkRenderPass renderPass;
VkCommandPool commandPool;
std::vector<VkFramebuffer*> frameBuffers;
std::vector<VkPipelineShaderStageCreateInfo> shaderStages;
// Pointer to mapped vertex buffer
glm::vec4 *mapped = nullptr;
// Used during text updates
glm::vec4 *mappedLocal = nullptr;
stb_fontchar stbFontData[STB_NUM_CHARS];
uint32_t numLetters;
// Try to find appropriate memory type for a memory allocation
uint32_t getMemoryType(uint32_t typeBits, VkFlags properties)
{
for (uint32_t i = 0; i < 32; i++)
{
if ((typeBits & 1) == 1)
{
if ((deviceMemoryProperties.memoryTypes[i].propertyFlags & properties) == properties)
{
return i;
}
}
typeBits >>= 1;
}
// todo : throw error
return 0;
}
public:
enum TextAlign { alignLeft, alignCenter, alignRight };
bool visible = true;
bool invalidated = false;
std::vector<VkCommandBuffer> cmdBuffers;
VulkanTextOverlay(
VkPhysicalDevice physicalDevice,
VkDevice device,
VkQueue queue,
std::vector<VkFramebuffer> &framebuffers,
VkFormat colorformat,
VkFormat depthformat,
uint32_t *framebufferwidth,
uint32_t *framebufferheight,
std::vector<VkPipelineShaderStageCreateInfo> shaderstages)
{
this->physicalDevice = physicalDevice;
this->device = device;
this->queue = queue;
this->colorFormat = colorformat;
this->depthFormat = depthformat;
this->frameBuffers.resize(framebuffers.size());
for (uint32_t i = 0; i < framebuffers.size(); i++)
{
this->frameBuffers[i] = &framebuffers[i];
}
this->shaderStages = shaderstages;
this->frameBufferWidth = framebufferwidth;
this->frameBufferHeight = framebufferheight;
vkGetPhysicalDeviceMemoryProperties(physicalDevice, &deviceMemoryProperties);
cmdBuffers.resize(framebuffers.size());
prepareResources();
prepareRenderPass();
preparePipeline();
}
~VulkanTextOverlay()
{
// Free up all Vulkan resources requested by the text overlay
vkDestroySampler(device, sampler, nullptr);
vkDestroyImage(device, image, nullptr);
vkDestroyImageView(device, view, nullptr);
vkDestroyBuffer(device, buffer, nullptr);
vkFreeMemory(device, memory, nullptr);
vkFreeMemory(device, imageMemory, nullptr);
vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
vkDestroyDescriptorPool(device, descriptorPool, nullptr);
vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
vkDestroyPipelineCache(device, pipelineCache, nullptr);
vkDestroyPipeline(device, pipeline, nullptr);
vkDestroyRenderPass(device, renderPass, nullptr);
vkFreeCommandBuffers(device, commandPool, static_cast<uint32_t>(cmdBuffers.size()), cmdBuffers.data());
vkDestroyCommandPool(device, commandPool, nullptr);
}
// Prepare all vulkan resources required to render the font
// The text overlay uses separate resources for descriptors (pool, sets, layouts), pipelines and command buffers
void prepareResources()
{
static unsigned char font24pixels[STB_FONT_HEIGHT][STB_FONT_WIDTH];
STB_FONT_NAME(stbFontData, font24pixels, STB_FONT_HEIGHT);
// Command buffer
// Pool
VkCommandPoolCreateInfo cmdPoolInfo = {};
cmdPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
cmdPoolInfo.queueFamilyIndex = 0; // todo : pass from example base / swap chain
cmdPoolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
VK_CHECK_RESULT(vkCreateCommandPool(device, &cmdPoolInfo, nullptr, &commandPool));
VkCommandBufferAllocateInfo cmdBufAllocateInfo =
vkTools::initializers::commandBufferAllocateInfo(
commandPool,
VK_COMMAND_BUFFER_LEVEL_PRIMARY,
(uint32_t)cmdBuffers.size());
VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, cmdBuffers.data()));
// Vertex buffer, 4 per character.
VkDeviceSize bufferSize = MAX_CHAR_COUNT * sizeof(glm::vec4) * 4;
VkBufferCreateInfo bufferInfo = vkTools::initializers::bufferCreateInfo(VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, bufferSize);
VK_CHECK_RESULT(vkCreateBuffer(device, &bufferInfo, nullptr, &buffer));
VkMemoryRequirements memReqs;
VkMemoryAllocateInfo allocInfo = vkTools::initializers::memoryAllocateInfo();
vkGetBufferMemoryRequirements(device, buffer, &memReqs);
allocInfo.allocationSize = memReqs.size;
allocInfo.memoryTypeIndex = getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
VK_CHECK_RESULT(vkAllocateMemory(device, &allocInfo, nullptr, &memory));
VK_CHECK_RESULT(vkBindBufferMemory(device, buffer, memory, 0));
// Map persistent
VK_CHECK_RESULT(vkMapMemory(device, memory, 0, VK_WHOLE_SIZE, 0, (void **)&mapped));
// Font texture
VkImageCreateInfo imageInfo = vkTools::initializers::imageCreateInfo();
imageInfo.imageType = VK_IMAGE_TYPE_2D;
imageInfo.format = VK_FORMAT_R8_UNORM;
imageInfo.extent.width = STB_FONT_WIDTH;
imageInfo.extent.height = STB_FONT_HEIGHT;
imageInfo.extent.depth = 1;
imageInfo.mipLevels = 1;
imageInfo.arrayLayers = 1;
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
VK_CHECK_RESULT(vkCreateImage(device, &imageInfo, nullptr, &image));
vkGetImageMemoryRequirements(device, image, &memReqs);
allocInfo.allocationSize = memReqs.size;
allocInfo.memoryTypeIndex = getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
VK_CHECK_RESULT(vkAllocateMemory(device, &allocInfo, nullptr, &imageMemory));
VK_CHECK_RESULT(vkBindImageMemory(device, image, imageMemory, 0));
// Staging
struct {
VkDeviceMemory memory;
VkBuffer buffer;
} stagingBuffer;
VkBufferCreateInfo bufferCreateInfo = vkTools::initializers::bufferCreateInfo();
bufferCreateInfo.size = allocInfo.allocationSize;
bufferCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
VK_CHECK_RESULT(vkCreateBuffer(device, &bufferCreateInfo, nullptr, &stagingBuffer.buffer));
// Get memory requirements for the staging buffer (alignment, memory type bits)
vkGetBufferMemoryRequirements(device, stagingBuffer.buffer, &memReqs);
allocInfo.allocationSize = memReqs.size;
// Get memory type index for a host visible buffer
allocInfo.memoryTypeIndex = getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);
VK_CHECK_RESULT(vkAllocateMemory(device, &allocInfo, nullptr, &stagingBuffer.memory));
VK_CHECK_RESULT(vkBindBufferMemory(device, stagingBuffer.buffer, stagingBuffer.memory, 0));
uint8_t *data;
VK_CHECK_RESULT(vkMapMemory(device, stagingBuffer.memory, 0, allocInfo.allocationSize, 0, (void **)&data));
memcpy(data, &font24pixels[0][0], STB_FONT_WIDTH * STB_FONT_HEIGHT);
vkUnmapMemory(device, stagingBuffer.memory);
// Copy to image
VkCommandBuffer copyCmd;
cmdBufAllocateInfo.commandBufferCount = 1;
VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, &copyCmd));
VkCommandBufferBeginInfo cmdBufInfo = vkTools::initializers::commandBufferBeginInfo();
VK_CHECK_RESULT(vkBeginCommandBuffer(copyCmd, &cmdBufInfo));
// Prepare for transfer
vkTools::setImageLayout(
copyCmd,
image,
VK_IMAGE_ASPECT_COLOR_BIT,
VK_IMAGE_LAYOUT_PREINITIALIZED,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
VkBufferImageCopy bufferCopyRegion = {};
bufferCopyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
bufferCopyRegion.imageSubresource.mipLevel = 0;
bufferCopyRegion.imageSubresource.layerCount = 1;
bufferCopyRegion.imageExtent.width = STB_FONT_WIDTH;
bufferCopyRegion.imageExtent.height = STB_FONT_HEIGHT;
bufferCopyRegion.imageExtent.depth = 1;
vkCmdCopyBufferToImage(
copyCmd,
stagingBuffer.buffer,
image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
1,
&bufferCopyRegion
);
// Prepare for shader read
vkTools::setImageLayout(
copyCmd,
image,
VK_IMAGE_ASPECT_COLOR_BIT,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
VK_CHECK_RESULT(vkEndCommandBuffer(copyCmd));
VkSubmitInfo submitInfo = vkTools::initializers::submitInfo();
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &copyCmd;
VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
VK_CHECK_RESULT(vkQueueWaitIdle(queue));
vkFreeCommandBuffers(device, commandPool, 1, &copyCmd);
vkFreeMemory(device, stagingBuffer.memory, nullptr);
vkDestroyBuffer(device, stagingBuffer.buffer, nullptr);
VkImageViewCreateInfo imageViewInfo = vkTools::initializers::imageViewCreateInfo();
imageViewInfo.image = image;
imageViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
imageViewInfo.format = imageInfo.format;
imageViewInfo.components = {
VK_COMPONENT_SWIZZLE_IDENTITY,
VK_COMPONENT_SWIZZLE_IDENTITY,
VK_COMPONENT_SWIZZLE_IDENTITY,
VK_COMPONENT_SWIZZLE_IDENTITY
};
imageViewInfo.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 };
VK_CHECK_RESULT(vkCreateImageView(device, &imageViewInfo, nullptr, &view));
// Sampler
VkSamplerCreateInfo samplerInfo = vkTools::initializers::samplerCreateInfo();
samplerInfo.magFilter = VK_FILTER_LINEAR;
samplerInfo.minFilter = VK_FILTER_LINEAR;
samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
samplerInfo.mipLodBias = 0.0f;
samplerInfo.compareOp = VK_COMPARE_OP_NEVER;
samplerInfo.minLod = 0.0f;
samplerInfo.maxLod = 1.0f;
samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
samplerInfo.anisotropyEnable = VK_FALSE;
samplerInfo.maxAnisotropy = 1.0;
VK_CHECK_RESULT(vkCreateSampler(device, &samplerInfo, nullptr, &sampler));
// Descriptor
// Font uses a separate descriptor pool
std::array<VkDescriptorPoolSize, 1> poolSizes;
poolSizes[0] = vkTools::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1);
VkDescriptorPoolCreateInfo descriptorPoolInfo =
vkTools::initializers::descriptorPoolCreateInfo(
static_cast<uint32_t>(poolSizes.size()),
poolSizes.data(),
1);
VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
// Descriptor set layout
std::array<VkDescriptorSetLayoutBinding, 1> setLayoutBindings;
setLayoutBindings[0] = vkTools::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0);
VkDescriptorSetLayoutCreateInfo descriptorSetLayoutInfo =
vkTools::initializers::descriptorSetLayoutCreateInfo(
setLayoutBindings.data(),
static_cast<uint32_t>(setLayoutBindings.size()));
VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorSetLayoutInfo, nullptr, &descriptorSetLayout));
// Pipeline layout
VkPipelineLayoutCreateInfo pipelineLayoutInfo =
vkTools::initializers::pipelineLayoutCreateInfo(
&descriptorSetLayout,
1);
VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout));
// Descriptor set
VkDescriptorSetAllocateInfo descriptorSetAllocInfo =
vkTools::initializers::descriptorSetAllocateInfo(
descriptorPool,
&descriptorSetLayout,
1);
VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &descriptorSetAllocInfo, &descriptorSet));
VkDescriptorImageInfo texDescriptor =
vkTools::initializers::descriptorImageInfo(
sampler,
view,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
std::array<VkWriteDescriptorSet, 1> writeDescriptorSets;
writeDescriptorSets[0] = vkTools::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &texDescriptor);
vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL);
// Pipeline cache
VkPipelineCacheCreateInfo pipelineCacheCreateInfo = {};
pipelineCacheCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO;
VK_CHECK_RESULT(vkCreatePipelineCache(device, &pipelineCacheCreateInfo, nullptr, &pipelineCache));
}
// Prepare a separate pipeline for the font rendering decoupled from the main application
void preparePipeline()
{
VkPipelineInputAssemblyStateCreateInfo inputAssemblyState =
vkTools::initializers::pipelineInputAssemblyStateCreateInfo(
VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP,
0,
// primmitiveRestartEnable not needed but disabling it results in a MoltenVK
// feature not present warning.
VK_TRUE);
VkPipelineRasterizationStateCreateInfo rasterizationState =
vkTools::initializers::pipelineRasterizationStateCreateInfo(
VK_POLYGON_MODE_FILL,
VK_CULL_MODE_BACK_BIT,
VK_FRONT_FACE_CLOCKWISE,
0);
// Because we haven't enabled the depthClamp device feature.
rasterizationState.depthClampEnable = VK_FALSE;
// Enable blending
VkPipelineColorBlendAttachmentState blendAttachmentState =
vkTools::initializers::pipelineColorBlendAttachmentState(0xf, VK_TRUE);
blendAttachmentState.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
blendAttachmentState.dstColorBlendFactor = VK_BLEND_FACTOR_ONE;
blendAttachmentState.colorBlendOp = VK_BLEND_OP_ADD;
blendAttachmentState.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
blendAttachmentState.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
blendAttachmentState.alphaBlendOp = VK_BLEND_OP_ADD;
blendAttachmentState.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
VkPipelineColorBlendStateCreateInfo colorBlendState =
vkTools::initializers::pipelineColorBlendStateCreateInfo(
1,
&blendAttachmentState);
VkPipelineDepthStencilStateCreateInfo depthStencilState =
vkTools::initializers::pipelineDepthStencilStateCreateInfo(
VK_FALSE,
VK_FALSE,
VK_COMPARE_OP_LESS_OR_EQUAL);
VkPipelineViewportStateCreateInfo viewportState =
vkTools::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
VkPipelineMultisampleStateCreateInfo multisampleState =
vkTools::initializers::pipelineMultisampleStateCreateInfo(
VK_SAMPLE_COUNT_1_BIT,
0);
std::vector<VkDynamicState> dynamicStateEnables = {
VK_DYNAMIC_STATE_VIEWPORT,
VK_DYNAMIC_STATE_SCISSOR
};
VkPipelineDynamicStateCreateInfo dynamicState =
vkTools::initializers::pipelineDynamicStateCreateInfo(
dynamicStateEnables.data(),
static_cast<uint32_t>(dynamicStateEnables.size()),
0);
std::array<VkVertexInputBindingDescription, 2> vertexBindings = {};
vertexBindings[0] = vkTools::initializers::vertexInputBindingDescription(0, sizeof(glm::vec4), VK_VERTEX_INPUT_RATE_VERTEX);
vertexBindings[1] = vkTools::initializers::vertexInputBindingDescription(1, sizeof(glm::vec4), VK_VERTEX_INPUT_RATE_VERTEX);
std::array<VkVertexInputAttributeDescription, 2> vertexAttribs = {};
// Position
vertexAttribs[0] = vkTools::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32_SFLOAT, 0);
// UV
vertexAttribs[1] = vkTools::initializers::vertexInputAttributeDescription(1, 1, VK_FORMAT_R32G32_SFLOAT, sizeof(glm::vec2));
VkPipelineVertexInputStateCreateInfo inputState = vkTools::initializers::pipelineVertexInputStateCreateInfo();
inputState.vertexBindingDescriptionCount = static_cast<uint32_t>(vertexBindings.size());
inputState.pVertexBindingDescriptions = vertexBindings.data();
inputState.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertexAttribs.size());
inputState.pVertexAttributeDescriptions = vertexAttribs.data();
VkGraphicsPipelineCreateInfo pipelineCreateInfo =
vkTools::initializers::pipelineCreateInfo(
pipelineLayout,
renderPass,
0);
pipelineCreateInfo.pVertexInputState = &inputState;
pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState;
pipelineCreateInfo.pRasterizationState = &rasterizationState;
pipelineCreateInfo.pColorBlendState = &colorBlendState;
pipelineCreateInfo.pMultisampleState = &multisampleState;
pipelineCreateInfo.pViewportState = &viewportState;
pipelineCreateInfo.pDepthStencilState = &depthStencilState;
pipelineCreateInfo.pDynamicState = &dynamicState;
pipelineCreateInfo.stageCount = static_cast<uint32_t>(shaderStages.size());
pipelineCreateInfo.pStages = shaderStages.data();
VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipeline));
}
// Prepare a separate render pass for rendering the text as an overlay
void prepareRenderPass()
{
VkAttachmentDescription attachments[2] = {};
// Color attachment
attachments[0].format = colorFormat;
attachments[0].samples = VK_SAMPLE_COUNT_1_BIT;
// Don't clear the framebuffer (like the renderpass from the example does)
attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
attachments[0].initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
attachments[0].finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
// Depth attachment
attachments[1].format = depthFormat;
attachments[1].samples = VK_SAMPLE_COUNT_1_BIT;
attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
attachments[1].initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
VkAttachmentReference colorReference = {};
colorReference.attachment = 0;
colorReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
VkAttachmentReference depthReference = {};
depthReference.attachment = 1;
depthReference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
VkSubpassDescription subpass = {};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass.flags = 0;
subpass.inputAttachmentCount = 0;
subpass.pInputAttachments = NULL;
subpass.colorAttachmentCount = 1;
subpass.pColorAttachments = &colorReference;
subpass.pResolveAttachments = NULL;
subpass.pDepthStencilAttachment = &depthReference;
subpass.preserveAttachmentCount = 0;
subpass.pPreserveAttachments = NULL;
VkRenderPassCreateInfo renderPassInfo = {};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
renderPassInfo.pNext = NULL;
renderPassInfo.attachmentCount = 2;
renderPassInfo.pAttachments = attachments;
renderPassInfo.subpassCount = 1;
renderPassInfo.pSubpasses = &subpass;
renderPassInfo.dependencyCount = 0;
renderPassInfo.pDependencies = NULL;
VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass));
}
// Map buffer
void beginTextUpdate()
{
mappedLocal = mapped;
numLetters = 0;
}
// Add text to the current buffer
// todo : drop shadow? color attribute?
void addText(std::string text, float x, float y, TextAlign align)
{
if (numLetters == MAX_CHAR_COUNT)
return;
assert(mapped != nullptr);
const float charW = 1.5f / *frameBufferWidth;
const float charH = 1.5f / *frameBufferHeight;
float fbW = (float)*frameBufferWidth;
float fbH = (float)*frameBufferHeight;
x = (x / fbW * 2.0f) - 1.0f;
y = (y / fbH * 2.0f) - 1.0f;
// Calculate text width
float textWidth = 0;
uint32_t codepoint;
auto it = text.begin();
while (it != text.end())
{
if (!advanceUTF8(it, text.end(), codepoint))
break;
// TODO: Get a UTF8 font. Consider changing to Dear ImGUI
// https://github.com/ocornut/imgui.
// Placeholder to avoid crashing.
if (codepoint > STB_NUM_CHARS + STB_FIRST_CHAR)
codepoint = STB_MISSING_GLPYH;
stb_fontchar *charData = &stbFontData[(uint32_t)codepoint - STB_FIRST_CHAR];
textWidth += charData->advance * charW;
}
switch (align)
{
case alignRight:
x -= textWidth;
break;
case alignCenter:
x -= textWidth / 2.0f;
break;
case alignLeft:
break;
}
// Generate a uv mapped quad per char in the new text
it = text.begin();
while (it != text.end())
{
if (!advanceUTF8(it, text.end(), codepoint))
break;
if (codepoint > STB_NUM_CHARS + STB_FIRST_CHAR)
codepoint = STB_MISSING_GLPYH;
stb_fontchar *charData = &stbFontData[(uint32_t)codepoint - STB_FIRST_CHAR];
mappedLocal->x = (x + (float)charData->x0 * charW);
mappedLocal->y = (y + (float)charData->y0 * charH);
mappedLocal->z = charData->s0;
mappedLocal->w = charData->t0;
mappedLocal++;
mappedLocal->x = (x + (float)charData->x1 * charW);
mappedLocal->y = (y + (float)charData->y0 * charH);
mappedLocal->z = charData->s1;
mappedLocal->w = charData->t0;
mappedLocal++;
mappedLocal->x = (x + (float)charData->x0 * charW);
mappedLocal->y = (y + (float)charData->y1 * charH);
mappedLocal->z = charData->s0;
mappedLocal->w = charData->t1;
mappedLocal++;
mappedLocal->x = (x + (float)charData->x1 * charW);
mappedLocal->y = (y + (float)charData->y1 * charH);
mappedLocal->z = charData->s1;
mappedLocal->w = charData->t1;
mappedLocal++;
x += charData->advance * charW;
numLetters++;
if (numLetters == MAX_CHAR_COUNT)
break; // Truncate the text.
}
}
// Unmap buffer and update command buffers
void endTextUpdate()
{
updateCommandBuffers();
}
// Needs to be called by the application
void updateCommandBuffers()
{
VkCommandBufferBeginInfo cmdBufInfo = vkTools::initializers::commandBufferBeginInfo();
VkRenderPassBeginInfo renderPassBeginInfo = vkTools::initializers::renderPassBeginInfo();
renderPassBeginInfo.renderPass = renderPass;
renderPassBeginInfo.renderArea.extent.width = *frameBufferWidth;
renderPassBeginInfo.renderArea.extent.height = *frameBufferHeight;
renderPassBeginInfo.clearValueCount = 0;
renderPassBeginInfo.pClearValues = nullptr;
for (uint32_t i = 0; i < cmdBuffers.size(); ++i)
{
renderPassBeginInfo.framebuffer = *frameBuffers[i];
VK_CHECK_RESULT(vkBeginCommandBuffer(cmdBuffers[i], &cmdBufInfo));
if (vkDebug::DebugMarker::active)
{
vkDebug::DebugMarker::beginRegion(cmdBuffers[i], "Text overlay", glm::vec4(1.0f, 0.94f, 0.3f, 1.0f));
}
vkCmdBeginRenderPass(cmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
VkViewport viewport = vkTools::initializers::viewport((float)*frameBufferWidth, (float)*frameBufferHeight, 0.0f, 1.0f);
vkCmdSetViewport(cmdBuffers[i], 0, 1, &viewport);
VkRect2D scissor = vkTools::initializers::rect2D(*frameBufferWidth, *frameBufferHeight, 0, 0);
vkCmdSetScissor(cmdBuffers[i], 0, 1, &scissor);
vkCmdBindPipeline(cmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
vkCmdBindDescriptorSets(cmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL);
VkDeviceSize offsets = 0;
vkCmdBindVertexBuffers(cmdBuffers[i], 0, 1, &buffer, &offsets);
vkCmdBindVertexBuffers(cmdBuffers[i], 1, 1, &buffer, &offsets);
for (uint32_t j = 0; j < numLetters; j++)
{
vkCmdDraw(cmdBuffers[i], 4, 1, j * 4, 0);
}
vkCmdEndRenderPass(cmdBuffers[i]);
if (vkDebug::DebugMarker::active)
{
vkDebug::DebugMarker::endRegion(cmdBuffers[i]);
}
VK_CHECK_RESULT(vkEndCommandBuffer(cmdBuffers[i]));
}
}
// Submit the text command buffers to a queue
void submit(VkQueue targetQueue, uint32_t bufferindex, VkSubmitInfo submitInfo)
{
if (!visible)
{
return;
}
submitInfo.pCommandBuffers = &cmdBuffers[bufferindex];
submitInfo.commandBufferCount = 1;
VK_CHECK_RESULT(vkQueueSubmit(targetQueue, 1, &submitInfo, VK_NULL_HANDLE));
}
void reallocateCommandBuffers()
{
vkFreeCommandBuffers(device, commandPool, static_cast<uint32_t>(cmdBuffers.size()), cmdBuffers.data());
VkCommandBufferAllocateInfo cmdBufAllocateInfo =
vkTools::initializers::commandBufferAllocateInfo(
commandPool,
VK_COMMAND_BUFFER_LEVEL_PRIMARY,
static_cast<uint32_t>(cmdBuffers.size()));
VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, cmdBuffers.data()));
}
};
// vi: set sw=2 ts=4 expandtab:
@@ -0,0 +1,918 @@
/*
* Copyright 2016 Sascha Willems - www.saschawillems.de
* SPDX-License-Identifier: MIT
*
* Assorted commonly used Vulkan helper functions
*
* asserts on unhandled cases in setImageLayout added by Mark Callow, 2017.3.3.
*/
#if defined(_WIN32)
#define _CRT_SECURE_NO_WARNINGS // For fopen
#endif
#include "vulkantools.h"
#include "unused.h"
namespace vkTools
{
VkBool32 checkGlobalExtensionPresent(const char* extensionName)
{
uint32_t extensionCount = 0;
std::vector<VkExtensionProperties> extensions;
vkEnumerateInstanceExtensionProperties(NULL, &extensionCount, NULL);
extensions.resize(extensionCount);
vkEnumerateInstanceExtensionProperties(NULL, &extensionCount, extensions.data());
for (auto& ext : extensions)
{
if (!strcmp(extensionName, ext.extensionName))
{
return true;
}
}
return false;
}
VkBool32 checkDeviceExtensionPresent(VkPhysicalDevice physicalDevice, const char* extensionName)
{
uint32_t extensionCount = 0;
std::vector<VkExtensionProperties> extensions;
vkEnumerateDeviceExtensionProperties(physicalDevice, NULL, &extensionCount, NULL);
extensions.resize(extensionCount);
vkEnumerateDeviceExtensionProperties(physicalDevice, NULL, &extensionCount, extensions.data());
for (auto& ext : extensions)
{
if (!strcmp(extensionName, ext.extensionName))
{
return true;
}
}
return false;
}
std::string errorString(VkResult errorCode)
{
switch (errorCode)
{
#define STR(r) case VK_ ##r: return #r
STR(NOT_READY);
STR(TIMEOUT);
STR(EVENT_SET);
STR(EVENT_RESET);
STR(INCOMPLETE);
STR(ERROR_OUT_OF_HOST_MEMORY);
STR(ERROR_OUT_OF_DEVICE_MEMORY);
STR(ERROR_INITIALIZATION_FAILED);
STR(ERROR_DEVICE_LOST);
STR(ERROR_MEMORY_MAP_FAILED);
STR(ERROR_LAYER_NOT_PRESENT);
STR(ERROR_EXTENSION_NOT_PRESENT);
STR(ERROR_FEATURE_NOT_PRESENT);
STR(ERROR_INCOMPATIBLE_DRIVER);
STR(ERROR_TOO_MANY_OBJECTS);
STR(ERROR_FORMAT_NOT_SUPPORTED);
STR(ERROR_SURFACE_LOST_KHR);
STR(ERROR_NATIVE_WINDOW_IN_USE_KHR);
STR(SUBOPTIMAL_KHR);
STR(ERROR_OUT_OF_DATE_KHR);
STR(ERROR_INCOMPATIBLE_DISPLAY_KHR);
STR(ERROR_VALIDATION_FAILED_EXT);
STR(ERROR_INVALID_SHADER_NV);
#undef STR
default:
return "UNKNOWN_ERROR";
}
}
VkBool32 getSupportedDepthFormat(VkPhysicalDevice physicalDevice, VkFormat *depthFormat)
{
// Since all depth formats may be optional, we need to find a suitable depth format to use
// Start with the highest precision packed format
std::vector<VkFormat> depthFormats = {
VK_FORMAT_D32_SFLOAT_S8_UINT,
VK_FORMAT_D32_SFLOAT,
VK_FORMAT_D24_UNORM_S8_UINT,
VK_FORMAT_D16_UNORM_S8_UINT,
VK_FORMAT_D16_UNORM
};
for (auto& format : depthFormats)
{
VkFormatProperties formatProps;
vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &formatProps);
// Format must support depth stencil attachment for optimal tiling
if (formatProps.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT)
{
*depthFormat = format;
return true;
}
}
return false;
}
// Create an image memory barrier for changing the layout of
// an image and put it into an active command buffer
// See chapter 11.4 "Image Layout" for details
void setImageLayout(
VkCommandBuffer cmdbuffer,
VkImage image,
VkImageAspectFlags /*aspectMask*/,
VkImageLayout oldImageLayout,
VkImageLayout newImageLayout,
VkImageSubresourceRange subresourceRange)
{
// Create an image barrier object
VkImageMemoryBarrier imageMemoryBarrier = vkTools::initializers::imageMemoryBarrier();
imageMemoryBarrier.oldLayout = oldImageLayout;
imageMemoryBarrier.newLayout = newImageLayout;
imageMemoryBarrier.image = image;
imageMemoryBarrier.subresourceRange = subresourceRange;
// Source layouts (old)
// Source access mask controls actions that have to be finished on the old layout
// before it will be transitioned to the new layout
switch (oldImageLayout)
{
case VK_IMAGE_LAYOUT_UNDEFINED:
// Image layout is undefined (or does not matter)
// Only valid as initial layout
// No flags required, listed only for completeness
imageMemoryBarrier.srcAccessMask = 0;
break;
case VK_IMAGE_LAYOUT_PREINITIALIZED:
// Image is preinitialized
// Only valid as initial layout for linear images, preserves memory contents
// Make sure host writes have been finished
imageMemoryBarrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT;
break;
case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
// Image is a color attachment
// Make sure any writes to the color buffer have been finished
imageMemoryBarrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
break;
case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL:
// Image is a depth/stencil attachment
// Make sure any writes to the depth/stencil buffer have been finished
imageMemoryBarrier.srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
break;
case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL:
// Image is a transfer source
// Make sure any reads from the image have been finished
imageMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
break;
case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
// Image is a transfer destination
// Make sure any writes to the image have been finished
imageMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
break;
case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
// Image is read by a shader
// Make sure any shader reads from the image have been finished
imageMemoryBarrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
break;
default:
assert(0); // Attempt to use unhandled case.
}
// Target layouts (new)
// Destination access mask controls the dependency for the new image layout
switch (newImageLayout)
{
case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
// Image will be used as a transfer destination
// Make sure any writes to the image have been finished
imageMemoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
break;
case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL:
// Image will be used as a transfer source
// Make sure any reads from and writes to the image have been finished
imageMemoryBarrier.srcAccessMask = imageMemoryBarrier.srcAccessMask | VK_ACCESS_TRANSFER_READ_BIT;
imageMemoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
break;
case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
// Image will be used as a color attachment
// Make sure any writes to the color buffer have been finished
imageMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
imageMemoryBarrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
break;
case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL:
// Image layout will be used as a depth/stencil attachment
// Make sure any writes to depth/stencil buffer have been finished
imageMemoryBarrier.dstAccessMask = imageMemoryBarrier.dstAccessMask | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
break;
case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
// Image will be read in a shader (sampler, input attachment)
// Make sure any writes to the image have been finished
if (imageMemoryBarrier.srcAccessMask == 0)
{
imageMemoryBarrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT;
}
imageMemoryBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
break;
default:
assert(0); // Attempt to use unhandled case.
}
// Put barrier on top
VkPipelineStageFlags srcStageFlags = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
VkPipelineStageFlags destStageFlags = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
// Put barrier inside setup command buffer
vkCmdPipelineBarrier(
cmdbuffer,
srcStageFlags,
destStageFlags,
0,
0, nullptr,
0, nullptr,
1, &imageMemoryBarrier);
}
// Fixed sub resource on first mip level and layer
void setImageLayout(
VkCommandBuffer cmdbuffer,
VkImage image,
VkImageAspectFlags aspectMask,
VkImageLayout oldImageLayout,
VkImageLayout newImageLayout)
{
VkImageSubresourceRange subresourceRange = {};
subresourceRange.aspectMask = aspectMask;
subresourceRange.baseMipLevel = 0;
subresourceRange.levelCount = 1;
subresourceRange.layerCount = 1;
setImageLayout(cmdbuffer, image, aspectMask, oldImageLayout, newImageLayout, subresourceRange);
}
void exitFatal(std::string message, std::string caption)
{
(void)SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, caption.c_str(),
message.c_str(), NULL);
std::cerr << message << "\n";
exit(1);
}
std::string readTextFile(const char *fileName)
{
std::string fileContent;
std::ifstream fileStream(fileName, std::ios::in);
if (!fileStream.is_open()) {
printf("File %s not found\n", fileName);
return "";
}
std::string line = "";
while (!fileStream.eof()) {
getline(fileStream, line);
fileContent.append(line + "\n");
}
fileStream.close();
return fileContent;
}
#if defined(__ANDROID__)
// Android shaders are stored as assets in the apk
// So they need to be loaded via the asset manager
VkShaderModule loadShader(AAssetManager* assetManager, const char *fileName, VkDevice device, VkShaderStageFlagBits stage)
{
// Load shader from compressed asset
AAsset* asset = AAssetManager_open(assetManager, fileName, AASSET_MODE_STREAMING);
assert(asset);
size_t size = AAsset_getLength(asset);
assert(size > 0);
char *shaderCode = new char[size];
AAsset_read(asset, shaderCode, size);
AAsset_close(asset);
VkShaderModule shaderModule;
VkShaderModuleCreateInfo moduleCreateInfo;
moduleCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
moduleCreateInfo.pNext = NULL;
moduleCreateInfo.codeSize = size;
moduleCreateInfo.pCode = (uint32_t*)shaderCode;
moduleCreateInfo.flags = 0;
VK_CHECK_RESULT(vkCreateShaderModule(device, &moduleCreateInfo, NULL, &shaderModule));
delete[] shaderCode;
return shaderModule;
}
#else
VkShaderModule loadShader(const char *fileName, VkDevice device, VkShaderStageFlagBits /*stage*/)
{
size_t size;
FILE *fp = fopen(fileName, "rb");
assert(fp);
fseek(fp, 0L, SEEK_END);
size = ftell(fp);
fseek(fp, 0L, SEEK_SET);
//shaderCode = malloc(size);
char *shaderCode = new char[size];
U_ASSERT_ONLY size_t retval = fread(shaderCode, size, 1, fp);
assert(retval == 1);
(void)retval; // Supress VC++ unused variable warning for Release config.
assert(size > 0);
fclose(fp);
VkShaderModule shaderModule;
VkShaderModuleCreateInfo moduleCreateInfo;
moduleCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
moduleCreateInfo.pNext = NULL;
moduleCreateInfo.codeSize = size;
moduleCreateInfo.pCode = (uint32_t*)shaderCode;
moduleCreateInfo.flags = 0;
VK_CHECK_RESULT(vkCreateShaderModule(device, &moduleCreateInfo, NULL, &shaderModule));
delete[] shaderCode;
return shaderModule;
}
#endif
VkShaderModule loadShaderGLSL(const char *fileName, VkDevice device, VkShaderStageFlagBits stage)
{
std::string shaderSrc = readTextFile(fileName);
const char *shaderCode = shaderSrc.c_str();
size_t size = strlen(shaderCode);
assert(size > 0);
VkShaderModule shaderModule;
VkShaderModuleCreateInfo moduleCreateInfo;
moduleCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
moduleCreateInfo.pNext = NULL;
moduleCreateInfo.codeSize = 3 * sizeof(uint32_t) + size + 1;
moduleCreateInfo.pCode = (uint32_t*)malloc(moduleCreateInfo.codeSize);
moduleCreateInfo.flags = 0;
// Magic SPV number
((uint32_t *)moduleCreateInfo.pCode)[0] = 0x07230203;
((uint32_t *)moduleCreateInfo.pCode)[1] = 0;
((uint32_t *)moduleCreateInfo.pCode)[2] = stage;
memcpy(((uint32_t *)moduleCreateInfo.pCode + 3), shaderCode, size + 1);
VK_CHECK_RESULT(vkCreateShaderModule(device, &moduleCreateInfo, NULL, &shaderModule));
return shaderModule;
}
VkImageMemoryBarrier prePresentBarrier(VkImage presentImage)
{
VkImageMemoryBarrier imageMemoryBarrier = vkTools::initializers::imageMemoryBarrier();
imageMemoryBarrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
imageMemoryBarrier.dstAccessMask = 0;
imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
imageMemoryBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
imageMemoryBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
imageMemoryBarrier.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 };
imageMemoryBarrier.image = presentImage;
return imageMemoryBarrier;
}
VkImageMemoryBarrier postPresentBarrier(VkImage presentImage)
{
VkImageMemoryBarrier imageMemoryBarrier = vkTools::initializers::imageMemoryBarrier();
imageMemoryBarrier.srcAccessMask = 0;
imageMemoryBarrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
imageMemoryBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
imageMemoryBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
imageMemoryBarrier.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 };
imageMemoryBarrier.image = presentImage;
return imageMemoryBarrier;
}
void destroyUniformData(VkDevice device, vkTools::UniformData *uniformData)
{
if (uniformData->mapped != nullptr)
{
vkUnmapMemory(device, uniformData->memory);
}
vkDestroyBuffer(device, uniformData->buffer, nullptr);
vkFreeMemory(device, uniformData->memory, nullptr);
}
}
VkMemoryAllocateInfo vkTools::initializers::memoryAllocateInfo()
{
VkMemoryAllocateInfo memAllocInfo = {};
memAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
memAllocInfo.pNext = NULL;
memAllocInfo.allocationSize = 0;
memAllocInfo.memoryTypeIndex = 0;
return memAllocInfo;
}
VkCommandBufferAllocateInfo vkTools::initializers::commandBufferAllocateInfo(VkCommandPool commandPool, VkCommandBufferLevel level, uint32_t bufferCount)
{
VkCommandBufferAllocateInfo commandBufferAllocateInfo = {};
commandBufferAllocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
commandBufferAllocateInfo.commandPool = commandPool;
commandBufferAllocateInfo.level = level;
commandBufferAllocateInfo.commandBufferCount = bufferCount;
return commandBufferAllocateInfo;
}
VkCommandPoolCreateInfo vkTools::initializers::commandPoolCreateInfo()
{
VkCommandPoolCreateInfo cmdPoolCreateInfo = {};
cmdPoolCreateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
return cmdPoolCreateInfo;
}
VkCommandBufferBeginInfo vkTools::initializers::commandBufferBeginInfo()
{
VkCommandBufferBeginInfo cmdBufferBeginInfo = {};
cmdBufferBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
cmdBufferBeginInfo.pNext = NULL;
return cmdBufferBeginInfo;
}
VkCommandBufferInheritanceInfo vkTools::initializers::commandBufferInheritanceInfo()
{
VkCommandBufferInheritanceInfo cmdBufferInheritanceInfo = {};
cmdBufferInheritanceInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO;
return cmdBufferInheritanceInfo;
}
VkRenderPassBeginInfo vkTools::initializers::renderPassBeginInfo()
{
VkRenderPassBeginInfo renderPassBeginInfo = {};
renderPassBeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
renderPassBeginInfo.pNext = NULL;
return renderPassBeginInfo;
}
VkRenderPassCreateInfo vkTools::initializers::renderPassCreateInfo()
{
VkRenderPassCreateInfo renderPassCreateInfo = {};
renderPassCreateInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
renderPassCreateInfo.pNext = NULL;
return renderPassCreateInfo;
}
VkImageMemoryBarrier vkTools::initializers::imageMemoryBarrier()
{
VkImageMemoryBarrier imageMemoryBarrier = {};
imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
imageMemoryBarrier.pNext = NULL;
// Some default values
imageMemoryBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
imageMemoryBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
return imageMemoryBarrier;
}
VkBufferMemoryBarrier vkTools::initializers::bufferMemoryBarrier()
{
VkBufferMemoryBarrier bufferMemoryBarrier = {};
bufferMemoryBarrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
bufferMemoryBarrier.pNext = NULL;
return bufferMemoryBarrier;
}
VkMemoryBarrier vkTools::initializers::memoryBarrier()
{
VkMemoryBarrier memoryBarrier = {};
memoryBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER;
memoryBarrier.pNext = NULL;
return memoryBarrier;
}
VkImageCreateInfo vkTools::initializers::imageCreateInfo()
{
VkImageCreateInfo imageCreateInfo = {};
imageCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
imageCreateInfo.pNext = NULL;
return imageCreateInfo;
}
VkSamplerCreateInfo vkTools::initializers::samplerCreateInfo()
{
VkSamplerCreateInfo samplerCreateInfo = {};
samplerCreateInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
samplerCreateInfo.pNext = NULL;
return samplerCreateInfo;
}
VkImageViewCreateInfo vkTools::initializers::imageViewCreateInfo()
{
VkImageViewCreateInfo imageViewCreateInfo = {};
imageViewCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
imageViewCreateInfo.pNext = NULL;
return imageViewCreateInfo;
}
VkFramebufferCreateInfo vkTools::initializers::framebufferCreateInfo()
{
VkFramebufferCreateInfo framebufferCreateInfo = {};
framebufferCreateInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
framebufferCreateInfo.pNext = NULL;
return framebufferCreateInfo;
}
VkSemaphoreCreateInfo vkTools::initializers::semaphoreCreateInfo()
{
VkSemaphoreCreateInfo semaphoreCreateInfo = {};
semaphoreCreateInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
semaphoreCreateInfo.pNext = NULL;
semaphoreCreateInfo.flags = 0;
return semaphoreCreateInfo;
}
VkFenceCreateInfo vkTools::initializers::fenceCreateInfo(VkFenceCreateFlags flags)
{
VkFenceCreateInfo fenceCreateInfo = {};
fenceCreateInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
fenceCreateInfo.flags = flags;
return fenceCreateInfo;
}
VkEventCreateInfo vkTools::initializers::eventCreateInfo()
{
VkEventCreateInfo eventCreateInfo = {};
eventCreateInfo.sType = VK_STRUCTURE_TYPE_EVENT_CREATE_INFO;
return eventCreateInfo;
}
VkSubmitInfo vkTools::initializers::submitInfo()
{
VkSubmitInfo submitInfo = {};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submitInfo.pNext = NULL;
return submitInfo;
}
VkViewport vkTools::initializers::viewport(
float width,
float height,
float minDepth,
float maxDepth)
{
VkViewport viewport = {};
viewport.width = width;
viewport.height = height;
viewport.minDepth = minDepth;
viewport.maxDepth = maxDepth;
return viewport;
}
VkRect2D vkTools::initializers::rect2D(
int32_t width,
int32_t height,
int32_t offsetX,
int32_t offsetY)
{
VkRect2D rect2D = {};
rect2D.extent.width = width;
rect2D.extent.height = height;
rect2D.offset.x = offsetX;
rect2D.offset.y = offsetY;
return rect2D;
}
VkBufferCreateInfo vkTools::initializers::bufferCreateInfo()
{
VkBufferCreateInfo bufCreateInfo = {};
bufCreateInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
return bufCreateInfo;
}
VkBufferCreateInfo vkTools::initializers::bufferCreateInfo(
VkBufferUsageFlags usage,
VkDeviceSize size)
{
VkBufferCreateInfo bufCreateInfo = {};
bufCreateInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
bufCreateInfo.pNext = NULL;
bufCreateInfo.usage = usage;
bufCreateInfo.size = size;
bufCreateInfo.flags = 0;
return bufCreateInfo;
}
VkDescriptorPoolCreateInfo vkTools::initializers::descriptorPoolCreateInfo(
uint32_t poolSizeCount,
VkDescriptorPoolSize* pPoolSizes,
uint32_t maxSets)
{
VkDescriptorPoolCreateInfo descriptorPoolInfo = {};
descriptorPoolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
descriptorPoolInfo.pNext = NULL;
descriptorPoolInfo.poolSizeCount = poolSizeCount;
descriptorPoolInfo.pPoolSizes = pPoolSizes;
descriptorPoolInfo.maxSets = maxSets;
return descriptorPoolInfo;
}
VkDescriptorPoolSize vkTools::initializers::descriptorPoolSize(
VkDescriptorType type,
uint32_t descriptorCount)
{
VkDescriptorPoolSize descriptorPoolSize = {};
descriptorPoolSize.type = type;
descriptorPoolSize.descriptorCount = descriptorCount;
return descriptorPoolSize;
}
VkDescriptorSetLayoutBinding vkTools::initializers::descriptorSetLayoutBinding(
VkDescriptorType type,
VkShaderStageFlags stageFlags,
uint32_t binding)
{
VkDescriptorSetLayoutBinding setLayoutBinding = {};
setLayoutBinding.descriptorType = type;
setLayoutBinding.stageFlags = stageFlags;
setLayoutBinding.binding = binding;
// Default value in all examples
setLayoutBinding.descriptorCount = 1;
return setLayoutBinding;
}
VkDescriptorSetLayoutCreateInfo vkTools::initializers::descriptorSetLayoutCreateInfo(
const VkDescriptorSetLayoutBinding* pBindings,
uint32_t bindingCount)
{
VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo = {};
descriptorSetLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
descriptorSetLayoutCreateInfo.pNext = NULL;
descriptorSetLayoutCreateInfo.pBindings = pBindings;
descriptorSetLayoutCreateInfo.bindingCount = bindingCount;
return descriptorSetLayoutCreateInfo;
}
VkPipelineLayoutCreateInfo vkTools::initializers::pipelineLayoutCreateInfo(
const VkDescriptorSetLayout* pSetLayouts,
uint32_t setLayoutCount)
{
VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = {};
pipelineLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipelineLayoutCreateInfo.pNext = NULL;
pipelineLayoutCreateInfo.setLayoutCount = setLayoutCount;
pipelineLayoutCreateInfo.pSetLayouts = pSetLayouts;
return pipelineLayoutCreateInfo;
}
VkDescriptorSetAllocateInfo vkTools::initializers::descriptorSetAllocateInfo(
VkDescriptorPool descriptorPool,
const VkDescriptorSetLayout* pSetLayouts,
uint32_t descriptorSetCount)
{
VkDescriptorSetAllocateInfo descriptorSetAllocateInfo = {};
descriptorSetAllocateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
descriptorSetAllocateInfo.pNext = NULL;
descriptorSetAllocateInfo.descriptorPool = descriptorPool;
descriptorSetAllocateInfo.pSetLayouts = pSetLayouts;
descriptorSetAllocateInfo.descriptorSetCount = descriptorSetCount;
return descriptorSetAllocateInfo;
}
VkDescriptorImageInfo vkTools::initializers::descriptorImageInfo(VkSampler sampler, VkImageView imageView, VkImageLayout imageLayout)
{
VkDescriptorImageInfo descriptorImageInfo = {};
descriptorImageInfo.sampler = sampler;
descriptorImageInfo.imageView = imageView;
descriptorImageInfo.imageLayout = imageLayout;
return descriptorImageInfo;
}
VkWriteDescriptorSet vkTools::initializers::writeDescriptorSet(
VkDescriptorSet dstSet,
VkDescriptorType type,
uint32_t binding,
VkDescriptorBufferInfo* bufferInfo)
{
VkWriteDescriptorSet writeDescriptorSet = {};
writeDescriptorSet.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
writeDescriptorSet.pNext = NULL;
writeDescriptorSet.dstSet = dstSet;
writeDescriptorSet.descriptorType = type;
writeDescriptorSet.dstBinding = binding;
writeDescriptorSet.pBufferInfo = bufferInfo;
// Default value in all examples
writeDescriptorSet.descriptorCount = 1;
return writeDescriptorSet;
}
VkWriteDescriptorSet vkTools::initializers::writeDescriptorSet(
VkDescriptorSet dstSet,
VkDescriptorType type,
uint32_t binding,
VkDescriptorImageInfo * imageInfo)
{
VkWriteDescriptorSet writeDescriptorSet = {};
writeDescriptorSet.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
writeDescriptorSet.pNext = NULL;
writeDescriptorSet.dstSet = dstSet;
writeDescriptorSet.descriptorType = type;
writeDescriptorSet.dstBinding = binding;
writeDescriptorSet.pImageInfo = imageInfo;
// Default value in all examples
writeDescriptorSet.descriptorCount = 1;
return writeDescriptorSet;
}
VkVertexInputBindingDescription vkTools::initializers::vertexInputBindingDescription(
uint32_t binding,
uint32_t stride,
VkVertexInputRate inputRate)
{
VkVertexInputBindingDescription vInputBindDescription = {};
vInputBindDescription.binding = binding;
vInputBindDescription.stride = stride;
vInputBindDescription.inputRate = inputRate;
return vInputBindDescription;
}
VkVertexInputAttributeDescription vkTools::initializers::vertexInputAttributeDescription(
uint32_t binding,
uint32_t location,
VkFormat format,
uint32_t offset)
{
VkVertexInputAttributeDescription vInputAttribDescription = {};
vInputAttribDescription.location = location;
vInputAttribDescription.binding = binding;
vInputAttribDescription.format = format;
vInputAttribDescription.offset = offset;
return vInputAttribDescription;
}
VkPipelineVertexInputStateCreateInfo vkTools::initializers::pipelineVertexInputStateCreateInfo()
{
VkPipelineVertexInputStateCreateInfo pipelineVertexInputStateCreateInfo = {};
pipelineVertexInputStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
pipelineVertexInputStateCreateInfo.pNext = NULL;
return pipelineVertexInputStateCreateInfo;
}
VkPipelineInputAssemblyStateCreateInfo vkTools::initializers::pipelineInputAssemblyStateCreateInfo(
VkPrimitiveTopology topology,
VkPipelineInputAssemblyStateCreateFlags flags,
VkBool32 primitiveRestartEnable)
{
VkPipelineInputAssemblyStateCreateInfo pipelineInputAssemblyStateCreateInfo = {};
pipelineInputAssemblyStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
pipelineInputAssemblyStateCreateInfo.topology = topology;
pipelineInputAssemblyStateCreateInfo.flags = flags;
pipelineInputAssemblyStateCreateInfo.primitiveRestartEnable = primitiveRestartEnable;
return pipelineInputAssemblyStateCreateInfo;
}
VkPipelineRasterizationStateCreateInfo vkTools::initializers::pipelineRasterizationStateCreateInfo(
VkPolygonMode polygonMode,
VkCullModeFlags cullMode,
VkFrontFace frontFace,
VkPipelineRasterizationStateCreateFlags flags)
{
VkPipelineRasterizationStateCreateInfo pipelineRasterizationStateCreateInfo = {};
pipelineRasterizationStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
pipelineRasterizationStateCreateInfo.polygonMode = polygonMode;
pipelineRasterizationStateCreateInfo.cullMode = cullMode;
pipelineRasterizationStateCreateInfo.frontFace = frontFace;
pipelineRasterizationStateCreateInfo.flags = flags;
pipelineRasterizationStateCreateInfo.depthClampEnable = VK_TRUE;
pipelineRasterizationStateCreateInfo.lineWidth = 1.0f;
return pipelineRasterizationStateCreateInfo;
}
VkPipelineColorBlendAttachmentState vkTools::initializers::pipelineColorBlendAttachmentState(
VkColorComponentFlags colorWriteMask,
VkBool32 blendEnable)
{
VkPipelineColorBlendAttachmentState pipelineColorBlendAttachmentState = {};
pipelineColorBlendAttachmentState.colorWriteMask = colorWriteMask;
pipelineColorBlendAttachmentState.blendEnable = blendEnable;
return pipelineColorBlendAttachmentState;
}
VkPipelineColorBlendStateCreateInfo vkTools::initializers::pipelineColorBlendStateCreateInfo(
uint32_t attachmentCount,
const VkPipelineColorBlendAttachmentState * pAttachments)
{
VkPipelineColorBlendStateCreateInfo pipelineColorBlendStateCreateInfo = {};
pipelineColorBlendStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
pipelineColorBlendStateCreateInfo.pNext = NULL;
pipelineColorBlendStateCreateInfo.attachmentCount = attachmentCount;
pipelineColorBlendStateCreateInfo.pAttachments = pAttachments;
return pipelineColorBlendStateCreateInfo;
}
VkPipelineDepthStencilStateCreateInfo vkTools::initializers::pipelineDepthStencilStateCreateInfo(
VkBool32 depthTestEnable,
VkBool32 depthWriteEnable,
VkCompareOp depthCompareOp)
{
VkPipelineDepthStencilStateCreateInfo pipelineDepthStencilStateCreateInfo = {};
pipelineDepthStencilStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
pipelineDepthStencilStateCreateInfo.depthTestEnable = depthTestEnable;
pipelineDepthStencilStateCreateInfo.depthWriteEnable = depthWriteEnable;
pipelineDepthStencilStateCreateInfo.depthCompareOp = depthCompareOp;
pipelineDepthStencilStateCreateInfo.front = pipelineDepthStencilStateCreateInfo.back;
pipelineDepthStencilStateCreateInfo.back.compareOp = VK_COMPARE_OP_ALWAYS;
return pipelineDepthStencilStateCreateInfo;
}
VkPipelineViewportStateCreateInfo vkTools::initializers::pipelineViewportStateCreateInfo(
uint32_t viewportCount,
uint32_t scissorCount,
VkPipelineViewportStateCreateFlags flags)
{
VkPipelineViewportStateCreateInfo pipelineViewportStateCreateInfo = {};
pipelineViewportStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
pipelineViewportStateCreateInfo.viewportCount = viewportCount;
pipelineViewportStateCreateInfo.scissorCount = scissorCount;
pipelineViewportStateCreateInfo.flags = flags;
return pipelineViewportStateCreateInfo;
}
VkPipelineMultisampleStateCreateInfo vkTools::initializers::pipelineMultisampleStateCreateInfo(
VkSampleCountFlagBits rasterizationSamples,
VkPipelineMultisampleStateCreateFlags flags)
{
VkPipelineMultisampleStateCreateInfo pipelineMultisampleStateCreateInfo = {};
pipelineMultisampleStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
pipelineMultisampleStateCreateInfo.rasterizationSamples = rasterizationSamples;
pipelineMultisampleStateCreateInfo.flags = flags;
return pipelineMultisampleStateCreateInfo;
}
VkPipelineDynamicStateCreateInfo vkTools::initializers::pipelineDynamicStateCreateInfo(
const VkDynamicState * pDynamicStates,
uint32_t dynamicStateCount,
VkPipelineDynamicStateCreateFlags flags)
{
VkPipelineDynamicStateCreateInfo pipelineDynamicStateCreateInfo = {};
pipelineDynamicStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
pipelineDynamicStateCreateInfo.pDynamicStates = pDynamicStates;
pipelineDynamicStateCreateInfo.dynamicStateCount = dynamicStateCount;
pipelineDynamicStateCreateInfo.flags = flags;
return pipelineDynamicStateCreateInfo;
}
VkPipelineTessellationStateCreateInfo vkTools::initializers::pipelineTessellationStateCreateInfo(uint32_t patchControlPoints)
{
VkPipelineTessellationStateCreateInfo pipelineTessellationStateCreateInfo = {};
pipelineTessellationStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO;
pipelineTessellationStateCreateInfo.patchControlPoints = patchControlPoints;
return pipelineTessellationStateCreateInfo;
}
VkGraphicsPipelineCreateInfo vkTools::initializers::pipelineCreateInfo(
VkPipelineLayout layout,
VkRenderPass renderPass,
VkPipelineCreateFlags flags)
{
VkGraphicsPipelineCreateInfo pipelineCreateInfo = {};
pipelineCreateInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
pipelineCreateInfo.pNext = NULL;
pipelineCreateInfo.layout = layout;
pipelineCreateInfo.renderPass = renderPass;
pipelineCreateInfo.flags = flags;
return pipelineCreateInfo;
}
VkComputePipelineCreateInfo vkTools::initializers::computePipelineCreateInfo(VkPipelineLayout layout, VkPipelineCreateFlags flags)
{
VkComputePipelineCreateInfo computePipelineCreateInfo = {};
computePipelineCreateInfo.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO;
computePipelineCreateInfo.layout = layout;
computePipelineCreateInfo.flags = flags;
return computePipelineCreateInfo;
}
VkPushConstantRange vkTools::initializers::pushConstantRange(
VkShaderStageFlags stageFlags,
uint32_t size,
uint32_t offset)
{
VkPushConstantRange pushConstantRange = {};
pushConstantRange.stageFlags = stageFlags;
pushConstantRange.offset = offset;
pushConstantRange.size = size;
return pushConstantRange;
}
// vi: set sw=2 ts=4 expandtab:
@@ -0,0 +1,312 @@
/*
* Copyright 2016 Sascha Willems - www.saschawillems.de
* SPDX-License-Identifier: MIT
*
* Assorted commonly used Vulkan helper functions
*/
#pragma once
#include "vulkan/vulkan.h"
#include <math.h>
#include <stdlib.h>
#include <string>
#include <cstring>
#include <fstream>
#include <assert.h>
#include <stdio.h>
#include <vector>
#include <iostream>
#include <stdexcept>
#if defined(_WIN32)
#include <windows.h>
#include <fcntl.h>
#include <io.h>
#elif defined(__ANDROID__)
#include "vulkanandroid.h"
#include <android/asset_manager.h>
#endif
#include <SDL3/SDL_messagebox.h>
// Custom define for better code readability
#define VK_FLAGS_NONE 0
// Default fence timeout in nanoseconds
#define DEFAULT_FENCE_TIMEOUT 100000000000
#if 0
// Macro to check and display Vulkan return results
#define VK_CHECK_RESULT(f) \
{ \
VkResult res = (f); \
if (res != VK_SUCCESS) \
{ \
std::cout << "Fatal : VkResult is \"" << vkTools::errorString(res) << "\" in " << __FILE__ << " at line " << __LINE__ << std::endl; \
assert(res == VK_SUCCESS); \
} \
}
#endif
#if defined(DEBUG)
#include <sstream>
extern const char* appName();
#define VK_CHECK_RESULT(f) \
{ \
VkResult res = (f); \
if (res != VK_SUCCESS) \
{ \
std::stringstream msg; \
msg << "Fatal error. VkResult is \"" \
<< vkTools::errorString(res) << "\" in " << __FILE__ \
<< " at line " << __LINE__ << std::endl; \
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, \
appName(), \
msg.str().c_str(), \
NULL); \
assert(res == VK_SUCCESS); \
} \
}
#else
#define VK_CHECK_RESULT(f) (void)f
#endif
namespace vkTools
{
// Check if extension is globally available
VkBool32 checkGlobalExtensionPresent(const char* extensionName);
// Check if extension is present on the given device
VkBool32 checkDeviceExtensionPresent(VkPhysicalDevice physicalDevice, const char* extensionName);
// Return string representation of a vulkan error string
std::string errorString(VkResult errorCode);
// Selected a suitable supported depth format starting with 32 bit down to 16 bit
// Returns false if none of the depth formats in the list is supported by the device
VkBool32 getSupportedDepthFormat(VkPhysicalDevice physicalDevice, VkFormat *depthFormat);
// Put an image memory barrier for setting an image layout on the sub resource into the given command buffer
void setImageLayout(
VkCommandBuffer cmdbuffer,
VkImage image,
VkImageAspectFlags aspectMask,
VkImageLayout oldImageLayout,
VkImageLayout newImageLayout,
VkImageSubresourceRange subresourceRange);
// Uses a fixed sub resource layout with first mip level and layer
void setImageLayout(
VkCommandBuffer cmdbuffer,
VkImage image,
VkImageAspectFlags aspectMask,
VkImageLayout oldImageLayout,
VkImageLayout newImageLayout);
// Display error message and exit on fatal error
void exitFatal(std::string message, std::string caption);
// Load a text file (e.g. GLGL shader) into a std::string
std::string readTextFile(const char *fileName);
// Load a binary file into a buffer (e.g. SPIR-V)
char *readBinaryFile(const char *filename, size_t *psize);
// Load a SPIR-V shader
#if defined(__ANDROID__)
VkShaderModule loadShader(AAssetManager* assetManager, const char *fileName, VkDevice device, VkShaderStageFlagBits stage);
#else
VkShaderModule loadShader(const char *fileName, VkDevice device, VkShaderStageFlagBits stage);
#endif
// Load a GLSL shader
// Note : Only for testing purposes, support for directly feeding GLSL shaders into Vulkan
// may be dropped at some point
VkShaderModule loadShaderGLSL(const char *fileName, VkDevice device, VkShaderStageFlagBits stage);
// Returns a pre-present image memory barrier
// Transforms the image's layout from color attachment to present khr
VkImageMemoryBarrier prePresentBarrier(VkImage presentImage);
// Returns a post-present image memory barrier
// Transforms the image's layout back from present khr to color attachment
VkImageMemoryBarrier postPresentBarrier(VkImage presentImage);
// Contains all vulkan objects
// required for a uniform data object
struct UniformData
{
VkBuffer buffer;
VkDeviceMemory memory;
VkDescriptorBufferInfo descriptor;
uint32_t allocSize;
void* mapped = nullptr;
};
// Destroy (and free) Vulkan resources used by a uniform data structure
void destroyUniformData(VkDevice device, vkTools::UniformData *uniformData);
// Contains often used vulkan object initializers
// Save lot of VK_STRUCTURE_TYPE assignments
// Some initializers are parameterized for convenience
namespace initializers
{
VkMemoryAllocateInfo memoryAllocateInfo();
VkCommandBufferAllocateInfo commandBufferAllocateInfo(
VkCommandPool commandPool,
VkCommandBufferLevel level,
uint32_t bufferCount);
VkCommandPoolCreateInfo commandPoolCreateInfo();
VkCommandBufferBeginInfo commandBufferBeginInfo();
VkCommandBufferInheritanceInfo commandBufferInheritanceInfo();
VkRenderPassBeginInfo renderPassBeginInfo();
VkRenderPassCreateInfo renderPassCreateInfo();
VkImageMemoryBarrier imageMemoryBarrier();
VkBufferMemoryBarrier bufferMemoryBarrier();
VkMemoryBarrier memoryBarrier();
VkImageCreateInfo imageCreateInfo();
VkSamplerCreateInfo samplerCreateInfo();
VkImageViewCreateInfo imageViewCreateInfo();
VkFramebufferCreateInfo framebufferCreateInfo();
VkSemaphoreCreateInfo semaphoreCreateInfo();
VkFenceCreateInfo fenceCreateInfo(VkFenceCreateFlags flags);
VkEventCreateInfo eventCreateInfo();
VkSubmitInfo submitInfo();
VkViewport viewport(
float width,
float height,
float minDepth,
float maxDepth);
VkRect2D rect2D(
int32_t width,
int32_t height,
int32_t offsetX,
int32_t offsetY);
VkBufferCreateInfo bufferCreateInfo();
VkBufferCreateInfo bufferCreateInfo(
VkBufferUsageFlags usage,
VkDeviceSize size);
VkDescriptorPoolCreateInfo descriptorPoolCreateInfo(
uint32_t poolSizeCount,
VkDescriptorPoolSize* pPoolSizes,
uint32_t maxSets);
VkDescriptorPoolSize descriptorPoolSize(
VkDescriptorType type,
uint32_t descriptorCount);
VkDescriptorSetLayoutBinding descriptorSetLayoutBinding(
VkDescriptorType type,
VkShaderStageFlags stageFlags,
uint32_t binding);
VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo(
const VkDescriptorSetLayoutBinding* pBindings,
uint32_t bindingCount);
VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo(
const VkDescriptorSetLayout* pSetLayouts,
uint32_t setLayoutCount );
VkDescriptorSetAllocateInfo descriptorSetAllocateInfo(
VkDescriptorPool descriptorPool,
const VkDescriptorSetLayout* pSetLayouts,
uint32_t descriptorSetCount);
VkDescriptorImageInfo descriptorImageInfo(
VkSampler sampler,
VkImageView imageView,
VkImageLayout imageLayout);
VkWriteDescriptorSet writeDescriptorSet(
VkDescriptorSet dstSet,
VkDescriptorType type,
uint32_t binding,
VkDescriptorBufferInfo* bufferInfo);
VkWriteDescriptorSet writeDescriptorSet(
VkDescriptorSet dstSet,
VkDescriptorType type,
uint32_t binding,
VkDescriptorImageInfo* imageInfo);
VkVertexInputBindingDescription vertexInputBindingDescription(
uint32_t binding,
uint32_t stride,
VkVertexInputRate inputRate);
VkVertexInputAttributeDescription vertexInputAttributeDescription(
uint32_t binding,
uint32_t location,
VkFormat format,
uint32_t offset);
VkPipelineVertexInputStateCreateInfo pipelineVertexInputStateCreateInfo();
VkPipelineInputAssemblyStateCreateInfo pipelineInputAssemblyStateCreateInfo(
VkPrimitiveTopology topology,
VkPipelineInputAssemblyStateCreateFlags flags,
VkBool32 primitiveRestartEnable);
VkPipelineRasterizationStateCreateInfo pipelineRasterizationStateCreateInfo(
VkPolygonMode polygonMode,
VkCullModeFlags cullMode,
VkFrontFace frontFace,
VkPipelineRasterizationStateCreateFlags flags);
VkPipelineColorBlendAttachmentState pipelineColorBlendAttachmentState(
VkColorComponentFlags colorWriteMask,
VkBool32 blendEnable);
VkPipelineColorBlendStateCreateInfo pipelineColorBlendStateCreateInfo(
uint32_t attachmentCount,
const VkPipelineColorBlendAttachmentState* pAttachments);
VkPipelineDepthStencilStateCreateInfo pipelineDepthStencilStateCreateInfo(
VkBool32 depthTestEnable,
VkBool32 depthWriteEnable,
VkCompareOp depthCompareOp);
VkPipelineViewportStateCreateInfo pipelineViewportStateCreateInfo(
uint32_t viewportCount,
uint32_t scissorCount,
VkPipelineViewportStateCreateFlags flags);
VkPipelineMultisampleStateCreateInfo pipelineMultisampleStateCreateInfo(
VkSampleCountFlagBits rasterizationSamples,
VkPipelineMultisampleStateCreateFlags flags);
VkPipelineDynamicStateCreateInfo pipelineDynamicStateCreateInfo(
const VkDynamicState *pDynamicStates,
uint32_t dynamicStateCount,
VkPipelineDynamicStateCreateFlags flags);
VkPipelineTessellationStateCreateInfo pipelineTessellationStateCreateInfo(
uint32_t patchControlPoints);
VkGraphicsPipelineCreateInfo pipelineCreateInfo(
VkPipelineLayout layout,
VkRenderPass renderPass,
VkPipelineCreateFlags flags);
VkComputePipelineCreateInfo computePipelineCreateInfo(
VkPipelineLayout layout,
VkPipelineCreateFlags flags);
VkPushConstantRange pushConstantRange(
VkShaderStageFlags stageFlags,
uint32_t size,
uint32_t offset);
}
}
// vi: set sw=2 ts=4 expandtab: