vk: Fix unsupported depth blitting (#7789)

* vk: Fix unsupported depth blitting

On certain hardware (pixel 4 for example), blitting of depth texture
is not supported as an "optimalTilingFeature".  In these cases, we'd
would need to do a shader-based blit. We
 - Add the shader blit in PostProcessingManager
 - Add a driver API to check for support for blitting depthStencil
   attachments.
 - Fix some debugging ifdefs in vk backend.

The validation fixed is:
`[ VUID-vkCmdBlitImage-dstImage-02000 ] Object 0: handle = 0xb400007c300701d0, type = VK_OBJECT_TYPE_COMMAND_BUFFER; Object 1: handle = 0xf2039b0000000771, type = VK_OBJECT_TYPE_IMAGE; | MessageID = 0x86bc2a78 | In vkCmdBlitImage, VkFormatFeatureFlags (0x1c601) does not support required feature VK_FORMAT_FEATURE_2_BLIT_DST_BIT for format 126 used by VkImage 0xf2039b0000000771[] with tiling VK_IMAGE_TILING_OPTIMAL. The Vulkan spec states: The format features of dstImage must contain VK_FORMAT_FEATURE_BLIT_DST_BIT`
This commit is contained in:
Powei Feng
2024-04-24 18:27:57 +00:00
committed by GitHub
parent 99eac62b4e
commit c8335fade7
15 changed files with 203 additions and 65 deletions
+1
View File
@@ -214,6 +214,7 @@ set(PRIVATE_HDRS
set(MATERIAL_SRCS
src/materials/antiAliasing/fxaa.mat
src/materials/antiAliasing/taa.mat
src/materials/blitDepth.mat
src/materials/blitLow.mat
src/materials/blitArray.mat
src/materials/bloom/bloomDownsample.mat
@@ -304,6 +304,7 @@ DECL_DRIVER_API_SYNCHRONOUS_0(bool, isProtectedContentSupported)
DECL_DRIVER_API_SYNCHRONOUS_N(bool, isStereoSupported, backend::StereoscopicType, stereoscopicType)
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isParallelShaderCompileSupported)
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isDepthStencilResolveSupported)
DECL_DRIVER_API_SYNCHRONOUS_N(bool, isDepthStencilBlitSupported, backend::TextureFormat, format)
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isProtectedTexturesSupported)
DECL_DRIVER_API_SYNCHRONOUS_0(uint8_t, getMaxDrawBuffers)
DECL_DRIVER_API_SYNCHRONOUS_0(size_t, getMaxUniformBufferSize)
@@ -806,6 +806,10 @@ bool MetalDriver::isDepthStencilResolveSupported() {
return false;
}
bool MetalDriver::isDepthStencilBlitSupported(TextureFormat format) {
return true;
}
bool MetalDriver::isProtectedTexturesSupported() {
return false;
}
+4
View File
@@ -194,6 +194,10 @@ bool NoopDriver::isDepthStencilResolveSupported() {
return true;
}
bool NoopDriver::isDepthStencilBlitSupported(TextureFormat format) {
return true;
}
bool NoopDriver::isProtectedTexturesSupported() {
return true;
}
@@ -2023,6 +2023,10 @@ bool OpenGLDriver::isDepthStencilResolveSupported() {
return true;
}
bool OpenGLDriver::isDepthStencilBlitSupported(TextureFormat format) {
return true;
}
bool OpenGLDriver::isProtectedTexturesSupported() {
return getContext().ext.EXT_protected_textures;
}
@@ -26,8 +26,6 @@
#include <smolv.h>
#include "generated/vkshaders/vkshaders.h"
using namespace bluevk;
using namespace utils;
@@ -40,7 +38,6 @@ namespace {
inline void blitFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspect, VkFilter filter,
VulkanAttachment src, VulkanAttachment dst,
const VkOffset3D srcRect[2], const VkOffset3D dstRect[2]) {
if constexpr (FVK_ENABLED(FVK_DEBUG_BLITTER)) {
utils::slog.d << "Fast blit from=" << src.texture->getVkImage() << ",level=" << (int) src.level
<< " layout=" << src.getLayout()
@@ -93,7 +90,6 @@ inline void blitFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspect,
inline void resolveFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspect,
VulkanAttachment src, VulkanAttachment dst) {
if constexpr (FVK_ENABLED(FVK_DEBUG_BLITTER)) {
utils::slog.d << "Fast blit from=" << src.texture->getVkImage() << ",level=" << (int) src.level
<< " layout=" << src.getLayout()
@@ -47,7 +47,7 @@
// granualarity of a renderpass. You can enable this along with FVK_DEBUG_DEBUG_UTILS to take
// advantage of vkCmdBegin/EndDebugUtilsLabelEXT. You can also just enable this with
// FVK_DEBUG_PRINT_GROUP_MARKERS to print the current marker to stdout.
#define FVK_DEBUG_GROUP_MARKERS 0x00000002
#define FVK_DEBUG_GROUP_MARKERS 0x00000002
#define FVK_DEBUG_TEXTURE 0x00000004
#define FVK_DEBUG_LAYOUT_TRANSITION 0x00000008
@@ -112,7 +112,7 @@ static_assert(FVK_ENABLED(FVK_DEBUG_VALIDATION));
// end dependcy checks
// Shorthand for combination of enabled debug flags
#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS) || FVK_ENABLED(FVK_DEBUG_TEXTURE)
#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS) && FVK_ENABLED(FVK_DEBUG_TEXTURE)
#define FVK_ENABLED_DEBUG_SAMPLER_NAME 1
#else
#define FVK_ENABLED_DEBUG_SAMPLER_NAME 0
+8 -3
View File
@@ -101,8 +101,12 @@ public:
return (uint32_t) VK_MAX_MEMORY_TYPES;
}
inline VkFormatList const& getAttachmentDepthFormats() const {
return mDepthFormats;
inline VkFormatList const& getAttachmentDepthStencilFormats() const {
return mDepthStencilFormats;
}
inline VkFormatList const& getBlittableDepthStencilFormats() const {
return mBlittableDepthStencilFormats;
}
inline VkPhysicalDeviceLimits const& getPhysicalDeviceLimits() const noexcept {
@@ -131,7 +135,8 @@ private:
bool mDebugMarkersSupported = false;
bool mDebugUtilsSupported = false;
VkFormatList mDepthFormats;
VkFormatList mDepthStencilFormats;
VkFormatList mBlittableDepthStencilFormats;
// For convenience so that VulkanPlatform can initialize the private fields.
friend class VulkanPlatform;
+6 -1
View File
@@ -921,6 +921,11 @@ bool VulkanDriver::isDepthStencilResolveSupported() {
return false;
}
bool VulkanDriver::isDepthStencilBlitSupported(TextureFormat format) {
auto const& formats = mContext.getBlittableDepthStencilFormats();
return std::find(formats.begin(), formats.end(), getVkFormat(format)) != formats.end();
}
bool VulkanDriver::isProtectedTexturesSupported() {
return false;
}
@@ -1807,7 +1812,7 @@ void VulkanDriver::bindPipeline(PipelineState pipelineState) {
// This fallback path is very flaky because the dummy texture might not have
// matching characteristics. (e.g. if the missing texture is a 3D texture)
if (UTILS_UNLIKELY(texture->getPrimaryImageLayout() == VulkanLayout::UNDEFINED)) {
#if FVK_ENABLED(FVK_DEBUG_TEXTURE)
#if FVK_ENABLED(FVK_DEBUG_TEXTURE) && FVK_ENABLED_DEBUG_SAMPLER_NAME
utils::slog.w << "Uninitialized texture bound to '" << bindingToName[binding] << "'";
utils::slog.w << " in material '" << program->name.c_str() << "'";
utils::slog.w << " at binding point " << +binding << utils::io::endl;
@@ -217,7 +217,7 @@ bool operator<(const VkImageSubresourceRange& a, const VkImageSubresourceRange&
return false;
}
#if FVK_ENABLED(FVK_DEBUG_LAYOUT_TRANSITION | FVK_DEBUG_TEXTURE)
#if FVK_ENABLED(FVK_DEBUG_LAYOUT_TRANSITION) || FVK_ENABLED(FVK_DEBUG_TEXTURE)
#define CASE(VALUE) \
case filament::backend::VulkanLayout::VALUE: { \
out << #VALUE; \
@@ -344,13 +344,13 @@ std::tuple<ExtensionSet, ExtensionSet> pruneExtensions(VkPhysicalDevice device,
ExtensionSet newInstExts = instExts;
ExtensionSet newDeviceExts = deviceExts;
#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS)
#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS)
// debugUtils and debugMarkers extensions are used mutually exclusively.
if (newInstExts.find(VK_EXT_DEBUG_UTILS_EXTENSION_NAME) != newInstExts.end()
&& newDeviceExts.find(VK_EXT_DEBUG_MARKER_EXTENSION_NAME) != newDeviceExts.end()) {
newDeviceExts.erase(VK_EXT_DEBUG_MARKER_EXTENSION_NAME);
}
#endif
#endif
#if FVK_ENABLED(FVK_DEBUG_VALIDATION)
// debugMarker must also request debugReport the instance extension. So check if that's present.
@@ -500,7 +500,7 @@ VkPhysicalDevice selectPhysicalDevice(VkInstance instance,
return device;
}
VkFormatList findAttachmentDepthFormats(VkPhysicalDevice device) {
VkFormatList findAttachmentDepthStencilFormats(VkPhysicalDevice device) {
VkFormatFeatureFlags const features = VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT;
// The ordering here indicates the preference of choosing depth+stencil format.
@@ -524,6 +524,28 @@ VkFormatList findAttachmentDepthFormats(VkPhysicalDevice device) {
return ret;
}
VkFormatList findBlittableDepthStencilFormats(VkPhysicalDevice device) {
std::vector<VkFormat> selectedFormats;
VkFormatFeatureFlags const required = VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT |
VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT;
for (VkFormat format = (VkFormat) 1;;) {
if (isVkDepthFormat(format)) {
VkFormatProperties props;
vkGetPhysicalDeviceFormatProperties(device, format, &props);
if ((props.optimalTilingFeatures & required) == required) {
selectedFormats.push_back(format);
}
}
format = (VkFormat) (1 + (int) format);
if (format == VK_FORMAT_ASTC_12x12_SRGB_BLOCK) {
break;
}
}
VkFormatList ret(selectedFormats.size());
std::copy(selectedFormats.begin(), selectedFormats.end(), ret.begin());
return ret;
}
}// anonymous namespace
using SwapChainPtr = VulkanPlatform::SwapChainPtr;
@@ -669,9 +691,11 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
"Debug utils should not be enabled in release build.");
#endif
context.mDepthFormats = findAttachmentDepthFormats(mImpl->mPhysicalDevice);
context.mDepthStencilFormats = findAttachmentDepthStencilFormats(mImpl->mPhysicalDevice);
context.mBlittableDepthStencilFormats =
findBlittableDepthStencilFormats(mImpl->mPhysicalDevice);
assert_invariant(context.mDepthFormats.size() > 0);
assert_invariant(context.mDepthStencilFormats.size() > 0);
#if FVK_ENABLED(FVK_DEBUG_VALIDATION)
printDepthFormats(mImpl->mPhysicalDevice);
@@ -243,7 +243,7 @@ VkResult VulkanPlatformSurfaceSwapChain::create() {
mSwapChainBundle.colors = enumerate(vkGetSwapchainImagesKHR, mDevice, mSwapchain);
mSwapChainBundle.colorFormat = surfaceFormat.format;
mSwapChainBundle.depthFormat =
selectDepthFormat(mContext.getAttachmentDepthFormats(), mHasStencil);
selectDepthFormat(mContext.getAttachmentDepthStencilFormats(), mHasStencil);
mSwapChainBundle.depth = createImage(mSwapChainBundle.extent, mSwapChainBundle.depthFormat);
slog.i << "vkCreateSwapchain"
@@ -330,7 +330,7 @@ VulkanPlatformHeadlessSwapChain::VulkanPlatformHeadlessSwapChain(VulkanContext c
bool const hasStencil = (flags & backend::SWAP_CHAIN_HAS_STENCIL_BUFFER) != 0;
mSwapChainBundle.depthFormat =
selectDepthFormat(mContext.getAttachmentDepthFormats(), hasStencil);
selectDepthFormat(mContext.getAttachmentDepthStencilFormats(), hasStencil);
mSwapChainBundle.depth = createImage(extent, mSwapChainBundle.depthFormat);
}
+94 -46
View File
@@ -265,6 +265,7 @@ static const PostProcessManager::MaterialInfo sMaterialList[] = {
{ "bilateralBlur", MATERIAL(BILATERALBLUR) },
{ "bilateralBlurBentNormals", MATERIAL(BILATERALBLURBENTNORMALS) },
{ "blitArray", MATERIAL(BLITARRAY) },
{ "blitDepth", MATERIAL(BLITDEPTH) },
{ "bloomDownsample", MATERIAL(BLOOMDOWNSAMPLE) },
{ "bloomDownsample2x", MATERIAL(BLOOMDOWNSAMPLE2X) },
{ "bloomDownsample9", MATERIAL(BLOOMDOWNSAMPLE9) },
@@ -720,46 +721,16 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::screenSpaceAmbientOcclusion(
* attachment, even if writes are not enabled. This restriction is lifted on desktop GL and
* Vulkan. The Metal situation is unclear.
* In this case, we need to duplicate the depth texture to use it as an attachment.
* The pass below that does this is automatically culled if not needed, which is decided by
* each backend.
*
* This is also needed in Vulkan for a similar reason.
*/
FrameGraphId<FrameGraphTexture> duplicateDepthOutput = {};
if (!mWorkaroundAllowReadOnlyAncillaryFeedbackLoop) {
duplicateDepthOutput = blitDepth(fg, depth);
}
struct DuplicateDepthPassData {
FrameGraphId<FrameGraphTexture> input;
FrameGraphId<FrameGraphTexture> output;
};
// Needed for Vulkan and GLES. Some GLES implementations don't need it. Never needed for Metal.
auto& duplicateDepthPass = fg.addPass<DuplicateDepthPassData>("Duplicate Depth Pass",
[&](FrameGraph::Builder& builder, auto& data) {
data.input = builder.read(depth,
FrameGraphTexture::Usage::BLIT_SRC);
auto desc = builder.getDescriptor(data.input);
desc.levels = 1; // only copy the base level
// create a new buffer for the copy
data.output = builder.createTexture("Depth Texture Copy", desc);
// output is an attachment
data.output = builder.write(data.output,
FrameGraphTexture::Usage::BLIT_DST);
},
[=](FrameGraphResources const& resources, auto const& data, DriverApi& driver) {
auto const& src = resources.getTexture(data.input);
auto const& dst = resources.getTexture(data.output);
auto const& srcSubDesc = resources.getSubResourceDescriptor(data.input);
auto const& dstSubDesc = resources.getSubResourceDescriptor(data.output);
auto const& desc = resources.getDescriptor(data.output);
assert_invariant(desc.samples == resources.getDescriptor(data.input).samples);
// here we can guarantee that src and dst format and size match, by construction.
driver.blit(
dst, dstSubDesc.level, dstSubDesc.layer, { 0, 0 },
src, srcSubDesc.level, srcSubDesc.layer, { 0, 0 },
{ desc.width, desc.height });
});
auto& SSAOPass = fg.addPass<SSAOPassData>("SSAO Pass",
auto& SSAOPass = fg.addPass<SSAOPassData>(
"SSAO Pass",
[&](FrameGraph::Builder& builder, auto& data) {
auto const& desc = builder.getDescriptor(depth);
@@ -788,10 +759,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::screenSpaceAmbientOcclusion(
// reading into it even though they were not written in the depth buffer.
// The bilateral filter in the blur pass will ignore pixels at infinity.
auto depthAttachment = data.depth;
if (!mWorkaroundAllowReadOnlyAncillaryFeedbackLoop) {
depthAttachment = duplicateDepthPass->output;
}
auto depthAttachment = duplicateDepthOutput ? duplicateDepthOutput : data.depth;
depthAttachment = builder.read(depthAttachment,
FrameGraphTexture::Usage::DEPTH_ATTACHMENT);
@@ -801,8 +769,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::screenSpaceAmbientOcclusion(
.clearFlags = TargetBufferFlags::COLOR0 | TargetBufferFlags::COLOR1
});
},
[=](FrameGraphResources const& resources,
auto const& data, DriverApi& driver) {
[=](FrameGraphResources const& resources, auto const& data, DriverApi& driver) {
auto depth = resources.getTexture(data.depth);
auto ssao = resources.getRenderPassInfo();
auto const& desc = resources.getDescriptor(data.depth);
@@ -3043,7 +3010,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::blit(FrameGraph& fg, bool tr
auto& ppQuadBlit = fg.addPass<QuadBlitData>("blitting",
[&](FrameGraph::Builder& builder, auto& data) {
data.input = builder.sample(input);
data.output = builder.createTexture("upscaled output", outDesc);
data.output = builder.createTexture("blit output", outDesc);
data.output = builder.write(data.output,
FrameGraphTexture::Usage::COLOR_ATTACHMENT);
builder.declareRenderPass(builder.getName(data.output), {
@@ -3087,6 +3054,87 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::blit(FrameGraph& fg, bool tr
return ppQuadBlit->output;
}
FrameGraphId<FrameGraphTexture> PostProcessManager::blitDepth(FrameGraph& fg,
FrameGraphId<FrameGraphTexture> input) noexcept {
auto const& inputDesc = fg.getDescriptor(input);
filament::Viewport const vp = {0, 0, inputDesc.width, inputDesc.height};
bool const hardwareBlitSupported =
mEngine.getDriverApi().isDepthStencilBlitSupported(inputDesc.format);
struct BlitData {
FrameGraphId<FrameGraphTexture> input;
FrameGraphId<FrameGraphTexture> output;
};
if (hardwareBlitSupported) {
auto& depthPass = fg.addPass<BlitData>(
"Depth Blit",
[&](FrameGraph::Builder& builder, auto& data) {
data.input = builder.read(input, FrameGraphTexture::Usage::BLIT_SRC);
auto desc = builder.getDescriptor(data.input);
desc.levels = 1;// only copy the base level
// create a new buffer for the copy
data.output = builder.createTexture("depth blit output", desc);
// output is an attachment
data.output = builder.write(data.output, FrameGraphTexture::Usage::BLIT_DST);
},
[=](FrameGraphResources const& resources, auto const& data, DriverApi& driver) {
auto const& src = resources.getTexture(data.input);
auto const& dst = resources.getTexture(data.output);
auto const& srcSubDesc = resources.getSubResourceDescriptor(data.input);
auto const& dstSubDesc = resources.getSubResourceDescriptor(data.output);
auto const& desc = resources.getDescriptor(data.output);
assert_invariant(desc.samples == resources.getDescriptor(data.input).samples);
// here we can guarantee that src and dst format and size match, by
// construction.
driver.blit(
dst, dstSubDesc.level, dstSubDesc.layer, { 0, 0 },
src, srcSubDesc.level, srcSubDesc.layer, { 0, 0 },
{ desc.width, desc.height });
});
return depthPass->output;
}
// Otherwise, we would do a shader-based blit.
auto& ppQuadBlit = fg.addPass<BlitData>(
"Depth Blit (Shader)",
[&](FrameGraph::Builder& builder, auto& data) {
data.input = builder.sample(input);
// Note that this is a same size/format blit.
auto const& outputDesc = inputDesc;
data.output = builder.createTexture("depth blit output", outputDesc);
data.output =
builder.write(data.output, FrameGraphTexture::Usage::DEPTH_ATTACHMENT);
builder.declareRenderPass(builder.getName(data.output),
{.attachments = {.depth = {data.output}}});
},
[=](FrameGraphResources const& resources, auto const& data, DriverApi& driver) {
auto depth = resources.getTexture(data.input);
auto const& inputDesc = resources.getDescriptor(data.input);
auto out = resources.getRenderPassInfo();
// --------------------------------------------------------------------------------
// set uniforms
PostProcessMaterial const& material = getPostProcessMaterial("blitDepth");
auto* mi = material.getMaterialInstance(mEngine);
mi->setParameter("depth", depth,
{
.filterMag = SamplerMagFilter::NEAREST,
.filterMin = SamplerMinFilter::NEAREST,
});
mi->setParameter("viewport",
float4{float(vp.left) / inputDesc.width,
float(vp.bottom) / inputDesc.height, float(vp.width) / inputDesc.width,
float(vp.height) / inputDesc.height});
commitAndRender(out, material, driver);
});
return ppQuadBlit->output;
}
FrameGraphId<FrameGraphTexture> PostProcessManager::resolve(FrameGraph& fg,
const char* outputBufferName, FrameGraphId<FrameGraphTexture> input,
FrameGraphTexture::Descriptor outDesc) noexcept {
@@ -3283,7 +3331,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::debugShadowCascades(FrameGra
return debugShadowCascadePass->output;
}
FrameGraphId<FrameGraphTexture> PostProcessManager::debugCombineArrayTexture(FrameGraph& fg,
FrameGraphId<FrameGraphTexture> PostProcessManager::debugCombineArrayTexture(FrameGraph& fg,
bool translucent, FrameGraphId<FrameGraphTexture> input,
filament::Viewport const& vp, FrameGraphTexture::Descriptor const& outDesc,
SamplerMagFilter filterMag,
+5 -1
View File
@@ -250,13 +250,17 @@ public:
FrameGraphTexture::Descriptor const& outDesc,
bool translucent);
// upscale/downscale blitter using shaders
// color blitter using shaders
FrameGraphId<FrameGraphTexture> blit(FrameGraph& fg, bool translucent,
FrameGraphId<FrameGraphTexture> input,
filament::Viewport const& vp, FrameGraphTexture::Descriptor const& outDesc,
backend::SamplerMagFilter filterMag,
backend::SamplerMinFilter filterMin) noexcept;
// depth blitter using shaders
FrameGraphId<FrameGraphTexture> blitDepth(FrameGraph& fg,
FrameGraphId<FrameGraphTexture> input) noexcept;
// Resolves base level of input and outputs a texture from outDesc.
// outDesc with, height, format and samples will be overridden.
FrameGraphId<FrameGraphTexture> resolve(FrameGraph& fg,
+42
View File
@@ -0,0 +1,42 @@
material {
name : blitDepth,
parameters : [
{
type : sampler2d,
name : depth,
precision: medium
},
{
type : float4,
name : viewport,
precision: high
}
],
outputs : [
{
name : depth,
target : depth,
type : float
}
],
variables : [
vertex
],
culling: none,
depthWrite : true,
depthCulling : false,
domain: postprocess,
}
vertex {
void postProcessVertex(inout PostProcessVertexInputs postProcess) {
postProcess.vertex.xy = materialParams.viewport.xy + postProcess.normalizedUV * materialParams.viewport.zw;
postProcess.vertex.xy = uvToRenderTargetUV(postProcess.vertex.xy);
}
}
fragment {
void postProcess(inout PostProcessInputs postProcess) {
postProcess.depth = textureLod(materialParams_depth, variable_vertex.xy, 0.0).r;
}
}