diff --git a/filament/backend/include/backend/TargetBufferInfo.h b/filament/backend/include/backend/TargetBufferInfo.h index bce359ba8f..57c26c3e8d 100644 --- a/filament/backend/include/backend/TargetBufferInfo.h +++ b/filament/backend/include/backend/TargetBufferInfo.h @@ -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 { diff --git a/filament/backend/src/metal/MetalHandles.h b/filament/backend/src/metal/MetalHandles.h index e4113156c4..5304aba7f0 100644 --- a/filament/backend/src/metal/MetalHandles.h +++ b/filament/backend/src/metal/MetalHandles.h @@ -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 multisampledColor[4] = { 0 }; + id multisampledColor[MRT::TARGET_COUNT] = { 0 }; id multisampledDepth = nil; }; diff --git a/filament/backend/src/metal/MetalHandles.mm b/filament/backend/src/metal/MetalHandles.mm index 438bc739a8..9cae36b2e4 100644 --- a/filament/backend/src/metal/MetalHandles.mm +++ b/filament/backend/src/metal/MetalHandles.mm @@ -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); diff --git a/filament/backend/src/vulkan/VulkanBinder.cpp b/filament/backend/src/vulkan/VulkanBinder.cpp index 024794df7a..84d5763946 100644 --- a/filament/backend/src/vulkan/VulkanBinder.cpp +++ b/filament/backend/src/vulkan/VulkanBinder.cpp @@ -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, }; } diff --git a/filament/backend/src/vulkan/VulkanBinder.h b/filament/backend/src/vulkan/VulkanBinder.h index 326751f56f..1980d35a15 100644 --- a/filament/backend/src/vulkan/VulkanBinder.h +++ b/filament/backend/src/vulkan/VulkanBinder.h @@ -18,6 +18,7 @@ #define TNT_FILAMENT_DRIVER_VULKANBINDER_H #include +#include #include @@ -101,6 +102,7 @@ public: VkPipelineColorBlendAttachmentState blending; VkPipelineDepthStencilStateCreateInfo depthStencil; VkPipelineMultisampleStateCreateInfo multisampling; + uint32_t getColorTargetCount; }; static_assert(std::is_pod::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 diff --git a/filament/backend/src/vulkan/VulkanContext.h b/filament/backend/src/vulkan/VulkanContext.h index 93ba3d9f08..cf2dd8bafc 100644 --- a/filament/backend/src/vulkan/VulkanContext.h +++ b/filament/backend/src/vulkan/VulkanContext.h @@ -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(). diff --git a/filament/backend/src/vulkan/VulkanDriver.cpp b/filament/backend/src/vulkan/VulkanDriver.cpp index 3be1ceaa24..c005e9ff47 100644 --- a/filament/backend/src/vulkan/VulkanDriver.cpp +++ b/filament/backend/src/vulkan/VulkanDriver.cpp @@ -443,10 +443,28 @@ void VulkanDriver::createDefaultRenderTargetR(Handle rth, int) { void VulkanDriver::createRenderTargetR(Handle 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(mHandleMap, color[0].handle) : nullptr; - auto depthTexture = depth.handle ? handle_cast(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(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(mHandleMap, handle) : nullptr; + depthStencil[0].level = depth.level; + depthStencil[0].layer = depth.layer; + + handle = stencil.handle; + depthStencil[1].texture = handle ? handle_cast(mHandleMap, handle) : nullptr; + depthStencil[1].level = stencil.level; + depthStencil[1].layer = stencil.layer; + auto renderTarget = construct_handle(mHandleMap, rth, mContext, - width, height, color[0], colorTexture, depth, depthTexture); + width, height, colorTargets, depthStencil); mDisposer.createDisposable(renderTarget, [this, rth] () { destruct_handle(mHandleMap, rth); }); @@ -859,50 +877,57 @@ void VulkanDriver::beginRenderPass(Handle 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 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 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(mHandleMap, dst); auto srcTarget = handle_cast(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 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); diff --git a/filament/backend/src/vulkan/VulkanFboCache.cpp b/filament/backend/src/vulkan/VulkanFboCache.cpp index c7987d5ba0..a67c3524e4 100644 --- a/filament/backend/src/vulkan/VulkanFboCache.cpp +++ b/filament/backend/src/vulkan/VulkanFboCache.cpp @@ -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; diff --git a/filament/backend/src/vulkan/VulkanFboCache.h b/filament/backend/src/vulkan/VulkanFboCache.h index bc8ca67e8f..377252c0e1 100644 --- a/filament/backend/src/vulkan/VulkanFboCache.h +++ b/filament/backend/src/vulkan/VulkanFboCache.h @@ -21,6 +21,8 @@ #include +#include + #include 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; 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; 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; diff --git a/filament/backend/src/vulkan/VulkanHandles.cpp b/filament/backend/src/vulkan/VulkanHandles.cpp index 30360c581f..eb99b520fd 100644 --- a/filament/backend/src/vulkan/VulkanHandles.cpp +++ b/filament/backend/src/vulkan/VulkanHandles.cpp @@ -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; diff --git a/filament/backend/src/vulkan/VulkanHandles.h b/filament/backend/src/vulkan/VulkanHandles.h index 001cac064c..7f9dc8c694 100644 --- a/filament/backend/src/vulkan/VulkanHandles.h +++ b/filament/backend/src/vulkan/VulkanHandles.h @@ -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 {