|
1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
|
2 * This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 #include "ReadbackManagerD3D10.h" |
|
7 #include "ReadbackProcessor.h" |
|
8 #include "ReadbackLayer.h" |
|
9 |
|
10 #include "nsIThread.h" |
|
11 #include "nsThreadUtils.h" |
|
12 #include "gfxImageSurface.h" |
|
13 #include "gfxContext.h" |
|
14 |
|
15 namespace mozilla { |
|
16 namespace layers { |
|
17 |
|
18 // Structure that contains the information required to execute a readback task, |
|
19 // the only member accessed off the main thread here is mReadbackTexture. Since |
|
20 // mLayer may be released only on the main thread this object should always be |
|
21 // destroyed on the main thread! |
|
22 struct ReadbackTask { |
|
23 // The texture that we copied the contents of the thebeslayer to. |
|
24 nsRefPtr<ID3D10Texture2D> mReadbackTexture; |
|
25 // This exists purely to keep the ReadbackLayer alive for the lifetime of |
|
26 // mUpdate. Note that this addref and release should occur -solely- on the |
|
27 // main thread. |
|
28 nsRefPtr<ReadbackLayer> mLayer; |
|
29 ReadbackProcessor::Update mUpdate; |
|
30 // The origin in ThebesLayer coordinates of mReadbackTexture. |
|
31 gfxPoint mOrigin; |
|
32 // mLayer->GetBackgroundOffset() when the task is created. We have |
|
33 // to save this in the ReadbackTask because it might change before |
|
34 // the update is delivered to the readback sink. |
|
35 nsIntPoint mBackgroundOffset; |
|
36 }; |
|
37 |
|
38 // This class is created and dispatched from the Readback thread but it must be |
|
39 // destroyed by the main thread. |
|
40 class ReadbackResultWriter MOZ_FINAL : public nsIRunnable |
|
41 { |
|
42 NS_DECL_THREADSAFE_ISUPPORTS |
|
43 public: |
|
44 ReadbackResultWriter(ReadbackTask *aTask) : mTask(aTask) {} |
|
45 |
|
46 NS_IMETHODIMP Run() |
|
47 { |
|
48 ReadbackProcessor::Update *update = &mTask->mUpdate; |
|
49 |
|
50 if (!update->mLayer->GetSink()) { |
|
51 // This can happen when a plugin is destroyed. |
|
52 return NS_OK; |
|
53 } |
|
54 |
|
55 nsIntPoint offset = mTask->mBackgroundOffset; |
|
56 |
|
57 D3D10_TEXTURE2D_DESC desc; |
|
58 mTask->mReadbackTexture->GetDesc(&desc); |
|
59 |
|
60 D3D10_MAPPED_TEXTURE2D mappedTex; |
|
61 // We know this map will immediately succeed, as we've already mapped this |
|
62 // copied data on our task thread. |
|
63 HRESULT hr = mTask->mReadbackTexture->Map(0, D3D10_MAP_READ, 0, &mappedTex); |
|
64 |
|
65 if (FAILED(hr)) { |
|
66 // If this fails we're never going to get our ThebesLayer content. |
|
67 update->mLayer->GetSink()->SetUnknown(update->mSequenceCounter); |
|
68 return NS_OK; |
|
69 } |
|
70 |
|
71 nsRefPtr<gfxImageSurface> sourceSurface = |
|
72 new gfxImageSurface((unsigned char*)mappedTex.pData, |
|
73 gfxIntSize(desc.Width, desc.Height), |
|
74 mappedTex.RowPitch, |
|
75 gfxImageFormat::RGB24); |
|
76 |
|
77 nsRefPtr<gfxContext> ctx = |
|
78 update->mLayer->GetSink()->BeginUpdate(update->mUpdateRect + offset, |
|
79 update->mSequenceCounter); |
|
80 |
|
81 if (ctx) { |
|
82 ctx->Translate(gfxPoint(offset.x, offset.y)); |
|
83 ctx->SetSource(sourceSurface, gfxPoint(mTask->mOrigin.x, |
|
84 mTask->mOrigin.y)); |
|
85 ctx->Paint(); |
|
86 |
|
87 update->mLayer->GetSink()->EndUpdate(ctx, update->mUpdateRect + offset); |
|
88 } |
|
89 |
|
90 mTask->mReadbackTexture->Unmap(0); |
|
91 |
|
92 return NS_OK; |
|
93 } |
|
94 |
|
95 private: |
|
96 nsAutoPtr<ReadbackTask> mTask; |
|
97 }; |
|
98 |
|
99 NS_IMPL_ISUPPORTS(ReadbackResultWriter, nsIRunnable) |
|
100 |
|
101 DWORD WINAPI StartTaskThread(void *aManager) |
|
102 { |
|
103 static_cast<ReadbackManagerD3D10*>(aManager)->ProcessTasks(); |
|
104 |
|
105 return 0; |
|
106 } |
|
107 |
|
108 ReadbackManagerD3D10::ReadbackManagerD3D10() |
|
109 : mRefCnt(0) |
|
110 { |
|
111 ::InitializeCriticalSection(&mTaskMutex); |
|
112 mShutdownEvent = ::CreateEventA(nullptr, FALSE, FALSE, nullptr); |
|
113 mTaskSemaphore = ::CreateSemaphoreA(nullptr, 0, 1000000, nullptr); |
|
114 mTaskThread = ::CreateThread(nullptr, 0, StartTaskThread, this, 0, 0); |
|
115 } |
|
116 |
|
117 ReadbackManagerD3D10::~ReadbackManagerD3D10() |
|
118 { |
|
119 ::SetEvent(mShutdownEvent); |
|
120 |
|
121 // This shouldn't take longer than 5 seconds, if it does we're going to choose |
|
122 // to leak the thread and its synchronisation in favor of crashing or freezing |
|
123 DWORD result = ::WaitForSingleObject(mTaskThread, 5000); |
|
124 if (result != WAIT_TIMEOUT) { |
|
125 ::DeleteCriticalSection(&mTaskMutex); |
|
126 ::CloseHandle(mShutdownEvent); |
|
127 ::CloseHandle(mTaskSemaphore); |
|
128 ::CloseHandle(mTaskThread); |
|
129 } else { |
|
130 NS_RUNTIMEABORT("ReadbackManager: Task thread did not shutdown in 5 seconds."); |
|
131 } |
|
132 } |
|
133 |
|
134 void |
|
135 ReadbackManagerD3D10::PostTask(ID3D10Texture2D *aTexture, void *aUpdate, const gfxPoint &aOrigin) |
|
136 { |
|
137 ReadbackTask *task = new ReadbackTask; |
|
138 task->mReadbackTexture = aTexture; |
|
139 task->mUpdate = *static_cast<ReadbackProcessor::Update*>(aUpdate); |
|
140 task->mOrigin = aOrigin; |
|
141 task->mLayer = task->mUpdate.mLayer; |
|
142 task->mBackgroundOffset = task->mLayer->GetBackgroundLayerOffset(); |
|
143 |
|
144 ::EnterCriticalSection(&mTaskMutex); |
|
145 mPendingReadbackTasks.AppendElement(task); |
|
146 ::LeaveCriticalSection(&mTaskMutex); |
|
147 |
|
148 ::ReleaseSemaphore(mTaskSemaphore, 1, nullptr); |
|
149 } |
|
150 |
|
151 HRESULT |
|
152 ReadbackManagerD3D10::QueryInterface(REFIID riid, void **ppvObject) |
|
153 { |
|
154 if (!ppvObject) { |
|
155 return E_POINTER; |
|
156 } |
|
157 |
|
158 if (riid == IID_IUnknown) { |
|
159 *ppvObject = this; |
|
160 } else { |
|
161 return E_NOINTERFACE; |
|
162 } |
|
163 |
|
164 return S_OK; |
|
165 } |
|
166 |
|
167 ULONG |
|
168 ReadbackManagerD3D10::AddRef() |
|
169 { |
|
170 NS_ASSERTION(NS_IsMainThread(), |
|
171 "ReadbackManagerD3D10 should only be refcounted on main thread."); |
|
172 return ++mRefCnt; |
|
173 } |
|
174 |
|
175 ULONG |
|
176 ReadbackManagerD3D10::Release() |
|
177 { |
|
178 NS_ASSERTION(NS_IsMainThread(), |
|
179 "ReadbackManagerD3D10 should only be refcounted on main thread."); |
|
180 ULONG newRefCnt = --mRefCnt; |
|
181 if (!newRefCnt) { |
|
182 mRefCnt++; |
|
183 delete this; |
|
184 } |
|
185 return newRefCnt; |
|
186 } |
|
187 |
|
188 void |
|
189 ReadbackManagerD3D10::ProcessTasks() |
|
190 { |
|
191 HANDLE handles[] = { mTaskSemaphore, mShutdownEvent }; |
|
192 |
|
193 while (true) { |
|
194 DWORD result = ::WaitForMultipleObjects(2, handles, FALSE, INFINITE); |
|
195 if (result != WAIT_OBJECT_0) { |
|
196 return; |
|
197 } |
|
198 |
|
199 ::EnterCriticalSection(&mTaskMutex); |
|
200 if (mPendingReadbackTasks.Length() == 0) { |
|
201 NS_RUNTIMEABORT("Trying to read from an empty array, bad bad bad"); |
|
202 } |
|
203 ReadbackTask *nextReadbackTask = mPendingReadbackTasks[0].forget(); |
|
204 mPendingReadbackTasks.RemoveElementAt(0); |
|
205 ::LeaveCriticalSection(&mTaskMutex); |
|
206 |
|
207 // We want to block here until the texture contents are available, the |
|
208 // easiest thing is to simply map and unmap. |
|
209 D3D10_MAPPED_TEXTURE2D mappedTex; |
|
210 nextReadbackTask->mReadbackTexture->Map(0, D3D10_MAP_READ, 0, &mappedTex); |
|
211 nextReadbackTask->mReadbackTexture->Unmap(0); |
|
212 |
|
213 // We can only send the update to the sink on the main thread, so post an |
|
214 // event there to do so. Ownership of the task is passed from |
|
215 // mPendingReadbackTasks to ReadbackResultWriter here. |
|
216 nsCOMPtr<nsIThread> thread = do_GetMainThread(); |
|
217 thread->Dispatch(new ReadbackResultWriter(nextReadbackTask), |
|
218 nsIEventTarget::DISPATCH_NORMAL); |
|
219 } |
|
220 } |
|
221 |
|
222 } |
|
223 } |