зеркало из https://github.com/mozilla/gecko-dev.git
452 строки
15 KiB
C++
452 строки
15 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 "mozilla/gfx/2D.h"
|
|
#include "mozilla/gfx/GPUParent.h"
|
|
#include "mozilla/gfx/Logging.h"
|
|
#include "mozilla/layers/TextureClient.h"
|
|
#include "mozilla/SyncRunnable.h"
|
|
#include "nsTHashtable.h"
|
|
#include "RecordedCanvasEventImpl.h"
|
|
|
|
#if defined(XP_WIN)
|
|
# include "mozilla/gfx/DeviceManagerDx.h"
|
|
# include "mozilla/layers/TextureD3D11.h"
|
|
#endif
|
|
|
|
namespace mozilla {
|
|
namespace layers {
|
|
|
|
// 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.
|
|
static const TimeDuration kReadEventTimeout = TimeDuration::FromMilliseconds(5);
|
|
|
|
class RingBufferReaderServices final
|
|
: public CanvasEventRingBuffer::ReaderServices {
|
|
public:
|
|
explicit RingBufferReaderServices(RefPtr<CanvasTranslator> aCanvasTranslator)
|
|
: mCanvasTranslator(std::move(aCanvasTranslator)) {}
|
|
|
|
~RingBufferReaderServices() final = default;
|
|
|
|
bool WriterClosed() final {
|
|
return !mCanvasTranslator->GetIPCChannel()->CanSend();
|
|
}
|
|
|
|
private:
|
|
RefPtr<CanvasTranslator> mCanvasTranslator;
|
|
};
|
|
|
|
TextureData* CanvasTranslator::CreateTextureData(TextureType aTextureType,
|
|
const gfx::IntSize& aSize,
|
|
gfx::SurfaceFormat aFormat) {
|
|
TextureData* textureData = nullptr;
|
|
switch (aTextureType) {
|
|
#ifdef XP_WIN
|
|
case TextureType::D3D11: {
|
|
textureData =
|
|
D3D11TextureData::Create(aSize, aFormat, ALLOC_CLEAR_BUFFER, mDevice);
|
|
break;
|
|
}
|
|
#endif
|
|
default:
|
|
MOZ_CRASH("Unsupported TextureType for CanvasTranslator.");
|
|
}
|
|
|
|
return textureData;
|
|
}
|
|
|
|
typedef nsTHashtable<nsRefPtrHashKey<CanvasTranslator>> CanvasTranslatorSet;
|
|
|
|
static CanvasTranslatorSet& CanvasTranslators() {
|
|
MOZ_ASSERT(CanvasThreadHolder::IsInCanvasThread());
|
|
static CanvasTranslatorSet* sCanvasTranslator = new CanvasTranslatorSet();
|
|
return *sCanvasTranslator;
|
|
}
|
|
|
|
static void EnsureAllClosed() {
|
|
for (auto iter = CanvasTranslators().Iter(); !iter.Done(); iter.Next()) {
|
|
iter.Get()->GetKey()->Close();
|
|
}
|
|
}
|
|
|
|
/* static */ void CanvasTranslator::Shutdown() {
|
|
// If the dispatch fails there is no canvas thread and so no translators.
|
|
CanvasThreadHolder::MaybeDispatchToCanvasThread(NewRunnableFunction(
|
|
"CanvasTranslator::EnsureAllClosed", &EnsureAllClosed));
|
|
}
|
|
|
|
/* static */ already_AddRefed<CanvasTranslator> CanvasTranslator::Create(
|
|
ipc::Endpoint<PCanvasParent>&& aEndpoint) {
|
|
MOZ_ASSERT(NS_IsInCompositorThread());
|
|
|
|
RefPtr<CanvasThreadHolder> threadHolder =
|
|
CanvasThreadHolder::EnsureCanvasThread();
|
|
RefPtr<CanvasTranslator> canvasTranslator =
|
|
new CanvasTranslator(do_AddRef(threadHolder));
|
|
threadHolder->DispatchToCanvasThread(
|
|
NewRunnableMethod<Endpoint<PCanvasParent>&&>(
|
|
"CanvasTranslator::Bind", canvasTranslator, &CanvasTranslator::Bind,
|
|
std::move(aEndpoint)));
|
|
return canvasTranslator.forget();
|
|
}
|
|
|
|
CanvasTranslator::CanvasTranslator(
|
|
already_AddRefed<CanvasThreadHolder> aCanvasThreadHolder)
|
|
: gfx::InlineTranslator(), mCanvasThreadHolder(aCanvasThreadHolder) {}
|
|
|
|
CanvasTranslator::~CanvasTranslator() {
|
|
if (mReferenceTextureData) {
|
|
mReferenceTextureData->Unlock();
|
|
}
|
|
}
|
|
|
|
void CanvasTranslator::Bind(Endpoint<PCanvasParent>&& aEndpoint) {
|
|
if (!aEndpoint.Bind(this)) {
|
|
return;
|
|
}
|
|
|
|
CanvasTranslators().PutEntry(this);
|
|
}
|
|
|
|
mozilla::ipc::IPCResult CanvasTranslator::RecvInitTranslator(
|
|
const TextureType& aTextureType,
|
|
const ipc::SharedMemoryBasic::Handle& aReadHandle,
|
|
const CrossProcessSemaphoreHandle& aReaderSem,
|
|
const CrossProcessSemaphoreHandle& aWriterSem) {
|
|
mTextureType = aTextureType;
|
|
#if defined(XP_WIN)
|
|
if (!CheckForFreshCanvasDevice(__LINE__)) {
|
|
gfxCriticalNote << "GFX: CanvasTranslator failed to get device";
|
|
return IPC_FAIL(this, "Failed to get canvas device.");
|
|
}
|
|
#endif
|
|
|
|
mStream = MakeUnique<CanvasEventRingBuffer>();
|
|
if (!mStream->InitReader(aReadHandle, aReaderSem, aWriterSem,
|
|
MakeUnique<RingBufferReaderServices>(this))) {
|
|
return IPC_FAIL(this, "Failed to initialize ring buffer reader.");
|
|
}
|
|
|
|
mTranslationTaskQueue = mCanvasThreadHolder->CreateWorkerTaskQueue();
|
|
return RecvResumeTranslation();
|
|
}
|
|
|
|
ipc::IPCResult CanvasTranslator::RecvResumeTranslation() {
|
|
if (!IsValid()) {
|
|
return IPC_FAIL(this, "Canvas Translation failed.");
|
|
}
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(mTranslationTaskQueue->Dispatch(
|
|
NewRunnableMethod("CanvasTranslator::StartTranslation", this,
|
|
&CanvasTranslator::StartTranslation)));
|
|
|
|
return IPC_OK();
|
|
}
|
|
|
|
void CanvasTranslator::StartTranslation() {
|
|
if (!TranslateRecording() && !GetIPCChannel()->Unsound_IsClosed()) {
|
|
MOZ_ALWAYS_SUCCEEDS(mTranslationTaskQueue->Dispatch(
|
|
NewRunnableMethod("CanvasTranslator::StartTranslation", this,
|
|
&CanvasTranslator::StartTranslation)));
|
|
}
|
|
}
|
|
|
|
void CanvasTranslator::ActorDestroy(ActorDestroyReason why) {
|
|
mTranslationTaskQueue->BeginShutdown()->Then(
|
|
MessageLoop::current()->SerialEventTarget(), __func__, this,
|
|
&CanvasTranslator::FinishShutdown, &CanvasTranslator::FinishShutdown);
|
|
}
|
|
|
|
void CanvasTranslator::FinishShutdown() {
|
|
// mTranslationTaskQueue has shutdown we can safely drop the ring buffer to
|
|
// break the cycle caused by RingBufferReaderServices.
|
|
mStream = nullptr;
|
|
CanvasThreadHolder::ReleaseOnCompositorThread(mCanvasThreadHolder.forget());
|
|
CanvasTranslators().RemoveEntry(this);
|
|
}
|
|
|
|
bool CanvasTranslator::TranslateRecording() {
|
|
MOZ_ASSERT(CanvasThreadHolder::IsInCanvasWorker());
|
|
|
|
int32_t eventType = mStream->ReadNextEvent();
|
|
while (mStream->good()) {
|
|
bool success = RecordedEvent::DoWithEventFromStream(
|
|
*mStream, static_cast<RecordedEvent::EventType>(eventType),
|
|
[&](RecordedEvent* recordedEvent) -> bool {
|
|
// Make sure that the whole event was read from the stream.
|
|
if (!mStream->good()) {
|
|
if (GetIPCChannel()->Unsound_IsClosed()) {
|
|
// 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);
|
|
});
|
|
|
|
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 (!mStream->good()) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (!mIsInTransaction) {
|
|
return mStream->StopIfEmpty();
|
|
}
|
|
|
|
if (!mStream->HasDataToRead()) {
|
|
// We're going to wait for the next event, so take the opportunity to
|
|
// flush the rendering.
|
|
Flush();
|
|
if (!mStream->WaitForDataToRead(kReadEventTimeout, 0)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
eventType = mStream->ReadNextEvent();
|
|
}
|
|
|
|
mIsValid = false;
|
|
return true;
|
|
}
|
|
|
|
#define READ_AND_PLAY_CANVAS_EVENT_TYPE(_typeenum, _class) \
|
|
case _typeenum: { \
|
|
auto e = _class(*mStream); \
|
|
if (!mStream->good()) { \
|
|
if (GetIPCChannel()->Unsound_IsClosed()) { \
|
|
/* 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() { mIsInTransaction = true; }
|
|
|
|
void CanvasTranslator::Flush() {
|
|
#if defined(XP_WIN)
|
|
gfx::AutoSerializeWithMoz2D serializeWithMoz2D(
|
|
GetReferenceDrawTarget()->GetBackendType());
|
|
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;
|
|
}
|
|
|
|
bool CanvasTranslator::CreateReferenceTexture() {
|
|
if (mReferenceTextureData) {
|
|
mReferenceTextureData->Unlock();
|
|
}
|
|
|
|
mReferenceTextureData.reset(CreateTextureData(
|
|
mTextureType, gfx::IntSize(1, 1), gfx::SurfaceFormat::B8G8R8A8));
|
|
if (!mReferenceTextureData) {
|
|
return false;
|
|
}
|
|
|
|
mReferenceTextureData->Lock(OpenMode::OPEN_READ_WRITE);
|
|
mBaseDT = mReferenceTextureData->BorrowDrawTarget();
|
|
return true;
|
|
}
|
|
|
|
bool CanvasTranslator::CheckForFreshCanvasDevice(int aLineNumber) {
|
|
#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();
|
|
}
|
|
|
|
if (mDevice) {
|
|
if (mDevice->GetDeviceRemovedReason() == S_OK) {
|
|
return false;
|
|
}
|
|
|
|
gfxCriticalNote << "GFX: CanvasTranslator detected a device reset at "
|
|
<< aLineNumber;
|
|
NotifyDeviceChanged();
|
|
}
|
|
|
|
RefPtr<Runnable> runnable = NS_NewRunnableFunction(
|
|
"CanvasTranslator NotifyDeviceReset",
|
|
[]() { gfx::GPUParent::GetSingleton()->NotifyDeviceReset(); });
|
|
|
|
// 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(GetMainThreadEventTarget(), runnable,
|
|
/*aForceDispatch*/ true);
|
|
|
|
mDevice = gfx::DeviceManagerDx::Get()->GetCanvasDevice();
|
|
return mDevice && CreateReferenceTexture();
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
void CanvasTranslator::NotifyDeviceChanged() {
|
|
mDeviceResetInProgress = true;
|
|
mCanvasThreadHolder->DispatchToCanvasThread(
|
|
NewRunnableMethod("CanvasTranslator::SendNotifyDeviceChanged", this,
|
|
&CanvasTranslator::SendNotifyDeviceChanged));
|
|
}
|
|
|
|
void CanvasTranslator::AddSurfaceDescriptor(gfx::ReferencePtr aRefPtr,
|
|
TextureData* aTextureData) {
|
|
UniquePtr<SurfaceDescriptor> descriptor = MakeUnique<SurfaceDescriptor>();
|
|
if (!aTextureData->Serialize(*descriptor)) {
|
|
MOZ_CRASH("Failed to serialize");
|
|
}
|
|
|
|
MonitorAutoLock lock(mSurfaceDescriptorsMonitor);
|
|
mSurfaceDescriptors[aRefPtr] = std::move(descriptor);
|
|
mSurfaceDescriptorsMonitor.Notify();
|
|
}
|
|
|
|
already_AddRefed<gfx::DrawTarget> CanvasTranslator::CreateDrawTarget(
|
|
gfx::ReferencePtr aRefPtr, const gfx::IntSize& aSize,
|
|
gfx::SurfaceFormat aFormat) {
|
|
RefPtr<gfx::DrawTarget> dt;
|
|
do {
|
|
// It is important that AutoSerializeWithMoz2D is called within the loop
|
|
// and doesn't hold during calls to CheckForFreshCanvasDevice, because that
|
|
// might cause a deadlock with device reset code on the main thread.
|
|
gfx::AutoSerializeWithMoz2D serializeWithMoz2D(
|
|
GetReferenceDrawTarget()->GetBackendType());
|
|
TextureData* textureData = CreateTextureData(mTextureType, aSize, aFormat);
|
|
if (textureData) {
|
|
textureData->Lock(OpenMode::OPEN_READ_WRITE);
|
|
mTextureDatas[aRefPtr] = UniquePtr<TextureData>(textureData);
|
|
AddSurfaceDescriptor(aRefPtr, textureData);
|
|
dt = textureData->BorrowDrawTarget();
|
|
}
|
|
} while (!dt && CheckForFreshCanvasDevice(__LINE__));
|
|
AddDrawTarget(aRefPtr, dt);
|
|
|
|
return dt.forget();
|
|
}
|
|
|
|
void CanvasTranslator::RemoveDrawTarget(gfx::ReferencePtr aDrawTarget) {
|
|
InlineTranslator::RemoveDrawTarget(aDrawTarget);
|
|
gfx::AutoSerializeWithMoz2D serializeWithMoz2D(
|
|
GetReferenceDrawTarget()->GetBackendType());
|
|
mTextureDatas.erase(aDrawTarget);
|
|
|
|
// It is possible that the texture from the content process has never been
|
|
// forwarded to the GPU process, so we have to make sure it is removed here
|
|
// otherwise if the same pointer gets used for a DrawTarget again in the
|
|
// content process then it could pick up the old (now invalid) descriptor.
|
|
MonitorAutoLock lock(mSurfaceDescriptorsMonitor);
|
|
mSurfaceDescriptors.erase(aDrawTarget);
|
|
}
|
|
|
|
TextureData* CanvasTranslator::LookupTextureData(
|
|
gfx::ReferencePtr aDrawTarget) {
|
|
TextureMap::const_iterator result = mTextureDatas.find(aDrawTarget);
|
|
if (result == mTextureDatas.end()) {
|
|
return nullptr;
|
|
}
|
|
return result->second.get();
|
|
}
|
|
|
|
UniquePtr<SurfaceDescriptor> CanvasTranslator::WaitForSurfaceDescriptor(
|
|
gfx::ReferencePtr aDrawTarget) {
|
|
MonitorAutoLock lock(mSurfaceDescriptorsMonitor);
|
|
DescriptorMap::iterator result;
|
|
while ((result = mSurfaceDescriptors.find(aDrawTarget)) ==
|
|
mSurfaceDescriptors.end()) {
|
|
mSurfaceDescriptorsMonitor.Wait();
|
|
}
|
|
|
|
UniquePtr<SurfaceDescriptor> descriptor = std::move(result->second);
|
|
mSurfaceDescriptors.erase(aDrawTarget);
|
|
return descriptor;
|
|
}
|
|
|
|
gfx::DataSourceSurface* CanvasTranslator::LookupDataSurface(
|
|
gfx::ReferencePtr aRefPtr) {
|
|
return mDataSurfaces.GetWeak(aRefPtr);
|
|
}
|
|
|
|
void CanvasTranslator::AddDataSurface(
|
|
gfx::ReferencePtr aRefPtr, RefPtr<gfx::DataSourceSurface>&& aSurface) {
|
|
mDataSurfaces.Put(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
|