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