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