Bug 1444452: Implement screenshots on LayerManagerMLGPU. r=mstange

Differential Revision: https://phabricator.services.mozilla.com/D19323

--HG--
extra : rebase_source : 38fbabe7c4b1ea0c4627b02b527b848e0d415a0f
This commit is contained in:
Bas Schouten 2019-02-11 02:31:24 +01:00
Родитель 15ee3d3e8f
Коммит c57c0dec93
8 изменённых файлов: 413 добавлений и 0 удалений

Просмотреть файл

@ -320,6 +320,10 @@ void FrameBuilder::RetainTemporaryLayer(LayerMLGPU* aLayer) {
mTemporaryLayers.push_back(aLayer->GetLayer());
}
MLGRenderTarget* FrameBuilder::GetWidgetRT() {
return mWidgetRenderView->GetRenderTarget();
}
LayerConstants* FrameBuilder::AllocateLayerInfo(ItemInfo& aItem) {
if (((mCurrentLayerBuffer.Length() + 1) * sizeof(LayerConstants)) >
mDevice->GetMaxConstantBufferBindSize()) {

Просмотреть файл

@ -66,6 +66,8 @@ class FrameBuilder final {
// Hold a layer alive until the frame ends.
void RetainTemporaryLayer(LayerMLGPU* aLayer);
MLGRenderTarget* GetWidgetRT();
private:
void AssignLayer(Layer* aLayer, RenderViewMLGPU* aView,
const RenderTargetIntRect& aClipRect,

Просмотреть файл

@ -91,6 +91,7 @@ void LayerManagerMLGPU::Destroy() {
}
LayerManager::Destroy();
mProfilerScreenshotGrabber.Destroy();
if (mDevice && mDevice->IsValid()) {
mDevice->Flush();
@ -264,6 +265,7 @@ void LayerManagerMLGPU::Composite() {
// will tell us if we still need to render.
if (!mSwapChain->ApplyNewInvalidRegion(std::move(mInvalidRegion),
diagnosticRect)) {
mProfilerScreenshotGrabber.NotifyEmptyFrame();
return;
}
@ -346,6 +348,9 @@ void LayerManagerMLGPU::RenderLayers() {
// Execute all render passes.
builder.Render();
mProfilerScreenshotGrabber.MaybeGrabScreenshot(
mDevice, builder.GetWidgetRT()->GetTexture());
mCurrentFrame = nullptr;
if (mDrawDiagnostics) {
@ -505,6 +510,7 @@ bool LayerManagerMLGPU::PreRender() {
void LayerManagerMLGPU::PostRender() {
mWidget->PostRender(mWidgetContext.ptr());
mProfilerScreenshotGrabber.MaybeProcessQueue();
mWidgetContext = Nothing();
}

Просмотреть файл

@ -10,6 +10,7 @@
#include "Layers.h"
#include "mozilla/layers/LayerManagerComposite.h"
#include "LayerMLGPU.h"
#include "mozilla/layers/MLGPUScreenshotGrabber.h"
namespace mozilla {
namespace layers {
@ -124,6 +125,9 @@ class LayerManagerMLGPU final : public HostLayerManager {
// a frame in RenderDoc to spew in the console.
uint32_t mDebugFrameNumber;
RefPtr<MLGBuffer> mDiagnosticVertices;
// Screenshotting for the profiler.
MLGPUScreenshotGrabber mProfilerScreenshotGrabber;
};
} // namespace layers

Просмотреть файл

@ -0,0 +1,335 @@
/* -*- 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 "MLGPUScreenshotGrabber.h"
#include "mozilla/RefPtr.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/layers/ProfilerScreenshots.h"
#include "mozilla/gfx/Point.h"
#include "mozilla/gfx/Swizzle.h"
#include "SharedBufferMLGPU.h"
#include "ShaderDefinitionsMLGPU.h"
#include "nsTArray.h"
namespace mozilla {
using namespace gfx;
namespace layers {
using namespace mlg;
/**
* The actual implementation of screenshot grabbing.
* The MLGPUScreenshotGrabberImpl object is destroyed if the profiler is
* disabled and MaybeGrabScreenshot notices it.
*/
class MLGPUScreenshotGrabberImpl final {
public:
explicit MLGPUScreenshotGrabberImpl(const IntSize& aReadbackTextureSize);
~MLGPUScreenshotGrabberImpl();
void GrabScreenshot(MLGDevice* aDevice, MLGTexture* aTexture);
void ProcessQueue();
private:
struct QueueItem final {
mozilla::TimeStamp mTimeStamp;
RefPtr<MLGTexture> mScreenshotReadbackTexture;
gfx::IntSize mScreenshotSize;
gfx::IntSize mWindowSize;
RefPtr<MLGDevice> mDevice;
uintptr_t mWindowIdentifier;
};
RefPtr<MLGTexture> ScaleDownWindowTargetToSize(MLGDevice* aCompositor,
const gfx::IntSize& aDestSize,
MLGTexture* aWindowTarget,
size_t aLevel);
struct CachedLevel {
RefPtr<MLGRenderTarget> mRenderTarget;
RefPtr<MLGBuffer> mVertexBuffer;
RefPtr<MLGBuffer> mWorldConstants;
};
bool BlitTexture(MLGDevice* aDevice, CachedLevel& aDest, MLGTexture* aSource,
const IntSize& aSourceSize, const IntSize& aDestSize);
already_AddRefed<MLGTexture> TakeNextReadbackTexture(MLGDevice* aCompositor);
void ReturnReadbackTexture(MLGTexture* aReadbackTexture);
nsTArray<CachedLevel> mCachedLevels;
nsTArray<RefPtr<MLGTexture>> mAvailableReadbackTextures;
Maybe<QueueItem> mCurrentFrameQueueItem;
nsTArray<QueueItem> mQueue;
UniquePtr<ProfilerScreenshots> mProfilerScreenshots;
const IntSize mReadbackTextureSize;
};
MLGPUScreenshotGrabber::MLGPUScreenshotGrabber() {}
MLGPUScreenshotGrabber::~MLGPUScreenshotGrabber() {}
void MLGPUScreenshotGrabber::MaybeGrabScreenshot(MLGDevice* aDevice,
MLGTexture* aTexture) {
if (ProfilerScreenshots::IsEnabled()) {
if (!mImpl) {
mImpl = MakeUnique<MLGPUScreenshotGrabberImpl>(
ProfilerScreenshots::ScreenshotSize());
}
mImpl->GrabScreenshot(aDevice, aTexture);
} else if (mImpl) {
Destroy();
}
}
void MLGPUScreenshotGrabber::MaybeProcessQueue() {
if (ProfilerScreenshots::IsEnabled()) {
if (!mImpl) {
mImpl = MakeUnique<MLGPUScreenshotGrabberImpl>(
ProfilerScreenshots::ScreenshotSize());
}
mImpl->ProcessQueue();
} else if (mImpl) {
Destroy();
}
}
void MLGPUScreenshotGrabber::NotifyEmptyFrame() {
#ifdef MOZ_GECKO_PROFILER
profiler_add_marker("NoCompositorScreenshot because nothing changed",
js::ProfilingStackFrame::Category::GRAPHICS);
#endif
}
void MLGPUScreenshotGrabber::Destroy() { mImpl = nullptr; }
MLGPUScreenshotGrabberImpl::MLGPUScreenshotGrabberImpl(
const IntSize& aReadbackTextureSize)
: mReadbackTextureSize(aReadbackTextureSize) {}
MLGPUScreenshotGrabberImpl::~MLGPUScreenshotGrabberImpl() {
// Any queue items in mQueue or mCurrentFrameQueueItem will be lost.
// That's ok: Either the profiler has stopped and we don't care about these
// screenshots, or the window is closing and we don't really need the last
// few frames from the window.
}
// Scale down aWindowTexture into a MLGTexture of size
// mReadbackTextureSize * (1 << aLevel) and return that MLGTexture.
// Don't scale down by more than a factor of 2 with a single scaling operation,
// because it'll look bad. If higher scales are needed, use another
// intermediate target by calling this function recursively with aLevel + 1.
RefPtr<MLGTexture> MLGPUScreenshotGrabberImpl::ScaleDownWindowTargetToSize(
MLGDevice* aDevice, const IntSize& aDestSize, MLGTexture* aWindowTexture,
size_t aLevel) {
aDevice->SetScissorRect(Nothing());
aDevice->SetDepthTestMode(MLGDepthTestMode::Disabled);
aDevice->SetTopology(MLGPrimitiveTopology::UnitQuad);
// DiagnosticText happens to be the simplest shader we have to draw a quad.
aDevice->SetVertexShader(VertexShaderID::DiagnosticText);
aDevice->SetPixelShader(PixelShaderID::DiagnosticText);
aDevice->SetBlendState(MLGBlendState::Copy);
aDevice->SetSamplerMode(0, SamplerMode::LinearClamp);
if (aLevel == mCachedLevels.Length()) {
RefPtr<MLGRenderTarget> rt =
aDevice->CreateRenderTarget(mReadbackTextureSize * (1 << aLevel));
mCachedLevels.AppendElement(CachedLevel{rt, nullptr, nullptr});
}
MOZ_RELEASE_ASSERT(aLevel < mCachedLevels.Length());
RefPtr<MLGTexture> sourceTarget = aWindowTexture;
IntSize sourceSize = aWindowTexture->GetSize();
if (aWindowTexture->GetSize().width > aDestSize.width * 2) {
sourceSize = aDestSize * 2;
sourceTarget = ScaleDownWindowTargetToSize(aDevice, sourceSize,
aWindowTexture, aLevel + 1);
}
if (sourceTarget) {
if (BlitTexture(aDevice, mCachedLevels[aLevel], sourceTarget, sourceSize,
aDestSize)) {
return mCachedLevels[aLevel].mRenderTarget->GetTexture();
}
}
return nullptr;
}
bool MLGPUScreenshotGrabberImpl::BlitTexture(MLGDevice* aDevice,
CachedLevel& aLevel,
MLGTexture* aSource,
const IntSize& aSourceSize,
const IntSize& aDestSize) {
MOZ_ASSERT(aLevel.mRenderTarget);
MLGRenderTarget* rt = aLevel.mRenderTarget;
MOZ_ASSERT(aDestSize <= rt->GetSize());
struct TextureRect {
Rect bounds;
Rect texCoords;
};
if (!aLevel.mVertexBuffer) {
TextureRect rect;
rect.bounds = Rect(Point(), Size(aDestSize));
rect.texCoords =
Rect(0.0, 0.0, Float(aSourceSize.width) / aSource->GetSize().width,
Float(aSourceSize.height) / aSource->GetSize().height);
VertexStagingBuffer instances;
if (!instances.AppendItem(rect)) {
return false;
}
RefPtr<MLGBuffer> vertices = aDevice->CreateBuffer(
MLGBufferType::Vertex, instances.NumItems() * instances.SizeOfItem(),
MLGUsage::Immutable, instances.GetBufferStart());
if (!vertices) {
return false;
}
aLevel.mVertexBuffer = vertices;
}
if (!aLevel.mWorldConstants) {
WorldConstants vsConstants;
Matrix4x4 projection = Matrix4x4::Translation(-1.0, 1.0, 0.0);
projection.PreScale(2.0 / float(rt->GetSize().width),
2.0 / float(rt->GetSize().height), 1.0f);
projection.PreScale(1.0f, -1.0f, 1.0f);
memcpy(vsConstants.projection, &projection._11, 64);
vsConstants.targetOffset = Point();
vsConstants.sortIndexOffset = 0;
vsConstants.debugFrameNumber = 0;
aLevel.mWorldConstants =
aDevice->CreateBuffer(MLGBufferType::Constant, sizeof(vsConstants),
MLGUsage::Immutable, &vsConstants);
if (!aLevel.mWorldConstants) {
return false;
}
}
aDevice->SetRenderTarget(rt);
aDevice->SetPSTexture(0, aSource);
aDevice->SetViewport(IntRect(IntPoint(0, 0), rt->GetSize()));
aDevice->SetVertexBuffer(1, aLevel.mVertexBuffer, sizeof(TextureRect));
aDevice->SetVSConstantBuffer(kWorldConstantBufferSlot,
aLevel.mWorldConstants);
aDevice->DrawInstanced(4, 1, 0, 0);
return true;
}
void MLGPUScreenshotGrabberImpl::GrabScreenshot(MLGDevice* aDevice,
MLGTexture* aTexture) {
Size windowSize(aTexture->GetSize());
float scale = std::min(mReadbackTextureSize.width / windowSize.width,
mReadbackTextureSize.height / windowSize.height);
IntSize scaledSize = IntSize::Round(windowSize * scale);
// The initial target is non-GPU readable. This copy could probably be
// avoided if we had created the swap chain differently. However we
// don't know if that may inadvertently affect performance in the
// non-profiling case.
RefPtr<MLGTexture> windowTexture = aDevice->CreateTexture(
aTexture->GetSize(), SurfaceFormat::B8G8R8A8, MLGUsage::Default,
MLGTextureFlags::ShaderResource);
aDevice->CopyTexture(windowTexture, IntPoint(), aTexture,
IntRect(IntPoint(), aTexture->GetSize()));
RefPtr<MLGTexture> scaledTarget =
ScaleDownWindowTargetToSize(aDevice, scaledSize, windowTexture, 0);
if (!scaledTarget) {
PROFILER_ADD_MARKER(
"NoCompositorScreenshot because ScaleDownWindowTargetToSize failed",
GRAPHICS);
return;
}
RefPtr<MLGTexture> readbackTexture = TakeNextReadbackTexture(aDevice);
if (!readbackTexture) {
PROFILER_ADD_MARKER(
"NoCompositorScreenshot because AsyncReadbackReadbackTexture creation "
"failed",
GRAPHICS);
return;
}
aDevice->CopyTexture(readbackTexture, IntPoint(), scaledTarget,
IntRect(IntPoint(), mReadbackTextureSize));
// This QueueItem will be added to the queue at the end of the next call to
// ProcessQueue(). This ensures that the ReadbackTexture isn't mapped into
// main memory until the next frame. If we did it in this frame, we'd block on
// the GPU.
mCurrentFrameQueueItem =
Some(QueueItem{TimeStamp::Now(), readbackTexture.forget(), scaledSize,
aTexture->GetSize(), aDevice,
reinterpret_cast<uintptr_t>(static_cast<void*>(this))});
}
already_AddRefed<MLGTexture>
MLGPUScreenshotGrabberImpl::TakeNextReadbackTexture(MLGDevice* aDevice) {
if (!mAvailableReadbackTextures.IsEmpty()) {
RefPtr<MLGTexture> readbackTexture = mAvailableReadbackTextures[0];
mAvailableReadbackTextures.RemoveElementAt(0);
return readbackTexture.forget();
}
return aDevice
->CreateTexture(mReadbackTextureSize, SurfaceFormat::B8G8R8A8,
MLGUsage::Staging, MLGTextureFlags::None)
.forget();
}
void MLGPUScreenshotGrabberImpl::ReturnReadbackTexture(
MLGTexture* aReadbackTexture) {
mAvailableReadbackTextures.AppendElement(aReadbackTexture);
}
void MLGPUScreenshotGrabberImpl::ProcessQueue() {
if (!mQueue.IsEmpty()) {
if (!mProfilerScreenshots) {
mProfilerScreenshots = MakeUnique<ProfilerScreenshots>();
}
for (const auto& item : mQueue) {
mProfilerScreenshots->SubmitScreenshot(
item.mWindowIdentifier, item.mWindowSize, item.mScreenshotSize,
item.mTimeStamp, [&item](DataSourceSurface* aTargetSurface) {
MLGMappedResource map;
if (!item.mDevice->Map(item.mScreenshotReadbackTexture,
MLGMapType::READ, &map)) {
return false;
}
DataSourceSurface::ScopedMap destMap(aTargetSurface,
DataSourceSurface::WRITE);
bool result =
SwizzleData(map.mData, map.mStride, SurfaceFormat::B8G8R8A8,
destMap.GetData(), destMap.GetStride(),
aTargetSurface->GetFormat(), item.mScreenshotSize);
item.mDevice->Unmap(item.mScreenshotReadbackTexture);
return result;
});
ReturnReadbackTexture(item.mScreenshotReadbackTexture);
}
}
mQueue.Clear();
if (mCurrentFrameQueueItem) {
mQueue.AppendElement(std::move(*mCurrentFrameQueueItem));
mCurrentFrameQueueItem = Nothing();
}
}
} // namespace layers
} // namespace mozilla

Просмотреть файл

@ -0,0 +1,59 @@
/* -*- 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/. */
#ifndef mozilla_layers_MLGPUScreenshotGrabber_h
#define mozilla_layers_MLGPUScreenshotGrabber_h
#include "mozilla/UniquePtr.h"
#include "mozilla/layers/MLGDevice.h"
namespace mozilla {
namespace layers {
class MLGPUScreenshotGrabberImpl;
/**
* Used by LayerManagerComposite to grab snapshots from the compositor and
* submit them to the Gecko profiler.
* Doesn't do any work if the profiler is not running or the "screenshots"
* feature is not enabled.
* Screenshots are scaled down to fit within a fixed size, and read back to
* main memory using async readback. Scaling is done in multiple scale-by-0.5x
* steps using CompositingRenderTargets and Compositor::BlitFromRenderTarget,
* and readback is done using AsyncReadbackBuffers.
*/
class MLGPUScreenshotGrabber final {
public:
MLGPUScreenshotGrabber();
~MLGPUScreenshotGrabber();
// Scale the contents of aTexture into an appropriately sized MLGTexture
// and read its contents into an AsyncReadbackBuffer. The AsyncReadbackBuffer
// is not mapped into main memory until the second call to
// MaybeProcessQueue() after this call to MaybeGrabScreenshot().
void MaybeGrabScreenshot(MLGDevice* aDevice, MLGTexture* aTexture);
// Map the contents of any outstanding AsyncReadbackBuffers from previous
// composites into main memory and submit each screenshot to the profiler.
void MaybeProcessQueue();
// Insert a special profiler marker for a composite that didn't do any actual
// compositing, so that the profiler knows why no screenshot was taken for
// this frame.
void NotifyEmptyFrame();
// Destroy all Compositor-related resources that this class is holding on to.
void Destroy();
private:
// non-null while ProfilerScreenshots::IsEnabled() returns true
UniquePtr<MLGPUScreenshotGrabberImpl> mImpl;
};
} // namespace layers
} // namespace mozilla
#endif // mozilla_layers_MLGPUScreenshotGrabber_h

Просмотреть файл

@ -215,6 +215,7 @@ EXPORTS.mozilla.layers += [
'mlgpu/MemoryReportingMLGPU.h',
'mlgpu/MLGDevice.h',
'mlgpu/MLGDeviceTypes.h',
'mlgpu/MLGPUScreenshotGrabber.h',
'mlgpu/ShaderDefinitionsMLGPU.h',
'mlgpu/UtilityMLGPU.h',
'opengl/CompositingRenderTargetOGL.h',
@ -459,6 +460,7 @@ UNIFIED_SOURCES += [
'mlgpu/MaskOperation.cpp',
'mlgpu/MemoryReportingMLGPU.cpp',
'mlgpu/MLGDevice.cpp',
'mlgpu/MLGPUScreenshotGrabber.cpp',
'mlgpu/PaintedLayerMLGPU.cpp',
'mlgpu/RenderPassMLGPU.cpp',
'mlgpu/RenderViewMLGPU.cpp',

Просмотреть файл

@ -8,6 +8,7 @@
#include "mozilla/gfx/DrawEventRecorder.h"
#include "mozilla/gfx/InlineTranslator.h"
#include "mozilla/webrender/webrender_ffi.h"
namespace mozilla {
namespace layers {