CanvasTranslator::RemoveTexture delayed the call to RemoveDrawTarget till all keepalives are gone. However, this may take place long after RecordedTextureDestruction actually called RemoveTexture. This gives time for subsequent DrawTargets with the same ReferencePtr to be created, causing the wrong DrawTargets to be removed. This patch ensures that when RemoveTexture is called from a RecordedTextureDestruction event that the DrawTarget is always removed then and there, rather than waiting. Differential Revision: https://phabricator.services.mozilla.com/D265867
1847 lines
59 KiB
C++
1847 lines
59 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/DataMutex.h"
|
|
#include "mozilla/gfx/2D.h"
|
|
#include "mozilla/gfx/CanvasManagerParent.h"
|
|
#include "mozilla/gfx/CanvasRenderThread.h"
|
|
#include "mozilla/gfx/DataSourceSurfaceWrapper.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/gfx/Swizzle.h"
|
|
#include "mozilla/ipc/Endpoint.h"
|
|
#include "mozilla/ipc/SharedMemoryHandle.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 "GLContext.h"
|
|
#include "HostWebGLContext.h"
|
|
#include "WebGLParent.h"
|
|
#include "RecordedCanvasEventImpl.h"
|
|
|
|
#if defined(XP_WIN)
|
|
# include "mozilla/gfx/DeviceManagerDx.h"
|
|
# include "mozilla/layers/TextureD3D11.h"
|
|
# include "mozilla/layers/VideoProcessorD3D11.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: {
|
|
// Prefer keyed mutex than D3D11Fence if remote canvas is enabled. See Bug
|
|
// 1966082
|
|
if (gfx::gfxVars::RemoteCanvasEnabled()) {
|
|
allocFlags =
|
|
(TextureAllocationFlags)(allocFlags | USE_D3D11_KEYED_MUTEX);
|
|
}
|
|
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),
|
|
#if defined(XP_WIN)
|
|
mVideoProcessorD3D11("CanvasTranslator::mVideoProcessorD3D11"),
|
|
#endif
|
|
mMaxSpinCount(StaticPrefs::gfx_canvas_remote_max_spin_count()),
|
|
mContentId(aContentId),
|
|
mManagerId(aManagerId),
|
|
mCanvasTranslatorEventsLock(
|
|
"CanvasTranslator::mCanvasTranslatorEventsLock") {
|
|
mNextEventTimeout = TimeDuration::FromMilliseconds(
|
|
StaticPrefs::gfx_canvas_remote_event_timeout_ms());
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
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, ipc::MutableSharedMemoryHandle&& aReadHandle,
|
|
nsTArray<ipc::ReadOnlySharedMemoryHandle>&& aBufferHandles,
|
|
CrossProcessSemaphoreHandle&& aReaderSem,
|
|
CrossProcessSemaphoreHandle&& aWriterSem) {
|
|
if (mHeaderShmem) {
|
|
return IPC_FAIL(this, "RecvInitTranslator called twice.");
|
|
}
|
|
|
|
mTextureType = aTextureType;
|
|
mWebglTextureType = aWebglTextureType;
|
|
mBackendType = aBackendType;
|
|
mOtherPid = OtherPid();
|
|
|
|
mHeaderShmem = aReadHandle.Map();
|
|
if (!mHeaderShmem) {
|
|
Deactivate();
|
|
return IPC_FAIL(this, "Failed to map canvas header shared memory.");
|
|
}
|
|
|
|
mHeader = mHeaderShmem.DataAs<Header>();
|
|
|
|
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.
|
|
if (aBufferHandles.IsEmpty()) {
|
|
Deactivate();
|
|
return IPC_FAIL(this, "No canvas buffer shared memory supplied.");
|
|
}
|
|
mDefaultBufferSize = aBufferHandles[0].Size();
|
|
auto handleIter = aBufferHandles.begin();
|
|
mCurrentShmem.shmem = std::move(*handleIter).Map();
|
|
if (!mCurrentShmem.shmem) {
|
|
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;
|
|
newShmem.shmem = std::move(*handleIter).Map();
|
|
if (!newShmem.shmem) {
|
|
Deactivate();
|
|
return IPC_FAIL(this, "Failed to map canvas buffer shared memory.");
|
|
}
|
|
mCanvasShmems.emplace(std::move(newShmem));
|
|
}
|
|
|
|
if (UsePendingCanvasTranslatorEvents()) {
|
|
MutexAutoLock lock(mCanvasTranslatorEventsLock);
|
|
mPendingCanvasTranslatorEvents.push_back(
|
|
CanvasTranslatorEvent::TranslateRecording());
|
|
PostCanvasTranslatorEvents(lock);
|
|
} else {
|
|
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();
|
|
}
|
|
|
|
if (UsePendingCanvasTranslatorEvents()) {
|
|
MutexAutoLock lock(mCanvasTranslatorEventsLock);
|
|
mPendingCanvasTranslatorEvents.push_back(
|
|
CanvasTranslatorEvent::TranslateRecording());
|
|
PostCanvasTranslatorEvents(lock);
|
|
} else {
|
|
DispatchToTaskQueue(
|
|
NewRunnableMethod("CanvasTranslator::TranslateRecording", this,
|
|
&CanvasTranslator::TranslateRecording));
|
|
}
|
|
|
|
return IPC_OK();
|
|
}
|
|
|
|
ipc::IPCResult CanvasTranslator::RecvAddBuffer(
|
|
ipc::ReadOnlySharedMemoryHandle&& aBufferHandle) {
|
|
if (mDeactivated) {
|
|
// The other side might have sent a resume message before we deactivated.
|
|
return IPC_OK();
|
|
}
|
|
|
|
if (UsePendingCanvasTranslatorEvents()) {
|
|
MutexAutoLock lock(mCanvasTranslatorEventsLock);
|
|
mPendingCanvasTranslatorEvents.push_back(
|
|
CanvasTranslatorEvent::AddBuffer(std::move(aBufferHandle)));
|
|
PostCanvasTranslatorEvents(lock);
|
|
} else {
|
|
DispatchToTaskQueue(NewRunnableMethod<ipc::ReadOnlySharedMemoryHandle&&>(
|
|
"CanvasTranslator::AddBuffer", this, &CanvasTranslator::AddBuffer,
|
|
std::move(aBufferHandle)));
|
|
}
|
|
|
|
return IPC_OK();
|
|
}
|
|
|
|
bool CanvasTranslator::AddBuffer(
|
|
ipc::ReadOnlySharedMemoryHandle&& aBufferHandle) {
|
|
MOZ_ASSERT(IsInTaskQueue());
|
|
if (mHeader->readerState == State::Failed) {
|
|
// We failed before we got to the pause event.
|
|
return false;
|
|
}
|
|
|
|
if (mHeader->readerState != State::Paused) {
|
|
gfxCriticalNote << "CanvasTranslator::AddBuffer bad state "
|
|
<< uint32_t(State(mHeader->readerState));
|
|
#ifndef FUZZING_SNAPSHOT
|
|
MOZ_DIAGNOSTIC_CRASH("mHeader->readerState == State::Paused");
|
|
#endif
|
|
Deactivate();
|
|
return false;
|
|
}
|
|
|
|
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.IsValid() && mCurrentShmem.Size() == mDefaultBufferSize) {
|
|
mCanvasShmems.emplace(std::move(mCurrentShmem));
|
|
}
|
|
|
|
CanvasShmem newShmem;
|
|
newShmem.shmem = aBufferHandle.Map();
|
|
if (!newShmem.shmem) {
|
|
return false;
|
|
}
|
|
|
|
mCurrentShmem = std::move(newShmem);
|
|
mCurrentMemReader = mCurrentShmem.CreateMemReader();
|
|
|
|
return TranslateRecording();
|
|
}
|
|
|
|
ipc::IPCResult CanvasTranslator::RecvSetDataSurfaceBuffer(
|
|
ipc::MutableSharedMemoryHandle&& aBufferHandle) {
|
|
if (mDeactivated) {
|
|
// The other side might have sent a resume message before we deactivated.
|
|
return IPC_OK();
|
|
}
|
|
|
|
if (UsePendingCanvasTranslatorEvents()) {
|
|
MutexAutoLock lock(mCanvasTranslatorEventsLock);
|
|
mPendingCanvasTranslatorEvents.push_back(
|
|
CanvasTranslatorEvent::SetDataSurfaceBuffer(std::move(aBufferHandle)));
|
|
PostCanvasTranslatorEvents(lock);
|
|
} else {
|
|
DispatchToTaskQueue(NewRunnableMethod<ipc::MutableSharedMemoryHandle&&>(
|
|
"CanvasTranslator::SetDataSurfaceBuffer", this,
|
|
&CanvasTranslator::SetDataSurfaceBuffer, std::move(aBufferHandle)));
|
|
}
|
|
|
|
return IPC_OK();
|
|
}
|
|
|
|
bool CanvasTranslator::SetDataSurfaceBuffer(
|
|
ipc::MutableSharedMemoryHandle&& aBufferHandle) {
|
|
MOZ_ASSERT(IsInTaskQueue());
|
|
if (mHeader->readerState == State::Failed) {
|
|
// We failed before we got to the pause event.
|
|
return false;
|
|
}
|
|
|
|
if (mHeader->readerState != State::Paused) {
|
|
gfxCriticalNote << "CanvasTranslator::SetDataSurfaceBuffer bad state "
|
|
<< uint32_t(State(mHeader->readerState));
|
|
#ifndef FUZZING_SNAPSHOT
|
|
MOZ_DIAGNOSTIC_CRASH("mHeader->readerState == State::Paused");
|
|
#endif
|
|
Deactivate();
|
|
return false;
|
|
}
|
|
|
|
mDataSurfaceShmem = aBufferHandle.Map();
|
|
if (!mDataSurfaceShmem) {
|
|
return false;
|
|
}
|
|
|
|
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 = mDataSurfaceShmem.DataAs<uint8_t>();
|
|
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;
|
|
}
|
|
}
|
|
|
|
already_AddRefed<gfx::DataSourceSurface> CanvasTranslator::WaitForSurface(
|
|
uintptr_t aId) {
|
|
// If it's not safe to flush the event queue, then don't try to wait.
|
|
if (!gfx::gfxVars::UseAcceleratedCanvas2D() ||
|
|
!UsePendingCanvasTranslatorEvents() || !IsInTaskQueue()) {
|
|
return nullptr;
|
|
}
|
|
ReferencePtr idRef(aId);
|
|
auto* surf = LookupExportSurface(idRef);
|
|
if (!surf) {
|
|
if (!HasPendingEvent()) {
|
|
return nullptr;
|
|
}
|
|
|
|
// If the surface doesn't exist yet, that may be because the events
|
|
// that produce it still need to be processed. Flush out any events
|
|
// currently in the queue, that by now should have been placed in
|
|
// the queue but for which processing has not yet occurred..
|
|
mFlushCheckpoint = mHeader->eventCount;
|
|
HandleCanvasTranslatorEvents();
|
|
mFlushCheckpoint = 0;
|
|
// If there is still no surface, then it is unlikely to be produced
|
|
// now, so give up.
|
|
surf = LookupExportSurface(idRef);
|
|
if (!surf) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
// The surface exists, so get its data.
|
|
return surf->GetDataSurface();
|
|
}
|
|
|
|
void CanvasTranslator::RecycleBuffer() {
|
|
if (!mCurrentShmem.IsValid()) {
|
|
return;
|
|
}
|
|
|
|
mCanvasShmems.emplace(std::move(mCurrentShmem));
|
|
NextBuffer();
|
|
}
|
|
|
|
void CanvasTranslator::NextBuffer() {
|
|
if (mCanvasShmems.empty()) {
|
|
return;
|
|
}
|
|
|
|
// 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;
|
|
|
|
{
|
|
MutexAutoLock lock(mCanvasTranslatorEventsLock);
|
|
mPendingCanvasTranslatorEvents.clear();
|
|
}
|
|
|
|
#if defined(XP_WIN)
|
|
{
|
|
auto lock = mVideoProcessorD3D11.Lock();
|
|
auto& videoProcessor = lock.ref();
|
|
videoProcessor = nullptr;
|
|
}
|
|
#endif
|
|
|
|
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(
|
|
const RemoteTextureOwnerId aTextureOwnerId, gfx::DrawTargetWebgl* aWebgl) {
|
|
NotifyRequiresRefresh(aTextureOwnerId);
|
|
|
|
const auto& info = mTextureInfo[aTextureOwnerId];
|
|
if (info.mTextureData) {
|
|
return true;
|
|
}
|
|
if (RefPtr<gfx::DrawTarget> dt =
|
|
CreateFallbackDrawTarget(info.mRefPtr, aTextureOwnerId,
|
|
aWebgl->GetSize(), aWebgl->GetFormat())) {
|
|
bool success = aWebgl->CopyToFallback(dt);
|
|
if (info.mRefPtr) {
|
|
AddDrawTarget(info.mRefPtr, dt);
|
|
}
|
|
return success;
|
|
}
|
|
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& ownerId = entry.first;
|
|
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(ownerId)) {
|
|
lost.insert(ownerId);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!lost.empty()) {
|
|
NotifyDeviceReset(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);
|
|
if (!mCurrentMemReader.good()) {
|
|
mHeader->readerState = State::Failed;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CanvasTranslator::ReadNextEvent(EventType& aEventType) {
|
|
MOZ_DIAGNOSTIC_ASSERT(mHeader->readerState == State::Processing);
|
|
|
|
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;
|
|
}
|
|
|
|
bool CanvasTranslator::TranslateRecording() {
|
|
MOZ_ASSERT(IsInTaskQueue());
|
|
MOZ_DIAGNOSTIC_ASSERT_IF(mFlushCheckpoint, HasPendingEvent());
|
|
|
|
if (mHeader->readerState == State::Failed) {
|
|
return false;
|
|
}
|
|
|
|
if (mSharedContext && EnsureSharedContextWebgl()) {
|
|
mSharedContext->EnterTlsScope();
|
|
}
|
|
auto exitTlsScope = MakeScopeExit([&] {
|
|
if (mSharedContext) {
|
|
mSharedContext->ExitTlsScope();
|
|
}
|
|
});
|
|
|
|
auto start = TimeStamp::Now();
|
|
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();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
return recordedEvent->PlayEvent(this);
|
|
});
|
|
|
|
// Check the stream is good here or we will log the issue twice.
|
|
if (!mCurrentMemReader.good()) {
|
|
mHeader->readerState = State::Failed;
|
|
return false;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
if (!mCurrentMemReader.good()) {
|
|
mHeader->readerState = State::Failed;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
mHeader->processedCount++;
|
|
|
|
if (mHeader->readerState == State::Paused || PauseUntilSync()) {
|
|
// We're waiting for an IPDL message return false, because we will resume
|
|
// translation after it is received.
|
|
Flush();
|
|
return false;
|
|
}
|
|
|
|
if (mFlushCheckpoint) {
|
|
// If we processed past the checkpoint return true to ensure translation
|
|
// after the checkpoint resumes later.
|
|
if (mHeader->processedCount >= mFlushCheckpoint) {
|
|
return true;
|
|
}
|
|
} else {
|
|
if (UsePendingCanvasTranslatorEvents()) {
|
|
const auto maxDurationMs = 100;
|
|
const auto now = TimeStamp::Now();
|
|
const auto waitDurationMs =
|
|
static_cast<uint32_t>((now - start).ToMilliseconds());
|
|
if (waitDurationMs > maxDurationMs) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CanvasTranslator::UsePendingCanvasTranslatorEvents() {
|
|
// XXX remove !mTranslationTaskQueue check.
|
|
return StaticPrefs::
|
|
gfx_canvas_remote_use_canvas_translator_event_AtStartup() &&
|
|
!mTranslationTaskQueue;
|
|
}
|
|
|
|
void CanvasTranslator::PostCanvasTranslatorEvents(
|
|
const MutexAutoLock& aProofOfLock) {
|
|
if (mIPDLClosed) {
|
|
return;
|
|
}
|
|
|
|
// Runnable has already been triggered.
|
|
if (mCanvasTranslatorEventsRunnable) {
|
|
return;
|
|
}
|
|
|
|
RefPtr<nsIRunnable> runnable =
|
|
NewRunnableMethod("CanvasTranslator::HandleCanvasTranslatorEvents", this,
|
|
&CanvasTranslator::HandleCanvasTranslatorEvents);
|
|
mCanvasTranslatorEventsRunnable = runnable;
|
|
|
|
// Runnable has not been triggered yet.
|
|
DispatchToTaskQueue(runnable.forget());
|
|
}
|
|
|
|
void CanvasTranslator::HandleCanvasTranslatorEvents() {
|
|
MOZ_ASSERT(IsInTaskQueue());
|
|
|
|
UniquePtr<CanvasTranslatorEvent> event;
|
|
{
|
|
MutexAutoLock lock(mCanvasTranslatorEventsLock);
|
|
MOZ_ASSERT_IF(mIPDLClosed, mPendingCanvasTranslatorEvents.empty());
|
|
if (mPendingCanvasTranslatorEvents.empty() || PauseUntilSync()) {
|
|
mCanvasTranslatorEventsRunnable = nullptr;
|
|
return;
|
|
}
|
|
auto& front = mPendingCanvasTranslatorEvents.front();
|
|
event = std::move(front);
|
|
mPendingCanvasTranslatorEvents.pop_front();
|
|
}
|
|
|
|
MOZ_RELEASE_ASSERT(event.get());
|
|
|
|
bool dispatchTranslate = false;
|
|
while (!dispatchTranslate && event) {
|
|
switch (event->mTag) {
|
|
case CanvasTranslatorEvent::Tag::TranslateRecording:
|
|
dispatchTranslate = TranslateRecording();
|
|
break;
|
|
case CanvasTranslatorEvent::Tag::AddBuffer:
|
|
dispatchTranslate = AddBuffer(event->TakeBufferHandle());
|
|
break;
|
|
case CanvasTranslatorEvent::Tag::SetDataSurfaceBuffer:
|
|
dispatchTranslate =
|
|
SetDataSurfaceBuffer(event->TakeDataSurfaceBufferHandle());
|
|
break;
|
|
case CanvasTranslatorEvent::Tag::ClearCachedResources:
|
|
ClearCachedResources();
|
|
break;
|
|
case CanvasTranslatorEvent::Tag::DropFreeBuffersWhenDormant:
|
|
DropFreeBuffersWhenDormant();
|
|
break;
|
|
}
|
|
|
|
event.reset(nullptr);
|
|
|
|
{
|
|
MutexAutoLock lock(mCanvasTranslatorEventsLock);
|
|
MOZ_ASSERT_IF(mIPDLClosed, mPendingCanvasTranslatorEvents.empty());
|
|
if (mIPDLClosed) {
|
|
return;
|
|
}
|
|
if (PauseUntilSync()) {
|
|
mCanvasTranslatorEventsRunnable = nullptr;
|
|
mPendingCanvasTranslatorEvents.push_front(
|
|
CanvasTranslatorEvent::TranslateRecording());
|
|
return;
|
|
}
|
|
if (!mIPDLClosed && !dispatchTranslate &&
|
|
!mPendingCanvasTranslatorEvents.empty()) {
|
|
auto& front = mPendingCanvasTranslatorEvents.front();
|
|
event = std::move(front);
|
|
mPendingCanvasTranslatorEvents.pop_front();
|
|
}
|
|
}
|
|
}
|
|
|
|
MOZ_ASSERT(!event);
|
|
|
|
{
|
|
MutexAutoLock lock(mCanvasTranslatorEventsLock);
|
|
mCanvasTranslatorEventsRunnable = nullptr;
|
|
|
|
MOZ_ASSERT_IF(mIPDLClosed, mPendingCanvasTranslatorEvents.empty());
|
|
if (mIPDLClosed) {
|
|
return;
|
|
}
|
|
|
|
if (dispatchTranslate) {
|
|
// Handle TranslateRecording at first in next
|
|
// HandleCanvasTranslatorEvents().
|
|
mPendingCanvasTranslatorEvents.push_front(
|
|
CanvasTranslatorEvent::TranslateRecording());
|
|
}
|
|
|
|
if (!mPendingCanvasTranslatorEvents.empty()) {
|
|
PostCanvasTranslatorEvents(lock);
|
|
}
|
|
}
|
|
}
|
|
|
|
#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();
|
|
}
|
|
}
|
|
|
|
void CanvasTranslator::DeviceResetAcknowledged() { DeviceChangeAcknowledged(); }
|
|
|
|
bool CanvasTranslator::CreateReferenceTexture() {
|
|
if (mReferenceTextureData) {
|
|
if (mBaseDT) {
|
|
mReferenceTextureData->ReturnDrawTarget(mBaseDT.forget());
|
|
}
|
|
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.
|
|
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));
|
|
}
|
|
|
|
void CanvasTranslator::NotifyDeviceReset(const RemoteTextureOwnerIdSet& aIds) {
|
|
if (aIds.empty()) {
|
|
return;
|
|
}
|
|
if (mRemoteTextureOwner) {
|
|
mRemoteTextureOwner->NotifyContextLost(&aIds);
|
|
}
|
|
nsTArray<RemoteTextureOwnerId> idArray(aIds.size());
|
|
for (const auto& id : aIds) {
|
|
idArray.AppendElement(id);
|
|
}
|
|
gfx::CanvasRenderThread::Dispatch(
|
|
NewRunnableMethod<nsTArray<RemoteTextureOwnerId>&&>(
|
|
"CanvasTranslator::SendNotifyDeviceReset", this,
|
|
&CanvasTranslator::SendNotifyDeviceReset, std::move(idArray)));
|
|
}
|
|
|
|
gfx::DrawTargetWebgl* CanvasTranslator::GetDrawTargetWebgl(
|
|
const RemoteTextureOwnerId aTextureOwnerId, bool aCheckForFallback) const {
|
|
auto result = mTextureInfo.find(aTextureOwnerId);
|
|
if (result != mTextureInfo.end()) {
|
|
return result->second.GetDrawTargetWebgl(aCheckForFallback);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void CanvasTranslator::NotifyRequiresRefresh(
|
|
const RemoteTextureOwnerId aTextureOwnerId, bool aDispatch) {
|
|
if (aDispatch) {
|
|
auto& info = mTextureInfo[aTextureOwnerId];
|
|
if (!info.mNotifiedRequiresRefresh) {
|
|
info.mNotifiedRequiresRefresh = true;
|
|
DispatchToTaskQueue(NewRunnableMethod<RemoteTextureOwnerId, bool>(
|
|
"CanvasTranslator::NotifyRequiresRefresh", this,
|
|
&CanvasTranslator::NotifyRequiresRefresh, aTextureOwnerId, false));
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (mTextureInfo.find(aTextureOwnerId) != mTextureInfo.end()) {
|
|
Unused << SendNotifyRequiresRefresh(aTextureOwnerId);
|
|
}
|
|
}
|
|
|
|
void CanvasTranslator::CacheSnapshotShmem(
|
|
const RemoteTextureOwnerId aTextureOwnerId, bool aDispatch) {
|
|
if (aDispatch) {
|
|
DispatchToTaskQueue(NewRunnableMethod<RemoteTextureOwnerId, bool>(
|
|
"CanvasTranslator::CacheSnapshotShmem", this,
|
|
&CanvasTranslator::CacheSnapshotShmem, aTextureOwnerId, false));
|
|
return;
|
|
}
|
|
|
|
if (gfx::DrawTargetWebgl* webgl = GetDrawTargetWebgl(aTextureOwnerId)) {
|
|
if (auto shmemHandle = webgl->TakeShmemHandle()) {
|
|
// Lock the DT so that it doesn't get removed while shmem is in transit.
|
|
AddTextureKeepAlive(aTextureOwnerId);
|
|
nsCOMPtr<nsIThread> thread =
|
|
gfx::CanvasRenderThread::GetCanvasRenderThread();
|
|
RefPtr<CanvasTranslator> translator = this;
|
|
SendSnapshotShmem(aTextureOwnerId, std::move(shmemHandle))
|
|
->Then(
|
|
thread, __func__,
|
|
[=](bool) {
|
|
translator->RemoveTextureKeepAlive(aTextureOwnerId);
|
|
},
|
|
[=](ipc::ResponseRejectReason) {
|
|
translator->RemoveTextureKeepAlive(aTextureOwnerId);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
void CanvasTranslator::PrepareShmem(
|
|
const RemoteTextureOwnerId aTextureOwnerId) {
|
|
if (gfx::DrawTargetWebgl* webgl =
|
|
GetDrawTargetWebgl(aTextureOwnerId, false)) {
|
|
if (RefPtr<gfx::DrawTarget> dt =
|
|
mTextureInfo[aTextureOwnerId].mFallbackDrawTarget) {
|
|
// If there was a fallback, copy the fallback to the software framebuffer
|
|
// shmem for reading.
|
|
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::CacheDataSnapshots() {
|
|
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.
|
|
for (auto const& entry : mTextureInfo) {
|
|
if (gfx::DrawTargetWebgl* webgl = entry.second.GetDrawTargetWebgl()) {
|
|
webgl->EnsureDataSnapshot();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CanvasTranslator::ClearCachedResources() {
|
|
mUsedDataSurfaceForSurfaceDescriptor = nullptr;
|
|
mUsedWrapperForSurfaceDescriptor = nullptr;
|
|
mUsedSurfaceDescriptorForSurfaceDescriptor = Nothing();
|
|
|
|
if (mSharedContext) {
|
|
mSharedContext->OnMemoryPressure();
|
|
}
|
|
|
|
CacheDataSnapshots();
|
|
}
|
|
|
|
ipc::IPCResult CanvasTranslator::RecvClearCachedResources() {
|
|
if (mDeactivated) {
|
|
// The other side might have sent a message before we deactivated.
|
|
return IPC_OK();
|
|
}
|
|
|
|
if (UsePendingCanvasTranslatorEvents()) {
|
|
MutexAutoLock lock(mCanvasTranslatorEventsLock);
|
|
mPendingCanvasTranslatorEvents.emplace_back(
|
|
CanvasTranslatorEvent::ClearCachedResources());
|
|
PostCanvasTranslatorEvents(lock);
|
|
} else {
|
|
DispatchToTaskQueue(
|
|
NewRunnableMethod("CanvasTranslator::ClearCachedResources", this,
|
|
&CanvasTranslator::ClearCachedResources));
|
|
}
|
|
return IPC_OK();
|
|
}
|
|
|
|
void CanvasTranslator::DropFreeBuffersWhenDormant() { CacheDataSnapshots(); }
|
|
|
|
ipc::IPCResult CanvasTranslator::RecvDropFreeBuffersWhenDormant() {
|
|
if (mDeactivated) {
|
|
// The other side might have sent a message before we deactivated.
|
|
return IPC_OK();
|
|
}
|
|
|
|
if (UsePendingCanvasTranslatorEvents()) {
|
|
MutexAutoLock lock(mCanvasTranslatorEventsLock);
|
|
mPendingCanvasTranslatorEvents.emplace_back(
|
|
CanvasTranslatorEvent::DropFreeBuffersWhenDormant());
|
|
PostCanvasTranslatorEvents(lock);
|
|
} else {
|
|
DispatchToTaskQueue(
|
|
NewRunnableMethod("CanvasTranslator::DropFreeBuffersWhenDormant", this,
|
|
&CanvasTranslator::DropFreeBuffersWhenDormant));
|
|
}
|
|
return IPC_OK();
|
|
}
|
|
|
|
static const OpenMode kInitMode = OpenMode::OPEN_READ_WRITE;
|
|
|
|
already_AddRefed<gfx::DrawTarget> CanvasTranslator::CreateFallbackDrawTarget(
|
|
gfx::ReferencePtr aRefPtr, const 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[aTextureOwnerId];
|
|
info.mRefPtr = aRefPtr;
|
|
info.mFallbackDrawTarget = dt;
|
|
info.mTextureData = std::move(textureData);
|
|
info.mTextureLockMode = kInitMode;
|
|
} while (!dt && CheckForFreshCanvasDevice(__LINE__));
|
|
return dt.forget();
|
|
}
|
|
|
|
already_AddRefed<gfx::DrawTarget> CanvasTranslator::CreateDrawTarget(
|
|
gfx::ReferencePtr aRefPtr, const RemoteTextureOwnerId aTextureOwnerId,
|
|
const gfx::IntSize& aSize, gfx::SurfaceFormat aFormat) {
|
|
if (!aTextureOwnerId.IsValid()) {
|
|
#ifndef FUZZING_SNAPSHOT
|
|
MOZ_DIAGNOSTIC_CRASH("No texture owner set");
|
|
#endif
|
|
return nullptr;
|
|
}
|
|
|
|
{
|
|
auto result = mTextureInfo.find(aTextureOwnerId);
|
|
if (result != mTextureInfo.end()) {
|
|
const TextureInfo& info = result->second;
|
|
if (info.mTextureData || info.mDrawTarget) {
|
|
#ifndef FUZZING_SNAPSHOT
|
|
MOZ_DIAGNOSTIC_CRASH("DrawTarget already exists");
|
|
#endif
|
|
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[aTextureOwnerId];
|
|
info.mRefPtr = aRefPtr;
|
|
info.mDrawTarget = dt;
|
|
info.mTextureLockMode = kInitMode;
|
|
CacheSnapshotShmem(aTextureOwnerId);
|
|
}
|
|
}
|
|
if (!dt) {
|
|
NotifyRequiresRefresh(aTextureOwnerId);
|
|
}
|
|
}
|
|
|
|
if (!dt) {
|
|
dt = CreateFallbackDrawTarget(aRefPtr, aTextureOwnerId, aSize, aFormat);
|
|
}
|
|
|
|
if (dt && aRefPtr) {
|
|
AddDrawTarget(aRefPtr, dt);
|
|
}
|
|
return dt.forget();
|
|
}
|
|
|
|
already_AddRefed<gfx::DrawTarget> CanvasTranslator::CreateDrawTarget(
|
|
gfx::ReferencePtr aRefPtr, const gfx::IntSize& aSize,
|
|
gfx::SurfaceFormat aFormat) {
|
|
#ifndef FUZZING_SNAPSHOT
|
|
MOZ_DIAGNOSTIC_CRASH("Unexpected CreateDrawTarget call!");
|
|
#endif
|
|
return nullptr;
|
|
}
|
|
|
|
void CanvasTranslator::NotifyTextureDestruction(
|
|
const RemoteTextureOwnerId aTextureOwnerId) {
|
|
MOZ_ASSERT(gfx::CanvasRenderThread::IsInCanvasRenderThread());
|
|
|
|
if (mIPDLClosed) {
|
|
return;
|
|
}
|
|
Unused << SendNotifyTextureDestruction(aTextureOwnerId);
|
|
}
|
|
|
|
void CanvasTranslator::AddTextureKeepAlive(const RemoteTextureOwnerId& aId) {
|
|
auto result = mTextureInfo.find(aId);
|
|
if (result == mTextureInfo.end()) {
|
|
return;
|
|
}
|
|
auto& info = result->second;
|
|
++info.mKeepAlive;
|
|
}
|
|
|
|
void CanvasTranslator::RemoveTextureKeepAlive(const RemoteTextureOwnerId& aId) {
|
|
RemoveTexture(aId, 0, 0, false);
|
|
}
|
|
|
|
void CanvasTranslator::RemoveTexture(const RemoteTextureOwnerId aTextureOwnerId,
|
|
RemoteTextureTxnType aTxnType,
|
|
RemoteTextureTxnId aTxnId,
|
|
bool aFinalize) {
|
|
// Don't erase the texture if still in use
|
|
auto result = mTextureInfo.find(aTextureOwnerId);
|
|
if (result == mTextureInfo.end()) {
|
|
return;
|
|
}
|
|
auto& info = result->second;
|
|
if (mRemoteTextureOwner && aTxnType && aTxnId) {
|
|
mRemoteTextureOwner->WaitForTxn(aTextureOwnerId, aTxnType, aTxnId);
|
|
}
|
|
// Remove the DrawTarget only if this is being called from a recorded event
|
|
// or if there are no remaining keepalives. If this is being called only to
|
|
// remove a keepalive without forcing removal, then the DrawTarget is still
|
|
// being used by the recording.
|
|
if ((aFinalize || info.mKeepAlive <= 1) && info.mRefPtr) {
|
|
RemoveDrawTarget(info.mRefPtr);
|
|
info.mRefPtr = ReferencePtr();
|
|
}
|
|
if (--info.mKeepAlive > 0) {
|
|
return;
|
|
}
|
|
if (info.mTextureData) {
|
|
if (info.mFallbackDrawTarget) {
|
|
info.mTextureData->ReturnDrawTarget(info.mFallbackDrawTarget.forget());
|
|
}
|
|
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.
|
|
if (aTextureOwnerId.IsValid()) {
|
|
mRemoteTextureOwner->UnregisterTextureOwner(aTextureOwnerId);
|
|
}
|
|
}
|
|
|
|
gfx::CanvasRenderThread::Dispatch(NewRunnableMethod<RemoteTextureOwnerId>(
|
|
"CanvasTranslator::NotifyTextureDestruction", this,
|
|
&CanvasTranslator::NotifyTextureDestruction, aTextureOwnerId));
|
|
|
|
mTextureInfo.erase(result);
|
|
}
|
|
|
|
bool CanvasTranslator::LockTexture(const RemoteTextureOwnerId aTextureOwnerId,
|
|
OpenMode aMode, bool aInvalidContents) {
|
|
if (aMode == OpenMode::OPEN_NONE) {
|
|
return false;
|
|
}
|
|
auto result = mTextureInfo.find(aTextureOwnerId);
|
|
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(
|
|
const RemoteTextureOwnerId aTextureOwnerId) {
|
|
auto result = mTextureInfo.find(aTextureOwnerId);
|
|
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(aTextureOwnerId);
|
|
}
|
|
}
|
|
}
|
|
info.mTextureLockMode = OpenMode::OPEN_NONE;
|
|
return true;
|
|
}
|
|
|
|
bool CanvasTranslator::PresentTexture(
|
|
const RemoteTextureOwnerId aTextureOwnerId, RemoteTextureId aId) {
|
|
AUTO_PROFILER_MARKER_TEXT("CanvasTranslator", GRAPHICS, {},
|
|
"CanvasTranslator::PresentTexture"_ns);
|
|
auto result = mTextureInfo.find(aTextureOwnerId);
|
|
if (result == mTextureInfo.end()) {
|
|
return false;
|
|
}
|
|
auto& info = result->second;
|
|
if (gfx::DrawTargetWebgl* webgl = info.GetDrawTargetWebgl()) {
|
|
EnsureRemoteTextureOwner(aTextureOwnerId);
|
|
if (webgl->CopyToSwapChain(mWebglTextureType, aId, aTextureOwnerId,
|
|
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(aTextureOwnerId, webgl)) {
|
|
RemoteTextureOwnerIdSet lost = {aTextureOwnerId};
|
|
NotifyDeviceReset(lost);
|
|
}
|
|
}
|
|
}
|
|
if (TextureData* data = info.mTextureData.get()) {
|
|
PushRemoteTexture(aTextureOwnerId, data, aId, aTextureOwnerId);
|
|
}
|
|
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(
|
|
const RemoteTextureOwnerId aTextureOwnerId, 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() {
|
|
MOZ_ASSERT(mIPDLClosed);
|
|
|
|
mUsedDataSurfaceForSurfaceDescriptor = nullptr;
|
|
mUsedWrapperForSurfaceDescriptor = nullptr;
|
|
mUsedSurfaceDescriptorForSurfaceDescriptor = Nothing();
|
|
|
|
for (auto& entry : mTextureInfo) {
|
|
auto& info = entry.second;
|
|
if (info.mTextureData) {
|
|
if (info.mFallbackDrawTarget) {
|
|
info.mTextureData->ReturnDrawTarget(info.mFallbackDrawTarget.forget());
|
|
}
|
|
info.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();
|
|
}
|
|
if (mReferenceTextureData) {
|
|
if (mBaseDT) {
|
|
mReferenceTextureData->ReturnDrawTarget(mBaseDT.forget());
|
|
}
|
|
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 ||
|
|
subdescType == RemoteDecoderVideoSubDescriptor::TSurfaceDescriptorD3D10) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
already_AddRefed<gfx::DataSourceSurface>
|
|
CanvasTranslator::MaybeRecycleDataSurfaceForSurfaceDescriptor(
|
|
TextureHost* aTextureHost,
|
|
const SurfaceDescriptorRemoteDecoder& aSurfaceDescriptor) {
|
|
if (!StaticPrefs::gfx_canvas_remote_recycle_used_data_surface()) {
|
|
return nullptr;
|
|
}
|
|
|
|
auto& usedSurf = mUsedDataSurfaceForSurfaceDescriptor;
|
|
auto& usedWrapper = mUsedWrapperForSurfaceDescriptor;
|
|
auto& usedDescriptor = mUsedSurfaceDescriptorForSurfaceDescriptor;
|
|
|
|
if (usedDescriptor.isSome() && usedDescriptor.ref() == aSurfaceDescriptor) {
|
|
MOZ_ASSERT(usedSurf);
|
|
MOZ_ASSERT(usedWrapper);
|
|
MOZ_ASSERT(aTextureHost->GetSize() == usedSurf->GetSize());
|
|
|
|
// Since the data is the same as before, the DataSourceSurfaceWrapper can be
|
|
// reused.
|
|
return do_AddRef(usedWrapper);
|
|
}
|
|
|
|
usedWrapper = nullptr;
|
|
usedDescriptor = Some(aSurfaceDescriptor);
|
|
|
|
bool isYuvVideo = false;
|
|
if (aTextureHost->AsMacIOSurfaceTextureHost()) {
|
|
if (aTextureHost->GetFormat() == SurfaceFormat::NV12 ||
|
|
aTextureHost->GetFormat() == SurfaceFormat::YUY2) {
|
|
isYuvVideo = true;
|
|
}
|
|
} else if (aTextureHost->GetFormat() == gfx::SurfaceFormat::YUV420) {
|
|
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.
|
|
usedSurf = aTextureHost->GetAsSurface(usedSurf);
|
|
// Wrap DataSourceSurface with DataSourceSurfaceWrapper to force upload in
|
|
// DrawTargetWebgl::DrawSurface().
|
|
usedWrapper =
|
|
new gfx::DataSourceSurfaceWrapper(mUsedDataSurfaceForSurfaceDescriptor);
|
|
return do_AddRef(usedWrapper);
|
|
}
|
|
|
|
usedSurf = aTextureHost->GetAsSurface(nullptr);
|
|
// Wrap DataSourceSurface with DataSourceSurfaceWrapper to force upload in
|
|
// DrawTargetWebgl::DrawSurface().
|
|
usedWrapper =
|
|
new gfx::DataSourceSurfaceWrapper(mUsedDataSurfaceForSurfaceDescriptor);
|
|
return do_AddRef(usedWrapper);
|
|
}
|
|
|
|
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 defined(XP_WIN)
|
|
if (subdescType == RemoteDecoderVideoSubDescriptor::TSurfaceDescriptorD3D10) {
|
|
auto* textureHostD3D11 = texture->AsDXGITextureHostD3D11();
|
|
if (!textureHostD3D11) {
|
|
MOZ_ASSERT_UNREACHABLE("unexpected to be called");
|
|
return nullptr;
|
|
}
|
|
auto& usedSurf = mUsedDataSurfaceForSurfaceDescriptor;
|
|
auto& usedDescriptor = mUsedSurfaceDescriptorForSurfaceDescriptor;
|
|
|
|
// TODO reuse DataSourceSurface if no update.
|
|
|
|
usedSurf =
|
|
textureHostD3D11->GetAsSurfaceWithDevice(mDevice, mVideoProcessorD3D11);
|
|
if (!usedSurf) {
|
|
MOZ_ASSERT_UNREACHABLE("unexpected to be called");
|
|
usedDescriptor = Nothing();
|
|
return nullptr;
|
|
}
|
|
usedDescriptor = Some(sdrd);
|
|
|
|
return do_AddRef(usedSurf);
|
|
}
|
|
#endif
|
|
|
|
if (subdescType ==
|
|
RemoteDecoderVideoSubDescriptor::TSurfaceDescriptorMacIOSurface) {
|
|
MOZ_ASSERT(texture->AsMacIOSurfaceTextureHost());
|
|
|
|
RefPtr<gfx::DataSourceSurface> surf =
|
|
MaybeRecycleDataSurfaceForSurfaceDescriptor(texture, sdrd);
|
|
return surf.forget();
|
|
}
|
|
|
|
if (subdescType == RemoteDecoderVideoSubDescriptor::Tnull_t) {
|
|
RefPtr<gfx::DataSourceSurface> surf =
|
|
MaybeRecycleDataSurfaceForSurfaceDescriptor(texture, sdrd);
|
|
return surf.forget();
|
|
}
|
|
|
|
MOZ_ASSERT_UNREACHABLE("unexpected to be called");
|
|
return nullptr;
|
|
}
|
|
|
|
void CanvasTranslator::CheckpointReached() { CheckAndSignalWriter(); }
|
|
|
|
void CanvasTranslator::PauseTranslation() {
|
|
mHeader->readerState = State::Paused;
|
|
}
|
|
|
|
void CanvasTranslator::AwaitTranslationSync(uint64_t aSyncId) {
|
|
if (NS_WARN_IF(!UsePendingCanvasTranslatorEvents()) ||
|
|
NS_WARN_IF(!IsInTaskQueue()) || NS_WARN_IF(mAwaitSyncId >= aSyncId)) {
|
|
return;
|
|
}
|
|
|
|
mAwaitSyncId = aSyncId;
|
|
}
|
|
|
|
void CanvasTranslator::SyncTranslation(uint64_t aSyncId) {
|
|
if (NS_WARN_IF(!IsInTaskQueue()) || NS_WARN_IF(aSyncId <= mLastSyncId)) {
|
|
return;
|
|
}
|
|
|
|
bool wasPaused = PauseUntilSync();
|
|
mLastSyncId = aSyncId;
|
|
// If translation was previously paused waiting on a sync-id, check if sync-id
|
|
// encountered requires restarting translation.
|
|
if (wasPaused && !PauseUntilSync()) {
|
|
HandleCanvasTranslatorEvents();
|
|
}
|
|
}
|
|
|
|
mozilla::ipc::IPCResult CanvasTranslator::RecvSnapshotExternalCanvas(
|
|
uint64_t aSyncId, uint32_t aManagerId, ActorId aCanvasId) {
|
|
if (NS_WARN_IF(!IsInTaskQueue())) {
|
|
return IPC_FAIL(this,
|
|
"RecvSnapshotExternalCanvas used outside of task queue.");
|
|
}
|
|
|
|
// Verify that snapshot requests are not received out of order order.
|
|
if (NS_WARN_IF(aSyncId <= mLastSyncId)) {
|
|
return IPC_FAIL(this, "RecvSnapShotExternalCanvas received too late.");
|
|
}
|
|
|
|
// Attempt to snapshot an external canvas that is associated with the same
|
|
// content process as this canvas. On success, associate it with the sync-id.
|
|
RefPtr<gfx::SourceSurface> surf;
|
|
if (auto* actor = gfx::CanvasManagerParent::GetCanvasActor(
|
|
mContentId, aManagerId, aCanvasId)) {
|
|
switch (actor->GetProtocolId()) {
|
|
case ProtocolId::PWebGLMsgStart:
|
|
if (auto* hostContext =
|
|
static_cast<dom::WebGLParent*>(actor)->GetHostWebGLContext()) {
|
|
surf = hostContext->GetWebGLContext()->GetBackBufferSnapshot(true);
|
|
}
|
|
break;
|
|
default:
|
|
MOZ_ASSERT_UNREACHABLE("Unsupported protocol");
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (surf) {
|
|
mExternalSnapshots.InsertOrUpdate(aSyncId, surf);
|
|
}
|
|
|
|
// Regardless, sync translation so it may resume after attempting snapshot.
|
|
SyncTranslation(aSyncId);
|
|
|
|
if (!surf) {
|
|
return IPC_FAIL(this, "SnapshotExternalCanvas failed to get surface.");
|
|
}
|
|
|
|
return IPC_OK();
|
|
}
|
|
|
|
already_AddRefed<gfx::SourceSurface> CanvasTranslator::LookupExternalSnapshot(
|
|
uint64_t aSyncId) {
|
|
MOZ_ASSERT(IsInTaskQueue());
|
|
uint64_t prevSyncId = mLastSyncId;
|
|
if (NS_WARN_IF(aSyncId > mLastSyncId)) {
|
|
// If arriving here, a previous SnapshotExternalCanvas IPDL message never
|
|
// arrived for some reason. Sync translation here to avoid locking up.
|
|
SyncTranslation(aSyncId);
|
|
}
|
|
RefPtr<gfx::SourceSurface> surf;
|
|
// Check if the snapshot was added. This should only ever be called once per
|
|
// snapshot, as it is removed from the table when resolved.
|
|
if (mExternalSnapshots.Remove(aSyncId, getter_AddRefs(surf))) {
|
|
return surf.forget();
|
|
}
|
|
// There was no snapshot available, which can happen if this was called
|
|
// before or without a corresponding SnapshotExternalCanvas, or if called
|
|
// multiple times.
|
|
if (aSyncId > prevSyncId) {
|
|
gfxCriticalNoteOnce << "External canvas snapshot resolved before creation.";
|
|
} else {
|
|
gfxCriticalNoteOnce << "Exernal canvas snapshot already resolved.";
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
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
|