зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
b526a32af7
Коммит
f5ab9bc353
|
@ -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
|
||||
|
|
Загрузка…
Ссылка в новой задаче