1 /*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9 #include "RenderCapture.h"
10 #include "ServiceBroker.h"
11 #include "utils/log.h"
12 #include "windowing/WinSystem.h"
13 #include "settings/AdvancedSettings.h"
14 #include "settings/SettingsComponent.h"
15 #include "cores/IPlayer.h"
16 #ifdef TARGET_WINDOWS
17 #include "rendering/dx/DeviceResources.h"
18 #include "rendering/dx/RenderContext.h"
19 #else
20 #include "rendering/RenderSystem.h"
21 #endif
22
23 extern "C" {
24 #include <libavutil/mem.h>
25 }
26
CRenderCaptureBase()27 CRenderCaptureBase::CRenderCaptureBase()
28 {
29 m_state = CAPTURESTATE_FAILED;
30 m_userState = CAPTURESTATE_FAILED;
31 m_pixels = NULL;
32 m_width = 0;
33 m_height = 0;
34 m_bufferSize = 0;
35 m_flags = 0;
36 m_asyncSupported = false;
37 m_asyncChecked = false;
38 }
39
40 CRenderCaptureBase::~CRenderCaptureBase() = default;
41
UseOcclusionQuery()42 bool CRenderCaptureBase::UseOcclusionQuery()
43 {
44 if (m_flags & CAPTUREFLAG_IMMEDIATELY)
45 return false;
46 else if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoCaptureUseOcclusionQuery == 0)
47 return false;
48 else
49 return true;
50 }
51
52 #if defined(HAS_GL) || defined(HAS_GLES)
53
CRenderCaptureGL()54 CRenderCaptureGL::CRenderCaptureGL()
55 {
56 m_pbo = 0;
57 m_query = 0;
58 m_occlusionQuerySupported = false;
59 }
60
~CRenderCaptureGL()61 CRenderCaptureGL::~CRenderCaptureGL()
62 {
63 #ifndef HAS_GLES
64 if (m_asyncSupported)
65 {
66 if (m_pbo)
67 {
68 glBindBuffer(GL_PIXEL_PACK_BUFFER, m_pbo);
69 glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
70 glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
71 glDeleteBuffers(1, &m_pbo);
72 }
73
74 if (m_query)
75 glDeleteQueries(1, &m_query);
76 }
77 #endif
78
79 delete[] m_pixels;
80 }
81
GetCaptureFormat()82 int CRenderCaptureGL::GetCaptureFormat()
83 {
84 return CAPTUREFORMAT_BGRA;
85 }
86
BeginRender()87 void CRenderCaptureGL::BeginRender()
88 {
89 if (!m_asyncChecked)
90 {
91 #ifndef HAS_GLES
92 unsigned int major, minor, glversion;
93 CServiceBroker::GetRenderSystem()->GetRenderVersion(major, minor);
94 glversion = 10 * major + minor;
95 if (glversion >= 21)
96 {
97 m_asyncSupported = true;
98 m_occlusionQuerySupported = true;
99 }
100 else if (glversion > 14)
101 {
102 m_occlusionQuerySupported = true;
103 }
104 else
105 {
106 CLog::Log(LOGWARNING, "CRenderCaptureGL: Occlusion_query not supported, upgrade your GL drivers to support at least GL 2.1");
107 }
108 if (m_flags & CAPTUREFLAG_CONTINUOUS)
109 {
110 if (!m_occlusionQuerySupported)
111 CLog::Log(LOGWARNING, "CRenderCaptureGL: Occlusion_query not supported, performance might suffer");
112 if (!CServiceBroker::GetRenderSystem()->IsExtSupported("GL_ARB_pixel_buffer_object"))
113 CLog::Log(LOGWARNING, "CRenderCaptureGL: GL_ARB_pixel_buffer_object not supported, performance might suffer");
114 if (!UseOcclusionQuery())
115 CLog::Log(LOGWARNING, "CRenderCaptureGL: GL_ARB_occlusion_query disabled, performance might suffer");
116 }
117 #endif
118 m_asyncChecked = true;
119 }
120
121 #ifndef HAS_GLES
122 if (m_asyncSupported)
123 {
124 if (!m_pbo)
125 glGenBuffers(1, &m_pbo);
126
127 if (UseOcclusionQuery() && m_occlusionQuerySupported)
128 {
129 //generate an occlusion query if we don't have one
130 if (!m_query)
131 glGenQueries(1, &m_query);
132 }
133 else
134 {
135 //don't use an occlusion query, clean up any old one
136 if (m_query)
137 {
138 glDeleteQueries(1, &m_query);
139 m_query = 0;
140 }
141 }
142
143 //start the occlusion query
144 if (m_query)
145 glBeginQuery(GL_SAMPLES_PASSED, m_query);
146
147 //allocate data on the pbo and pixel buffer
148 glBindBuffer(GL_PIXEL_PACK_BUFFER, m_pbo);
149 if (m_bufferSize != m_width * m_height * 4)
150 {
151 m_bufferSize = m_width * m_height * 4;
152 glBufferData(GL_PIXEL_PACK_BUFFER, m_bufferSize, 0, GL_STREAM_READ);
153 delete[] m_pixels;
154 m_pixels = new uint8_t[m_bufferSize];
155 }
156 }
157 else
158 #endif
159 {
160 if (m_bufferSize != m_width * m_height * 4)
161 {
162 delete[] m_pixels;
163 m_bufferSize = m_width * m_height * 4;
164 m_pixels = new uint8_t[m_bufferSize];
165 }
166 }
167 }
168
EndRender()169 void CRenderCaptureGL::EndRender()
170 {
171 #ifndef HAS_GLES
172 if (m_asyncSupported)
173 {
174 glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
175
176 if (m_query)
177 glEndQuery(GL_SAMPLES_PASSED);
178
179 if (m_flags & CAPTUREFLAG_IMMEDIATELY)
180 PboToBuffer();
181 else
182 SetState(CAPTURESTATE_NEEDSREADOUT);
183 }
184 else
185 #endif
186 {
187 SetState(CAPTURESTATE_DONE);
188 }
189 }
190
GetRenderBuffer()191 void* CRenderCaptureGL::GetRenderBuffer()
192 {
193 #ifndef HAS_GLES
194 if (m_asyncSupported)
195 {
196 return NULL; //offset into the pbo
197 }
198 else
199 #endif
200 {
201 return m_pixels;
202 }
203 }
204
ReadOut()205 void CRenderCaptureGL::ReadOut()
206 {
207 #ifndef HAS_GLES
208 if (m_asyncSupported)
209 {
210 //we don't care about the occlusion query, we just want to know if the result is available
211 //when it is, the write into the pbo is probably done as well,
212 //so it can be mapped and read without a busy wait
213
214 GLuint readout = 1;
215 if (m_query)
216 glGetQueryObjectuiv(m_query, GL_QUERY_RESULT_AVAILABLE, &readout);
217
218 if (readout)
219 PboToBuffer();
220 }
221 #endif
222 }
223
PboToBuffer()224 void CRenderCaptureGL::PboToBuffer()
225 {
226 #ifndef HAS_GLES
227 glBindBuffer(GL_PIXEL_PACK_BUFFER, m_pbo);
228 GLvoid* pboPtr = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
229
230 if (pboPtr)
231 {
232 memcpy(m_pixels, pboPtr, m_bufferSize);
233 SetState(CAPTURESTATE_DONE);
234 }
235 else
236 {
237 CLog::Log(LOGERROR, "CRenderCaptureGL::PboToBuffer: glMapBuffer failed");
238 SetState(CAPTURESTATE_FAILED);
239 }
240
241 glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
242 glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
243 #endif
244 }
245
246 #elif HAS_DX /*HAS_GL*/
247
CRenderCaptureDX()248 CRenderCaptureDX::CRenderCaptureDX()
249 {
250 m_query = nullptr;
251 m_surfaceWidth = 0;
252 m_surfaceHeight = 0;
253 DX::Windowing()->Register(this);
254 }
255
~CRenderCaptureDX()256 CRenderCaptureDX::~CRenderCaptureDX()
257 {
258 CleanupDX();
259 av_freep(&m_pixels);
260 DX::Windowing()->Unregister(this);
261 }
262
GetCaptureFormat()263 int CRenderCaptureDX::GetCaptureFormat()
264 {
265 return CAPTUREFORMAT_BGRA;
266 }
267
BeginRender()268 void CRenderCaptureDX::BeginRender()
269 {
270 Microsoft::WRL::ComPtr<ID3D11DeviceContext> pContext = DX::DeviceResources::Get()->GetD3DContext();
271 Microsoft::WRL::ComPtr<ID3D11Device> pDevice = DX::DeviceResources::Get()->GetD3DDevice();
272 CD3D11_QUERY_DESC queryDesc(D3D11_QUERY_EVENT);
273
274 if (!m_asyncChecked)
275 {
276 m_asyncSupported = SUCCEEDED(pDevice->CreateQuery(&queryDesc, nullptr));
277 if (m_flags & CAPTUREFLAG_CONTINUOUS)
278 {
279 if (!m_asyncSupported)
280 CLog::Log(LOGWARNING, "%s: D3D11_QUERY_OCCLUSION not supported, performance might suffer.", __FUNCTION__);
281 if (!UseOcclusionQuery())
282 CLog::Log(LOGWARNING, "%s: D3D11_QUERY_OCCLUSION disabled, performance might suffer.", __FUNCTION__);
283 }
284 m_asyncChecked = true;
285 }
286
287 HRESULT result;
288
289 if (m_surfaceWidth != m_width || m_surfaceHeight != m_height)
290 {
291 m_renderTex.Release();
292 m_copyTex.Release();
293
294 if (!m_renderTex.Create(m_width, m_height, 1, D3D11_USAGE_DEFAULT, DXGI_FORMAT_B8G8R8A8_UNORM))
295 {
296 CLog::LogF(LOGERROR, "CreateTexture2D (RENDER_TARGET) failed.");
297 SetState(CAPTURESTATE_FAILED);
298 return;
299 }
300
301 if (!m_copyTex.Create(m_width, m_height, 1, D3D11_USAGE_STAGING, DXGI_FORMAT_B8G8R8A8_UNORM))
302 {
303 CLog::LogF(LOGERROR, "CreateRenderTargetView failed.");
304 SetState(CAPTURESTATE_FAILED);
305 return;
306 }
307
308 m_surfaceWidth = m_width;
309 m_surfaceHeight = m_height;
310 }
311
312 if (m_bufferSize != m_width * m_height * 4)
313 {
314 m_bufferSize = m_width * m_height * 4;
315 av_freep(&m_pixels);
316 m_pixels = (uint8_t*)av_malloc(m_bufferSize);
317 }
318
319 if (m_asyncSupported && UseOcclusionQuery())
320 {
321 //generate an occlusion query if we don't have one
322 if (!m_query)
323 {
324 result = pDevice->CreateQuery(&queryDesc, m_query.ReleaseAndGetAddressOf());
325 if (FAILED(result))
326 {
327 CLog::LogF(LOGERROR, "CreateQuery failed %s",
328 DX::GetErrorDescription(result).c_str());
329 m_asyncSupported = false;
330 m_query = nullptr;
331 }
332 }
333 }
334 else
335 {
336 //don't use an occlusion query, clean up any old one
337 m_query = nullptr;
338 }
339 }
340
EndRender()341 void CRenderCaptureDX::EndRender()
342 {
343 // send commands to the GPU queue
344 auto deviceResources = DX::DeviceResources::Get();
345 deviceResources->FinishCommandList();
346 Microsoft::WRL::ComPtr<ID3D11DeviceContext> pContext = deviceResources->GetImmediateContext();
347
348 pContext->CopyResource(m_copyTex.Get(), m_renderTex.Get());
349
350 if (m_query)
351 {
352 pContext->End(m_query.Get());
353 }
354
355 if (m_flags & CAPTUREFLAG_IMMEDIATELY)
356 SurfaceToBuffer();
357 else
358 SetState(CAPTURESTATE_NEEDSREADOUT);
359 }
360
ReadOut()361 void CRenderCaptureDX::ReadOut()
362 {
363 if (m_query)
364 {
365 //if the result of the occlusion query is available, the data is probably also written into m_copySurface
366 HRESULT result = DX::DeviceResources::Get()->GetImmediateContext()->GetData(m_query.Get(), nullptr, 0, 0);
367 if (SUCCEEDED(result))
368 {
369 if (S_OK == result)
370 SurfaceToBuffer();
371 }
372 else
373 {
374 CLog::Log(LOGERROR, "%s: GetData failed.", __FUNCTION__);
375 SurfaceToBuffer();
376 }
377 }
378 else
379 {
380 SurfaceToBuffer();
381 }
382 }
383
SurfaceToBuffer()384 void CRenderCaptureDX::SurfaceToBuffer()
385 {
386 D3D11_MAPPED_SUBRESOURCE lockedRect;
387 if (m_copyTex.LockRect(0, &lockedRect, D3D11_MAP_READ))
388 {
389 //if pitch is same, do a direct copy, otherwise copy one line at a time
390 if (lockedRect.RowPitch == m_width * 4)
391 {
392 memcpy(m_pixels, lockedRect.pData, m_width * m_height * 4);
393 }
394 else
395 {
396 for (unsigned int y = 0; y < m_height; y++)
397 memcpy(m_pixels + y * m_width * 4, (uint8_t*)lockedRect.pData + y * lockedRect.RowPitch, m_width * 4);
398 }
399 m_copyTex.UnlockRect(0);
400 SetState(CAPTURESTATE_DONE);
401 }
402 else
403 {
404 CLog::Log(LOGERROR, "%s: locking m_copySurface failed.", __FUNCTION__);
405 SetState(CAPTURESTATE_FAILED);
406 }
407 }
408
OnDestroyDevice(bool fatal)409 void CRenderCaptureDX::OnDestroyDevice(bool fatal)
410 {
411 CleanupDX();
412 SetState(CAPTURESTATE_FAILED);
413 }
414
CleanupDX()415 void CRenderCaptureDX::CleanupDX()
416 {
417 m_renderTex.Release();
418 m_copyTex.Release();
419 m_query = nullptr;
420 m_surfaceWidth = 0;
421 m_surfaceHeight = 0;
422 }
423
424 #endif /*HAS_DX*/
425