Bug 1551735 - Add a thread-safe composition recorder for WebRender r=kvark,kats

Since WebRender does its rendering on a separate thread from the compositor
thread, we need a composition recorder that can be shared between threads.

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

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Barret Rennie 2019-05-31 00:31:24 +00:00
Родитель b526a32af7
Коммит f5ab9bc353
6 изменённых файлов: 267 добавлений и 7 удалений

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

@ -27,8 +27,6 @@ namespace layers {
CompositionRecorder::CompositionRecorder(TimeStamp aRecordingStart)
: mRecordingStart(aRecordingStart) {}
CompositionRecorder::~CompositionRecorder() {}
void CompositionRecorder::RecordFrame(RecordedFrame* aFrame) {
mCollectedFrames.AppendElement(aFrame);
}

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

@ -49,8 +49,8 @@ class RecordedFrame {
* If GPU-accelerated rendering is used, the frames will not be mapped into
* memory until |WriteCollectedFrames| is called.
*/
class CompositionRecorder final {
NS_INLINE_DECL_REFCOUNTING(CompositionRecorder)
class CompositionRecorder {
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CompositionRecorder)
public:
explicit CompositionRecorder(TimeStamp aRecordingStart);
@ -58,15 +58,15 @@ class CompositionRecorder final {
/**
* Record a composited frame.
*/
void RecordFrame(RecordedFrame* aFrame);
virtual void RecordFrame(RecordedFrame* aFrame);
/**
* Write out the collected frames as a series of timestamped images.
*/
void WriteCollectedFrames();
virtual void WriteCollectedFrames();
protected:
~CompositionRecorder();
virtual ~CompositionRecorder() = default;
private:
nsTArray<RefPtr<RecordedFrame>> mCollectedFrames;

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

@ -255,6 +255,7 @@ EXPORTS.mozilla.layers += [
'wr/WebRenderBridgeParent.h',
'wr/WebRenderCanvasRenderer.h',
'wr/WebRenderCommandBuilder.h',
'wr/WebRenderCompositionRecorder.h',
'wr/WebRenderDrawEventRecorder.h',
'wr/WebRenderImageHost.h',
'wr/WebRenderLayerManager.h',
@ -503,6 +504,7 @@ UNIFIED_SOURCES += [
'wr/WebRenderBridgeParent.cpp',
'wr/WebRenderCanvasRenderer.cpp',
'wr/WebRenderCommandBuilder.cpp',
'wr/WebRenderCompositionRecorder.cpp',
'wr/WebRenderDrawEventRecorder.cpp',
'wr/WebRenderImageHost.cpp',
'wr/WebRenderLayerManager.cpp',

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

@ -0,0 +1,144 @@
/* 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 "WebRenderCompositionRecorder.h"
#include "mozilla/webrender/RenderThread.h"
namespace mozilla {
namespace layers {
class RendererRecordedFrame final : public layers::RecordedFrame {
public:
RendererRecordedFrame(const TimeStamp& aTimeStamp, wr::Renderer* aRenderer,
const wr::RecordedFrameHandle aHandle,
const gfx::IntSize& aSize)
: RecordedFrame(aTimeStamp),
mRenderer(aRenderer),
mSize(aSize),
mHandle(aHandle) {}
already_AddRefed<gfx::DataSourceSurface> GetSourceSurface() override {
if (!mSurface) {
mSurface = gfx::Factory::CreateDataSourceSurface(
mSize, gfx::SurfaceFormat::B8G8R8A8, /* aZero = */ false);
gfx::DataSourceSurface::ScopedMap map(mSurface,
gfx::DataSourceSurface::WRITE);
if (!wr_renderer_map_recorded_frame(mRenderer, mHandle, map.GetData(),
mSize.width * mSize.height * 4,
mSize.width * 4)) {
return nullptr;
}
}
return do_AddRef(mSurface);
}
private:
wr::Renderer* mRenderer;
RefPtr<gfx::DataSourceSurface> mSurface;
gfx::IntSize mSize;
wr::RecordedFrameHandle mHandle;
};
void WebRenderCompositionRecorder::RecordFrame(RecordedFrame* aFrame) {
MOZ_CRASH(
"WebRenderCompositionRecorder::RecordFrame should not be called; call "
"MaybeRecordFrame instead.");
}
bool WebRenderCompositionRecorder::MaybeRecordFrame(
wr::Renderer* aRenderer, wr::WebRenderPipelineInfo* aFrameEpochs) {
MOZ_ASSERT(wr::RenderThread::IsInRenderThread());
if (!aRenderer || !aFrameEpochs) {
return false;
}
if (!mMutex.TryLock()) {
// If we cannot lock the mutex, then the |CompositorBridgeParent|
// is holding the mutex in |WriteCollectedFrames|.
//
// In either case we do not want to wait to acquire the mutex to record a
// frame since frames recorded now will not be written to disk.
return false;
}
auto unlockGuard = MakeScopeExit([&]() { mMutex.Unlock(); });
if (mFinishedRecording) {
return true;
}
if (!DidPaintContent(aFrameEpochs)) {
return false;
}
wr::RecordedFrameHandle handle{0};
gfx::IntSize size(0, 0);
if (wr_renderer_record_frame(aRenderer, wr::ImageFormat::BGRA8, &handle,
&size.width, &size.height)) {
RefPtr<RecordedFrame> frame =
new RendererRecordedFrame(TimeStamp::Now(), aRenderer, handle, size);
CompositionRecorder::RecordFrame(frame);
}
return false;
}
void WebRenderCompositionRecorder::WriteCollectedFrames() {
MutexAutoLock guard(mMutex);
MOZ_RELEASE_ASSERT(
!mFinishedRecording,
"WebRenderCompositionRecorder: Attempting to write frames from invalid "
"state.");
CompositionRecorder::WriteCollectedFrames();
mFinishedRecording = true;
}
bool WebRenderCompositionRecorder::DidPaintContent(
wr::WebRenderPipelineInfo* aFrameEpochs) {
const wr::WrPipelineInfo& info = aFrameEpochs->Raw();
bool didPaintContent = false;
for (wr::usize i = 0; i < info.epochs.length; i++) {
const wr::PipelineId pipelineId = info.epochs.data[i].pipeline_id;
if (pipelineId == mRootPipelineId) {
continue;
}
const auto it = mContentPipelines.find(AsUint64(pipelineId));
if (it == mContentPipelines.end() ||
it->second != info.epochs.data[i].epoch) {
// This content pipeline has updated list last render or has newly
// rendered.
didPaintContent = true;
mContentPipelines[AsUint64(pipelineId)] = info.epochs.data[i].epoch;
}
}
for (wr::usize i = 0; i < info.removed_pipelines.length; i++) {
const wr::PipelineId pipelineId =
info.removed_pipelines.data[i].pipeline_id;
if (pipelineId == mRootPipelineId) {
continue;
}
mContentPipelines.erase(AsUint64(pipelineId));
}
return didPaintContent;
}
} // namespace layers
} // namespace mozilla

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

@ -0,0 +1,115 @@
/* 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_WebRenderCompositionRecorder_h
#define mozilla_layers_WebRenderCompositionRecorder_h
#include "CompositionRecorder.h"
#include "mozilla/Mutex.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/webrender/webrender_ffi.h"
#include <unordered_map>
namespace mozilla {
namespace wr {
class WebRenderPipelineInfo;
}
namespace layers {
/**
* A thread-safe version of the |CompositionRecorder|.
*
* Composition recording for WebRender occurs on the |RenderThread| whereas the
* frames are written on the thread holding the |CompositorBridgeParent|.
*
*/
class WebRenderCompositionRecorder final : public CompositionRecorder {
public:
explicit WebRenderCompositionRecorder(TimeStamp aRecordingStart,
wr::PipelineId aRootPipelineId)
: CompositionRecorder(aRecordingStart),
mMutex("CompositionRecorder"),
mFinishedRecording(false),
mRootPipelineId(aRootPipelineId) {}
WebRenderCompositionRecorder() = delete;
WebRenderCompositionRecorder(WebRenderCompositionRecorder&) = delete;
WebRenderCompositionRecorder(WebRenderCompositionRecorder&&) = delete;
WebRenderCompositionRecorder& operator=(WebRenderCompositionRecorder&) =
delete;
WebRenderCompositionRecorder& operator=(WebRenderCompositionRecorder&&) =
delete;
/**
* Do not call this method.
*
* Instead, call |MaybeRecordFrame|, which will only attempt to record a
* frame if we have not yet written frames to disk.
*/
void RecordFrame(RecordedFrame* aFrame) override;
/**
* Write the collected frames to disk.
*
* This method should not be called if frames have already been written or if
* |ForceFinishRecording| has been called as the object will be in an invalid
* state to write to disk.
*
* Note: This method will block acquiring a lock.
*/
void WriteCollectedFrames() override;
/**
* Attempt to record a frame from the given renderer.
*
* This method will only record a frame if the following are true:
*
* - this object's lock was acquired immediately (i.e., we are not currently
* writing frames to disk);
* - we have not yet written frames to disk; and
* - one of the pipelines in |aFrameEpochs| has updated and it is not the
* root pipeline.
*
* Returns whether or not the recorder has finished recording frames. If
* true, it is safe to release both this object and Web Render's composition
* recorder structures.
*/
bool MaybeRecordFrame(wr::Renderer* aRenderer,
wr::WebRenderPipelineInfo* aFrameEpochs);
protected:
~WebRenderCompositionRecorder() = default;
/**
* Determine if any content pipelines updated.
*/
bool DidPaintContent(wr::WebRenderPipelineInfo* aFrameEpochs);
private:
Mutex mMutex;
// Whether or not we have finished recording.
bool mFinishedRecording;
// The id of the root WebRender pipeline.
//
// All other pipelines are considered content.
wr::PipelineId mRootPipelineId;
// A mapping of wr::PipelineId to the epochs when last they updated.
//
// We need to use uint64_t here since wr::PipelineId is not default
// constructable.
std::unordered_map<uint64_t, wr::Epoch> mContentPipelines;
};
} // namespace layers
} // namespace mozilla
#endif // mozilla_layers_WebRenderCompositionRecorder_h

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

@ -74,6 +74,7 @@ pub enum OpacityType {
/// cbindgen:field-names=[mHandle]
/// cbindgen:derive-lt=true
/// cbindgen:derive-lte=true
/// cbindgen:derive-neq=true
type WrEpoch = Epoch;
/// cbindgen:field-names=[mHandle]
/// cbindgen:derive-lt=true