/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "FrameBuilder.h" #include "ContainerLayerMLGPU.h" #include "GeckoProfiler.h" // for profiler_* #include "LayerMLGPU.h" #include "LayerManagerMLGPU.h" #include "MaskOperation.h" #include "MLGDevice.h" // for MLGSwapChain #include "RenderPassMLGPU.h" #include "RenderViewMLGPU.h" #include "mozilla/gfx/Logging.h" #include "mozilla/gfx/Polygon.h" #include "mozilla/layers/BSPTree.h" #include "mozilla/layers/LayersHelpers.h" namespace mozilla { namespace layers { using namespace mlg; FrameBuilder::FrameBuilder(LayerManagerMLGPU* aManager, MLGSwapChain* aSwapChain) : mManager(aManager), mDevice(aManager->GetDevice()), mSwapChain(aSwapChain) { // test_bug1124898.html has a root ColorLayer, so we don't assume the root is // a container. mRoot = mManager->GetRoot()->AsHostLayer()->AsLayerMLGPU(); } FrameBuilder::~FrameBuilder() = default; bool FrameBuilder::Build() { AUTO_PROFILER_LABEL("FrameBuilder::Build", GRAPHICS); // AcquireBackBuffer can fail, so we check the result here. RefPtr target = mSwapChain->AcquireBackBuffer(); if (!target) { return false; } // This updates the frame sequence number, so layers can quickly check if // they've already been prepared. LayerMLGPU::BeginFrame(); // Note: we don't clip draw calls to the invalid region per se, but instead // the region bounds. Clipping all draw calls would incur a significant // CPU cost on large layer trees, and would greatly complicate how draw // rects are added in RenderPassMLGPU, since we would need to break // each call into additional items based on the intersection with the // invalid region. // // Instead we scissor to the invalid region bounds. As a result, all items // affecting the invalid bounds are redrawn, even if not all are in the // precise region. const nsIntRegion& region = mSwapChain->GetBackBufferInvalidRegion(); mWidgetRenderView = new RenderViewMLGPU(this, target, region); // Traverse the layer tree and compute visible region for intermediate // surfaces if (ContainerLayerMLGPU* root = mRoot->AsLayerMLGPU()->AsContainerLayerMLGPU()) { root->ComputeIntermediateSurfaceBounds(); } // Traverse the layer tree and assign each layer to tiles. { Maybe geometry; RenderTargetIntRect clip(0, 0, target->GetSize().width, target->GetSize().height); AssignLayer(mRoot->GetLayer(), mWidgetRenderView, clip, std::move(geometry)); } // Build the default mask buffer. { MaskInformation defaultMaskInfo(1.0f, false); if (!mDevice->GetSharedPSBuffer()->Allocate(&mDefaultMaskInfo, defaultMaskInfo)) { return false; } } // Build render passes and buffer information for each pass. mWidgetRenderView->FinishBuilding(); mWidgetRenderView->Prepare(); // Prepare masks that need to be combined. for (const auto& pair : mCombinedTextureMasks) { pair.second->PrepareForRendering(); } FinishCurrentLayerBuffer(); FinishCurrentMaskRectBuffer(); return true; } void FrameBuilder::Render() { AUTO_PROFILER_LABEL("FrameBuilder::Render", GRAPHICS); // Render combined masks into single mask textures. for (const auto& pair : mCombinedTextureMasks) { pair.second->Render(); } // Render to all targets, front-to-back. mWidgetRenderView->Render(); } void FrameBuilder::AssignLayer(Layer* aLayer, RenderViewMLGPU* aView, const RenderTargetIntRect& aClipRect, Maybe&& aGeometry) { LayerMLGPU* layer = aLayer->AsHostLayer()->AsLayerMLGPU(); if (ContainerLayer* container = aLayer->AsContainerLayer()) { // This returns false if we don't need to (or can't) process the layer any // further. This always returns false for non-leaf ContainerLayers. if (!ProcessContainerLayer(container, aView, aClipRect, aGeometry)) { return; } } else { // Set the precomputed clip and any textures/resources that are needed. if (!layer->PrepareToRender(this, aClipRect)) { return; } } // If we are dealing with a nested 3D context, we might need to transform // the geometry back to the coordinate space of the current layer. if (aGeometry) { TransformLayerGeometry(aLayer, aGeometry); } // Finally, assign the layer to a rendering batch in the current render // target. layer->AssignToView(this, aView, std::move(aGeometry)); } bool FrameBuilder::ProcessContainerLayer(ContainerLayer* aContainer, RenderViewMLGPU* aView, const RenderTargetIntRect& aClipRect, Maybe& aGeometry) { LayerMLGPU* layer = aContainer->AsHostLayer()->AsLayerMLGPU(); // Diagnostic information for bug 1387467. if (!layer) { gfxDevCrash(gfx::LogReason::InvalidLayerType) << "Layer type is invalid: " << aContainer->Name(); return false; } // We don't want to traverse containers twice, so we only traverse them if // they haven't been prepared yet. bool isFirstVisit = !layer->IsPrepared(); if (isFirstVisit && !layer->PrepareToRender(this, aClipRect)) { return false; } if (!aContainer->UseIntermediateSurface()) { // In case the layer previously required an intermediate surface, we // clear any intermediate render targets here. layer->ClearCachedResources(); // This is a pass-through container, so we just process children and // instruct AssignLayer to early-return. ProcessChildList(aContainer, aView, aClipRect, aGeometry); return false; } // If this is the first visit of the container this frame, and the // container has an unpainted area, we traverse the container. Note that // RefLayers do not have intermediate surfaces so this is guaranteed // to be a full-fledged ContainerLayerMLGPU. ContainerLayerMLGPU* viewContainer = layer->AsContainerLayerMLGPU(); if (!viewContainer) { gfxDevCrash(gfx::LogReason::InvalidLayerType) << "Container layer type is invalid: " << aContainer->Name(); return false; } if (isFirstVisit && !viewContainer->GetInvalidRect().IsEmpty()) { // The RenderView constructor automatically attaches itself to the parent. RefPtr view = new RenderViewMLGPU(this, viewContainer, aView); ProcessChildList(aContainer, view, aClipRect, Nothing()); view->FinishBuilding(); } return true; } void FrameBuilder::ProcessChildList( ContainerLayer* aContainer, RenderViewMLGPU* aView, const RenderTargetIntRect& aParentClipRect, const Maybe& aParentGeometry) { nsTArray polygons = aContainer->SortChildrenBy3DZOrder( ContainerLayer::SortMode::WITH_GEOMETRY); // Visit layers in front-to-back order. for (auto iter = polygons.rbegin(); iter != polygons.rend(); iter++) { LayerPolygon& entry = *iter; Layer* child = entry.layer; if (child->IsBackfaceHidden() || !child->IsVisible()) { continue; } RenderTargetIntRect clip = child->CalculateScissorRect(aParentClipRect); if (clip.IsEmpty()) { continue; } Maybe geometry; if (aParentGeometry && entry.geometry) { // Both parent and child are split. geometry = Some(aParentGeometry->ClipPolygon(*entry.geometry)); } else if (aParentGeometry) { geometry = aParentGeometry; } else if (entry.geometry) { geometry = std::move(entry.geometry); } AssignLayer(child, aView, clip, std::move(geometry)); } } bool FrameBuilder::AddLayerToConstantBuffer(ItemInfo& aItem) { LayerMLGPU* layer = aItem.layer; // If this layer could appear multiple times, cache it. if (aItem.geometry) { if (mLayerBufferMap.Get(layer, &aItem.layerIndex)) { return true; } } LayerConstants* info = AllocateLayerInfo(aItem); if (!info) { return false; } // Note we do not use GetEffectiveTransformForBuffer, since we calculate // the correct scaling when we build texture coordinates. Layer* baseLayer = layer->GetLayer(); const gfx::Matrix4x4& transform = baseLayer->GetEffectiveTransform(); memcpy(&info->transform, &transform._11, 64); info->clipRect = gfx::Rect(layer->GetComputedClipRect().ToUnknownRect()); info->maskIndex = 0; if (MaskOperation* op = layer->GetMask()) { // Note: we use 0 as an invalid index, and so indices are offset by 1. gfx::Rect rect = op->ComputeMaskRect(baseLayer); AddMaskRect(rect, &info->maskIndex); } if (aItem.geometry) { mLayerBufferMap.Put(layer, aItem.layerIndex); } return true; } MaskOperation* FrameBuilder::AddMaskOperation(LayerMLGPU* aLayer) { Layer* layer = aLayer->GetLayer(); MOZ_ASSERT(layer->HasMaskLayers()); // Multiple masks are combined into a single mask. if ((layer->GetMaskLayer() && layer->GetAncestorMaskLayerCount()) || layer->GetAncestorMaskLayerCount() > 1) { // Since each mask can be moved independently of the other, we must create // a separate combined mask for every new positioning we encounter. MaskTextureList textures; if (Layer* maskLayer = layer->GetMaskLayer()) { AppendToMaskTextureList(textures, maskLayer); } for (size_t i = 0; i < layer->GetAncestorMaskLayerCount(); i++) { AppendToMaskTextureList(textures, layer->GetAncestorMaskLayerAt(i)); } auto iter = mCombinedTextureMasks.find(textures); if (iter != mCombinedTextureMasks.end()) { return iter->second; } RefPtr op = new MaskCombineOperation(this); op->Init(textures); mCombinedTextureMasks[textures] = op; return op; } Layer* maskLayer = layer->GetMaskLayer() ? layer->GetMaskLayer() : layer->GetAncestorMaskLayerAt(0); RefPtr texture = GetMaskLayerTexture(maskLayer); if (!texture) { return nullptr; } RefPtr op; mSingleTextureMasks.Get(texture, getter_AddRefs(op)); if (op) { return op; } RefPtr wrapped = mDevice->CreateTexture(texture); op = new MaskOperation(this, wrapped); mSingleTextureMasks.Put(texture, RefPtr{op}); return op; } void FrameBuilder::RetainTemporaryLayer(LayerMLGPU* aLayer) { // This should only be used with temporary layers. Temporary layers do not // have parents. MOZ_ASSERT(!aLayer->GetLayer()->GetParent()); mTemporaryLayers.push_back(aLayer->GetLayer()); } MLGRenderTarget* FrameBuilder::GetWidgetRT() { return mWidgetRenderView->GetRenderTarget(); } LayerConstants* FrameBuilder::AllocateLayerInfo(ItemInfo& aItem) { if (((mCurrentLayerBuffer.Length() + 1) * sizeof(LayerConstants)) > mDevice->GetMaxConstantBufferBindSize()) { FinishCurrentLayerBuffer(); mLayerBufferMap.Clear(); mCurrentLayerBuffer.ClearAndRetainStorage(); } LayerConstants* info = mCurrentLayerBuffer.AppendElement(mozilla::fallible); if (!info) { return nullptr; } aItem.layerIndex = mCurrentLayerBuffer.Length() - 1; return info; } void FrameBuilder::FinishCurrentLayerBuffer() { if (mCurrentLayerBuffer.IsEmpty()) { return; } // Note: we append the buffer even if we couldn't allocate one, since // that keeps the indices sane. ConstantBufferSection section; mDevice->GetSharedVSBuffer()->Allocate( §ion, mCurrentLayerBuffer.Elements(), mCurrentLayerBuffer.Length()); mLayerBuffers.AppendElement(section); } size_t FrameBuilder::CurrentLayerBufferIndex() const { // The mask rect buffer list doesn't contain the buffer currently being // built, so we don't subtract 1 here. return mLayerBuffers.Length(); } ConstantBufferSection FrameBuilder::GetLayerBufferByIndex(size_t aIndex) const { if (aIndex >= mLayerBuffers.Length()) { return ConstantBufferSection(); } return mLayerBuffers[aIndex]; } bool FrameBuilder::AddMaskRect(const gfx::Rect& aRect, uint32_t* aOutIndex) { if (((mCurrentMaskRectList.Length() + 1) * sizeof(gfx::Rect)) > mDevice->GetMaxConstantBufferBindSize()) { FinishCurrentMaskRectBuffer(); mCurrentMaskRectList.ClearAndRetainStorage(); } mCurrentMaskRectList.AppendElement(aRect); // Mask indices start at 1 so the shader can use 0 as a no-mask indicator. *aOutIndex = mCurrentMaskRectList.Length(); return true; } void FrameBuilder::FinishCurrentMaskRectBuffer() { if (mCurrentMaskRectList.IsEmpty()) { return; } // Note: we append the buffer even if we couldn't allocate one, since // that keeps the indices sane. ConstantBufferSection section; mDevice->GetSharedVSBuffer()->Allocate( §ion, mCurrentMaskRectList.Elements(), mCurrentMaskRectList.Length()); mMaskRectBuffers.AppendElement(section); } size_t FrameBuilder::CurrentMaskRectBufferIndex() const { // The mask rect buffer list doesn't contain the buffer currently being // built, so we don't subtract 1 here. return mMaskRectBuffers.Length(); } ConstantBufferSection FrameBuilder::GetMaskRectBufferByIndex( size_t aIndex) const { if (aIndex >= mMaskRectBuffers.Length()) { return ConstantBufferSection(); } return mMaskRectBuffers[aIndex]; } } // namespace layers } // namespace mozilla