Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qgraphicsframecapturerenderdoc.cpp
Go to the documentation of this file.
1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5#include <QtCore/qcoreapplication.h>
6#include <QtCore/qfile.h>
7#include <QtCore/qlibrary.h>
8#include <QtCore/qmutex.h>
9#include "QtGui/rhi/qrhi.h"
10#include "QtGui/rhi/qrhi_platform.h"
11#include "QtGui/qopenglcontext.h"
12
14
15Q_LOGGING_CATEGORY(lcGraphicsFrameCapture, "qt.gui.graphicsframecapture")
16
17RENDERDOC_API_1_6_0 *QGraphicsFrameCaptureRenderDoc::s_rdocApi = nullptr;
18#if QT_CONFIG(thread)
19QBasicMutex QGraphicsFrameCaptureRenderDoc::s_frameCaptureMutex;
20#endif
21
22#if QT_CONFIG(opengl)
23static void *glNativeContext(QOpenGLContext *context) {
24 void *nctx = nullptr;
25 if (context != nullptr && context->isValid()) {
26#ifdef Q_OS_WIN
27 nctx = context->nativeInterface<QNativeInterface::QWGLContext>()->nativeContext();
28#endif
29
30#ifdef Q_OS_LINUX
31#if QT_CONFIG(egl)
32 QNativeInterface::QEGLContext *eglItf = context->nativeInterface<QNativeInterface::QEGLContext>();
33 if (eglItf)
34 nctx = eglItf->nativeContext();
35#endif
36
37#if QT_CONFIG(xcb_glx_plugin)
38 QNativeInterface::QGLXContext *glxItf = context->nativeInterface<QNativeInterface::QGLXContext>();
39 if (glxItf)
40 nctx = glxItf->nativeContext();
41#endif
42#endif
43
44#if QT_CONFIG(metal)
45 nctx = context->nativeInterface<QNativeInterface::QCocoaGLContext>()->nativeContext();
46#endif
47 }
48 return nctx;
49}
50#endif // QT_CONFIG(opengl)
51
52/*!
53 \class QGraphicsFrameCaptureRenderDoc
54 \internal
55 \brief The QGraphicsFrameCaptureRenderDoc class provides a way to capture a record of draw calls
56 for different graphics APIs.
57 \since 6.6
58 \inmodule QtGui
59
60 For applications that render using graphics APIs like Vulkan or OpenGL, it would be
61 convenient to have a way to check the draw calls done by the application. Specially
62 for applications that make a large amount of draw calls and the output is different
63 from what is expected.
64
65 This class acts as a wrapper over \l {https://renderdoc.org/}{RenderDoc} that allows
66 applications to capture a rendered frame either programmatically, by clicking a key
67 on the keyboard or both. The captured frame could be viewed later using RenderDoc GUI.
68
69 Read the \l {https://renderdoc.org/docs/index.html} {RenderDoc Documentation}
70 for more information.
71
72 \section1 API device handle
73
74 The functions that capture a frame like QGraphicsFrameCaptureRenderDoc::startCaptureFrame takes a device
75 pointer as argument. This pointer is unique for each graphics API and is associated
76 with the window that will be captured. This pointer has a default value of \c nullptr.
77 If no value is passed to the function the underlying API will try to find the device to
78 use, but it is not guaranteed specially in a multi-window applications.
79
80 For OpenGL, the pointer should be the OpenGL context on the platform OpenGL is being
81 used. For example, on Windows it should be \c HGLRC.
82
83 For Vulkan, the pointer should point to the dispatch table within the \c VkInstance.
84*/
85
86
87
88/*!
89 Creates a new object of this class. The constructor will load RenderDoc library
90 from the default path.
91
92 Only one instance of RenderDoc library is loaded at runtime which means creating
93 several instances of this class will not affect the RenderDoc initialization.
94*/
95
97 : m_nativeHandlesSet(false)
98{
99 if (!s_rdocApi)
100 init();
101}
102
104{
105 if (!rhi)
106 return;
107
108 QRhi::Implementation backend = rhi->backend();
109 const QRhiNativeHandles *nh = rhi->nativeHandles();
110
111 switch (backend) {
112 case QRhi::Implementation::D3D11: {
113#ifdef Q_OS_WIN
114 const QRhiD3D11NativeHandles *d3d11nh = static_cast<const QRhiD3D11NativeHandles *>(nh);
115 m_nativeHandle = d3d11nh->dev;
116 break;
117#endif
118 qCWarning(lcGraphicsFrameCapture) << "Could not find valid handles for D3D11. Check platform support";
119 break;
120 }
121 case QRhi::Implementation::D3D12: {
122#ifdef Q_OS_WIN
123 const QRhiD3D12NativeHandles *d3d12nh = static_cast<const QRhiD3D12NativeHandles *>(nh);
124 m_nativeHandle = d3d12nh->dev;
125 break;
126#endif
127 qCWarning(lcGraphicsFrameCapture) << "Could not find valid handles for D3D12. Check platform support";
128 break;
129 }
130 case QRhi::Implementation::Vulkan: {
131#if QT_CONFIG(vulkan)
132 const QRhiVulkanNativeHandles *vknh = static_cast<const QRhiVulkanNativeHandles *>(nh);
133 m_nativeHandle = RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(vknh->inst->vkInstance());
134 break;
135#endif
136 qCWarning(lcGraphicsFrameCapture) << "Could not find valid handles for Vulkan. Check platform support";
137 break;
138 }
139 case QRhi::Implementation::OpenGLES2: {
140#ifndef QT_NO_OPENGL
141 const QRhiGles2NativeHandles *glnh = static_cast<const QRhiGles2NativeHandles *>(nh);
142 m_nativeHandle = glNativeContext(glnh->context);
143 if (m_nativeHandle)
144 break;
145#endif
146 qCWarning(lcGraphicsFrameCapture) << "Could not find valid handles for OpenGL. Check platform support";
147 break;
148 }
149 case QRhi::Implementation::Metal:
150 case QRhi::Implementation::Null:
151 qCWarning(lcGraphicsFrameCapture) << "Invalid handles were provided."
152 " Metal and Null backends are not supported with RenderDoc";
153 break;
154 }
155
156 if (m_nativeHandle)
157 m_nativeHandlesSet = true;
158}
159
160/*!
161 Starts a frame capture using the set native handles provided through QGraphicsFrameCaptureRenderDoc::setRhi
162 \a device. This function must be called before QGraphicsFrameCaptureRenderDoc::endCaptureFrame.
163 \sa {API device handle}
164*/
166{
167
168 if (!initialized()) {
169 qCWarning(lcGraphicsFrameCapture) << "RenderDoc was not initialized."
170 " Starting capturing can not be done.";
171 return;
172 }
173
174#if QT_CONFIG(thread)
175 // There is a single instance of RenderDoc library and it needs mutex for multithreading access.
176 QMutexLocker locker(&s_frameCaptureMutex);
177#endif
178 if (s_rdocApi->IsFrameCapturing()) {
179 qCWarning(lcGraphicsFrameCapture) << "A frame capture is already in progress, "
180 "will not initiate another one until"
181 " QGraphicsFrameCapture::endCaptureFrame is called.";
182 return;
183 }
184
185 qCInfo(lcGraphicsFrameCapture) << "A frame capture is going to start.";
186 updateCapturePathAndTemplate();
187 s_rdocApi->StartFrameCapture(m_nativeHandle, nullptr);
188}
189
190/*!
191 Ends a frame capture started by a call to QGraphicsFrameCaptureRenderDoc::startCaptureFrame
192 using the set native handles provided through QGraphicsFrameCaptureRenderDoc::setRhi.
193 This function must be called after QGraphicsFrameCaptureRenderDoc::startCaptureFrame.
194 Otherwise, a warning message will be printend and nothing will happen.
195 \sa {API device handle}
196*/
198{
199 if (!initialized()) {
200 qCWarning(lcGraphicsFrameCapture) << "RenderDoc was not initialized."
201 " End capturing can not be done.";
202 return;
203 }
204
205#if QT_CONFIG(thread)
206 // There is a single instance of RenderDoc library and it needs mutex for multithreading access.
207 QMutexLocker locker(&s_frameCaptureMutex);
208#endif
209 if (!s_rdocApi->IsFrameCapturing()) {
210 qCWarning(lcGraphicsFrameCapture) << "A call to QGraphicsFrameCapture::endCaptureFrame can not be done"
211 " without a call to QGraphicsFrameCapture::startCaptureFrame";
212 return;
213 }
214
215 qCInfo(lcGraphicsFrameCapture) << "A frame capture is going to end.";
216 uint32_t result = s_rdocApi->EndFrameCapture(m_nativeHandle, nullptr);
217
218 if (result) {
219 uint32_t count = s_rdocApi->GetNumCaptures();
220 uint32_t pathLength = 0;
221 s_rdocApi->GetCapture(count - 1, nullptr, &pathLength, nullptr);
222 if (pathLength > 0) {
223 QVarLengthArray<char> name(pathLength, 0);
224 s_rdocApi->GetCapture(count - 1, name.data(), &pathLength, nullptr);
225 m_capturedFilesNames.append(QString::fromUtf8(name.data(), -1));
226 }
227 }
228}
229
230void QGraphicsFrameCaptureRenderDoc::updateCapturePathAndTemplate()
231{
232 if (!initialized()) {
233 qCWarning(lcGraphicsFrameCapture) << "RenderDoc was not initialized."
234 " Updating save location can not be done.";
235 return;
236 }
237
238
239 QString rdocFilePathTemplate = m_capturePath + QStringLiteral("/") + m_capturePrefix;
240 s_rdocApi->SetCaptureFilePathTemplate(rdocFilePathTemplate.toUtf8().constData());
241}
242
243/*!
244 Returns true if the API is loaded and can capture frames or not.
245*/
247{
248 return s_rdocApi && m_nativeHandlesSet;
249}
250
252{
253 if (!initialized()) {
254 qCWarning(lcGraphicsFrameCapture) << "RenderDoc was not initialized."
255 " Can not query if capturing is in progress or not.";
256 return false;
257 }
258
259 return s_rdocApi->IsFrameCapturing();
260}
261
263{
264 if (!initialized()) {
265 qCWarning(lcGraphicsFrameCapture) << "RenderDoc was not initialized."
266 " Can not open RenderDoc UI tool.";
267 return;
268 }
269
270#if QT_CONFIG(thread)
271 // There is a single instance of RenderDoc library and it needs mutex for multithreading access.
272 QMutexLocker locker(&s_frameCaptureMutex);
273#endif
274 if (s_rdocApi->IsTargetControlConnected())
275 s_rdocApi->ShowReplayUI();
276 else
277 s_rdocApi->LaunchReplayUI(1, nullptr);
278}
279
281{
282#if QT_CONFIG(thread)
283 // There is a single instance of RenderDoc library and it needs mutex for multithreading access.
284 QMutexLocker locker(&s_frameCaptureMutex);
285#endif
286
287 QLibrary renderDocLib(QStringLiteral("renderdoc"));
288 pRENDERDOC_GetAPI RENDERDOC_GetAPI = (pRENDERDOC_GetAPI) renderDocLib.resolve("RENDERDOC_GetAPI");
289 if (!renderDocLib.isLoaded() || (RENDERDOC_GetAPI == nullptr)) {
290 qCWarning(lcGraphicsFrameCapture) << renderDocLib.errorString().toLatin1();
291 return;
292 }
293
294 int ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_6_0, static_cast<void **>(static_cast<void *>(&s_rdocApi)));
295
296 if (ret == 0) {
297 qCWarning(lcGraphicsFrameCapture) << "The requested RenderDoc API is invalid or not supported";
298 return;
299 }
300
301 s_rdocApi->MaskOverlayBits(RENDERDOC_OverlayBits::eRENDERDOC_Overlay_None,
302 RENDERDOC_OverlayBits::eRENDERDOC_Overlay_None);
303 s_rdocApi->SetCaptureKeys(nullptr, 0);
304 s_rdocApi->SetFocusToggleKeys(nullptr, 0);
305
306 QString rdocFilePathTemplate = m_capturePath + QStringLiteral("/") + m_capturePrefix;
307 s_rdocApi->SetCaptureFilePathTemplate(rdocFilePathTemplate.toUtf8().constData());
308}
309
310QT_END_NAMESPACE
The QGraphicsFrameCaptureRenderDoc class provides a way to capture a record of draw calls for differe...
bool initialized() const override
Returns true if the API is loaded and can capture frames or not.
QGraphicsFrameCaptureRenderDoc()
Creates a new object of this class.
void startCaptureFrame() override
Starts a frame capture using the set native handles provided through QGraphicsFrameCaptureRenderDoc::...
void endCaptureFrame() override
Ends a frame capture started by a call to QGraphicsFrameCaptureRenderDoc::startCaptureFrame using the...
Combined button and popup list for selecting options.