Vulkan: add support for MRT.

This commit is contained in:
Philip Rideout
2020-07-01 12:17:19 -07:00
parent c067d76d1d
commit c8aad378c4
11 changed files with 269 additions and 174 deletions

View File

@@ -53,7 +53,11 @@ public:
};
class MRT {
TargetBufferInfo mInfos[4];
public:
static constexpr int TARGET_COUNT = 4;
private:
TargetBufferInfo mInfos[TARGET_COUNT];
public:
TargetBufferInfo operator[](size_t i) const noexcept {

View File

@@ -164,7 +164,7 @@ public:
};
MetalRenderTarget(MetalContext* context, uint32_t width, uint32_t height, uint8_t samples,
Attachment colorAttachments[4], Attachment depthAttachment);
Attachment colorAttachments[MRT::TARGET_COUNT], Attachment depthAttachment);
explicit MetalRenderTarget(MetalContext* context)
: HwRenderTarget(0, 0), context(context), defaultRenderTarget(true) {}
@@ -187,11 +187,11 @@ private:
bool defaultRenderTarget = false;
uint8_t samples = 1;
Attachment color[4] = {};
Attachment color[MRT::TARGET_COUNT] = {};
Attachment depth = {};
// "Sidecar" textures used to implement automatic MSAA resolve.
id<MTLTexture> multisampledColor[4] = { 0 };
id<MTLTexture> multisampledColor[MRT::TARGET_COUNT] = { 0 };
id<MTLTexture> multisampledDepth = nil;
};

View File

@@ -476,13 +476,13 @@ void MetalTexture::updateLodRange(uint32_t level) {
}
MetalRenderTarget::MetalRenderTarget(MetalContext* context, uint32_t width, uint32_t height,
uint8_t samples, Attachment colorAttachments[4], Attachment depthAttachment) :
uint8_t samples, Attachment colorAttachments[MRT::TARGET_COUNT], Attachment depthAttachment) :
HwRenderTarget(width, height), context(context), samples(samples) {
// If we were given a single-sampled texture but the samples parameter is > 1, we create
// multisampled sidecar textures and do a resolve automatically.
const bool msaaResolve = samples > 1;
for (size_t i = 0; i < 4; i++) {
for (size_t i = 0; i < MRT::TARGET_COUNT; i++) {
if (!colorAttachments[i]) {
continue;
}
@@ -523,7 +523,7 @@ void MetalRenderTarget::setUpRenderPassAttachments(MTLRenderPassDescriptor* desc
const auto discardFlags = params.flags.discardEnd;
for (size_t i = 0; i < 4; i++) {
for (size_t i = 0; i < MRT::TARGET_COUNT; i++) {
Attachment attachment = getColorAttachment(i);
if (!attachment) {
continue;
@@ -566,7 +566,7 @@ void MetalRenderTarget::setUpRenderPassAttachments(MTLRenderPassDescriptor* desc
}
MetalRenderTarget::Attachment MetalRenderTarget::getColorAttachment(size_t index) {
assert(index < 4);
assert(index < MRT::TARGET_COUNT);
Attachment result = color[index];
if (index == 0 && defaultRenderTarget) {
result.texture = acquireDrawable(context);

View File

@@ -46,7 +46,7 @@ VulkanBinder::VulkanBinder() : mDefaultRasterState(createDefaultRasterState()) {
mColorBlendState = VkPipelineColorBlendStateCreateInfo{};
mColorBlendState.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
mColorBlendState.attachmentCount = 1;
mColorBlendState.pAttachments = &mPipelineKey.rasterState.blending;
mColorBlendState.pAttachments = mColorBlendAttachments;
mShaderStages[0] = VkPipelineShaderStageCreateInfo{};
mShaderStages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
mShaderStages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
@@ -260,8 +260,17 @@ bool VulkanBinder::getOrCreatePipeline(VkPipeline* pipeline) noexcept {
pipelineCreateInfo.pDepthStencilState = &mPipelineKey.rasterState.depthStencil;
pipelineCreateInfo.pDynamicState = &dynamicState;
// Filament assumes consistent blend state across all color attachments.
mColorBlendState.attachmentCount = mPipelineKey.rasterState.getColorTargetCount;
for (auto& target : mColorBlendAttachments) {
target = mPipelineKey.rasterState.blending;
}
// There are no color attachments if there is no bound fragment shader. (e.g. shadow map gen)
mColorBlendState.attachmentCount = hasFragmentShader ? 1 : 0;
// TODO: This should be handled in a higher layer.
if (!hasFragmentShader) {
mColorBlendState.attachmentCount = 0;
}
#if FILAMENT_VULKAN_VERBOSE
utils::slog.d << "vkCreateGraphicsPipelines with shaders = ("
@@ -304,6 +313,7 @@ void VulkanBinder::bindRasterState(const RasterState& rasterState) noexcept {
VkPipelineMultisampleStateCreateInfo& ms0 = mPipelineKey.rasterState.multisampling;
const VkPipelineMultisampleStateCreateInfo& ms1 = rasterState.multisampling;
if (
mPipelineKey.rasterState.getColorTargetCount != rasterState.getColorTargetCount ||
raster0.polygonMode != raster1.polygonMode ||
raster0.cullMode != raster1.cullMode ||
raster0.frontFace != raster1.frontFace ||
@@ -659,6 +669,7 @@ static VulkanBinder::RasterState createDefaultRasterState() {
blending,
depthStencil,
multisampling,
1,
};
}

View File

@@ -18,6 +18,7 @@
#define TNT_FILAMENT_DRIVER_VULKANBINDER_H
#include <backend/DriverEnums.h>
#include <backend/TargetBufferInfo.h>
#include <private/backend/Program.h>
@@ -101,6 +102,7 @@ public:
VkPipelineColorBlendAttachmentState blending;
VkPipelineDepthStencilStateCreateInfo depthStencil;
VkPipelineMultisampleStateCreateInfo multisampling;
uint32_t getColorTargetCount;
};
static_assert(std::is_pod<RasterState>::value, "RasterState must be a POD for fast hashing.");
@@ -225,6 +227,7 @@ private:
VkDescriptorBufferInfo mDescriptorBuffers[UBUFFER_BINDING_COUNT];
VkDescriptorImageInfo mDescriptorSamplers[SAMPLER_BINDING_COUNT];
VkWriteDescriptorSet mDescriptorWrites[UBUFFER_BINDING_COUNT + SAMPLER_BINDING_COUNT];
VkPipelineColorBlendAttachmentState mColorBlendAttachments[MRT::TARGET_COUNT];
// Current bindings are divided into two "keys" which are composed of a mix of actual values
// (e.g., blending is OFF) and weak references to Vulkan objects (e.g., shader programs and

View File

@@ -114,6 +114,8 @@ struct VulkanAttachment {
VkDeviceMemory memory;
VulkanTexture* texture = nullptr;
VkImageLayout layout;
uint8_t level;
uint16_t layer;
};
// The SwapContext is the set of objects that gets "swapped" at each beginFrame().

View File

@@ -443,10 +443,28 @@ void VulkanDriver::createDefaultRenderTargetR(Handle<HwRenderTarget> rth, int) {
void VulkanDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
TargetBufferFlags targets, uint32_t width, uint32_t height, uint8_t samples,
backend::MRT color, TargetBufferInfo depth, TargetBufferInfo stencil) {
auto colorTexture = color[0].handle ? handle_cast<VulkanTexture>(mHandleMap, color[0].handle) : nullptr;
auto depthTexture = depth.handle ? handle_cast<VulkanTexture>(mHandleMap, depth.handle) : nullptr;
VulkanAttachment colorTargets[MRT::TARGET_COUNT] = {};
for (int i = 0; i < MRT::TARGET_COUNT; i++) {
if (color[i].handle) {
colorTargets[i].texture = handle_cast<VulkanTexture>(mHandleMap, color[i].handle);
}
colorTargets[i].level = color[i].level;
colorTargets[i].layer = color[i].layer;
}
VulkanAttachment depthStencil[2] = {};
TextureHandle handle = depth.handle;
depthStencil[0].texture = handle ? handle_cast<VulkanTexture>(mHandleMap, handle) : nullptr;
depthStencil[0].level = depth.level;
depthStencil[0].layer = depth.layer;
handle = stencil.handle;
depthStencil[1].texture = handle ? handle_cast<VulkanTexture>(mHandleMap, handle) : nullptr;
depthStencil[1].level = stencil.level;
depthStencil[1].layer = stencil.layer;
auto renderTarget = construct_handle<VulkanRenderTarget>(mHandleMap, rth, mContext,
width, height, color[0], colorTexture, depth, depthTexture);
width, height, colorTargets, depthStencil);
mDisposer.createDisposable(renderTarget, [this, rth] () {
destruct_handle<VulkanRenderTarget>(mHandleMap, rth);
});
@@ -859,50 +877,57 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
const VkExtent2D extent = rt->getExtent();
assert(extent.width > 0 && extent.height > 0);
const VulkanAttachment color = rt->getColor();
const VulkanAttachment depth = rt->getDepth();
const bool hasColor = color.format != VK_FORMAT_UNDEFINED;
const bool hasDepth = depth.format != VK_FORMAT_UNDEFINED;
mDisposer.acquire(rt, mContext.currentCommands->resources);
mDisposer.acquire(color.texture, mContext.currentCommands->resources);
mDisposer.acquire(depth.texture, mContext.currentCommands->resources);
TargetBufferFlags discardStart = params.flags.discardStart;
for (int i = 0; i < MRT::TARGET_COUNT; i++) {
mDisposer.acquire(rt->getColor(i).texture, mContext.currentCommands->resources);
}
// Filament has the expectation that the contents of the swap chain are not preserved on the
// first render pass. Note however that its contents are often preserved on subsequent render
// passes, due to multiple views.
TargetBufferFlags discardStart = params.flags.discardStart;
if (rt->invalidate()) {
discardStart |= TargetBufferFlags::COLOR;
}
VkRenderPass renderPass = mFramebufferCache.getRenderPass({
.colorLayout = color.layout,
// Create the VkRenderPass or fetch it from cache.
VulkanFboCache::RenderPassKey rpkey = {
.depthLayout = depth.layout,
.colorFormat = color.format,
.depthFormat = depth.format,
.flags = {
.clear = params.flags.clear,
.discardStart = discardStart,
.discardEnd = params.flags.discardEnd
}
});
};
for (int i = 0; i < MRT::TARGET_COUNT; i++) {
rpkey.colorLayout[i] = rt->getColor(i).layout;
rpkey.colorFormat[i] = rt->getColor(i).format;
}
VkRenderPass renderPass = mFramebufferCache.getRenderPass(rpkey);
mBinder.bindRenderPass(renderPass);
VulkanFboCache::FboKey fbo { .renderPass = renderPass };
int numAttachments = 0;
if (hasColor) {
fbo.attachments[numAttachments++] = color.view;
// Create the VkFramebuffer or fetch it from cache.
VulkanFboCache::FboKey fbkey { .renderPass = renderPass };
for (int i = 0, j = 0; i < MRT::TARGET_COUNT; i++) {
if (rt->getColor(i).format != VK_FORMAT_UNDEFINED) {
fbkey.color[j++] = rt->getColor(i).view;
}
}
if (hasDepth) {
fbo.attachments[numAttachments++] = depth.view;
if (depth.format != VK_FORMAT_UNDEFINED) {
fbkey.depth = depth.view;
}
VkFramebuffer vkfb = mFramebufferCache.getFramebuffer(fbkey, extent.width, extent.height, 1);
// Populate the structures required for vkCmdBeginRenderPass.
VkRenderPassBeginInfo renderPassInfo {
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
.renderPass = renderPass,
.framebuffer = mFramebufferCache.getFramebuffer(fbo, extent.width, extent.height),
.framebuffer = vkfb,
.renderArea = {
.offset = {params.viewport.left, params.viewport.bottom},
.extent = {params.viewport.width, params.viewport.height}
@@ -911,15 +936,21 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
rt->transformClientRectToPlatform(&renderPassInfo.renderArea);
VkClearValue clearValues[2] = {};
if (hasColor) {
VkClearValue& clearValue = clearValues[renderPassInfo.clearValueCount++];
clearValue.color.float32[0] = params.clearColor.r;
clearValue.color.float32[1] = params.clearColor.g;
clearValue.color.float32[2] = params.clearColor.b;
clearValue.color.float32[3] = params.clearColor.a;
VkClearValue clearValues[MRT::TARGET_COUNT + 1] = {};
// NOTE: clearValues must be populated in the same order as the attachments array in
// VulkanFboCache::getFramebuffer. Values must be provided regardless of whether Vulkan is
// actually clearing that particular target.
for (int i = 0; i < MRT::TARGET_COUNT; i++) {
if (fbkey.color[i]) {
VkClearValue& clearValue = clearValues[renderPassInfo.clearValueCount++];
clearValue.color.float32[0] = params.clearColor.r;
clearValue.color.float32[1] = params.clearColor.g;
clearValue.color.float32[2] = params.clearColor.b;
clearValue.color.float32[3] = params.clearColor.a;
}
}
if (hasDepth) {
if (fbkey.depth) {
VkClearValue& clearValue = clearValues[renderPassInfo.clearValueCount++];
clearValue.depthStencil = {(float) params.clearDepth, 0};
}
@@ -931,16 +962,16 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
VK_SUBPASS_CONTENTS_INLINE);
VkViewport viewport = mContext.viewport = {
.x = (float) params.viewport.left,
.y = (float) params.viewport.bottom,
.width = (float) params.viewport.width,
.height = (float) params.viewport.height,
.minDepth = 0.0f,
.maxDepth = 1.0f
.x = (float) params.viewport.left,
.y = (float) params.viewport.bottom,
.width = (float) params.viewport.width,
.height = (float) params.viewport.height,
.minDepth = 0.0f,
.maxDepth = 1.0f
};
VkRect2D scissor {
.offset = { std::max(0, (int32_t) viewport.x), std::max(0, (int32_t) viewport.y) },
.extent = { (uint32_t) viewport.width, (uint32_t) viewport.height }
.offset = { std::max(0, (int32_t) viewport.x), std::max(0, (int32_t) viewport.y) },
.extent = { (uint32_t) viewport.width, (uint32_t) viewport.height }
};
mCurrentRenderTarget->transformClientRectToPlatform(&scissor);
@@ -1133,16 +1164,18 @@ void VulkanDriver::blit(TargetBufferFlags buffers,
auto dstTarget = handle_cast<VulkanRenderTarget>(mHandleMap, dst);
auto srcTarget = handle_cast<VulkanRenderTarget>(mHandleMap, src);
const int targetIndex = 0; // TODO: support MRT in blit
// In debug builds, verify that the two render targets have blittable formats.
#ifndef NDEBUG
const VkPhysicalDevice gpu = mContext.physicalDevice;
VkFormatProperties info;
vkGetPhysicalDeviceFormatProperties(gpu, srcTarget->getColor().format, &info);
vkGetPhysicalDeviceFormatProperties(gpu, srcTarget->getColor(targetIndex).format, &info);
if (!ASSERT_POSTCONDITION_NON_FATAL(info.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT,
"Source format is not blittable")) {
return;
}
vkGetPhysicalDeviceFormatProperties(gpu, dstTarget->getColor().format, &info);
vkGetPhysicalDeviceFormatProperties(gpu, dstTarget->getColor(targetIndex).format, &info);
if (!ASSERT_POSTCONDITION_NON_FATAL(info.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT,
"Destination format is not blittable")) {
return;
@@ -1154,11 +1187,11 @@ void VulkanDriver::blit(TargetBufferFlags buffers,
const int32_t srcRight = srcRect.left + srcRect.width;
const int32_t srcTop = srcRect.bottom + srcRect.height;
const uint32_t srcLevel = srcTarget->getColorLevel();
const uint32_t srcLevel = srcTarget->getColor(targetIndex).level;
const int32_t dstRight = dstRect.left + dstRect.width;
const int32_t dstTop = dstRect.bottom + dstRect.height;
const uint32_t dstLevel = dstTarget->getColorLevel();
const uint32_t dstLevel = dstTarget->getColor(targetIndex).level;
const VkImageAspectFlags aspect = VK_IMAGE_ASPECT_COLOR_BIT;
@@ -1170,11 +1203,11 @@ void VulkanDriver::blit(TargetBufferFlags buffers,
}};
auto vkblit = [=](VkCommandBuffer cmdbuffer) {
VkImage srcImage = srcTarget->getColor().image;
VkImage srcImage = srcTarget->getColor(targetIndex).image;
VulkanTexture::transitionImageLayout(cmdbuffer, srcImage, VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, srcLevel, 1, 1, aspect);
VkImage dstImage = dstTarget->getColor().image;
VkImage dstImage = dstTarget->getColor(targetIndex).image;
VulkanTexture::transitionImageLayout(cmdbuffer, dstImage, VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, dstLevel, 1, 1, aspect);
@@ -1185,10 +1218,12 @@ void VulkanDriver::blit(TargetBufferFlags buffers,
filter == SamplerMagFilter::NEAREST ? VK_FILTER_NEAREST : VK_FILTER_LINEAR);
VulkanTexture::transitionImageLayout(cmdbuffer, srcImage, VK_IMAGE_LAYOUT_UNDEFINED,
getTextureLayout(srcTarget->getColor().texture->usage), srcLevel, 1, 1, aspect);
getTextureLayout(srcTarget->getColor(targetIndex).texture->usage), srcLevel, 1, 1,
aspect);
VulkanTexture::transitionImageLayout(cmdbuffer, dstImage, VK_IMAGE_LAYOUT_UNDEFINED,
getTextureLayout(dstTarget->getColor().texture->usage), dstLevel, 1, 1, aspect);
getTextureLayout(dstTarget->getColor(targetIndex).texture->usage), dstLevel, 1, 1,
aspect);
};
if (!mContext.currentCommands) {
@@ -1241,15 +1276,17 @@ void VulkanDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> r
.colorWriteMask = (VkColorComponentFlags) (rasterState.colorWrite ? 0xf : 0x0),
};
auto& vkraster = mContext.rasterState.rasterization;
VkPipelineRasterizationStateCreateInfo& vkraster = mContext.rasterState.rasterization;
vkraster.cullMode = getCullMode(rasterState.culling);
vkraster.frontFace = getFrontFace(rasterState.inverseFrontFaces);
vkraster.depthBiasEnable = (depthOffset.constant || depthOffset.slope) ? VK_TRUE : VK_FALSE;
vkraster.depthBiasConstantFactor = depthOffset.constant;
vkraster.depthBiasSlopeFactor = depthOffset.slope;
VulkanBinder::ProgramBundle shaderHandles = program->bundle;
VulkanRenderTarget* rt = mCurrentRenderTarget;
mContext.rasterState.getColorTargetCount = rt->getColorTargetCount();
VulkanBinder::ProgramBundle shaderHandles = program->bundle;
// Push state changes to the VulkanBinder instance. This is fast and does not make VK calls.
mBinder.bindProgramBundle(shaderHandles);

View File

@@ -23,21 +23,23 @@ namespace backend {
bool VulkanFboCache::RenderPassEq::operator()(const RenderPassKey& k1,
const RenderPassKey& k2) const {
return
k1.colorLayout == k2.colorLayout &&
k1.depthLayout == k2.depthLayout &&
k1.colorFormat == k2.colorFormat &&
k1.depthFormat == k2.depthFormat &&
k1.flags.value == k2.flags.value;
if (k1.flags.value != k2.flags.value) return false;
if (k1.depthLayout != k2.depthLayout) return false;
if (k1.depthFormat != k2.depthFormat) return false;
for (int i = 0; i < MRT::TARGET_COUNT; i++) {
if (k1.colorLayout[i] != k2.colorLayout[i]) return false;
if (k1.colorFormat[i] != k2.colorFormat[i]) return false;
}
return true;
}
bool VulkanFboCache::FboKeyEqualFn::operator()(const FboKey& k1, const FboKey& k2) const {
static_assert(sizeof(FboKey::attachments) == 3 * sizeof(VkImageView), "Unexpected count.");
return
k1.renderPass == k2.renderPass &&
k1.attachments[0] == k2.attachments[0] &&
k1.attachments[1] == k2.attachments[1] &&
k1.attachments[2] == k2.attachments[2];
if (k1.renderPass != k2.renderPass) return false;
if (k1.depth != k2.depth) return false;
for (int i = 0; i < MRT::TARGET_COUNT; i++) {
if (k1.color[i] != k2.color[i]) return false;
}
return true;
}
VulkanFboCache::VulkanFboCache(VulkanContext& context) : mContext(context) {}
@@ -47,26 +49,31 @@ VulkanFboCache::~VulkanFboCache() {
"Please explicitly call reset() while the VkDevice is still alive.");
}
VkFramebuffer VulkanFboCache::getFramebuffer(FboKey config, uint32_t w, uint32_t h) noexcept {
VkFramebuffer VulkanFboCache::getFramebuffer(FboKey config, uint32_t width,
uint32_t height, uint32_t layers) noexcept {
auto iter = mFramebufferCache.find(config);
if (UTILS_LIKELY(iter != mFramebufferCache.end() && iter->second.handle != VK_NULL_HANDLE)) {
iter.value().timestamp = mCurrentTime;
return iter->second.handle;
}
VkImageView attachments[MRT::TARGET_COUNT + 1];
uint32_t nAttachments = 0;
for (auto attachment : config.attachments) {
for (VkImageView attachment : config.color) {
if (attachment) {
nAttachments++;
attachments[nAttachments++] = attachment;
}
}
if (config.depth) {
attachments[nAttachments++] = config.depth;
}
VkFramebufferCreateInfo info {
.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
.renderPass = config.renderPass,
.attachmentCount = nAttachments,
.pAttachments = config.attachments,
.width = w,
.height = h,
.layers = 1
.pAttachments = attachments,
.width = width,
.height = height,
.layers = layers,
};
mRenderPassRefCount[info.renderPass]++;
VkFramebuffer framebuffer;
@@ -82,22 +89,12 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept {
iter.value().timestamp = mCurrentTime;
return iter->second.handle;
}
const bool hasColor = config.colorFormat != VK_FORMAT_UNDEFINED;
const bool hasDepth = config.depthFormat != VK_FORMAT_UNDEFINED;
const bool isSwapChain = config.colorLayout == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
const bool isSwapChain = config.colorLayout[0] == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
// Set up some const aliases for terseness.
const VkAttachmentLoadOp clear = VK_ATTACHMENT_LOAD_OP_CLEAR;
const VkAttachmentLoadOp dontCare = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
const VkAttachmentLoadOp keep = VK_ATTACHMENT_LOAD_OP_LOAD;
const bool clearColor = any(config.flags.clear & TargetBufferFlags::COLOR);
const bool discardColor = any(config.flags.discardStart & TargetBufferFlags::COLOR);
const VkAttachmentLoadOp colorLoadOp = clearColor ? clear : (discardColor ? dontCare : keep);
const bool clearDepth = any(config.flags.clear & TargetBufferFlags::DEPTH);
const bool discardDepth = any(config.flags.discardStart & TargetBufferFlags::DEPTH);
const VkAttachmentLoadOp depthLoadOp = clearDepth ? clear : (discardDepth ? dontCare : keep);
const VkAttachmentLoadOp kClear = VK_ATTACHMENT_LOAD_OP_CLEAR;
const VkAttachmentLoadOp kDontCare = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
const VkAttachmentLoadOp kKeep = VK_ATTACHMENT_LOAD_OP_LOAD;
// In Vulkan, the subpass desc specifies the layout to transition to at the start of the render
// pass, and the attachment description specifies the layout to transition to at the end.
@@ -105,49 +102,34 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept {
// swap chain. We keep our offscreen images in GENERAL layout, which is simple and prevents
// thrashing the layout. Note that pipeline barriers are more powerful than render passes for
// performing layout transitions, because they allow for per-miplevel transitions.
struct { VkImageLayout subpass, initial, final; } colorLayouts;
const bool discard = any(config.flags.discardStart & TargetBufferFlags::COLOR);
struct { VkImageLayout subpass, initial, final; } colorLayouts[MRT::TARGET_COUNT];
if (isSwapChain) {
colorLayouts.subpass = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
colorLayouts.initial = discardColor ? VK_IMAGE_LAYOUT_UNDEFINED : colorLayouts.subpass;
colorLayouts.final = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
colorLayouts[0].subpass = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
colorLayouts[0].initial = discard ? VK_IMAGE_LAYOUT_UNDEFINED : colorLayouts[0].subpass;
colorLayouts[0].final = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
} else {
colorLayouts.subpass = config.colorLayout;
colorLayouts.initial = config.colorLayout;
colorLayouts.final = config.colorLayout;
for (int i = 0; i < MRT::TARGET_COUNT; i++) {
colorLayouts[i].subpass = config.colorLayout[i];
colorLayouts[i].initial = config.colorLayout[i];
colorLayouts[i].final = config.colorLayout[i];
}
}
VkAttachmentReference colorAttachmentRef = {};
VkAttachmentReference colorAttachmentRef[MRT::TARGET_COUNT] = {};
VkAttachmentReference depthAttachmentRef = {};
const bool hasDepth = config.depthFormat != VK_FORMAT_UNDEFINED;
VkSubpassDescription subpass {
.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
.colorAttachmentCount = hasColor ? 1u : 0u,
.pColorAttachments = hasColor ? &colorAttachmentRef : nullptr,
.colorAttachmentCount = 0u,
.pColorAttachments = colorAttachmentRef,
.pDepthStencilAttachment = hasDepth ? &depthAttachmentRef : nullptr
};
VkAttachmentDescription colorAttachment {
.format = config.colorFormat,
.samples = VK_SAMPLE_COUNT_1_BIT,
.loadOp = colorLoadOp,
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
.initialLayout = colorLayouts.initial,
.finalLayout = colorLayouts.final
};
VkAttachmentDescription depthAttachment {
.format = config.depthFormat,
.samples = VK_SAMPLE_COUNT_1_BIT,
.loadOp = depthLoadOp,
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
.initialLayout = config.depthLayout,
.finalLayout = config.depthLayout
};
VkAttachmentDescription attachments[MRT::TARGET_COUNT + 1] = {};
VkAttachmentDescription attachments[2];
VkRenderPassCreateInfo renderPassInfo {
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
.attachmentCount = 0u,
@@ -157,17 +139,48 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept {
.dependencyCount = 0u
};
if (hasColor) {
colorAttachmentRef.layout = colorLayouts.subpass;
colorAttachmentRef.attachment = renderPassInfo.attachmentCount;
attachments[renderPassInfo.attachmentCount++] = colorAttachment;
int numAttachments = 0;
for (int i = 0; i < MRT::TARGET_COUNT; i++) {
if (config.colorFormat[i] == VK_FORMAT_UNDEFINED) {
continue;
}
TargetBufferFlags flag = TargetBufferFlags(int(TargetBufferFlags::COLOR0) << i);
bool clear = any(config.flags.clear & flag);
bool discard = any(config.flags.discardStart & flag);
colorAttachmentRef[numAttachments].layout = colorLayouts[i].subpass;
colorAttachmentRef[numAttachments].attachment = numAttachments;
attachments[numAttachments] = {
.format = config.colorFormat[i],
.samples = VK_SAMPLE_COUNT_1_BIT,
.loadOp = clear ? kClear : (discard ? kDontCare : kKeep),
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
.initialLayout = colorLayouts[i].initial,
.finalLayout = colorLayouts[i].final
};
++numAttachments;
}
subpass.colorAttachmentCount = numAttachments;
if (hasDepth) {
bool clear = any(config.flags.clear & TargetBufferFlags::DEPTH);
bool discard = any(config.flags.discardStart & TargetBufferFlags::DEPTH);
depthAttachmentRef.layout = config.depthLayout;
depthAttachmentRef.attachment = renderPassInfo.attachmentCount;
attachments[renderPassInfo.attachmentCount++] = depthAttachment;
depthAttachmentRef.attachment = numAttachments;
attachments[numAttachments] = {
.format = config.depthFormat,
.samples = VK_SAMPLE_COUNT_1_BIT,
.loadOp = clear ? kClear : (discard ? kDontCare : kKeep),
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
.initialLayout = config.depthLayout,
.finalLayout = config.depthLayout
};
++numAttachments;
}
renderPassInfo.attachmentCount = numAttachments;
// Finally, create the VkRenderPass.
VkRenderPass renderPass;

View File

@@ -21,6 +21,8 @@
#include <utils/Hash.h>
#include <backend/TargetBufferInfo.h>
#include <tsl/robin_map.h>
namespace filament {
@@ -35,12 +37,11 @@ namespace backend {
class VulkanFboCache {
public:
// RenderPassKey is a small POD representing the immutable state that is used to construct
// a VkRenderPass. It is hashed and used as a lookup key. Portions of this are extracted
// from backend::RenderPassParams.
// a VkRenderPass. It is hashed and used as a lookup key.
struct alignas(8) RenderPassKey {
VkImageLayout colorLayout; // 4 bytes
VkImageLayout colorLayout[MRT::TARGET_COUNT]; // 16 bytes
VkFormat colorFormat[MRT::TARGET_COUNT]; // 16 bytes
VkImageLayout depthLayout; // 4 bytes
VkFormat colorFormat; // 4 bytes
VkFormat depthFormat; // 4 bytes
union {
struct {
@@ -57,20 +58,22 @@ public:
VkRenderPass handle;
uint32_t timestamp;
};
static_assert(sizeof(TargetBufferFlags) == 1, "TargetBufferFlags has unexpected size.");
static_assert(sizeof(VkFormat) == 4, "VkFormat has unexpected size.");
static_assert(sizeof(RenderPassKey) == 24, "RenderPassKey has unexpected size.");
static_assert(sizeof(RenderPassKey) == 48, "RenderPassKey has unexpected size.");
using RenderPassHash = utils::hash::MurmurHashFn<RenderPassKey>;
struct RenderPassEq {
bool operator()(const RenderPassKey& k1, const RenderPassKey& k2) const;
};
// FboKey is a small POD representing the immutable state that we wish to configure
// in VkFramebuffer. It is hashed and used as a lookup key. There are 1-3 attachments, but
// in VkFramebuffer. It is hashed and used as a lookup key. There are several attachments, but
// rather than storing a count, we simply zero out the unused slots. We do not bother storing
// width and height in the key since they are immutable aspects of the image views.
struct alignas(8) FboKey {
VkRenderPass renderPass; // 8 bytes
VkImageView attachments[3]; // 24 bytes
VkImageView color[MRT::TARGET_COUNT]; // 32 bytes
VkImageView depth; // 8 bytes
};
struct FboVal {
VkFramebuffer handle;
@@ -78,7 +81,7 @@ public:
};
static_assert(sizeof(VkRenderPass) == 8, "VkRenderPass has unexpected size.");
static_assert(sizeof(VkImageView) == 8, "VkImageView has unexpected size.");
static_assert(sizeof(FboKey) == 32, "FboKey has unexpected size.");
static_assert(sizeof(FboKey) == 48, "FboKey has unexpected size.");
using FboKeyHashFn = utils::hash::MurmurHashFn<FboKey>;
struct FboKeyEqualFn {
bool operator()(const FboKey& k1, const FboKey& k2) const;
@@ -87,9 +90,12 @@ public:
explicit VulkanFboCache(VulkanContext&);
~VulkanFboCache();
// Retrieves or creates a VkFramebuffer handle. Width and height are used only when
// creating the framebuffer (they are not used for lookup),
VkFramebuffer getFramebuffer(FboKey config, uint32_t width, uint32_t height) noexcept;
// Retrieves or creates a VkFramebuffer handle.
//
// NOTE: The dimensions are used only when creating the the framebuffer. They are not used for
// lookup because the attachments in the FboKey already have dimensions.
VkFramebuffer getFramebuffer(FboKey config, uint32_t width, uint32_t height,
uint32_t layers) noexcept;
// Retrieves or creates a VkRenderPass handle.
VkRenderPass getRenderPass(RenderPassKey config) noexcept;

View File

@@ -102,58 +102,68 @@ static VulkanAttachment createOffscreenAttachment(VulkanTexture* tex) {
// Creates a special "default" render target (i.e. associated with the swap chain)
// Note that the attachment structs are unused in this case in favor of VulkanSurfaceContext.
VulkanRenderTarget::VulkanRenderTarget(VulkanContext& context) : HwRenderTarget(0, 0),
mContext(context), mOffscreen(false), mColorLevel(0), mDepthLevel(0) {}
mContext(context), mOffscreen(false) {}
VulkanRenderTarget::VulkanRenderTarget(VulkanContext& context, uint32_t width, uint32_t height,
TargetBufferInfo colorInfo, VulkanTexture* color, TargetBufferInfo depthInfo,
VulkanTexture* depth) : HwRenderTarget(width, height), mContext(context), mOffscreen(true),
mColorLevel(colorInfo.level), mDepthLevel(depthInfo.level) {
mColor = color ? createOffscreenAttachment(color) : VulkanAttachment {};
mDepth = depth ? createOffscreenAttachment(depth) : VulkanAttachment {};
VulkanAttachment color[MRT::TARGET_COUNT], VulkanAttachment depthStencil[2]) :
HwRenderTarget(width, height), mContext(context), mOffscreen(true) {
for (int targetIndex = 0; targetIndex < MRT::TARGET_COUNT; targetIndex++) {
VulkanAttachment& attachment = mColor[targetIndex];
if (!color[targetIndex].texture) {
attachment = {};
continue;
}
attachment = createOffscreenAttachment(color[targetIndex].texture);
attachment.level = color[targetIndex].level;
// We cannot use the VkImageView that's in the texture because we need to select a single level.
if (color) {
VkImageViewCreateInfo viewInfo = {
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.image = mColor.image,
.format = mColor.format,
.image = mColor[targetIndex].image,
.format = mColor[targetIndex].format,
.subresourceRange = {
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.baseMipLevel = colorInfo.level,
.baseMipLevel = mColor[targetIndex].level,
.levelCount = 1
}
};
if (color->target == SamplerType::SAMPLER_CUBEMAP) {
if (attachment.texture->target == SamplerType::SAMPLER_CUBEMAP) {
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_CUBE;
viewInfo.subresourceRange.layerCount = 6;
} else if (color->target == SamplerType::SAMPLER_2D_ARRAY) {
} else if (attachment.texture->target == SamplerType::SAMPLER_2D_ARRAY) {
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY;
viewInfo.subresourceRange.layerCount = 1;
viewInfo.subresourceRange.baseArrayLayer = colorInfo.layer;
viewInfo.subresourceRange.baseArrayLayer = attachment.layer;
} else {
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
viewInfo.subresourceRange.layerCount = 1;
}
vkCreateImageView(context.device, &viewInfo, VKALLOC, &mColor.view);
vkCreateImageView(context.device, &viewInfo, VKALLOC, &attachment.view);
}
if (depth) {
mDepth = depthStencil[0].texture ? createOffscreenAttachment(depthStencil[0].texture) : VulkanAttachment {};
mDepth.level = depthStencil[0].level;
VulkanTexture* depthTexture = mDepth.texture;
if (depthTexture) {
VkImageViewCreateInfo viewInfo = {
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.image = mDepth.image,
.format = mDepth.format,
.subresourceRange = {
.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT,
.baseMipLevel = depthInfo.level,
.baseMipLevel = mDepth.level,
.levelCount = 1
}
};
if (depth->target == SamplerType::SAMPLER_CUBEMAP) {
if (depthTexture->target == SamplerType::SAMPLER_CUBEMAP) {
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_CUBE;
viewInfo.subresourceRange.layerCount = 6;
} else if (depth->target == SamplerType::SAMPLER_2D_ARRAY) {
} else if (depthTexture->target == SamplerType::SAMPLER_2D_ARRAY) {
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY;
viewInfo.subresourceRange.layerCount = 1;
viewInfo.subresourceRange.baseArrayLayer = depthInfo.layer;
viewInfo.subresourceRange.baseArrayLayer = mDepth.layer;
} else {
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
viewInfo.subresourceRange.layerCount = 1;
@@ -163,8 +173,10 @@ VulkanRenderTarget::VulkanRenderTarget(VulkanContext& context, uint32_t width, u
}
VulkanRenderTarget::~VulkanRenderTarget() {
if (mColor.view) {
vkDestroyImageView(mContext.device, mColor.view, VKALLOC);
for (int targetIndex = 0; targetIndex < MRT::TARGET_COUNT; targetIndex++) {
if (mColor[targetIndex].view) {
vkDestroyImageView(mContext.device, mColor[targetIndex].view, VKALLOC);
}
}
if (mDepth.view) {
vkDestroyImageView(mContext.device, mDepth.view, VKALLOC);
@@ -221,14 +233,27 @@ VkExtent2D VulkanRenderTarget::getExtent() const {
return mContext.currentSurface->surfaceCapabilities.currentExtent;
}
VulkanAttachment VulkanRenderTarget::getColor() const {
return mOffscreen ? mColor : getSwapContext(mContext).attachment;
VulkanAttachment VulkanRenderTarget::getColor(int target) const {
return (mOffscreen || target > 0) ? mColor[target] : getSwapContext(mContext).attachment;
}
VulkanAttachment VulkanRenderTarget::getDepth() const {
return mOffscreen ? mDepth : mContext.currentSurface->depth;
}
int VulkanRenderTarget::getColorTargetCount() const {
if (!mOffscreen) {
return 1;
}
int count = 0;
for (int i = 0; i < MRT::TARGET_COUNT; i++) {
if (mColor[i].format != VK_FORMAT_UNDEFINED) {
++count;
}
}
return count;
}
bool VulkanRenderTarget::invalidate() {
if (!mOffscreen && getSwapContext(mContext).invalid) {
getSwapContext(mContext).invalid = false;

View File

@@ -32,21 +32,18 @@ struct VulkanProgram : public HwProgram {
Program::SamplerGroupInfo samplerGroupInfo;
};
struct VulkanTexture;
// The render target bundles together a set of attachments, each of which can have one of the
// following ownership semantics:
//
// - The attachment's VkImage is shared and the owner is VulkanSwapChain (mOffScreen = false).
// - The attachment's VkImage is shared and the owner is VulkanTexture (mOffScreen = true).
// - The attachment's VkImage is shared and the owner is VulkanSwapChain (mOffscreen = false).
// - The attachment's VkImage is shared and the owner is VulkanTexture (mOffscreen = true).
//
// We use private inheritance to shield clients from the width / height fields in HwRenderTarget,
// which are not representative when this is the default render target.
struct VulkanRenderTarget : private HwRenderTarget {
// Creates an offscreen render target.
VulkanRenderTarget(VulkanContext& context, uint32_t w, uint32_t h, TargetBufferInfo colorInfo,
VulkanTexture* color, TargetBufferInfo depthInfo, VulkanTexture* depth);
VulkanRenderTarget(VulkanContext& context, uint32_t width, uint32_t height,
VulkanAttachment color[MRT::TARGET_COUNT], VulkanAttachment depthStencil[2]);
// Creates a special "default" render target (i.e. associated with the swap chain)
explicit VulkanRenderTarget(VulkanContext& context);
@@ -56,18 +53,15 @@ struct VulkanRenderTarget : private HwRenderTarget {
void transformClientRectToPlatform(VkRect2D* bounds) const;
void transformClientRectToPlatform(VkViewport* bounds) const;
VkExtent2D getExtent() const;
VulkanAttachment getColor() const;
VulkanAttachment getColor(int target) const;
VulkanAttachment getDepth() const;
int getColorTargetCount() const;
bool invalidate();
uint32_t getColorLevel() const { return mColorLevel; }
uint32_t getDepthLevel() const { return mDepthLevel; }
private:
VulkanAttachment mColor = {};
VulkanAttachment mColor[MRT::TARGET_COUNT] = {};
VulkanAttachment mDepth = {};
VulkanContext& mContext;
bool mOffscreen;
uint32_t mColorLevel;
uint32_t mDepthLevel;
};
struct VulkanSwapChain : public HwSwapChain {