diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp index d7680718..dc9c78dc 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp @@ -95,16 +95,50 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (attachment.IsLayered()) { const auto& texture = attachment.GetTexture(); const TextureTarget target = texture != nullptr ? texture->GetTarget() : TextureTarget::Unknown; + // A layered CUBE MAP names all six faces (GL 4.6 core 9.2.8), but the attachment model + // records only the REPRESENTATIVE upload target for it - the +X face + // (ResolveRepresentableFramebufferTextureUploadTarget) - and that face's level size has + // z = 1. Reading z here therefore attached one face to a layered framebuffer, so a + // geometry shader writing gl_Layer = 1..5 lost five sixths of its output. The image's + // six layers are the cube's faces, exactly as for a cube ARRAY (whose representative + // target does carry 6n in z and needs no special case). + if (target == TextureTarget::TextureCubeMap) { + return 6u; + } return static_cast(std::max(ToVulkanLevelExtent(target, attachment.GetSize()).z(), 1)); } return 1u; } + // VUID-VkFramebufferCreateInfo-flags-04113: every view handed to vkCreateFramebuffer must have + // been created as VK_IMAGE_VIEW_TYPE_2D or VK_IMAGE_VIEW_TYPE_2D_ARRAY. The image's OWN view + // type is not a legal answer for several of the targets GL can attach, and returning it + // unchanged is what took the process down on every layered 3D / cube-map-array attachment: + // a 3D view is refused outright by the layer-span guard in GetOrCreateAttachmentViewAtMipLevel + // (3D images have arrayLayers == 1) and a CUBE_ARRAY view is built happily and then rejected - + // or dereferenced - by the driver inside vkCreateFramebuffer. + // + // A 2D_ARRAY view is the legal spelling of all three: over a 2D-array-compatible 3D image its + // "layers" are the mip's z slices (VUID-VkImageViewCreateInfo-image-04970), and over a + // CUBE_COMPATIBLE 2D image - which is what both cube targets are - its layers are the faces. + // + // Knowingly NOT remapped: VK_IMAGE_VIEW_TYPE_1D / _1D_ARRAY, which 04113 also forbids. There is + // no legal alternative for them (a VK_IMAGE_TYPE_1D image admits no 2D-family view at all), so + // the only honest answer would be to decline the attachment - and every driver this has run on, + // lavapipe included, accepts them. Declining would turn working GL_TEXTURE_1D[_ARRAY] render + // targets into skipped draws to satisfy a VU nothing enforces. Left as-is, deliberately. static VkImageViewType ResolveAttachmentViewType( const MG_State::GLState::FramebufferAttachmentObject& attachment, const VkTextureManager::TextureResource& resource) { if (attachment.IsLayered()) { - return resource.viewType; + switch (resource.viewType) { + case VK_IMAGE_VIEW_TYPE_3D: + case VK_IMAGE_VIEW_TYPE_CUBE: + case VK_IMAGE_VIEW_TYPE_CUBE_ARRAY: + return VK_IMAGE_VIEW_TYPE_2D_ARRAY; + default: + return resource.viewType; + } } // A non-layered attachment names ONE layer, so the view over it is a plain 2D view whatever // the image's own view type is. The cube-face upload targets always meant this; a cube map @@ -112,8 +146,15 @@ namespace MobileGL::MG_Backend::DirectVulkan { // a single layer is not a legal attachment. The CUBE arm is inert today - no frontend path // produces a non-layered cube attachment without a face upload target - and is kept for // symmetry with CUBE_ARRAY. + // + // 3D belongs in the same list and was missing from it, which is why the "per-slice + // attachment view is a 2D view whose array layer is the slice" branch in + // GetOrCreateAttachmentViewAtMipLevel was unreachable: glFramebufferTextureLayer on a + // GL_TEXTURE_3D asked for a 3D view (illegal as an attachment) whose span was then checked + // against arrayLayers == 1, so every slice above z = 0 came back VK_NULL_HANDLE. if (IsCubeMapFaceUploadTarget(attachment.GetTextureUploadTarget()) || - resource.viewType == VK_IMAGE_VIEW_TYPE_CUBE_ARRAY || resource.viewType == VK_IMAGE_VIEW_TYPE_CUBE) { + resource.viewType == VK_IMAGE_VIEW_TYPE_CUBE_ARRAY || resource.viewType == VK_IMAGE_VIEW_TYPE_CUBE || + resource.viewType == VK_IMAGE_VIEW_TYPE_3D) { return VK_IMAGE_VIEW_TYPE_2D; } return resource.viewType; @@ -772,7 +813,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { return XXH64_digest(m_hashState); } - RenderPassEntry& VkRenderPassManager::GetOrCreateRenderPass(const MG_State::GLState::FramebufferObject& fbo, + RenderPassEntry* VkRenderPassManager::GetOrCreateRenderPass(const MG_State::GLState::FramebufferObject& fbo, Uint32 swapchainImageIndex, Bool drawUsesDepthStencil) { // Resolve the default-FBO depth flavor (see the header comment): keep the @@ -858,7 +899,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { auto activeIt = m_renderPasses.find(activeRenderPass->hash); if (activeIt != m_renderPasses.end()) { activeIt->second.lastUsedFrame = m_frameCounter; - return activeIt->second; + return &activeIt->second; } } @@ -882,13 +923,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_rpFastRenderPassHash = activeRenderPass->hash; m_rpFastHadDepthStencil = activeIt->second.hasDepthStencilAttachment; activeIt->second.lastUsedFrame = m_frameCounter; - return activeIt->second; + return &activeIt->second; } auto hash = ComputeHash(fbo, swapchainImageIndex, true, includeDefaultFboDepthStencil); auto it = m_renderPasses.find(hash); if (it != m_renderPasses.end()) { it->second.lastUsedFrame = m_frameCounter; - return it->second; + return &it->second; } Bool isDefaultFbo = fbo.IsDefaultFramebuffer(); @@ -1011,8 +1052,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { textureResources.emplace_back(nullptr); attachmentViews.emplace_back(rbAttachmentFormat != rbResource->format ? rbResource->unormTwinView : rbResource->view); - MOBILEGL_ASSERT(attachmentViews.back() != VK_NULL_HANDLE, - "GetOrCreateRenderPass: renderbuffer view missing at color attachment %d", i); + if (attachmentViews.back() == VK_NULL_HANDLE) { + MGLOG_E_ONCE("GetOrCreateRenderPass: renderbuffer %u has no usable view for color attachment " + "%u on FBO %u; declining the render pass", + renderbuffer->GetExternalIndex(), i, fbo.GetExternalIndex()); + return nullptr; + } colorAttachmentRefs[i].attachment = rbAttachmentIndex; continue; @@ -1100,8 +1145,15 @@ namespace MobileGL::MG_Backend::DirectVulkan { attachmentViews.emplace_back(swapchainViews[swapchainImageIndex]); } else { auto* textureResource = m_textureManager.SyncTextureAndGetDescriptor(*texture); - MOBILEGL_ASSERT(textureResource, - "GetOrCreateRenderPass: SyncTextureAndGetDescriptor failed at color attachment %d", i); + if (textureResource == nullptr) { + // SyncTextureResource legitimately declines - an unsupported format, + // sample count or image-flag combination, or a vkCreateImage the driver + // refused. There is no image to attach, so there is no render pass. + MGLOG_E_ONCE("GetOrCreateRenderPass: textureId=%d could not be backed for color " + "attachment %u on FBO %u; declining the render pass", + texture->GetExternalIndex(), i, fbo.GetExternalIndex()); + return nullptr; + } textureResources.emplace_back(textureResource); desc.format = ResolveSrgbAttachmentWriteFormat( textureResource->format, @@ -1122,8 +1174,15 @@ namespace MobileGL::MG_Backend::DirectVulkan { attachmentViews.emplace_back( m_textureManager.GetOrCreateAttachmentViewAtMipLevel( *texture, attachmentMipLevel, baseArrayLayer, layerCount, attachmentViewType)); - MOBILEGL_ASSERT(attachmentViews.back() != VK_NULL_HANDLE, - "GetOrCreateRenderPass: GetOrCreateAttachmentView failed at color attachment %d", i); + if (attachmentViews.back() == VK_NULL_HANDLE) { + MGLOG_E_ONCE("GetOrCreateRenderPass: no attachment view for textureId=%d mip=%u layers " + "[%u, %u) viewType=%d at color attachment %u on FBO %u; declining the " + "render pass", + texture->GetExternalIndex(), attachmentMipLevel, baseArrayLayer, + baseArrayLayer + layerCount, static_cast(attachmentViewType), i, + fbo.GetExternalIndex()); + return nullptr; + } } desc.samples = attachmentSampleCount; adoptRenderPassSampleCount(attachmentSampleCount, "color", texture->GetExternalIndex()); @@ -1216,8 +1275,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { } else if (selectedDepthStencilAttachment->IsTexture()) { auto& texture = *selectedDepthStencilAttachment->GetTexture(); depthTextureResource = m_textureManager.SyncTextureAndGetDescriptor(texture); - MOBILEGL_ASSERT(depthTextureResource, - "GetOrCreateRenderPass: SyncTextureAndGetDescriptor failed at depth attachment"); + if (depthTextureResource == nullptr) { + MGLOG_E_ONCE("GetOrCreateRenderPass: textureId=%d could not be backed for the depth/stencil " + "attachment of FBO %u; declining the render pass", + texture.GetExternalIndex(), fbo.GetExternalIndex()); + return nullptr; + } trackedDepthLayout = depthTextureResource->layout; depthAttachmentDescription.format = depthTextureResource->format; depthAttachmentSampleCount = depthTextureResource->sampleCount; @@ -1229,8 +1292,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { } else { const auto& renderbuffer = selectedDepthStencilAttachment->GetRenderbuffer(); depthRenderbufferResource = GetOrCreateRenderbufferResource(renderbuffer); - MOBILEGL_ASSERT(depthRenderbufferResource, - "GetOrCreateRenderPass: GetOrCreateRenderbufferResource failed at depth attachment"); + if (depthRenderbufferResource == nullptr) { + MGLOG_E_ONCE("GetOrCreateRenderPass: renderbuffer %u could not be backed for the depth/stencil " + "attachment of FBO %u; declining the render pass", + renderbuffer->GetExternalIndex(), fbo.GetExternalIndex()); + return nullptr; + } trackedDepthLayout = depthRenderbufferResource->layout; depthAttachmentDescription.format = depthRenderbufferResource->format; depthAttachmentSampleCount = depthRenderbufferResource->sampleCount; @@ -1304,8 +1371,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { attachmentViews.emplace_back( m_textureManager.GetOrCreateAttachmentViewAtMipLevel( texture, attachmentMipLevel, baseArrayLayer, layerCount, attachmentViewType)); - MOBILEGL_ASSERT(attachmentViews.back() != VK_NULL_HANDLE, - "GetOrCreateRenderPass: GetOrCreateAttachmentView failed at depth attachment"); + if (attachmentViews.back() == VK_NULL_HANDLE) { + MGLOG_E_ONCE("GetOrCreateRenderPass: no attachment view for textureId=%d mip=%u layers [%u, %u) " + "viewType=%d at the depth/stencil attachment of FBO %u; declining the render pass", + texture.GetExternalIndex(), attachmentMipLevel, baseArrayLayer, + baseArrayLayer + layerCount, static_cast(attachmentViewType), + fbo.GetExternalIndex()); + return nullptr; + } if (width == 0 || height == 0) { width = attachmentExtent.x(); height = attachmentExtent.y(); @@ -1327,6 +1400,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { }); textureResources.emplace_back(nullptr); attachmentViews.emplace_back(depthRenderbufferResource->view); + if (attachmentViews.back() == VK_NULL_HANDLE) { + MGLOG_E_ONCE("GetOrCreateRenderPass: renderbuffer %u has no usable view for the depth/stencil " + "attachment of FBO %u; declining the render pass", + renderbuffer->GetExternalIndex(), fbo.GetExternalIndex()); + return nullptr; + } if (width == 0 || height == 0) { width = attachmentExtent.x(); height = attachmentExtent.y(); @@ -1426,6 +1505,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkRenderPass renderPass = VK_NULL_HANDLE; VK_VERIFY(vkCreateRenderPass(m_device, &renderPassCreateInfo, nullptr, &renderPass)); + // VK_VERIFY only logs (and only in an INFO build); the handle is the truth. Caching an + // entry whose VkRenderPass is null would hand VK_NULL_HANDLE to vkCmdBeginRenderPass and + // to every pipeline built against it. + if (renderPass == VK_NULL_HANDLE) { + MGLOG_E_ONCE("GetOrCreateRenderPass: vkCreateRenderPass failed for FBO %u; declining the render pass", + fbo.GetExternalIndex()); + return nullptr; + } // Framebuffer VkFramebufferCreateInfo framebufferCreateInfo; @@ -1440,6 +1527,16 @@ namespace MobileGL::MG_Backend::DirectVulkan { framebufferCreateInfo.layers = framebufferLayers; VkFramebuffer framebuffer = VK_NULL_HANDLE; VK_VERIFY(vkCreateFramebuffer(m_device, &framebufferCreateInfo, nullptr, &framebuffer)); + if (framebuffer == VK_NULL_HANDLE) { + // The render pass has no entry to own it yet, so it is destroyed here rather than + // leaked - RenderPassEntry's destructor is the only other thing that would. + MGLOG_E_ONCE("GetOrCreateRenderPass: vkCreateFramebuffer failed for FBO %u (%dx%d, %u attachments, " + "%u layers); declining the render pass", + fbo.GetExternalIndex(), width, height, + static_cast(attachmentViews.size()), framebufferLayers); + vkDestroyRenderPass(m_device, renderPass, nullptr); + return nullptr; + } IntVec2 extent = {width, height}; RenderPassEntry renderPassEntry { hash, @@ -1464,7 +1561,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { extent.y()); auto [insertedIt, _] = m_renderPasses.emplace(hash, Move(renderPassEntry)); insertedIt->second.lastUsedFrame = m_frameCounter; - return insertedIt->second; + return &insertedIt->second; } void VkRenderPassManager::OnPresent() { diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h index 0cf11ee4..17080b55 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h @@ -243,9 +243,24 @@ namespace MobileGL::MG_Backend::DirectVulkan { // draw against a depth-less active pass resolves to a new (incompatible) // entry, which the caller's compatibility check turns into a pass split; // the new pass's depth loads DONT_CARE (content was undefined all along). - RenderPassEntry& GetOrCreateRenderPass(const MG_State::GLState::FramebufferObject& fbo, - Uint32 swapchainImageIndex, - Bool drawUsesDepthStencil = true); + // + // Returns NULLPTR when this framebuffer cannot be represented as a Vulkan render pass at + // all - a texture the texture manager declined to back (an unsupported format or sample + // count), or an attachment view it cannot construct (a layer span the image has no room + // for, a 3D image whose format was refused 2D-array compatibility). This used to be + // unrepresentable: the function returned a reference, so the only thing the two fallible + // calls it builds on could do was trip a MOBILEGL_ASSERT - which is compiled out of every + // INFO build - and then dereference the null resource, or hand VK_NULL_HANDLE to + // vkCreateFramebuffer. That took the whole process down (51 lost CTS records over 21 + // bodies, one runner restart each) where a declined draw is merely a wrong picture. + // + // EVERY caller must handle nullptr by dropping the operation, exactly as the draw path + // already drops a draw whose sampler descriptor could not be resolved + // (UniformManager::BindProgramUniformBuffers). The failure paths log MGLOG_E_ONCE + // themselves, so a caller needs no message of its own. + [[nodiscard]] RenderPassEntry* GetOrCreateRenderPass(const MG_State::GLState::FramebufferObject& fbo, + Uint32 swapchainImageIndex, + Bool drawUsesDepthStencil = true); void QueueRenderbufferClear(GLbitfield mask, const ClearFramebufferPayload& clearPayload, const MG_State::GLState::FramebufferObject& drawFbo); void QueueRenderbufferClear(const ClearAttachmentPayload& clearPayload, diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp index 29bc3d9c..63ae6165 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp @@ -921,18 +921,28 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (mipLevel >= resource->mipLevels) { return VK_NULL_HANDLE; } - // A 3D image has arrayLayers == 1 and keeps its GL layers on the z axis, so a per-slice - // attachment view is a 2D view whose "array layer" is the slice - legal only on a - // 2D-array-compatible image (VUID-VkImageViewCreateInfo-image-04970), which - // SyncTextureResource asks for and may have had refused per format. - if (resource->viewType == VK_IMAGE_VIEW_TYPE_3D && viewType == VK_IMAGE_VIEW_TYPE_2D) { + // A 3D image has arrayLayers == 1 and keeps its GL layers on the z axis, so an attachment + // view over it addresses SLICES through baseArrayLayer/layerCount: one slice for a + // non-layered attachment (a 2D view) and the whole span for a layered one (a 2D_ARRAY view, + // which is what a layered GL_TEXTURE_3D attachment plus a gl_Layer-writing geometry shader + // means). BOTH spellings are legal only on a 2D-array-compatible image + // (VUID-VkImageViewCreateInfo-image-04970 / -06723), which SyncTextureResource asks for and + // may have had refused per format. + // + // The span is validated against the MIP's slice count, never against arrayLayers: a 3D + // image's arrayLayers is 1 by construction, so measuring a layered span against it rejected + // every layered 3D attachment - the null view that used to reach vkCreateFramebuffer. + if (resource->viewType == VK_IMAGE_VIEW_TYPE_3D && + (viewType == VK_IMAGE_VIEW_TYPE_2D || viewType == VK_IMAGE_VIEW_TYPE_2D_ARRAY)) { const Uint32 sliceCount = std::max(resource->depth >> mipLevel, 1u); if ((resource->imageCreateFlags & VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT) == 0 || layerCount == 0 || baseArrayLayer >= sliceCount || baseArrayLayer + layerCount > sliceCount) { - MGLOG_D("%s: cannot name slice span [%u, %u) of 3D textureId=%d (mip %u has %u slices, " - "2D-array-compatible=%d)", + // Not an error line: the render-pass builder turns the null view into one + // MGLOG_E_ONCE and a skipped draw, which is the level this belongs at. + MGLOG_D("%s: cannot name slice span [%u, %u) of 3D textureId=%d as viewType=%d (mip %u has %u " + "slices, 2D-array-compatible=%d)", __func__, baseArrayLayer, baseArrayLayer + layerCount, texture.GetExternalIndex(), - mipLevel, sliceCount, + static_cast(viewType), mipLevel, sliceCount, (int)((resource->imageCreateFlags & VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT) != 0)); return VK_NULL_HANDLE; } @@ -2173,12 +2183,16 @@ namespace MobileGL::MG_Backend::DirectVulkan { } if (imageFormatResult != VK_SUCCESS && !isMultisampleTexture && (imageInfo.flags & VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT) != 0) { - // Losing 2D-array compatibility only costs per-slice framebuffer attachment for this - // format; failing creation would lose the texture entirely. Remembered so later syncs - // neither reprobe nor flag-mismatch against this image and recreate it. + // Losing 2D-array compatibility only costs framebuffer attachment of this format's + // 3D images - per-slice AND layered, since both are spelled as a 2D-family view over + // the z axis; failing creation would lose the texture entirely. Recorded here (the + // per-format set below) so later syncs neither reprobe nor flag-mismatch against this + // image and recreate it, and so GetOrCreateAttachmentViewAtMipLevel declines rather + // than handing back a view that cannot exist - the render-pass builder then turns + // that decline into a skipped draw instead of a null VkImageView in pAttachments. MGLOG_W_ONCE("%s: VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT is unsupported for format=%d " - "textureId=%d; creating without it (per-slice framebuffer attachment will be " - "unavailable for it)", + "textureId=%d; creating without it (per-slice and layered framebuffer " + "attachment of 3D textures in this format will be unavailable)", __func__, static_cast(format), texture.GetExternalIndex()); m_2dArrayCompatibleUnsupported.insert(format); imageInfo.flags &= ~VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 4b98c891..9b634a8e 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -6172,14 +6172,17 @@ void main() { // index, depth/stencil participation, image epochs, no pending clears) // was verified unchanged above, so this is a pure cache hit on the same // entry the snapshot's pipeline was built against. - const RenderPassEntry& renderPassEntry = m_renderPassManager->GetOrCreateRenderPass( + const RenderPassEntry* renderPassEntry = m_renderPassManager->GetOrCreateRenderPass( *drawFbo, m_imageIndexAcquired, snap.drawUsesDepthStencil); - if (!activeRenderPass->CompatibleWith(renderPassEntry)) { + // A decline (nullptr) is an attachment DirectVulkan cannot represent; the builder + // has already logged it. Fall out of the fast path the same way an incompatible + // pass does - the full path re-resolves, declines again and drops the draw. + if (renderPassEntry == nullptr || !activeRenderPass->CompatibleWith(*renderPassEntry)) { return false; } pipeline = GetOrCreatePipeline(mode, program, programObj, ProgramFactory::CompileOptionFlags(snap.resolvedTransformFlags), - vao, renderPassEntry, drawPrimitiveRestartEnable); + vao, *renderPassEntry, drawPrimitiveRestartEnable); if (pipeline == VK_NULL_HANDLE) { return false; } @@ -6564,12 +6567,23 @@ void main() { MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::DepthTest) || MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::StencilTest); auto* renderPassEntry = - &m_renderPassManager->GetOrCreateRenderPass(*drawFbo, m_imageIndexAcquired, drawUsesDepthStencil); + m_renderPassManager->GetOrCreateRenderPass(*drawFbo, m_imageIndexAcquired, drawUsesDepthStencil); + // nullptr: the framebuffer has an attachment DirectVulkan cannot represent (a texture the + // texture manager declined to back, or a view it could not build). The builder logged which + // one; drop the draw here, exactly as an unresolvable sampler descriptor drops one in + // BindProgramUniformBuffers. Before this existed the same condition dereferenced a null + // resource or handed VK_NULL_HANDLE to vkCreateFramebuffer and took the process down. + if (renderPassEntry == nullptr) { + return false; + } if (activeRenderPass && !activeRenderPass->CompatibleWith(*renderPassEntry)) { VkRenderPassManager::EndRenderPass(frame.commandBuffer); activeRenderPass = nullptr; renderPassEntry = - &m_renderPassManager->GetOrCreateRenderPass(*drawFbo, m_imageIndexAcquired, drawUsesDepthStencil); + m_renderPassManager->GetOrCreateRenderPass(*drawFbo, m_imageIndexAcquired, drawUsesDepthStencil); + if (renderPassEntry == nullptr) { + return false; + } } if (renderPassEntry->attachmentCount == 0 || renderPassEntry->extent.x() <= 0 || renderPassEntry->extent.y() <= 0) { MGLOG_D("SetupDraw skipped: drawFbo=%u resolved to an empty render pass (attachmentCount=%u extent=%dx%d)", @@ -6915,8 +6929,10 @@ void main() { } auto* activeRenderPass = VkRenderPassManager::GetActiveRenderPass(); - auto* renderPassEntry = &m_renderPassManager->GetOrCreateRenderPass(framebuffer, m_imageIndexAcquired); - if (renderPassEntry->attachmentCount == 0 || + auto* renderPassEntry = m_renderPassManager->GetOrCreateRenderPass(framebuffer, m_imageIndexAcquired); + // A declined render pass is the same answer as an empty one for a clear: there is nothing + // attached that can be cleared inside a pass. The builder has already logged the reason. + if (renderPassEntry == nullptr || renderPassEntry->attachmentCount == 0 || renderPassEntry->extent.x() <= 0 || renderPassEntry->extent.y() <= 0) { return ScissoredClearPrep::NoOp; } @@ -6946,7 +6962,10 @@ void main() { activeRenderPass = nullptr; // Re-resolve: ending the pass updates tracked attachment layouts, which feed the // entry's load ops and initial layouts. - renderPassEntry = &m_renderPassManager->GetOrCreateRenderPass(framebuffer, m_imageIndexAcquired); + renderPassEntry = m_renderPassManager->GetOrCreateRenderPass(framebuffer, m_imageIndexAcquired); + if (renderPassEntry == nullptr) { + return ScissoredClearPrep::NoOp; + } } // A still-active pass is necessarily compatible here: the block above ended any // incompatible one and nothing since can change the active pass. @@ -8111,8 +8130,14 @@ void main() { // A color-only blit never touches depth/stencil: let the default-FBO pass // it opens skip the depth attachment (depth-less flavor). - auto& renderPassEntry = + auto* renderPassEntryPtr = m_renderPassManager->GetOrCreateRenderPass(drawFbo, m_imageIndexAcquired, /*drawUsesDepthStencil=*/false); + if (renderPassEntryPtr == nullptr) { + // Declined (the builder logged which attachment). The caller's contract for `false` is + // "this blit was not serviced here", which is the honest answer. + return false; + } + auto& renderPassEntry = *renderPassEntryPtr; const Bool ok = VkRenderPassManager::BeginRenderPass(frame.commandBuffer, renderPassEntry); MOBILEGL_ASSERT(ok, "%s: BeginRenderPass failed", __func__);