Line data Source code
1 : /*
2 : * SPDX-License-Identifier: GPL-3.0-only
3 : * MuseScore-CLA-applies
4 : *
5 : * MuseScore Studio
6 : * Music Composition & Notation
7 : *
8 : * Copyright (C) 2024 MuseScore Limited and others
9 : *
10 : * This program is free software: you can redistribute it and/or modify
11 : * it under the terms of the GNU General Public License version 3 as
12 : * published by the Free Software Foundation.
13 : *
14 : * This program is distributed in the hope that it will be useful,
15 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 : * GNU General Public License for more details.
18 : *
19 : * You should have received a copy of the GNU General Public License
20 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 : */
22 : #include "graphicsapiprovider.h"
23 :
24 : #include <QFile>
25 : #include <QQuickWindow>
26 : #include <QSGRendererInterface>
27 :
28 : #include "global/internal/globalconfiguration.h"
29 : #include "global/logger.h"
30 :
31 : #include "log.h"
32 :
33 : using namespace muse::ui;
34 : using namespace muse::logger;
35 :
36 : #ifdef Q_OS_WIN
37 : static const std::vector<GraphicsApi > ALLOWED_APIS = {
38 : GraphicsApi::Direct3D11,
39 : GraphicsApi::OpenGL,
40 : GraphicsApi::Software
41 : };
42 : #endif
43 :
44 : #ifdef Q_OS_MACOS
45 : static const std::vector<GraphicsApi > ALLOWED_APIS = {
46 : GraphicsApi::Metal,
47 : GraphicsApi::OpenGL,
48 : GraphicsApi::Software
49 : };
50 : #endif
51 :
52 : #ifdef Q_OS_LINUX
53 : static const std::vector<GraphicsApi > ALLOWED_APIS = {
54 : GraphicsApi::OpenGL,
55 : GraphicsApi::Software
56 : };
57 : #endif
58 :
59 : #ifdef Q_OS_FREEBSD
60 : static const std::vector<GraphicsApi > ALLOWED_APIS = {
61 : GraphicsApi::OpenGL,
62 : GraphicsApi::Software
63 : };
64 : #endif
65 :
66 : #ifdef Q_OS_WASM
67 : static const std::vector<GraphicsApi > ALLOWED_APIS = {
68 : GraphicsApi::OpenGL,
69 : GraphicsApi::Software
70 : };
71 : #endif
72 :
73 : static const std::vector<std::string> BAD_MESSAGES = {
74 : "Failed to build graphics pipeline state",
75 : "Failed to create RHI",
76 : "Failed to build texture render target for layer",
77 : "Framebuffer incomplete:"
78 : };
79 :
80 : namespace muse::ui {
81 : class GraphicsProblemsDetectorLogDest : public LogDest
82 : {
83 : public:
84 :
85 0 : GraphicsProblemsDetectorLogDest()
86 0 : : LogDest(LogLayout("")) {}
87 :
88 0 : std::string name() const { return "GraphicsProblemsDetectorLogDest"; }
89 0 : void write(const LogMsg& logMsg)
90 : {
91 0 : if (isProblemDetected) {
92 0 : return;
93 : }
94 :
95 0 : for (const std::string& m : BAD_MESSAGES) {
96 0 : if (logMsg.message.find(m) != std::string::npos) {
97 0 : isProblemDetected = true;
98 0 : std::cout << "found message: " << m << std::endl;
99 0 : break;
100 : }
101 : }
102 : }
103 :
104 : bool isProblemDetected = false;
105 : };
106 : }
107 :
108 0 : GraphicsApiProvider::GraphicsApiProvider(const Version& appVersion)
109 0 : : m_appVersion(appVersion)
110 : {
111 0 : Logger* logger = Logger::instance();
112 :
113 0 : m_logDest = new GraphicsProblemsDetectorLogDest();
114 0 : logger->addDest(m_logDest);
115 :
116 0 : QObject::connect(&m_timer, &QTimer::timeout, [this]() {
117 : // fail case
118 0 : if (m_logDest->isProblemDetected) {
119 0 : if (m_onResult) {
120 0 : m_onResult(false);
121 : }
122 0 : m_timer.stop();
123 : } else
124 : // success case
125 0 : if (testObjectIsAlive && testObjectHasPainted) {
126 0 : if (m_onResult) {
127 0 : m_onResult(true);
128 : }
129 0 : m_timer.stop();
130 : }
131 0 : });
132 0 : }
133 :
134 0 : GraphicsApiProvider::~GraphicsApiProvider()
135 : {
136 0 : m_timer.stop();
137 0 : Logger* logger = Logger::instance();
138 0 : logger->removeDest(m_logDest);
139 0 : delete m_logDest;
140 0 : }
141 :
142 0 : void GraphicsApiProvider::setGraphicsApi(GraphicsApi api)
143 : {
144 0 : QQuickWindow::setGraphicsApi(static_cast<QSGRendererInterface::GraphicsApi>(api));
145 0 : }
146 :
147 0 : GraphicsApi GraphicsApiProvider::graphicsApi()
148 : {
149 : //! NOTE QQuickWindow::graphicsApi returns a not valid value,
150 : //! it does not take into account the current backend
151 :
152 0 : QString backend = QQuickWindow::sceneGraphBackend();
153 0 : if (backend == "software") {
154 0 : return GraphicsApi::Software;
155 0 : } else if (backend == "openvg") {
156 0 : return GraphicsApi::OpenVG;
157 : }
158 :
159 0 : return static_cast<GraphicsApi>(QQuickWindow::graphicsApi());
160 0 : }
161 :
162 0 : QString GraphicsApiProvider::graphicsApiName()
163 : {
164 0 : return apiName(graphicsApi());
165 : }
166 :
167 0 : GraphicsApi GraphicsApiProvider::apiByName(const QString& name)
168 : {
169 0 : for (const GraphicsApi& a : ALLOWED_APIS) {
170 0 : if (apiName(a) == name) {
171 0 : return a;
172 : }
173 : }
174 0 : return GraphicsApi::Default;
175 : }
176 :
177 0 : QString GraphicsApiProvider::apiName(GraphicsApi api)
178 : {
179 0 : switch (api) {
180 0 : case GraphicsApi::Default: return "default";
181 0 : case GraphicsApi::Software: return "software";
182 0 : case GraphicsApi::OpenVG: return "openvg";
183 0 : case GraphicsApi::OpenGL: return "opengl";
184 0 : case GraphicsApi::Direct3D11: return "d3d11";
185 0 : case GraphicsApi::Vulkan: return "vulkan";
186 0 : case GraphicsApi::Metal: return "metal";
187 0 : case GraphicsApi::Null: return "null";
188 : }
189 :
190 0 : return QString();
191 : }
192 :
193 0 : QString GraphicsApiProvider::dataFilePath() const
194 : {
195 0 : GlobalConfiguration conf;
196 0 : return conf.userAppDataPath().toQString() + "/required_graphicsapi.dat";
197 0 : }
198 :
199 0 : GraphicsApiProvider::Data GraphicsApiProvider::readData() const
200 : {
201 0 : QFile file(dataFilePath());
202 0 : if (!file.exists()) {
203 0 : return Data();
204 : }
205 :
206 0 : if (!file.open(QIODevice::ReadOnly)) {
207 0 : return Data();
208 : }
209 :
210 0 : QString str = QString::fromUtf8(file.readAll());
211 :
212 : // format: appversion|api|status
213 0 : QStringList vals = str.split('|');
214 :
215 : //! NOTE Check format
216 0 : if (vals.size() < 3) {
217 0 : LOGE() << "Graphics GraphicsApi data has bad format: " << str << ", file: " << file.fileName();
218 0 : return Data();
219 : }
220 :
221 0 : Data d;
222 0 : d.appVersion = vals.at(0).trimmed();
223 0 : d.apiName = vals.at(1).trimmed();
224 0 : d.status = static_cast<Status>(vals.at(2).trimmed().toInt());
225 :
226 0 : return d;
227 0 : }
228 :
229 0 : void GraphicsApiProvider::writeData(const Data& d)
230 : {
231 0 : QStringList vals = { d.appVersion, d.apiName, QString::number(int(d.status)) };
232 0 : QString str = vals.join('|');
233 :
234 0 : QFile file(dataFilePath());
235 0 : if (!file.open(QIODevice::WriteOnly)) {
236 0 : LOGE() << "Failed to write Graphics GraphicsApi data to file: " << file.fileName();
237 0 : return;
238 : }
239 0 : file.write(str.toUtf8());
240 0 : }
241 :
242 0 : GraphicsApi GraphicsApiProvider::requiredGraphicsApi()
243 : {
244 0 : Data data = readData();
245 0 : if (!data.isValid()) {
246 0 : return GraphicsApi::Default;
247 : }
248 :
249 : //! NOTE For the new version, we will repeat the check starting with the default API
250 0 : if (m_appVersion.toString() != data.appVersion) {
251 0 : LOGI() << "Graphics GraphicsApi data from another version, the check will be repeated starting from the default API,"
252 0 : << " data app version: " << data.appVersion
253 0 : << " current app version: " << m_appVersion.toString();
254 0 : return GraphicsApi::Default;
255 : }
256 :
257 0 : GraphicsApi api = apiByName(data.apiName);
258 0 : IF_ASSERT_FAILED(api != GraphicsApi::Default) {
259 0 : return switchToNextGraphicsApi(api);
260 : }
261 :
262 : //! NOTE If there is a required API, but it status is checking,
263 : //! then something bad happened with its use (for example, a crash),
264 : //! so we switch to the next one.
265 0 : if (data.status == Status::Checking) {
266 0 : return switchToNextGraphicsApi(api);
267 : }
268 :
269 0 : return api;
270 0 : }
271 :
272 0 : void GraphicsApiProvider::setGraphicsApiStatus(GraphicsApi api, Status status)
273 : {
274 0 : if (api == GraphicsApi::Default) {
275 0 : return;
276 : }
277 :
278 0 : Data d;
279 0 : d.appVersion = m_appVersion.toString();
280 0 : d.apiName = apiName(api);
281 0 : d.status = status;
282 0 : writeData(d);
283 0 : }
284 :
285 0 : GraphicsApi GraphicsApiProvider::nextGraphicsApi(GraphicsApi api) const
286 : {
287 : //! NOTE If this is the default value, then it means the current one, since we shouldn't have changed it yet.
288 0 : if (api == GraphicsApi::Default) {
289 0 : api = graphicsApi();
290 : }
291 :
292 0 : auto it = std::find(ALLOWED_APIS.begin(), ALLOWED_APIS.end(), api);
293 0 : IF_ASSERT_FAILED(it != ALLOWED_APIS.end()) {
294 : //! NOTE Something strange happened that shouldn't happen.
295 : //! Let's switch to software to avoid an infinite loop in which there is an error
296 0 : return GraphicsApi::Software;
297 : }
298 :
299 0 : ++it;
300 :
301 : //! NOTE This was the last api
302 0 : if (it == ALLOWED_APIS.end()) {
303 0 : return GraphicsApi::Software;
304 : }
305 :
306 0 : return *it;
307 : }
308 :
309 0 : GraphicsApi GraphicsApiProvider::switchToNextGraphicsApi(GraphicsApi current)
310 : {
311 0 : GraphicsApi api = nextGraphicsApi(current);
312 :
313 0 : Data d;
314 0 : d.appVersion = m_appVersion.toString();
315 0 : d.apiName = apiName(api);
316 0 : d.status = Status::NeedCheck;
317 0 : writeData(d);
318 :
319 0 : return api;
320 0 : }
321 :
322 0 : void GraphicsApiProvider::listen(const OnResult& f)
323 : {
324 0 : m_onResult = f;
325 0 : m_timer.start(10);
326 0 : }
327 :
328 0 : void GraphicsApiProvider::destroy()
329 : {
330 0 : GraphicsApiProvider* self = this;
331 0 : QTimer::singleShot(1, [self]() {
332 0 : delete self;
333 0 : });
334 0 : }
|