зеркало из https://github.com/mozilla/gecko-dev.git
438 строки
13 KiB
C++
438 строки
13 KiB
C++
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
|
* 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/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()
|
|
{
|
|
}
|
|
|
|
bool
|
|
FrameBuilder::Build()
|
|
{
|
|
AUTO_PROFILER_LABEL("FrameBuilder::Build", GRAPHICS);
|
|
|
|
// AcquireBackBuffer can fail, so we check the result here.
|
|
RefPtr<MLGRenderTarget> 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 assign each layer to tiles.
|
|
{
|
|
Maybe<gfx::Polygon> geometry;
|
|
RenderTargetIntRect clip(0, 0, target->GetSize().width, target->GetSize().height);
|
|
|
|
AssignLayer(mRoot->GetLayer(), mWidgetRenderView, clip, 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<gfx::Polygon>&& 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, Move(aGeometry));
|
|
}
|
|
|
|
bool
|
|
FrameBuilder::ProcessContainerLayer(ContainerLayer* aContainer,
|
|
RenderViewMLGPU* aView,
|
|
const RenderTargetIntRect& aClipRect,
|
|
Maybe<gfx::Polygon>& aGeometry)
|
|
{
|
|
LayerMLGPU* layer = aContainer->AsHostLayer()->AsLayerMLGPU();
|
|
|
|
// Diagnostic information for bug 1387467.
|
|
if (!layer) {
|
|
gfxDevCrash(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(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<RenderViewMLGPU> 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<gfx::Polygon>& aParentGeometry)
|
|
{
|
|
nsTArray<LayerPolygon> 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<gfx::Polygon> 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 = Move(entry.geometry);
|
|
}
|
|
|
|
AssignLayer(child, aView, clip, 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<MaskCombineOperation> op = new MaskCombineOperation(this);
|
|
op->Init(textures);
|
|
|
|
mCombinedTextureMasks[textures] = op;
|
|
return op;
|
|
}
|
|
|
|
Layer* maskLayer = layer->GetMaskLayer()
|
|
? layer->GetMaskLayer()
|
|
: layer->GetAncestorMaskLayerAt(0);
|
|
RefPtr<TextureSource> texture = GetMaskLayerTexture(maskLayer);
|
|
if (!texture) {
|
|
return nullptr;
|
|
}
|
|
|
|
RefPtr<MaskOperation> op;
|
|
mSingleTextureMasks.Get(texture, getter_AddRefs(op));
|
|
if (op) {
|
|
return op;
|
|
}
|
|
|
|
RefPtr<MLGTexture> wrapped = mDevice->CreateTexture(texture);
|
|
|
|
op = new MaskOperation(this, wrapped);
|
|
mSingleTextureMasks.Put(texture, 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());
|
|
}
|
|
|
|
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
|