diff -r 000000000000 -r 6474c204b198 gfx/layers/d3d10/ReadbackManagerD3D10.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gfx/layers/d3d10/ReadbackManagerD3D10.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,223 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ReadbackManagerD3D10.h" +#include "ReadbackProcessor.h" +#include "ReadbackLayer.h" + +#include "nsIThread.h" +#include "nsThreadUtils.h" +#include "gfxImageSurface.h" +#include "gfxContext.h" + +namespace mozilla { +namespace layers { + +// Structure that contains the information required to execute a readback task, +// the only member accessed off the main thread here is mReadbackTexture. Since +// mLayer may be released only on the main thread this object should always be +// destroyed on the main thread! +struct ReadbackTask { + // The texture that we copied the contents of the thebeslayer to. + nsRefPtr mReadbackTexture; + // This exists purely to keep the ReadbackLayer alive for the lifetime of + // mUpdate. Note that this addref and release should occur -solely- on the + // main thread. + nsRefPtr mLayer; + ReadbackProcessor::Update mUpdate; + // The origin in ThebesLayer coordinates of mReadbackTexture. + gfxPoint mOrigin; + // mLayer->GetBackgroundOffset() when the task is created. We have + // to save this in the ReadbackTask because it might change before + // the update is delivered to the readback sink. + nsIntPoint mBackgroundOffset; +}; + +// This class is created and dispatched from the Readback thread but it must be +// destroyed by the main thread. +class ReadbackResultWriter MOZ_FINAL : public nsIRunnable +{ + NS_DECL_THREADSAFE_ISUPPORTS +public: + ReadbackResultWriter(ReadbackTask *aTask) : mTask(aTask) {} + + NS_IMETHODIMP Run() + { + ReadbackProcessor::Update *update = &mTask->mUpdate; + + if (!update->mLayer->GetSink()) { + // This can happen when a plugin is destroyed. + return NS_OK; + } + + nsIntPoint offset = mTask->mBackgroundOffset; + + D3D10_TEXTURE2D_DESC desc; + mTask->mReadbackTexture->GetDesc(&desc); + + D3D10_MAPPED_TEXTURE2D mappedTex; + // We know this map will immediately succeed, as we've already mapped this + // copied data on our task thread. + HRESULT hr = mTask->mReadbackTexture->Map(0, D3D10_MAP_READ, 0, &mappedTex); + + if (FAILED(hr)) { + // If this fails we're never going to get our ThebesLayer content. + update->mLayer->GetSink()->SetUnknown(update->mSequenceCounter); + return NS_OK; + } + + nsRefPtr sourceSurface = + new gfxImageSurface((unsigned char*)mappedTex.pData, + gfxIntSize(desc.Width, desc.Height), + mappedTex.RowPitch, + gfxImageFormat::RGB24); + + nsRefPtr ctx = + update->mLayer->GetSink()->BeginUpdate(update->mUpdateRect + offset, + update->mSequenceCounter); + + if (ctx) { + ctx->Translate(gfxPoint(offset.x, offset.y)); + ctx->SetSource(sourceSurface, gfxPoint(mTask->mOrigin.x, + mTask->mOrigin.y)); + ctx->Paint(); + + update->mLayer->GetSink()->EndUpdate(ctx, update->mUpdateRect + offset); + } + + mTask->mReadbackTexture->Unmap(0); + + return NS_OK; + } + +private: + nsAutoPtr mTask; +}; + +NS_IMPL_ISUPPORTS(ReadbackResultWriter, nsIRunnable) + +DWORD WINAPI StartTaskThread(void *aManager) +{ + static_cast(aManager)->ProcessTasks(); + + return 0; +} + +ReadbackManagerD3D10::ReadbackManagerD3D10() + : mRefCnt(0) +{ + ::InitializeCriticalSection(&mTaskMutex); + mShutdownEvent = ::CreateEventA(nullptr, FALSE, FALSE, nullptr); + mTaskSemaphore = ::CreateSemaphoreA(nullptr, 0, 1000000, nullptr); + mTaskThread = ::CreateThread(nullptr, 0, StartTaskThread, this, 0, 0); +} + +ReadbackManagerD3D10::~ReadbackManagerD3D10() +{ + ::SetEvent(mShutdownEvent); + + // This shouldn't take longer than 5 seconds, if it does we're going to choose + // to leak the thread and its synchronisation in favor of crashing or freezing + DWORD result = ::WaitForSingleObject(mTaskThread, 5000); + if (result != WAIT_TIMEOUT) { + ::DeleteCriticalSection(&mTaskMutex); + ::CloseHandle(mShutdownEvent); + ::CloseHandle(mTaskSemaphore); + ::CloseHandle(mTaskThread); + } else { + NS_RUNTIMEABORT("ReadbackManager: Task thread did not shutdown in 5 seconds."); + } +} + +void +ReadbackManagerD3D10::PostTask(ID3D10Texture2D *aTexture, void *aUpdate, const gfxPoint &aOrigin) +{ + ReadbackTask *task = new ReadbackTask; + task->mReadbackTexture = aTexture; + task->mUpdate = *static_cast(aUpdate); + task->mOrigin = aOrigin; + task->mLayer = task->mUpdate.mLayer; + task->mBackgroundOffset = task->mLayer->GetBackgroundLayerOffset(); + + ::EnterCriticalSection(&mTaskMutex); + mPendingReadbackTasks.AppendElement(task); + ::LeaveCriticalSection(&mTaskMutex); + + ::ReleaseSemaphore(mTaskSemaphore, 1, nullptr); +} + +HRESULT +ReadbackManagerD3D10::QueryInterface(REFIID riid, void **ppvObject) +{ + if (!ppvObject) { + return E_POINTER; + } + + if (riid == IID_IUnknown) { + *ppvObject = this; + } else { + return E_NOINTERFACE; + } + + return S_OK; +} + +ULONG +ReadbackManagerD3D10::AddRef() +{ + NS_ASSERTION(NS_IsMainThread(), + "ReadbackManagerD3D10 should only be refcounted on main thread."); + return ++mRefCnt; +} + +ULONG +ReadbackManagerD3D10::Release() +{ + NS_ASSERTION(NS_IsMainThread(), + "ReadbackManagerD3D10 should only be refcounted on main thread."); + ULONG newRefCnt = --mRefCnt; + if (!newRefCnt) { + mRefCnt++; + delete this; + } + return newRefCnt; +} + +void +ReadbackManagerD3D10::ProcessTasks() +{ + HANDLE handles[] = { mTaskSemaphore, mShutdownEvent }; + + while (true) { + DWORD result = ::WaitForMultipleObjects(2, handles, FALSE, INFINITE); + if (result != WAIT_OBJECT_0) { + return; + } + + ::EnterCriticalSection(&mTaskMutex); + if (mPendingReadbackTasks.Length() == 0) { + NS_RUNTIMEABORT("Trying to read from an empty array, bad bad bad"); + } + ReadbackTask *nextReadbackTask = mPendingReadbackTasks[0].forget(); + mPendingReadbackTasks.RemoveElementAt(0); + ::LeaveCriticalSection(&mTaskMutex); + + // We want to block here until the texture contents are available, the + // easiest thing is to simply map and unmap. + D3D10_MAPPED_TEXTURE2D mappedTex; + nextReadbackTask->mReadbackTexture->Map(0, D3D10_MAP_READ, 0, &mappedTex); + nextReadbackTask->mReadbackTexture->Unmap(0); + + // We can only send the update to the sink on the main thread, so post an + // event there to do so. Ownership of the task is passed from + // mPendingReadbackTasks to ReadbackResultWriter here. + nsCOMPtr thread = do_GetMainThread(); + thread->Dispatch(new ReadbackResultWriter(nextReadbackTask), + nsIEventTarget::DISPATCH_NORMAL); + } +} + +} +}