/xbmc/cores/VideoRenderers/RenderCapture.cpp

http://github.com/xbmc/xbmc · C++ · 510 lines · 412 code · 66 blank · 32 comment · 85 complexity · 6851f1690cf4140495a627b3a46ea732 MD5 · raw file

  1. /*
  2. * Copyright (C) 2005-2013 Team XBMC
  3. * http://xbmc.org
  4. *
  5. * This Program is free software; you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation; either version 2, or (at your option)
  8. * any later version.
  9. *
  10. * This Program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with XBMC; see the file COPYING. If not, see
  17. * <http://www.gnu.org/licenses/>.
  18. *
  19. */
  20. #include "RenderCapture.h"
  21. #include "utils/log.h"
  22. #include "windowing/WindowingFactory.h"
  23. #include "utils/fastmemcpy.h"
  24. #include "settings/AdvancedSettings.h"
  25. #include "settings/Settings.h"
  26. CRenderCaptureBase::CRenderCaptureBase()
  27. {
  28. m_state = CAPTURESTATE_FAILED;
  29. m_userState = CAPTURESTATE_FAILED;
  30. m_pixels = NULL;
  31. m_width = 0;
  32. m_height = 0;
  33. m_bufferSize = 0;
  34. m_flags = 0;
  35. m_asyncSupported = false;
  36. m_asyncChecked = false;
  37. }
  38. CRenderCaptureBase::~CRenderCaptureBase()
  39. {
  40. }
  41. bool CRenderCaptureBase::UseOcclusionQuery()
  42. {
  43. if (m_flags & CAPTUREFLAG_IMMEDIATELY)
  44. return false;
  45. else if ((g_advancedSettings.m_videoCaptureUseOcclusionQuery == 0) ||
  46. (g_advancedSettings.m_videoCaptureUseOcclusionQuery == -1 &&
  47. g_Windowing.GetRenderQuirks() & RENDER_QUIRKS_BROKEN_OCCLUSION_QUERY))
  48. return false;
  49. else
  50. return true;
  51. }
  52. #if defined(TARGET_RASPBERRY_PI)
  53. CRenderCaptureDispmanX::CRenderCaptureDispmanX()
  54. {
  55. }
  56. CRenderCaptureDispmanX::~CRenderCaptureDispmanX()
  57. {
  58. delete[] m_pixels;
  59. }
  60. int CRenderCaptureDispmanX::GetCaptureFormat()
  61. {
  62. return CAPTUREFORMAT_BGRA;
  63. }
  64. void CRenderCaptureDispmanX::BeginRender()
  65. {
  66. }
  67. void CRenderCaptureDispmanX::EndRender()
  68. {
  69. m_pixels = g_RBP.CaptureDisplay(m_width, m_height, NULL, true);
  70. SetState(CAPTURESTATE_DONE);
  71. }
  72. void* CRenderCaptureDispmanX::GetRenderBuffer()
  73. {
  74. return m_pixels;
  75. }
  76. void CRenderCaptureDispmanX::ReadOut()
  77. {
  78. }
  79. #elif defined(HAS_GL) || defined(HAS_GLES)
  80. CRenderCaptureGL::CRenderCaptureGL()
  81. {
  82. m_pbo = 0;
  83. m_query = 0;
  84. m_occlusionQuerySupported = false;
  85. }
  86. CRenderCaptureGL::~CRenderCaptureGL()
  87. {
  88. #ifndef HAS_GLES
  89. if (m_asyncSupported)
  90. {
  91. if (m_pbo)
  92. {
  93. glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, m_pbo);
  94. glUnmapBufferARB(GL_PIXEL_PACK_BUFFER_ARB);
  95. glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, 0);
  96. glDeleteBuffersARB(1, &m_pbo);
  97. }
  98. if (m_query)
  99. glDeleteQueriesARB(1, &m_query);
  100. }
  101. #endif
  102. delete[] m_pixels;
  103. }
  104. int CRenderCaptureGL::GetCaptureFormat()
  105. {
  106. return CAPTUREFORMAT_BGRA;
  107. }
  108. void CRenderCaptureGL::BeginRender()
  109. {
  110. if (!m_asyncChecked)
  111. {
  112. #ifndef HAS_GLES
  113. bool usePbo = CSettings::Get().GetBool("videoplayer.usepbo");
  114. m_asyncSupported = g_Windowing.IsExtSupported("GL_ARB_pixel_buffer_object") && usePbo;
  115. m_occlusionQuerySupported = g_Windowing.IsExtSupported("GL_ARB_occlusion_query");
  116. if (m_flags & CAPTUREFLAG_CONTINUOUS)
  117. {
  118. if (!m_occlusionQuerySupported)
  119. CLog::Log(LOGWARNING, "CRenderCaptureGL: GL_ARB_occlusion_query not supported, performance might suffer");
  120. if (!g_Windowing.IsExtSupported("GL_ARB_pixel_buffer_object"))
  121. CLog::Log(LOGWARNING, "CRenderCaptureGL: GL_ARB_pixel_buffer_object not supported, performance might suffer");
  122. if (!usePbo)
  123. CLog::Log(LOGWARNING, "CRenderCaptureGL: GL_ARB_pixel_buffer_object disabled, performance might suffer");
  124. if (UseOcclusionQuery())
  125. CLog::Log(LOGWARNING, "CRenderCaptureGL: GL_ARB_occlusion_query disabled, performance might suffer");
  126. }
  127. #endif
  128. m_asyncChecked = true;
  129. }
  130. #ifndef HAS_GLES
  131. if (m_asyncSupported)
  132. {
  133. if (!m_pbo)
  134. glGenBuffersARB(1, &m_pbo);
  135. if (UseOcclusionQuery() && m_occlusionQuerySupported)
  136. {
  137. //generate an occlusion query if we don't have one
  138. if (!m_query)
  139. glGenQueriesARB(1, &m_query);
  140. }
  141. else
  142. {
  143. //don't use an occlusion query, clean up any old one
  144. if (m_query)
  145. {
  146. glDeleteQueriesARB(1, &m_query);
  147. m_query = 0;
  148. }
  149. }
  150. //start the occlusion query
  151. if (m_query)
  152. glBeginQueryARB(GL_SAMPLES_PASSED_ARB, m_query);
  153. //allocate data on the pbo and pixel buffer
  154. glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, m_pbo);
  155. if (m_bufferSize != m_width * m_height * 4)
  156. {
  157. m_bufferSize = m_width * m_height * 4;
  158. glBufferDataARB(GL_PIXEL_PACK_BUFFER_ARB, m_bufferSize, 0, GL_STREAM_READ_ARB);
  159. delete[] m_pixels;
  160. m_pixels = new uint8_t[m_bufferSize];
  161. }
  162. }
  163. else
  164. #endif
  165. {
  166. if (m_bufferSize != m_width * m_height * 4)
  167. {
  168. delete[] m_pixels;
  169. m_bufferSize = m_width * m_height * 4;
  170. m_pixels = new uint8_t[m_bufferSize];
  171. }
  172. }
  173. }
  174. void CRenderCaptureGL::EndRender()
  175. {
  176. #ifndef HAS_GLES
  177. if (m_asyncSupported)
  178. {
  179. glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, 0);
  180. if (m_query)
  181. glEndQueryARB(GL_SAMPLES_PASSED_ARB);
  182. if (m_flags & CAPTUREFLAG_IMMEDIATELY)
  183. PboToBuffer();
  184. else
  185. SetState(CAPTURESTATE_NEEDSREADOUT);
  186. }
  187. else
  188. #endif
  189. {
  190. SetState(CAPTURESTATE_DONE);
  191. }
  192. }
  193. void* CRenderCaptureGL::GetRenderBuffer()
  194. {
  195. #ifndef HAS_GLES
  196. if (m_asyncSupported)
  197. {
  198. return NULL; //offset into the pbo
  199. }
  200. else
  201. #endif
  202. {
  203. return m_pixels;
  204. }
  205. }
  206. void CRenderCaptureGL::ReadOut()
  207. {
  208. #ifndef HAS_GLES
  209. if (m_asyncSupported)
  210. {
  211. //we don't care about the occlusion query, we just want to know if the result is available
  212. //when it is, the write into the pbo is probably done as well,
  213. //so it can be mapped and read without a busy wait
  214. GLuint readout = 1;
  215. if (m_query)
  216. glGetQueryObjectuivARB(m_query, GL_QUERY_RESULT_AVAILABLE_ARB, &readout);
  217. if (readout)
  218. PboToBuffer();
  219. }
  220. #endif
  221. }
  222. void CRenderCaptureGL::PboToBuffer()
  223. {
  224. #ifndef HAS_GLES
  225. glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, m_pbo);
  226. GLvoid* pboPtr = glMapBufferARB(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_ONLY_ARB);
  227. if (pboPtr)
  228. {
  229. fast_memcpy(m_pixels, pboPtr, m_bufferSize);
  230. SetState(CAPTURESTATE_DONE);
  231. }
  232. else
  233. {
  234. CLog::Log(LOGERROR, "CRenderCaptureGL::PboToBuffer: glMapBufferARB failed");
  235. SetState(CAPTURESTATE_FAILED);
  236. }
  237. glUnmapBufferARB(GL_PIXEL_PACK_BUFFER_ARB);
  238. glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, 0);
  239. #endif
  240. }
  241. #elif HAS_DX /*HAS_GL*/
  242. CRenderCaptureDX::CRenderCaptureDX()
  243. {
  244. m_renderSurface = NULL;
  245. m_copySurface = NULL;
  246. m_query = NULL;
  247. m_surfaceWidth = 0;
  248. m_surfaceHeight = 0;
  249. g_Windowing.Register(this);
  250. }
  251. CRenderCaptureDX::~CRenderCaptureDX()
  252. {
  253. CleanupDX();
  254. delete[] m_pixels;
  255. g_Windowing.Unregister(this);
  256. }
  257. int CRenderCaptureDX::GetCaptureFormat()
  258. {
  259. return CAPTUREFORMAT_BGRA;
  260. }
  261. void CRenderCaptureDX::BeginRender()
  262. {
  263. LPDIRECT3DDEVICE9 pD3DDevice = g_Windowing.Get3DDevice();
  264. if (!m_asyncChecked)
  265. {
  266. m_asyncSupported = pD3DDevice->CreateQuery(D3DQUERYTYPE_OCCLUSION, NULL) == D3D_OK;
  267. if (m_flags & CAPTUREFLAG_CONTINUOUS)
  268. {
  269. if (!m_asyncSupported)
  270. CLog::Log(LOGWARNING, "CRenderCaptureDX: D3DQUERYTYPE_OCCLUSION not supported, performance might suffer");
  271. if (!UseOcclusionQuery())
  272. CLog::Log(LOGWARNING, "CRenderCaptureDX: D3DQUERYTYPE_OCCLUSION disabled, performance might suffer");
  273. }
  274. m_asyncChecked = true;
  275. }
  276. HRESULT result;
  277. if (m_surfaceWidth != m_width || m_surfaceHeight != m_height)
  278. {
  279. if (m_renderSurface)
  280. {
  281. while(m_renderSurface->Release() > 0);
  282. m_renderSurface = NULL;
  283. }
  284. if (m_copySurface)
  285. {
  286. while (m_copySurface->Release() > 0);
  287. m_copySurface = NULL;
  288. }
  289. result = pD3DDevice->CreateRenderTarget(m_width, m_height, D3DFMT_A8R8G8B8, D3DMULTISAMPLE_NONE, 0, TRUE, &m_renderSurface, NULL);
  290. if (result != D3D_OK)
  291. {
  292. CLog::Log(LOGERROR, "CRenderCaptureDX::BeginRender: CreateRenderTarget failed %s",
  293. g_Windowing.GetErrorDescription(result).c_str());
  294. SetState(CAPTURESTATE_FAILED);
  295. return;
  296. }
  297. result = pD3DDevice->CreateOffscreenPlainSurface(m_width, m_height, D3DFMT_A8R8G8B8, D3DPOOL_SYSTEMMEM, &m_copySurface, NULL);
  298. if (result != D3D_OK)
  299. {
  300. CLog::Log(LOGERROR, "CRenderCaptureDX::BeginRender: CreateOffscreenPlainSurface failed %s",
  301. g_Windowing.GetErrorDescription(result).c_str());
  302. SetState(CAPTURESTATE_FAILED);
  303. return;
  304. }
  305. m_surfaceWidth = m_width;
  306. m_surfaceHeight = m_height;
  307. }
  308. if (m_bufferSize != m_width * m_height * 4)
  309. {
  310. m_bufferSize = m_width * m_height * 4;
  311. delete[] m_pixels;
  312. m_pixels = new uint8_t[m_bufferSize];
  313. }
  314. result = pD3DDevice->SetRenderTarget(0, m_renderSurface);
  315. if (result != D3D_OK)
  316. {
  317. CLog::Log(LOGERROR, "CRenderCaptureDX::BeginRender: SetRenderTarget failed %s",
  318. g_Windowing.GetErrorDescription(result).c_str());
  319. SetState(CAPTURESTATE_FAILED);
  320. return;
  321. }
  322. if (m_asyncSupported && UseOcclusionQuery())
  323. {
  324. //generate an occlusion query if we don't have one
  325. if (!m_query)
  326. {
  327. result = pD3DDevice->CreateQuery(D3DQUERYTYPE_OCCLUSION, &m_query);
  328. if (result != D3D_OK)
  329. {
  330. CLog::Log(LOGERROR, "CRenderCaptureDX::BeginRender: CreateQuery failed %s",
  331. g_Windowing.GetErrorDescription(result).c_str());
  332. m_asyncSupported = false;
  333. if (m_query)
  334. {
  335. while (m_query->Release() > 0);
  336. m_query = NULL;
  337. }
  338. }
  339. }
  340. }
  341. else
  342. {
  343. //don't use an occlusion query, clean up any old one
  344. if (m_query)
  345. {
  346. while (m_query->Release() > 0);
  347. m_query = NULL;
  348. }
  349. }
  350. if (m_query)
  351. m_query->Issue(D3DISSUE_BEGIN);
  352. }
  353. void CRenderCaptureDX::EndRender()
  354. {
  355. LPDIRECT3DDEVICE9 pD3DDevice = g_Windowing.Get3DDevice();
  356. //GetRenderTargetData should be async on most drivers,
  357. //so the render thread doesn't have to wait for the gpu to copy the data to m_copySurface
  358. pD3DDevice->GetRenderTargetData(m_renderSurface, m_copySurface);
  359. if (m_query)
  360. {
  361. m_query->Issue(D3DISSUE_END);
  362. m_query->GetData(NULL, 0, D3DGETDATA_FLUSH); //flush the query request
  363. }
  364. if (m_flags & CAPTUREFLAG_IMMEDIATELY)
  365. SurfaceToBuffer();
  366. else
  367. SetState(CAPTURESTATE_NEEDSREADOUT);
  368. }
  369. void CRenderCaptureDX::ReadOut()
  370. {
  371. if (m_query)
  372. {
  373. //if the result of the occlusion query is available, the data is probably also written into m_copySurface
  374. HRESULT result = m_query->GetData(NULL, 0, D3DGETDATA_FLUSH);
  375. if (result == S_OK)
  376. {
  377. SurfaceToBuffer();
  378. }
  379. else if (result != S_FALSE)
  380. {
  381. CLog::Log(LOGERROR, "CRenderCaptureDX::ReadOut: GetData failed");
  382. SurfaceToBuffer();
  383. }
  384. }
  385. else
  386. {
  387. SurfaceToBuffer();
  388. }
  389. }
  390. void CRenderCaptureDX::SurfaceToBuffer()
  391. {
  392. D3DLOCKED_RECT lockedRect;
  393. if (m_copySurface->LockRect(&lockedRect, NULL, D3DLOCK_READONLY) == D3D_OK)
  394. {
  395. //if pitch is same, do a direct copy, otherwise copy one line at a time
  396. if (lockedRect.Pitch == m_width * 4)
  397. {
  398. fast_memcpy(m_pixels, lockedRect.pBits, m_width * m_height * 4);
  399. }
  400. else
  401. {
  402. for (unsigned int y = 0; y < m_height; y++)
  403. fast_memcpy(m_pixels + y * m_width * 4, (uint8_t*)lockedRect.pBits + y * lockedRect.Pitch, m_width * 4);
  404. }
  405. m_copySurface->UnlockRect();
  406. SetState(CAPTURESTATE_DONE);
  407. }
  408. else
  409. {
  410. CLog::Log(LOGERROR, "CRenderCaptureDX::SurfaceToBuffer: locking m_copySurface failed");
  411. SetState(CAPTURESTATE_FAILED);
  412. }
  413. }
  414. void CRenderCaptureDX::OnLostDevice()
  415. {
  416. CleanupDX();
  417. SetState(CAPTURESTATE_FAILED);
  418. }
  419. void CRenderCaptureDX::OnDestroyDevice()
  420. {
  421. CleanupDX();
  422. SetState(CAPTURESTATE_FAILED);
  423. }
  424. void CRenderCaptureDX::CleanupDX()
  425. {
  426. if (m_renderSurface)
  427. {
  428. while (m_renderSurface->Release() > 0);
  429. m_renderSurface = NULL;
  430. }
  431. if (m_copySurface)
  432. {
  433. while (m_copySurface->Release() > 0);
  434. m_copySurface = NULL;
  435. }
  436. if (m_query)
  437. {
  438. while (m_query->Release() > 0);
  439. m_query = NULL;
  440. }
  441. m_surfaceWidth = 0;
  442. m_surfaceHeight = 0;
  443. }
  444. #endif /*HAS_DX*/