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