The test uses the format VK_FORMAT_D16_UNORM for the shadow map, and it sets unconditionally VK_FILTER_LINEAR when using it. But by spec, it is not mandatory that format to support filtering. Explained here: https://www.khronos.org/registry/vulkan/specs/1.2/html/chap32.html table 51, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT. This commit checks if that flag is present with that format to decide between LINEAR (the default value) or NEAREST (if LINEAR is not supported). Adds a auxiliar method on VulkanTools just in case it could be useful for other demos. This is not detected by the Validation Layers, but raise an assertion with one of the development tools we use to implement the Mesa v3dv driver (rpi4 vulkan driver).
382 lines
No EOL
12 KiB
C++
382 lines
No EOL
12 KiB
C++
/*
|
|
* Assorted commonly used Vulkan helper functions
|
|
*
|
|
* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
|
|
*
|
|
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
|
|
*/
|
|
|
|
#include "VulkanTools.h"
|
|
|
|
const std::string getAssetPath()
|
|
{
|
|
#if defined(VK_USE_PLATFORM_ANDROID_KHR)
|
|
return "";
|
|
#elif defined(VK_EXAMPLE_DATA_DIR)
|
|
return VK_EXAMPLE_DATA_DIR;
|
|
#else
|
|
return "./../data/";
|
|
#endif
|
|
}
|
|
|
|
namespace vks
|
|
{
|
|
namespace tools
|
|
{
|
|
bool errorModeSilent = 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";
|
|
}
|
|
}
|
|
|
|
std::string physicalDeviceTypeString(VkPhysicalDeviceType type)
|
|
{
|
|
switch (type)
|
|
{
|
|
#define STR(r) case VK_PHYSICAL_DEVICE_TYPE_ ##r: return #r
|
|
STR(OTHER);
|
|
STR(INTEGRATED_GPU);
|
|
STR(DISCRETE_GPU);
|
|
STR(VIRTUAL_GPU);
|
|
#undef STR
|
|
default: return "UNKNOWN_DEVICE_TYPE";
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// Returns if a given format support LINEAR filtering
|
|
VkBool32 formatIsFilterable(VkPhysicalDevice physicalDevice, VkFormat format, VkImageTiling tiling)
|
|
{
|
|
VkFormatProperties formatProps;
|
|
vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &formatProps);
|
|
|
|
if (tiling == VK_IMAGE_TILING_OPTIMAL)
|
|
return formatProps.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT;
|
|
|
|
if (tiling == VK_IMAGE_TILING_LINEAR)
|
|
return formatProps.linearTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT;
|
|
|
|
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,
|
|
VkImageLayout oldImageLayout,
|
|
VkImageLayout newImageLayout,
|
|
VkImageSubresourceRange subresourceRange,
|
|
VkPipelineStageFlags srcStageMask,
|
|
VkPipelineStageFlags dstStageMask)
|
|
{
|
|
// Create an image barrier object
|
|
VkImageMemoryBarrier imageMemoryBarrier = vks::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:
|
|
// Other source layouts aren't handled (yet)
|
|
break;
|
|
}
|
|
|
|
// 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 the image have been finished
|
|
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.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:
|
|
// Other source layouts aren't handled (yet)
|
|
break;
|
|
}
|
|
|
|
// Put barrier inside setup command buffer
|
|
vkCmdPipelineBarrier(
|
|
cmdbuffer,
|
|
srcStageMask,
|
|
dstStageMask,
|
|
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,
|
|
VkPipelineStageFlags srcStageMask,
|
|
VkPipelineStageFlags dstStageMask)
|
|
{
|
|
VkImageSubresourceRange subresourceRange = {};
|
|
subresourceRange.aspectMask = aspectMask;
|
|
subresourceRange.baseMipLevel = 0;
|
|
subresourceRange.levelCount = 1;
|
|
subresourceRange.layerCount = 1;
|
|
setImageLayout(cmdbuffer, image, oldImageLayout, newImageLayout, subresourceRange, srcStageMask, dstStageMask);
|
|
}
|
|
|
|
void insertImageMemoryBarrier(
|
|
VkCommandBuffer cmdbuffer,
|
|
VkImage image,
|
|
VkAccessFlags srcAccessMask,
|
|
VkAccessFlags dstAccessMask,
|
|
VkImageLayout oldImageLayout,
|
|
VkImageLayout newImageLayout,
|
|
VkPipelineStageFlags srcStageMask,
|
|
VkPipelineStageFlags dstStageMask,
|
|
VkImageSubresourceRange subresourceRange)
|
|
{
|
|
VkImageMemoryBarrier imageMemoryBarrier = vks::initializers::imageMemoryBarrier();
|
|
imageMemoryBarrier.srcAccessMask = srcAccessMask;
|
|
imageMemoryBarrier.dstAccessMask = dstAccessMask;
|
|
imageMemoryBarrier.oldLayout = oldImageLayout;
|
|
imageMemoryBarrier.newLayout = newImageLayout;
|
|
imageMemoryBarrier.image = image;
|
|
imageMemoryBarrier.subresourceRange = subresourceRange;
|
|
|
|
vkCmdPipelineBarrier(
|
|
cmdbuffer,
|
|
srcStageMask,
|
|
dstStageMask,
|
|
0,
|
|
0, nullptr,
|
|
0, nullptr,
|
|
1, &imageMemoryBarrier);
|
|
}
|
|
|
|
void exitFatal(std::string message, int32_t exitCode)
|
|
{
|
|
#if defined(_WIN32)
|
|
if (!errorModeSilent) {
|
|
MessageBox(NULL, message.c_str(), NULL, MB_OK | MB_ICONERROR);
|
|
}
|
|
#elif defined(__ANDROID__)
|
|
LOGE("Fatal error: %s", message.c_str());
|
|
vks::android::showAlert(message.c_str());
|
|
#endif
|
|
std::cerr << message << "\n";
|
|
#if !defined(__ANDROID__)
|
|
exit(exitCode);
|
|
#endif
|
|
}
|
|
|
|
void exitFatal(std::string message, VkResult resultCode)
|
|
{
|
|
exitFatal(message, (int32_t)resultCode);
|
|
}
|
|
|
|
#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)
|
|
{
|
|
// 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)
|
|
{
|
|
std::ifstream is(fileName, std::ios::binary | std::ios::in | std::ios::ate);
|
|
|
|
if (is.is_open())
|
|
{
|
|
size_t size = is.tellg();
|
|
is.seekg(0, std::ios::beg);
|
|
char* shaderCode = new char[size];
|
|
is.read(shaderCode, size);
|
|
is.close();
|
|
|
|
assert(size > 0);
|
|
|
|
VkShaderModule shaderModule;
|
|
VkShaderModuleCreateInfo moduleCreateInfo{};
|
|
moduleCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
|
|
moduleCreateInfo.codeSize = size;
|
|
moduleCreateInfo.pCode = (uint32_t*)shaderCode;
|
|
|
|
VK_CHECK_RESULT(vkCreateShaderModule(device, &moduleCreateInfo, NULL, &shaderModule));
|
|
|
|
delete[] shaderCode;
|
|
|
|
return shaderModule;
|
|
}
|
|
else
|
|
{
|
|
std::cerr << "Error: Could not open shader file \"" << fileName << "\"" << std::endl;
|
|
return VK_NULL_HANDLE;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
bool fileExists(const std::string &filename)
|
|
{
|
|
std::ifstream f(filename.c_str());
|
|
return !f.fail();
|
|
}
|
|
}
|
|
} |