diff --git a/gfx/layers/CompositionRecorder.cpp b/gfx/layers/CompositionRecorder.cpp index 2c2791669154..8a309ad8c3c0 100644 --- a/gfx/layers/CompositionRecorder.cpp +++ b/gfx/layers/CompositionRecorder.cpp @@ -27,8 +27,6 @@ namespace layers { CompositionRecorder::CompositionRecorder(TimeStamp aRecordingStart) : mRecordingStart(aRecordingStart) {} -CompositionRecorder::~CompositionRecorder() {} - void CompositionRecorder::RecordFrame(RecordedFrame* aFrame) { mCollectedFrames.AppendElement(aFrame); } diff --git a/gfx/layers/CompositionRecorder.h b/gfx/layers/CompositionRecorder.h index f2052f61cb62..87fb210b8801 100644 --- a/gfx/layers/CompositionRecorder.h +++ b/gfx/layers/CompositionRecorder.h @@ -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> mCollectedFrames; diff --git a/gfx/layers/moz.build b/gfx/layers/moz.build index a841b58010b8..efcdbd2c7fa3 100755 --- a/gfx/layers/moz.build +++ b/gfx/layers/moz.build @@ -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', diff --git a/gfx/layers/wr/WebRenderCompositionRecorder.cpp b/gfx/layers/wr/WebRenderCompositionRecorder.cpp new file mode 100644 index 000000000000..d8b98369f15d --- /dev/null +++ b/gfx/layers/wr/WebRenderCompositionRecorder.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 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 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 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 diff --git a/gfx/layers/wr/WebRenderCompositionRecorder.h b/gfx/layers/wr/WebRenderCompositionRecorder.h new file mode 100644 index 000000000000..053a2ffd952b --- /dev/null +++ b/gfx/layers/wr/WebRenderCompositionRecorder.h @@ -0,0 +1,114 @@ +/* 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 + +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::WrPipelineId 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 mContentPipelines; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_WebRenderCompositionRecorder_h diff --git a/gfx/webrender_bindings/src/bindings.rs b/gfx/webrender_bindings/src/bindings.rs index ef1520e5d129..cf3b9efa1d89 100644 --- a/gfx/webrender_bindings/src/bindings.rs +++ b/gfx/webrender_bindings/src/bindings.rs @@ -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