By default, it is disabled by pref gfx.canvas.remote.recycle-used-data-surface = false. Add a capability to reuse used DataSourceSurface that was previously used for storing converted video data. Add argument gfx::DataSourceSurface* aSurface to TextureHost::GetAsSurface(). aSurface may be used as returned DataSourceSurfacefi the TextureHost supports it. Differential Revision: https://phabricator.services.mozilla.com/D212608
1351 lines
44 KiB
C++
1351 lines
44 KiB
C++
/* -*- 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 https://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "CanvasTranslator.h"
|
|
|
|
#include "gfxGradientCache.h"
|
|
#include "mozilla/gfx/2D.h"
|
|
#include "mozilla/gfx/CanvasManagerParent.h"
|
|
#include "mozilla/gfx/CanvasRenderThread.h"
|
|
#include "mozilla/gfx/DrawTargetWebgl.h"
|
|
#include "mozilla/gfx/gfxVars.h"
|
|
#include "mozilla/gfx/GPUParent.h"
|
|
#include "mozilla/gfx/GPUProcessManager.h"
|
|
#include "mozilla/gfx/Logging.h"
|
|
#include "mozilla/ipc/Endpoint.h"
|
|
#include "mozilla/layers/BufferTexture.h"
|
|
#include "mozilla/layers/CanvasTranslator.h"
|
|
#include "mozilla/layers/ImageDataSerializer.h"
|
|
#include "mozilla/layers/SharedSurfacesParent.h"
|
|
#include "mozilla/layers/TextureClient.h"
|
|
#include "mozilla/layers/VideoBridgeParent.h"
|
|
#include "mozilla/StaticPrefs_gfx.h"
|
|
#include "mozilla/SyncRunnable.h"
|
|
#include "mozilla/TaskQueue.h"
|
|
#include "mozilla/Telemetry.h"
|
|
#include "GLContext.h"
|
|
#include "RecordedCanvasEventImpl.h"
|
|
|
|
#if defined(XP_WIN)
|
|
# include "mozilla/gfx/DeviceManagerDx.h"
|
|
# include "mozilla/layers/TextureD3D11.h"
|
|
#endif
|
|
|
|
namespace mozilla {
|
|
namespace layers {
|
|
|
|
UniquePtr<TextureData> CanvasTranslator::CreateTextureData(
|
|
const gfx::IntSize& aSize, gfx::SurfaceFormat aFormat, bool aClear) {
|
|
TextureData* textureData = nullptr;
|
|
TextureAllocationFlags allocFlags =
|
|
aClear ? ALLOC_CLEAR_BUFFER : ALLOC_DEFAULT;
|
|
switch (mTextureType) {
|
|
#ifdef XP_WIN
|
|
case TextureType::D3D11: {
|
|
textureData =
|
|
D3D11TextureData::Create(aSize, aFormat, allocFlags, mDevice);
|
|
break;
|
|
}
|
|
#endif
|
|
case TextureType::Unknown:
|
|
textureData = BufferTextureData::Create(
|
|
aSize, aFormat, gfx::BackendType::SKIA, LayersBackend::LAYERS_WR,
|
|
TextureFlags::DEALLOCATE_CLIENT | TextureFlags::REMOTE_TEXTURE,
|
|
allocFlags, nullptr);
|
|
break;
|
|
default:
|
|
textureData = TextureData::Create(mTextureType, aFormat, aSize,
|
|
allocFlags, mBackendType);
|
|
break;
|
|
}
|
|
|
|
return WrapUnique(textureData);
|
|
}
|
|
|
|
CanvasTranslator::CanvasTranslator(
|
|
layers::SharedSurfacesHolder* aSharedSurfacesHolder,
|
|
const dom::ContentParentId& aContentId, uint32_t aManagerId)
|
|
: mTranslationTaskQueue(gfx::CanvasRenderThread::CreateWorkerTaskQueue()),
|
|
mSharedSurfacesHolder(aSharedSurfacesHolder),
|
|
mMaxSpinCount(StaticPrefs::gfx_canvas_remote_max_spin_count()),
|
|
mContentId(aContentId),
|
|
mManagerId(aManagerId) {
|
|
mNextEventTimeout = TimeDuration::FromMilliseconds(
|
|
StaticPrefs::gfx_canvas_remote_event_timeout_ms());
|
|
|
|
// Track when remote canvas has been activated.
|
|
Telemetry::ScalarAdd(Telemetry::ScalarID::GFX_CANVAS_REMOTE_ACTIVATED, 1);
|
|
}
|
|
|
|
CanvasTranslator::~CanvasTranslator() = default;
|
|
|
|
void CanvasTranslator::DispatchToTaskQueue(
|
|
already_AddRefed<nsIRunnable> aRunnable) {
|
|
if (mTranslationTaskQueue) {
|
|
MOZ_ALWAYS_SUCCEEDS(mTranslationTaskQueue->Dispatch(std::move(aRunnable)));
|
|
} else {
|
|
gfx::CanvasRenderThread::Dispatch(std::move(aRunnable));
|
|
}
|
|
}
|
|
|
|
bool CanvasTranslator::IsInTaskQueue() const {
|
|
if (mTranslationTaskQueue) {
|
|
return mTranslationTaskQueue->IsCurrentThreadIn();
|
|
}
|
|
return gfx::CanvasRenderThread::IsInCanvasRenderThread();
|
|
}
|
|
|
|
static bool CreateAndMapShmem(RefPtr<ipc::SharedMemoryBasic>& aShmem,
|
|
Handle&& aHandle,
|
|
ipc::SharedMemory::OpenRights aOpenRights,
|
|
size_t aSize) {
|
|
auto shmem = MakeRefPtr<ipc::SharedMemoryBasic>();
|
|
if (!shmem->SetHandle(std::move(aHandle), aOpenRights) ||
|
|
!shmem->Map(aSize)) {
|
|
return false;
|
|
}
|
|
|
|
shmem->CloseHandle();
|
|
aShmem = shmem.forget();
|
|
return true;
|
|
}
|
|
|
|
StaticRefPtr<gfx::SharedContextWebgl> CanvasTranslator::sSharedContext;
|
|
|
|
bool CanvasTranslator::EnsureSharedContextWebgl() {
|
|
if (!mSharedContext || mSharedContext->IsContextLost()) {
|
|
if (mSharedContext) {
|
|
ForceDrawTargetWebglFallback();
|
|
if (mRemoteTextureOwner) {
|
|
// Ensure any shared surfaces referring to the old context go away.
|
|
mRemoteTextureOwner->ClearRecycledTextures();
|
|
}
|
|
}
|
|
// Check if the global shared context is still valid. If not, instantiate
|
|
// a new one before we try to use it.
|
|
if (!sSharedContext || sSharedContext->IsContextLost()) {
|
|
sSharedContext = gfx::SharedContextWebgl::Create();
|
|
}
|
|
mSharedContext = sSharedContext;
|
|
// If we can't get a new context, then the only thing left to do is block
|
|
// new canvases.
|
|
if (!mSharedContext || mSharedContext->IsContextLost()) {
|
|
mSharedContext = nullptr;
|
|
BlockCanvas();
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void CanvasTranslator::Shutdown() {
|
|
if (sSharedContext) {
|
|
gfx::CanvasRenderThread::Dispatch(NS_NewRunnableFunction(
|
|
"CanvasTranslator::Shutdown", []() { sSharedContext = nullptr; }));
|
|
}
|
|
}
|
|
|
|
mozilla::ipc::IPCResult CanvasTranslator::RecvInitTranslator(
|
|
TextureType aTextureType, TextureType aWebglTextureType,
|
|
gfx::BackendType aBackendType, Handle&& aReadHandle,
|
|
nsTArray<Handle>&& aBufferHandles, uint64_t aBufferSize,
|
|
CrossProcessSemaphoreHandle&& aReaderSem,
|
|
CrossProcessSemaphoreHandle&& aWriterSem) {
|
|
if (mHeaderShmem) {
|
|
return IPC_FAIL(this, "RecvInitTranslator called twice.");
|
|
}
|
|
|
|
mTextureType = aTextureType;
|
|
mWebglTextureType = aWebglTextureType;
|
|
mBackendType = aBackendType;
|
|
mOtherPid = OtherPid();
|
|
|
|
mHeaderShmem = MakeAndAddRef<ipc::SharedMemoryBasic>();
|
|
if (!CreateAndMapShmem(mHeaderShmem, std::move(aReadHandle),
|
|
ipc::SharedMemory::RightsReadWrite, sizeof(Header))) {
|
|
Deactivate();
|
|
return IPC_FAIL(this, "Failed to map canvas header shared memory.");
|
|
}
|
|
|
|
mHeader = static_cast<Header*>(mHeaderShmem->memory());
|
|
|
|
mWriterSemaphore.reset(CrossProcessSemaphore::Create(std::move(aWriterSem)));
|
|
mWriterSemaphore->CloseHandle();
|
|
|
|
mReaderSemaphore.reset(CrossProcessSemaphore::Create(std::move(aReaderSem)));
|
|
mReaderSemaphore->CloseHandle();
|
|
|
|
if (!CheckForFreshCanvasDevice(__LINE__)) {
|
|
gfxCriticalNote << "GFX: CanvasTranslator failed to get device";
|
|
return IPC_OK();
|
|
}
|
|
|
|
if (gfx::gfxVars::UseAcceleratedCanvas2D() && !EnsureSharedContextWebgl()) {
|
|
gfxCriticalNote
|
|
<< "GFX: CanvasTranslator failed creating WebGL shared context";
|
|
}
|
|
|
|
// Use the first buffer as our current buffer.
|
|
mDefaultBufferSize = aBufferSize;
|
|
auto handleIter = aBufferHandles.begin();
|
|
if (!CreateAndMapShmem(mCurrentShmem.shmem, std::move(*handleIter),
|
|
ipc::SharedMemory::RightsReadOnly, aBufferSize)) {
|
|
Deactivate();
|
|
return IPC_FAIL(this, "Failed to map canvas buffer shared memory.");
|
|
}
|
|
mCurrentMemReader = mCurrentShmem.CreateMemReader();
|
|
|
|
// Add all other buffers to our recycled CanvasShmems.
|
|
for (handleIter++; handleIter < aBufferHandles.end(); handleIter++) {
|
|
CanvasShmem newShmem;
|
|
if (!CreateAndMapShmem(newShmem.shmem, std::move(*handleIter),
|
|
ipc::SharedMemory::RightsReadOnly, aBufferSize)) {
|
|
Deactivate();
|
|
return IPC_FAIL(this, "Failed to map canvas buffer shared memory.");
|
|
}
|
|
mCanvasShmems.emplace(std::move(newShmem));
|
|
}
|
|
|
|
DispatchToTaskQueue(NewRunnableMethod("CanvasTranslator::TranslateRecording",
|
|
this,
|
|
&CanvasTranslator::TranslateRecording));
|
|
return IPC_OK();
|
|
}
|
|
|
|
ipc::IPCResult CanvasTranslator::RecvRestartTranslation() {
|
|
if (mDeactivated) {
|
|
// The other side might have sent a message before we deactivated.
|
|
return IPC_OK();
|
|
}
|
|
|
|
DispatchToTaskQueue(NewRunnableMethod("CanvasTranslator::TranslateRecording",
|
|
this,
|
|
&CanvasTranslator::TranslateRecording));
|
|
|
|
return IPC_OK();
|
|
}
|
|
|
|
ipc::IPCResult CanvasTranslator::RecvAddBuffer(
|
|
ipc::SharedMemoryBasic::Handle&& aBufferHandle, uint64_t aBufferSize) {
|
|
if (mDeactivated) {
|
|
// The other side might have sent a resume message before we deactivated.
|
|
return IPC_OK();
|
|
}
|
|
|
|
DispatchToTaskQueue(
|
|
NewRunnableMethod<ipc::SharedMemoryBasic::Handle&&, size_t>(
|
|
"CanvasTranslator::AddBuffer", this, &CanvasTranslator::AddBuffer,
|
|
std::move(aBufferHandle), aBufferSize));
|
|
|
|
return IPC_OK();
|
|
}
|
|
|
|
void CanvasTranslator::AddBuffer(ipc::SharedMemoryBasic::Handle&& aBufferHandle,
|
|
size_t aBufferSize) {
|
|
MOZ_ASSERT(IsInTaskQueue());
|
|
if (mHeader->readerState == State::Failed) {
|
|
// We failed before we got to the pause event.
|
|
return;
|
|
}
|
|
|
|
if (mHeader->readerState != State::Paused) {
|
|
gfxCriticalNote << "CanvasTranslator::AddBuffer bad state "
|
|
<< uint32_t(State(mHeader->readerState));
|
|
MOZ_DIAGNOSTIC_ASSERT(false, "mHeader->readerState == State::Paused");
|
|
Deactivate();
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(mDefaultBufferSize != 0);
|
|
|
|
// Check and signal the writer when we finish with a buffer, because it
|
|
// might have hit the buffer count limit and be waiting to use our old one.
|
|
CheckAndSignalWriter();
|
|
|
|
// Default sized buffers will have been queued for recycling.
|
|
if (mCurrentShmem.Size() == mDefaultBufferSize) {
|
|
mCanvasShmems.emplace(std::move(mCurrentShmem));
|
|
}
|
|
|
|
CanvasShmem newShmem;
|
|
if (!CreateAndMapShmem(newShmem.shmem, std::move(aBufferHandle),
|
|
ipc::SharedMemory::RightsReadOnly, aBufferSize)) {
|
|
return;
|
|
}
|
|
|
|
mCurrentShmem = std::move(newShmem);
|
|
mCurrentMemReader = mCurrentShmem.CreateMemReader();
|
|
|
|
TranslateRecording();
|
|
}
|
|
|
|
ipc::IPCResult CanvasTranslator::RecvSetDataSurfaceBuffer(
|
|
ipc::SharedMemoryBasic::Handle&& aBufferHandle, uint64_t aBufferSize) {
|
|
if (mDeactivated) {
|
|
// The other side might have sent a resume message before we deactivated.
|
|
return IPC_OK();
|
|
}
|
|
|
|
DispatchToTaskQueue(
|
|
NewRunnableMethod<ipc::SharedMemoryBasic::Handle&&, size_t>(
|
|
"CanvasTranslator::SetDataSurfaceBuffer", this,
|
|
&CanvasTranslator::SetDataSurfaceBuffer, std::move(aBufferHandle),
|
|
aBufferSize));
|
|
|
|
return IPC_OK();
|
|
}
|
|
|
|
void CanvasTranslator::SetDataSurfaceBuffer(
|
|
ipc::SharedMemoryBasic::Handle&& aBufferHandle, size_t aBufferSize) {
|
|
MOZ_ASSERT(IsInTaskQueue());
|
|
if (mHeader->readerState == State::Failed) {
|
|
// We failed before we got to the pause event.
|
|
return;
|
|
}
|
|
|
|
if (mHeader->readerState != State::Paused) {
|
|
gfxCriticalNote << "CanvasTranslator::SetDataSurfaceBuffer bad state "
|
|
<< uint32_t(State(mHeader->readerState));
|
|
MOZ_DIAGNOSTIC_ASSERT(false, "mHeader->readerState == State::Paused");
|
|
Deactivate();
|
|
return;
|
|
}
|
|
|
|
if (!CreateAndMapShmem(mDataSurfaceShmem, std::move(aBufferHandle),
|
|
ipc::SharedMemory::RightsReadWrite, aBufferSize)) {
|
|
return;
|
|
}
|
|
|
|
TranslateRecording();
|
|
}
|
|
|
|
void CanvasTranslator::GetDataSurface(uint64_t aSurfaceRef) {
|
|
MOZ_ASSERT(IsInTaskQueue());
|
|
|
|
ReferencePtr surfaceRef = reinterpret_cast<void*>(aSurfaceRef);
|
|
gfx::SourceSurface* surface = LookupSourceSurface(surfaceRef);
|
|
if (!surface) {
|
|
return;
|
|
}
|
|
|
|
UniquePtr<gfx::DataSourceSurface::ScopedMap> map = GetPreparedMap(surfaceRef);
|
|
if (!map) {
|
|
return;
|
|
}
|
|
|
|
auto dstSize = surface->GetSize();
|
|
auto srcSize = map->GetSurface()->GetSize();
|
|
gfx::SurfaceFormat format = surface->GetFormat();
|
|
int32_t bpp = BytesPerPixel(format);
|
|
int32_t dataFormatWidth = dstSize.width * bpp;
|
|
int32_t srcStride = map->GetStride();
|
|
if (dataFormatWidth > srcStride || srcSize != dstSize) {
|
|
return;
|
|
}
|
|
|
|
int32_t dstStride =
|
|
ImageDataSerializer::ComputeRGBStride(format, dstSize.width);
|
|
auto requiredSize =
|
|
ImageDataSerializer::ComputeRGBBufferSize(dstSize, format);
|
|
if (requiredSize <= 0 || size_t(requiredSize) > mDataSurfaceShmem->Size()) {
|
|
return;
|
|
}
|
|
|
|
uint8_t* dst = static_cast<uint8_t*>(mDataSurfaceShmem->memory());
|
|
const uint8_t* src = map->GetData();
|
|
const uint8_t* endSrc = src + (srcSize.height * srcStride);
|
|
while (src < endSrc) {
|
|
memcpy(dst, src, dataFormatWidth);
|
|
src += srcStride;
|
|
dst += dstStride;
|
|
}
|
|
}
|
|
|
|
void CanvasTranslator::RecycleBuffer() {
|
|
mCanvasShmems.emplace(std::move(mCurrentShmem));
|
|
NextBuffer();
|
|
}
|
|
|
|
void CanvasTranslator::NextBuffer() {
|
|
// Check and signal the writer when we finish with a buffer, because it
|
|
// might have hit the buffer count limit and be waiting to use our old one.
|
|
CheckAndSignalWriter();
|
|
|
|
mCurrentShmem = std::move(mCanvasShmems.front());
|
|
mCanvasShmems.pop();
|
|
mCurrentMemReader = mCurrentShmem.CreateMemReader();
|
|
}
|
|
|
|
void CanvasTranslator::ActorDestroy(ActorDestroyReason why) {
|
|
MOZ_ASSERT(gfx::CanvasRenderThread::IsInCanvasRenderThread());
|
|
|
|
// Since we might need to access the actor status off the owning IPDL thread,
|
|
// we need to cache it here.
|
|
mIPDLClosed = true;
|
|
|
|
DispatchToTaskQueue(NewRunnableMethod("CanvasTranslator::ClearTextureInfo",
|
|
this,
|
|
&CanvasTranslator::ClearTextureInfo));
|
|
|
|
if (mTranslationTaskQueue) {
|
|
gfx::CanvasRenderThread::ShutdownWorkerTaskQueue(mTranslationTaskQueue);
|
|
return;
|
|
}
|
|
}
|
|
|
|
bool CanvasTranslator::CheckDeactivated() {
|
|
if (mDeactivated) {
|
|
return true;
|
|
}
|
|
|
|
if (NS_WARN_IF(!gfx::gfxVars::RemoteCanvasEnabled() &&
|
|
!gfx::gfxVars::UseAcceleratedCanvas2D())) {
|
|
Deactivate();
|
|
}
|
|
|
|
return mDeactivated;
|
|
}
|
|
|
|
void CanvasTranslator::Deactivate() {
|
|
if (mDeactivated) {
|
|
return;
|
|
}
|
|
mDeactivated = true;
|
|
if (mHeader) {
|
|
mHeader->readerState = State::Failed;
|
|
}
|
|
|
|
// We need to tell the other side to deactivate. Make sure the stream is
|
|
// marked as bad so that the writing side won't wait for space to write.
|
|
gfx::CanvasRenderThread::Dispatch(
|
|
NewRunnableMethod("CanvasTranslator::SendDeactivate", this,
|
|
&CanvasTranslator::SendDeactivate));
|
|
|
|
// Disable remote canvas for all.
|
|
gfx::CanvasManagerParent::DisableRemoteCanvas();
|
|
}
|
|
|
|
inline gfx::DrawTargetWebgl* CanvasTranslator::TextureInfo::GetDrawTargetWebgl(
|
|
bool aCheckForFallback) const {
|
|
if ((!mTextureData || !aCheckForFallback) && mDrawTarget &&
|
|
mDrawTarget->GetBackendType() == gfx::BackendType::WEBGL) {
|
|
return static_cast<gfx::DrawTargetWebgl*>(mDrawTarget.get());
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
bool CanvasTranslator::TryDrawTargetWebglFallback(
|
|
int64_t aTextureId, gfx::DrawTargetWebgl* aWebgl) {
|
|
NotifyRequiresRefresh(aTextureId);
|
|
|
|
// An existing data snapshot is required for fallback, as we have to avoid
|
|
// trying to touch the WebGL context, which is assumed to be invalid and not
|
|
// suitable for readback.
|
|
if (!aWebgl->HasDataSnapshot()) {
|
|
return false;
|
|
}
|
|
|
|
const auto& info = mTextureInfo[aTextureId];
|
|
if (RefPtr<gfx::DrawTarget> dt = CreateFallbackDrawTarget(
|
|
info.mRefPtr, aTextureId, info.mRemoteTextureOwnerId,
|
|
aWebgl->GetSize(), aWebgl->GetFormat())) {
|
|
aWebgl->CopyToFallback(dt);
|
|
AddDrawTarget(info.mRefPtr, dt);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void CanvasTranslator::ForceDrawTargetWebglFallback() {
|
|
// This looks for any DrawTargetWebgls that have a cached data snapshot that
|
|
// can be used to recover a fallback TextureData in the event of a context
|
|
// loss.
|
|
RemoteTextureOwnerIdSet lost;
|
|
for (const auto& entry : mTextureInfo) {
|
|
const auto& info = entry.second;
|
|
if (gfx::DrawTargetWebgl* webgl = info.GetDrawTargetWebgl()) {
|
|
if (!TryDrawTargetWebglFallback(entry.first, webgl)) {
|
|
// No fallback could be created, so we need to notify the compositor the
|
|
// texture won't be pushed.
|
|
if (mRemoteTextureOwner &&
|
|
mRemoteTextureOwner->IsRegistered(info.mRemoteTextureOwnerId)) {
|
|
lost.insert(info.mRemoteTextureOwnerId);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!lost.empty()) {
|
|
mRemoteTextureOwner->NotifyContextLost(&lost);
|
|
}
|
|
}
|
|
|
|
void CanvasTranslator::BlockCanvas() {
|
|
if (mDeactivated || mBlocked) {
|
|
return;
|
|
}
|
|
mBlocked = true;
|
|
gfx::CanvasRenderThread::Dispatch(
|
|
NewRunnableMethod("CanvasTranslator::SendBlockCanvas", this,
|
|
&CanvasTranslator::SendBlockCanvas));
|
|
}
|
|
|
|
void CanvasTranslator::CheckAndSignalWriter() {
|
|
do {
|
|
switch (mHeader->writerState) {
|
|
case State::Processing:
|
|
case State::Failed:
|
|
return;
|
|
case State::AboutToWait:
|
|
// The writer is making a decision about whether to wait. So, we must
|
|
// wait until it has decided to avoid races. Check if the writer is
|
|
// closed to avoid hangs.
|
|
if (mIPDLClosed) {
|
|
return;
|
|
}
|
|
continue;
|
|
case State::Waiting:
|
|
if (mHeader->processedCount >= mHeader->writerWaitCount) {
|
|
mHeader->writerState = State::Processing;
|
|
mWriterSemaphore->Signal();
|
|
}
|
|
return;
|
|
default:
|
|
MOZ_ASSERT_UNREACHABLE("Invalid waiting state.");
|
|
return;
|
|
}
|
|
} while (true);
|
|
}
|
|
|
|
bool CanvasTranslator::HasPendingEvent() {
|
|
return mHeader->processedCount < mHeader->eventCount;
|
|
}
|
|
|
|
bool CanvasTranslator::ReadPendingEvent(EventType& aEventType) {
|
|
ReadElementConstrained(mCurrentMemReader, aEventType,
|
|
EventType::DRAWTARGETCREATION, LAST_CANVAS_EVENT_TYPE);
|
|
return mCurrentMemReader.good();
|
|
}
|
|
|
|
bool CanvasTranslator::ReadNextEvent(EventType& aEventType) {
|
|
if (mHeader->readerState == State::Paused) {
|
|
Flush();
|
|
return false;
|
|
}
|
|
|
|
uint32_t spinCount = mMaxSpinCount;
|
|
do {
|
|
if (HasPendingEvent()) {
|
|
return ReadPendingEvent(aEventType);
|
|
}
|
|
} while (--spinCount != 0);
|
|
|
|
Flush();
|
|
mHeader->readerState = State::AboutToWait;
|
|
if (HasPendingEvent()) {
|
|
mHeader->readerState = State::Processing;
|
|
return ReadPendingEvent(aEventType);
|
|
}
|
|
|
|
if (!mIsInTransaction) {
|
|
mHeader->readerState = State::Stopped;
|
|
return false;
|
|
}
|
|
|
|
// When in a transaction we wait for a short time because we're expecting more
|
|
// events from the content process. We don't want to wait for too long in case
|
|
// other content processes are waiting for events to process.
|
|
mHeader->readerState = State::Waiting;
|
|
if (mReaderSemaphore->Wait(Some(mNextEventTimeout))) {
|
|
MOZ_RELEASE_ASSERT(HasPendingEvent());
|
|
MOZ_RELEASE_ASSERT(mHeader->readerState == State::Processing);
|
|
return ReadPendingEvent(aEventType);
|
|
}
|
|
|
|
// We have to use compareExchange here because the writer can change our
|
|
// state if we are waiting.
|
|
if (!mHeader->readerState.compareExchange(State::Waiting, State::Stopped)) {
|
|
MOZ_RELEASE_ASSERT(HasPendingEvent());
|
|
MOZ_RELEASE_ASSERT(mHeader->readerState == State::Processing);
|
|
// The writer has just signaled us, so consume it before returning
|
|
MOZ_ALWAYS_TRUE(mReaderSemaphore->Wait());
|
|
return ReadPendingEvent(aEventType);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void CanvasTranslator::TranslateRecording() {
|
|
MOZ_ASSERT(IsInTaskQueue());
|
|
|
|
if (mSharedContext && EnsureSharedContextWebgl()) {
|
|
mSharedContext->EnterTlsScope();
|
|
}
|
|
auto exitTlsScope = MakeScopeExit([&] {
|
|
if (mSharedContext) {
|
|
mSharedContext->ExitTlsScope();
|
|
}
|
|
});
|
|
|
|
mHeader->readerState = State::Processing;
|
|
EventType eventType = EventType::INVALID;
|
|
while (ReadNextEvent(eventType)) {
|
|
bool success = RecordedEvent::DoWithEventFromReader(
|
|
mCurrentMemReader, eventType,
|
|
[&](RecordedEvent* recordedEvent) -> bool {
|
|
// Make sure that the whole event was read from the stream.
|
|
if (!mCurrentMemReader.good()) {
|
|
if (mIPDLClosed) {
|
|
// The other side has closed only warn about read failure.
|
|
gfxWarning() << "Failed to read event type: "
|
|
<< recordedEvent->GetType();
|
|
} else {
|
|
gfxCriticalNote << "Failed to read event type: "
|
|
<< recordedEvent->GetType();
|
|
}
|
|
mHeader->readerState = State::Failed;
|
|
return false;
|
|
}
|
|
|
|
return recordedEvent->PlayEvent(this);
|
|
});
|
|
|
|
// Check the stream is good here or we will log the issue twice.
|
|
if (!mCurrentMemReader.good()) {
|
|
return;
|
|
}
|
|
|
|
if (!success && !HandleExtensionEvent(eventType)) {
|
|
if (mDeviceResetInProgress) {
|
|
// We've notified the recorder of a device change, so we are expecting
|
|
// failures. Log as a warning to prevent crash reporting being flooded.
|
|
gfxWarning() << "Failed to play canvas event type: " << eventType;
|
|
} else {
|
|
gfxCriticalNote << "Failed to play canvas event type: " << eventType;
|
|
}
|
|
mHeader->readerState = State::Failed;
|
|
}
|
|
|
|
mHeader->processedCount++;
|
|
}
|
|
}
|
|
|
|
#define READ_AND_PLAY_CANVAS_EVENT_TYPE(_typeenum, _class) \
|
|
case _typeenum: { \
|
|
auto e = _class(mCurrentMemReader); \
|
|
if (!mCurrentMemReader.good()) { \
|
|
if (mIPDLClosed) { \
|
|
/* The other side has closed only warn about read failure. */ \
|
|
gfxWarning() << "Failed to read event type: " << _typeenum; \
|
|
} else { \
|
|
gfxCriticalNote << "Failed to read event type: " << _typeenum; \
|
|
} \
|
|
return false; \
|
|
} \
|
|
return e.PlayCanvasEvent(this); \
|
|
}
|
|
|
|
bool CanvasTranslator::HandleExtensionEvent(int32_t aType) {
|
|
// This is where we handle extensions to the Moz2D Recording events to handle
|
|
// canvas specific things.
|
|
switch (aType) {
|
|
FOR_EACH_CANVAS_EVENT(READ_AND_PLAY_CANVAS_EVENT_TYPE)
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void CanvasTranslator::BeginTransaction() {
|
|
PROFILER_MARKER_TEXT("CanvasTranslator", GRAPHICS, {},
|
|
"CanvasTranslator::BeginTransaction"_ns);
|
|
mIsInTransaction = true;
|
|
}
|
|
|
|
void CanvasTranslator::Flush() {
|
|
#if defined(XP_WIN)
|
|
// We can end up without a device, due to a reset and failure to re-create.
|
|
if (!mDevice) {
|
|
return;
|
|
}
|
|
|
|
gfx::AutoSerializeWithMoz2D serializeWithMoz2D(mBackendType);
|
|
RefPtr<ID3D11DeviceContext> deviceContext;
|
|
mDevice->GetImmediateContext(getter_AddRefs(deviceContext));
|
|
deviceContext->Flush();
|
|
#endif
|
|
}
|
|
|
|
void CanvasTranslator::EndTransaction() {
|
|
Flush();
|
|
// At the end of a transaction is a good time to check if a new canvas device
|
|
// has been created, even if a reset did not occur.
|
|
Unused << CheckForFreshCanvasDevice(__LINE__);
|
|
mIsInTransaction = false;
|
|
}
|
|
|
|
void CanvasTranslator::DeviceChangeAcknowledged() {
|
|
mDeviceResetInProgress = false;
|
|
if (mRemoteTextureOwner) {
|
|
mRemoteTextureOwner->NotifyContextRestored();
|
|
}
|
|
}
|
|
|
|
bool CanvasTranslator::CreateReferenceTexture() {
|
|
if (mReferenceTextureData) {
|
|
mReferenceTextureData->Unlock();
|
|
}
|
|
|
|
mReferenceTextureData =
|
|
CreateTextureData(gfx::IntSize(1, 1), gfx::SurfaceFormat::B8G8R8A8, true);
|
|
if (!mReferenceTextureData) {
|
|
Deactivate();
|
|
return false;
|
|
}
|
|
|
|
if (NS_WARN_IF(!mReferenceTextureData->Lock(OpenMode::OPEN_READ_WRITE))) {
|
|
gfxCriticalNote << "CanvasTranslator::CreateReferenceTexture lock failed";
|
|
mReferenceTextureData.reset();
|
|
Deactivate();
|
|
return false;
|
|
}
|
|
|
|
mBaseDT = mReferenceTextureData->BorrowDrawTarget();
|
|
|
|
if (!mBaseDT) {
|
|
// We might get a null draw target due to a device failure, deactivate and
|
|
// return false so that we can recover.
|
|
Deactivate();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CanvasTranslator::CheckForFreshCanvasDevice(int aLineNumber) {
|
|
// If not on D3D11, we are not dependent on a fresh device for DT creation if
|
|
// one already exists.
|
|
if (mBaseDT && mTextureType != TextureType::D3D11) {
|
|
return false;
|
|
}
|
|
|
|
#if defined(XP_WIN)
|
|
// If a new device has already been created, use that one.
|
|
RefPtr<ID3D11Device> device = gfx::DeviceManagerDx::Get()->GetCanvasDevice();
|
|
if (device && device != mDevice) {
|
|
if (mDevice) {
|
|
// We already had a device, notify child of change.
|
|
NotifyDeviceChanged();
|
|
}
|
|
mDevice = device.forget();
|
|
return CreateReferenceTexture();
|
|
}
|
|
|
|
gfx::DeviceResetReason reason = gfx::DeviceResetReason::OTHER;
|
|
|
|
if (mDevice) {
|
|
const auto d3d11Reason = mDevice->GetDeviceRemovedReason();
|
|
reason = DXGIErrorToDeviceResetReason(d3d11Reason);
|
|
if (reason == gfx::DeviceResetReason::OK) {
|
|
return false;
|
|
}
|
|
|
|
gfxCriticalNote << "GFX: CanvasTranslator detected a device reset at "
|
|
<< aLineNumber;
|
|
NotifyDeviceChanged();
|
|
}
|
|
|
|
RefPtr<Runnable> runnable =
|
|
NS_NewRunnableFunction("CanvasTranslator NotifyDeviceReset", [reason]() {
|
|
gfx::GPUProcessManager::GPUProcessManager::NotifyDeviceReset(
|
|
reason, gfx::DeviceResetDetectPlace::CANVAS_TRANSLATOR);
|
|
});
|
|
|
|
// It is safe to wait here because only the Compositor thread waits on us and
|
|
// the main thread doesn't wait on the compositor thread in the GPU process.
|
|
SyncRunnable::DispatchToThread(GetMainThreadSerialEventTarget(), runnable,
|
|
/*aForceDispatch*/ true);
|
|
|
|
mDevice = gfx::DeviceManagerDx::Get()->GetCanvasDevice();
|
|
if (!mDevice) {
|
|
// We don't have a canvas device, we need to deactivate.
|
|
Telemetry::ScalarAdd(
|
|
Telemetry::ScalarID::GFX_CANVAS_REMOTE_DEACTIVATED_NO_DEVICE, 1);
|
|
Deactivate();
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
return CreateReferenceTexture();
|
|
}
|
|
|
|
void CanvasTranslator::NotifyDeviceChanged() {
|
|
// Clear out any old recycled texture datas with the wrong device.
|
|
if (mRemoteTextureOwner) {
|
|
mRemoteTextureOwner->NotifyContextLost();
|
|
mRemoteTextureOwner->ClearRecycledTextures();
|
|
}
|
|
mDeviceResetInProgress = true;
|
|
gfx::CanvasRenderThread::Dispatch(
|
|
NewRunnableMethod("CanvasTranslator::SendNotifyDeviceChanged", this,
|
|
&CanvasTranslator::SendNotifyDeviceChanged));
|
|
}
|
|
|
|
gfx::DrawTargetWebgl* CanvasTranslator::GetDrawTargetWebgl(
|
|
int64_t aTextureId, bool aCheckForFallback) const {
|
|
auto result = mTextureInfo.find(aTextureId);
|
|
if (result != mTextureInfo.end()) {
|
|
return result->second.GetDrawTargetWebgl(aCheckForFallback);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void CanvasTranslator::NotifyRequiresRefresh(int64_t aTextureId,
|
|
bool aDispatch) {
|
|
if (aDispatch) {
|
|
auto& info = mTextureInfo[aTextureId];
|
|
if (!info.mNotifiedRequiresRefresh) {
|
|
info.mNotifiedRequiresRefresh = true;
|
|
DispatchToTaskQueue(NewRunnableMethod<int64_t, bool>(
|
|
"CanvasTranslator::NotifyRequiresRefresh", this,
|
|
&CanvasTranslator::NotifyRequiresRefresh, aTextureId, false));
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (mTextureInfo.find(aTextureId) != mTextureInfo.end()) {
|
|
Unused << SendNotifyRequiresRefresh(aTextureId);
|
|
}
|
|
}
|
|
|
|
void CanvasTranslator::CacheSnapshotShmem(int64_t aTextureId, bool aDispatch) {
|
|
if (aDispatch) {
|
|
DispatchToTaskQueue(NewRunnableMethod<int64_t, bool>(
|
|
"CanvasTranslator::CacheSnapshotShmem", this,
|
|
&CanvasTranslator::CacheSnapshotShmem, aTextureId, false));
|
|
return;
|
|
}
|
|
|
|
if (gfx::DrawTargetWebgl* webgl = GetDrawTargetWebgl(aTextureId)) {
|
|
if (auto shmemHandle = webgl->TakeShmemHandle()) {
|
|
// Lock the DT so that it doesn't get removed while shmem is in transit.
|
|
mTextureInfo[aTextureId].mLocked++;
|
|
nsCOMPtr<nsIThread> thread =
|
|
gfx::CanvasRenderThread::GetCanvasRenderThread();
|
|
RefPtr<CanvasTranslator> translator = this;
|
|
SendSnapshotShmem(aTextureId, std::move(shmemHandle),
|
|
webgl->GetShmemSize())
|
|
->Then(
|
|
thread, __func__,
|
|
[=](bool) { translator->RemoveTexture(aTextureId); },
|
|
[=](ipc::ResponseRejectReason) {
|
|
translator->RemoveTexture(aTextureId);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
void CanvasTranslator::PrepareShmem(int64_t aTextureId) {
|
|
if (gfx::DrawTargetWebgl* webgl = GetDrawTargetWebgl(aTextureId, false)) {
|
|
if (const auto& fallback = mTextureInfo[aTextureId].mTextureData) {
|
|
// If there was a fallback, copy the fallback to the software framebuffer
|
|
// shmem for reading.
|
|
if (RefPtr<gfx::DrawTarget> dt = fallback->BorrowDrawTarget()) {
|
|
if (RefPtr<gfx::SourceSurface> snapshot = dt->Snapshot()) {
|
|
webgl->CopySurface(snapshot, snapshot->GetRect(),
|
|
gfx::IntPoint(0, 0));
|
|
}
|
|
}
|
|
} else {
|
|
// Otherwise, just ensure the software framebuffer is up to date.
|
|
webgl->PrepareShmem();
|
|
}
|
|
}
|
|
}
|
|
|
|
void CanvasTranslator::ClearCachedResources() {
|
|
mUsedDataSurfaceForSurfaceDescriptor = nullptr;
|
|
if (mSharedContext) {
|
|
// If there are any DrawTargetWebgls, then try to cache their framebuffers
|
|
// in software surfaces, just in case the GL context is lost. So long as
|
|
// there is a software copy of the framebuffer, it can be copied into a
|
|
// fallback TextureData later even if the GL context goes away.
|
|
mSharedContext->OnMemoryPressure();
|
|
for (auto const& entry : mTextureInfo) {
|
|
if (gfx::DrawTargetWebgl* webgl = entry.second.GetDrawTargetWebgl()) {
|
|
webgl->EnsureDataSnapshot();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ipc::IPCResult CanvasTranslator::RecvClearCachedResources() {
|
|
if (mDeactivated) {
|
|
// The other side might have sent a message before we deactivated.
|
|
return IPC_OK();
|
|
}
|
|
|
|
DispatchToTaskQueue(
|
|
NewRunnableMethod("CanvasTranslator::ClearCachedResources", this,
|
|
&CanvasTranslator::ClearCachedResources));
|
|
return IPC_OK();
|
|
}
|
|
|
|
static const OpenMode kInitMode = OpenMode::OPEN_READ_WRITE;
|
|
|
|
already_AddRefed<gfx::DrawTarget> CanvasTranslator::CreateFallbackDrawTarget(
|
|
gfx::ReferencePtr aRefPtr, int64_t aTextureId,
|
|
RemoteTextureOwnerId aTextureOwnerId, const gfx::IntSize& aSize,
|
|
gfx::SurfaceFormat aFormat) {
|
|
RefPtr<gfx::DrawTarget> dt;
|
|
do {
|
|
UniquePtr<TextureData> textureData =
|
|
CreateOrRecycleTextureData(aSize, aFormat);
|
|
if (NS_WARN_IF(!textureData)) {
|
|
continue;
|
|
}
|
|
|
|
if (NS_WARN_IF(!textureData->Lock(kInitMode))) {
|
|
gfxCriticalNote << "CanvasTranslator::CreateDrawTarget lock failed";
|
|
continue;
|
|
}
|
|
|
|
dt = textureData->BorrowDrawTarget();
|
|
if (NS_WARN_IF(!dt)) {
|
|
textureData->Unlock();
|
|
continue;
|
|
}
|
|
// Recycled buffer contents may be uninitialized.
|
|
dt->ClearRect(gfx::Rect(dt->GetRect()));
|
|
|
|
TextureInfo& info = mTextureInfo[aTextureId];
|
|
info.mRefPtr = aRefPtr;
|
|
info.mTextureData = std::move(textureData);
|
|
info.mRemoteTextureOwnerId = aTextureOwnerId;
|
|
info.mTextureLockMode = kInitMode;
|
|
} while (!dt && CheckForFreshCanvasDevice(__LINE__));
|
|
return dt.forget();
|
|
}
|
|
|
|
already_AddRefed<gfx::DrawTarget> CanvasTranslator::CreateDrawTarget(
|
|
gfx::ReferencePtr aRefPtr, int64_t aTextureId,
|
|
RemoteTextureOwnerId aTextureOwnerId, const gfx::IntSize& aSize,
|
|
gfx::SurfaceFormat aFormat) {
|
|
if (aTextureId < 0) {
|
|
MOZ_DIAGNOSTIC_ASSERT(false, "No texture ID set");
|
|
return nullptr;
|
|
}
|
|
|
|
if (!aTextureOwnerId.IsValid()) {
|
|
MOZ_DIAGNOSTIC_ASSERT(false, "No texture owner set");
|
|
return nullptr;
|
|
}
|
|
|
|
RefPtr<gfx::DrawTarget> dt;
|
|
if (gfx::gfxVars::UseAcceleratedCanvas2D()) {
|
|
if (EnsureSharedContextWebgl()) {
|
|
mSharedContext->EnterTlsScope();
|
|
}
|
|
if (RefPtr<gfx::DrawTargetWebgl> webgl =
|
|
gfx::DrawTargetWebgl::Create(aSize, aFormat, mSharedContext)) {
|
|
webgl->BeginFrame(true);
|
|
dt = webgl.forget().downcast<gfx::DrawTarget>();
|
|
if (dt) {
|
|
TextureInfo& info = mTextureInfo[aTextureId];
|
|
info.mRefPtr = aRefPtr;
|
|
info.mDrawTarget = dt;
|
|
info.mRemoteTextureOwnerId = aTextureOwnerId;
|
|
info.mTextureLockMode = kInitMode;
|
|
CacheSnapshotShmem(aTextureId);
|
|
}
|
|
}
|
|
if (!dt) {
|
|
NotifyRequiresRefresh(aTextureId);
|
|
}
|
|
}
|
|
|
|
if (!dt) {
|
|
dt = CreateFallbackDrawTarget(aRefPtr, aTextureId, aTextureOwnerId, aSize,
|
|
aFormat);
|
|
}
|
|
|
|
AddDrawTarget(aRefPtr, dt);
|
|
return dt.forget();
|
|
}
|
|
|
|
already_AddRefed<gfx::DrawTarget> CanvasTranslator::CreateDrawTarget(
|
|
gfx::ReferencePtr aRefPtr, const gfx::IntSize& aSize,
|
|
gfx::SurfaceFormat aFormat) {
|
|
MOZ_DIAGNOSTIC_ASSERT(false, "Unexpected CreateDrawTarget call!");
|
|
return nullptr;
|
|
}
|
|
|
|
void CanvasTranslator::RemoveTexture(int64_t aTextureId,
|
|
RemoteTextureTxnType aTxnType,
|
|
RemoteTextureTxnId aTxnId) {
|
|
// Don't erase the texture if still in use
|
|
auto result = mTextureInfo.find(aTextureId);
|
|
if (result == mTextureInfo.end()) {
|
|
return;
|
|
}
|
|
auto& info = result->second;
|
|
if (mRemoteTextureOwner && aTxnType && aTxnId) {
|
|
mRemoteTextureOwner->WaitForTxn(info.mRemoteTextureOwnerId, aTxnType,
|
|
aTxnId);
|
|
}
|
|
if (--info.mLocked > 0) {
|
|
return;
|
|
}
|
|
if (info.mTextureData) {
|
|
info.mTextureData->Unlock();
|
|
}
|
|
if (mRemoteTextureOwner) {
|
|
// If this texture id was manually registered as a remote texture owner,
|
|
// unregister it so it does not stick around after the texture id goes away.
|
|
RemoteTextureOwnerId owner = info.mRemoteTextureOwnerId;
|
|
if (owner.IsValid()) {
|
|
mRemoteTextureOwner->UnregisterTextureOwner(owner);
|
|
}
|
|
}
|
|
mTextureInfo.erase(result);
|
|
}
|
|
|
|
bool CanvasTranslator::LockTexture(int64_t aTextureId, OpenMode aMode,
|
|
bool aInvalidContents) {
|
|
if (aMode == OpenMode::OPEN_NONE) {
|
|
return false;
|
|
}
|
|
auto result = mTextureInfo.find(aTextureId);
|
|
if (result == mTextureInfo.end()) {
|
|
return false;
|
|
}
|
|
auto& info = result->second;
|
|
if (info.mTextureLockMode != OpenMode::OPEN_NONE) {
|
|
return (info.mTextureLockMode & aMode) == aMode;
|
|
}
|
|
if (gfx::DrawTargetWebgl* webgl = info.GetDrawTargetWebgl()) {
|
|
if (aMode & OpenMode::OPEN_WRITE) {
|
|
webgl->BeginFrame(aInvalidContents);
|
|
}
|
|
}
|
|
info.mTextureLockMode = aMode;
|
|
return true;
|
|
}
|
|
|
|
bool CanvasTranslator::UnlockTexture(int64_t aTextureId) {
|
|
auto result = mTextureInfo.find(aTextureId);
|
|
if (result == mTextureInfo.end()) {
|
|
return false;
|
|
}
|
|
auto& info = result->second;
|
|
if (info.mTextureLockMode == OpenMode::OPEN_NONE) {
|
|
return false;
|
|
}
|
|
|
|
if (gfx::DrawTargetWebgl* webgl = info.GetDrawTargetWebgl()) {
|
|
if (info.mTextureLockMode & OpenMode::OPEN_WRITE) {
|
|
webgl->EndFrame();
|
|
if (webgl->RequiresRefresh()) {
|
|
NotifyRequiresRefresh(aTextureId);
|
|
}
|
|
}
|
|
}
|
|
info.mTextureLockMode = OpenMode::OPEN_NONE;
|
|
return true;
|
|
}
|
|
|
|
bool CanvasTranslator::PresentTexture(int64_t aTextureId, RemoteTextureId aId) {
|
|
AUTO_PROFILER_MARKER_TEXT("CanvasTranslator", GRAPHICS, {},
|
|
"CanvasTranslator::PresentTexture"_ns);
|
|
auto result = mTextureInfo.find(aTextureId);
|
|
if (result == mTextureInfo.end()) {
|
|
return false;
|
|
}
|
|
auto& info = result->second;
|
|
RemoteTextureOwnerId ownerId = info.mRemoteTextureOwnerId;
|
|
if (gfx::DrawTargetWebgl* webgl = info.GetDrawTargetWebgl()) {
|
|
EnsureRemoteTextureOwner(ownerId);
|
|
if (webgl->CopyToSwapChain(mWebglTextureType, aId, ownerId,
|
|
mRemoteTextureOwner)) {
|
|
return true;
|
|
}
|
|
if (mSharedContext && mSharedContext->IsContextLost()) {
|
|
// If the context was lost, try to create a fallback to push instead.
|
|
EnsureSharedContextWebgl();
|
|
} else {
|
|
// CopyToSwapChain failed for an unknown reason other than context loss.
|
|
// Try to read into fallback data if possible to recover, otherwise force
|
|
// the loss of the individual texture.
|
|
webgl->EnsureDataSnapshot();
|
|
if (!TryDrawTargetWebglFallback(aTextureId, webgl)) {
|
|
RemoteTextureOwnerIdSet lost = {info.mRemoteTextureOwnerId};
|
|
mRemoteTextureOwner->NotifyContextLost(&lost);
|
|
}
|
|
}
|
|
}
|
|
if (TextureData* data = info.mTextureData.get()) {
|
|
PushRemoteTexture(aTextureId, data, aId, ownerId);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void CanvasTranslator::EnsureRemoteTextureOwner(RemoteTextureOwnerId aOwnerId) {
|
|
if (!mRemoteTextureOwner) {
|
|
mRemoteTextureOwner = new RemoteTextureOwnerClient(mOtherPid);
|
|
}
|
|
if (aOwnerId.IsValid() && !mRemoteTextureOwner->IsRegistered(aOwnerId)) {
|
|
mRemoteTextureOwner->RegisterTextureOwner(aOwnerId,
|
|
/* aSharedRecycling */ true);
|
|
}
|
|
}
|
|
|
|
UniquePtr<TextureData> CanvasTranslator::CreateOrRecycleTextureData(
|
|
const gfx::IntSize& aSize, gfx::SurfaceFormat aFormat) {
|
|
if (mRemoteTextureOwner) {
|
|
if (mTextureType == TextureType::Unknown) {
|
|
return mRemoteTextureOwner->CreateOrRecycleBufferTextureData(aSize,
|
|
aFormat);
|
|
}
|
|
if (UniquePtr<TextureData> data =
|
|
mRemoteTextureOwner->GetRecycledTextureData(aSize, aFormat,
|
|
mTextureType)) {
|
|
return data;
|
|
}
|
|
}
|
|
return CreateTextureData(aSize, aFormat, false);
|
|
}
|
|
|
|
bool CanvasTranslator::PushRemoteTexture(int64_t aTextureId, TextureData* aData,
|
|
RemoteTextureId aId,
|
|
RemoteTextureOwnerId aOwnerId) {
|
|
EnsureRemoteTextureOwner(aOwnerId);
|
|
UniquePtr<TextureData> dstData;
|
|
if (!mDeviceResetInProgress) {
|
|
TextureData::Info info;
|
|
aData->FillInfo(info);
|
|
dstData = CreateOrRecycleTextureData(info.size, info.format);
|
|
}
|
|
bool success = false;
|
|
// Source data is already locked.
|
|
if (dstData) {
|
|
if (dstData->Lock(OpenMode::OPEN_WRITE)) {
|
|
if (RefPtr<gfx::DrawTarget> dstDT = dstData->BorrowDrawTarget()) {
|
|
if (RefPtr<gfx::DrawTarget> srcDT = aData->BorrowDrawTarget()) {
|
|
if (RefPtr<gfx::SourceSurface> snapshot = srcDT->Snapshot()) {
|
|
dstDT->CopySurface(snapshot, snapshot->GetRect(),
|
|
gfx::IntPoint(0, 0));
|
|
dstDT->Flush();
|
|
success = true;
|
|
}
|
|
}
|
|
}
|
|
dstData->Unlock();
|
|
} else {
|
|
gfxCriticalNote << "CanvasTranslator::PushRemoteTexture dst lock failed";
|
|
}
|
|
}
|
|
if (success) {
|
|
mRemoteTextureOwner->PushTexture(aId, aOwnerId, std::move(dstData));
|
|
} else {
|
|
mRemoteTextureOwner->PushDummyTexture(aId, aOwnerId);
|
|
}
|
|
return success;
|
|
}
|
|
|
|
void CanvasTranslator::ClearTextureInfo() {
|
|
mUsedDataSurfaceForSurfaceDescriptor = nullptr;
|
|
for (auto const& entry : mTextureInfo) {
|
|
if (entry.second.mTextureData) {
|
|
entry.second.mTextureData->Unlock();
|
|
}
|
|
}
|
|
mTextureInfo.clear();
|
|
mDrawTargets.Clear();
|
|
mSharedContext = nullptr;
|
|
// If the global shared context's ref is the last ref left, then clear out
|
|
// any internal caches and textures from the context, but still keep it
|
|
// alive. This saves on startup costs while not contributing significantly
|
|
// to memory usage.
|
|
if (sSharedContext && sSharedContext->hasOneRef()) {
|
|
sSharedContext->ClearCaches();
|
|
}
|
|
mBaseDT = nullptr;
|
|
if (mReferenceTextureData) {
|
|
mReferenceTextureData->Unlock();
|
|
}
|
|
if (mRemoteTextureOwner) {
|
|
mRemoteTextureOwner->UnregisterAllTextureOwners();
|
|
mRemoteTextureOwner = nullptr;
|
|
}
|
|
if (mTranslationTaskQueue) {
|
|
gfx::CanvasRenderThread::FinishShutdownWorkerTaskQueue(
|
|
mTranslationTaskQueue);
|
|
}
|
|
}
|
|
|
|
already_AddRefed<gfx::SourceSurface> CanvasTranslator::LookupExternalSurface(
|
|
uint64_t aKey) {
|
|
return mSharedSurfacesHolder->Get(wr::ToExternalImageId(aKey));
|
|
}
|
|
|
|
// Check if the surface descriptor describes a GPUVideo texture for which we
|
|
// only have an opaque source/handle from SurfaceDescriptorRemoteDecoder to
|
|
// derive the actual texture from.
|
|
static bool SDIsSupportedRemoteDecoder(const SurfaceDescriptor& sd) {
|
|
if (sd.type() != SurfaceDescriptor::TSurfaceDescriptorGPUVideo) {
|
|
return false;
|
|
}
|
|
|
|
const auto& sdv = sd.get_SurfaceDescriptorGPUVideo();
|
|
const auto& sdvType = sdv.type();
|
|
if (sdvType != SurfaceDescriptorGPUVideo::TSurfaceDescriptorRemoteDecoder) {
|
|
return false;
|
|
}
|
|
|
|
const auto& sdrd = sdv.get_SurfaceDescriptorRemoteDecoder();
|
|
const auto& subdesc = sdrd.subdesc();
|
|
const auto& subdescType = subdesc.type();
|
|
|
|
if (subdescType == RemoteDecoderVideoSubDescriptor::Tnull_t ||
|
|
subdescType ==
|
|
RemoteDecoderVideoSubDescriptor::TSurfaceDescriptorMacIOSurface) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
already_AddRefed<gfx::DataSourceSurface>
|
|
CanvasTranslator::GetRecycledDataSurfaceForSurfaceDescriptor(
|
|
TextureHost* aTextureHost) {
|
|
if (!StaticPrefs::gfx_canvas_remote_recycle_used_data_surface()) {
|
|
return nullptr;
|
|
}
|
|
|
|
auto& usedSurf = mUsedDataSurfaceForSurfaceDescriptor;
|
|
|
|
bool isYuvVideo = false;
|
|
if (aTextureHost->AsMacIOSurfaceTextureHost()) {
|
|
if (aTextureHost->GetFormat() == SurfaceFormat::NV12 ||
|
|
aTextureHost->GetFormat() == SurfaceFormat::YUV422) {
|
|
isYuvVideo = true;
|
|
}
|
|
} else if (aTextureHost->GetFormat() == gfx::SurfaceFormat::YUV) {
|
|
isYuvVideo = true;
|
|
}
|
|
|
|
if (isYuvVideo && usedSurf && usedSurf->refCount() == 1 &&
|
|
usedSurf->GetFormat() == gfx::SurfaceFormat::B8G8R8X8 &&
|
|
aTextureHost->GetSize() == usedSurf->GetSize()) {
|
|
// Reuse previously used DataSourceSurface if it is not used and same
|
|
// size/format.
|
|
return usedSurf.forget();
|
|
}
|
|
usedSurf = nullptr;
|
|
return nullptr;
|
|
}
|
|
|
|
already_AddRefed<gfx::SourceSurface>
|
|
CanvasTranslator::LookupSourceSurfaceFromSurfaceDescriptor(
|
|
const SurfaceDescriptor& aDesc) {
|
|
if (!SDIsSupportedRemoteDecoder(aDesc)) {
|
|
return nullptr;
|
|
}
|
|
|
|
const auto& sdrd = aDesc.get_SurfaceDescriptorGPUVideo()
|
|
.get_SurfaceDescriptorRemoteDecoder();
|
|
const auto& subdesc = sdrd.subdesc();
|
|
const auto& subdescType = subdesc.type();
|
|
|
|
RefPtr<VideoBridgeParent> parent =
|
|
VideoBridgeParent::GetSingleton(sdrd.source());
|
|
if (!parent) {
|
|
MOZ_ASSERT_UNREACHABLE("unexpected to be called");
|
|
gfxCriticalNote << "TexUnpackSurface failed to get VideoBridgeParent";
|
|
return nullptr;
|
|
}
|
|
RefPtr<TextureHost> texture =
|
|
parent->LookupTexture(mContentId, sdrd.handle());
|
|
if (!texture) {
|
|
MOZ_ASSERT_UNREACHABLE("unexpected to be called");
|
|
gfxCriticalNote << "TexUnpackSurface failed to get TextureHost";
|
|
return nullptr;
|
|
}
|
|
|
|
if (subdescType ==
|
|
RemoteDecoderVideoSubDescriptor::TSurfaceDescriptorMacIOSurface) {
|
|
MOZ_ASSERT(texture->AsMacIOSurfaceTextureHost());
|
|
|
|
RefPtr<gfx::DataSourceSurface> reuseSurf =
|
|
GetRecycledDataSurfaceForSurfaceDescriptor(texture);
|
|
RefPtr<gfx::DataSourceSurface> surf = texture->GetAsSurface(reuseSurf);
|
|
mUsedDataSurfaceForSurfaceDescriptor = surf;
|
|
return surf.forget();
|
|
}
|
|
|
|
if (subdescType == RemoteDecoderVideoSubDescriptor::Tnull_t) {
|
|
RefPtr<gfx::DataSourceSurface> reuseSurf =
|
|
GetRecycledDataSurfaceForSurfaceDescriptor(texture);
|
|
RefPtr<gfx::DataSourceSurface> surf = texture->GetAsSurface(reuseSurf);
|
|
mUsedDataSurfaceForSurfaceDescriptor = surf;
|
|
return surf.forget();
|
|
}
|
|
|
|
MOZ_ASSERT_UNREACHABLE("unexpected to be called");
|
|
return nullptr;
|
|
}
|
|
|
|
void CanvasTranslator::CheckpointReached() { CheckAndSignalWriter(); }
|
|
|
|
void CanvasTranslator::PauseTranslation() {
|
|
mHeader->readerState = State::Paused;
|
|
}
|
|
|
|
already_AddRefed<gfx::GradientStops> CanvasTranslator::GetOrCreateGradientStops(
|
|
gfx::DrawTarget* aDrawTarget, gfx::GradientStop* aRawStops,
|
|
uint32_t aNumStops, gfx::ExtendMode aExtendMode) {
|
|
MOZ_ASSERT(aDrawTarget);
|
|
nsTArray<gfx::GradientStop> rawStopArray(aRawStops, aNumStops);
|
|
return gfx::gfxGradientCache::GetOrCreateGradientStops(
|
|
aDrawTarget, rawStopArray, aExtendMode);
|
|
}
|
|
|
|
gfx::DataSourceSurface* CanvasTranslator::LookupDataSurface(
|
|
gfx::ReferencePtr aRefPtr) {
|
|
return mDataSurfaces.GetWeak(aRefPtr);
|
|
}
|
|
|
|
void CanvasTranslator::AddDataSurface(
|
|
gfx::ReferencePtr aRefPtr, RefPtr<gfx::DataSourceSurface>&& aSurface) {
|
|
mDataSurfaces.InsertOrUpdate(aRefPtr, std::move(aSurface));
|
|
}
|
|
|
|
void CanvasTranslator::RemoveDataSurface(gfx::ReferencePtr aRefPtr) {
|
|
mDataSurfaces.Remove(aRefPtr);
|
|
}
|
|
|
|
void CanvasTranslator::SetPreparedMap(
|
|
gfx::ReferencePtr aSurface,
|
|
UniquePtr<gfx::DataSourceSurface::ScopedMap> aMap) {
|
|
mMappedSurface = aSurface;
|
|
mPreparedMap = std::move(aMap);
|
|
}
|
|
|
|
UniquePtr<gfx::DataSourceSurface::ScopedMap> CanvasTranslator::GetPreparedMap(
|
|
gfx::ReferencePtr aSurface) {
|
|
if (!mPreparedMap) {
|
|
// We might fail to set the map during, for example, device resets.
|
|
return nullptr;
|
|
}
|
|
|
|
MOZ_RELEASE_ASSERT(mMappedSurface == aSurface,
|
|
"aSurface must match previously stored surface.");
|
|
|
|
mMappedSurface = nullptr;
|
|
return std::move(mPreparedMap);
|
|
}
|
|
|
|
} // namespace layers
|
|
} // namespace mozilla
|