1 /*
2 *
3 * OpenGL hardware capability viewer and database
4 *
5 * Main window class
6 *
7 * Copyright (C) 2011-2016 by Sascha Willems (www.saschawillems.de)
8 *
9 * This code is free software, you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License version 3 as published by the Free Software Foundation.
12 *
13 * Please review the following information to ensure the GNU Lesser
14 * General Public License version 3 requirements will be met:
15 * http://opensource.org/licenses/lgpl-3.0.html
16 *
17 * The code is distributed WITHOUT ANY WARRANTY; without even the
18 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
19 * PURPOSE. See the GNU LGPL 3.0 for more details.
20 *
21 */
22
23 #include "glCapsViewer.h"
24 #include "glCapsViewerHttp.h"
25 #include "settingsDialog.h"
26 #include "settings.h"
27 #include "submitDialog.h"
28 #include "internalFormatTarget.h"
29 #include <GL/glew.h>
30 #ifdef _WIN32
31 #include <GL/wglew.h>
32 #endif
33 #include <GLFW/glfw3.h>
34 #include <QDesktopServices>
35 #include <QtWidgets/QTextBrowser>
36 #include <QMessageBox>
37 #include <QStyleFactory>
38 #include <QDebug>
39 #include <QFileDialog>
40 #include <QNetworkAccessManager>
41 #include <QUrl>
42 #include <QNetworkRequest>
43 #include <QNetworkReply>
44 #include <QListWidgetItem>
45 #include <QTreeWidget>
46 #include <QComboBox>
47 #include <QInputDialog>
48 #include <sstream>
49 #include <QXmlStreamReader>
50 #include <QFormLayout>
51 #include <QLabel>
52 #include <QLineEdit>
53 #include <QDialogButtonBox>
54 #ifdef __DragonFly__
55 #include <GL/glxew.h>
56 #endif
57
glCapsViewer(QWidget * parent)58 glCapsViewer::glCapsViewer(QWidget *parent)
59 : QMainWindow(parent)
60 {
61 QApplication::setStyle(QStyleFactory::create("Fusion"));
62 qDebug() << "setup ui";
63 ui.setupUi(this);
64
65 setWindowTitle(QString::fromStdString(core.appVersion));
66
67 connect(ui.actionRefresh, SIGNAL(triggered()), this, SLOT(slotRefreshReport()));
68 connect(ui.actionExit, SIGNAL(triggered()), this, SLOT(slotClose()));
69 connect(ui.actionSave_xml, SIGNAL(triggered()), this, SLOT(slotExportXml()));
70 connect(ui.actionDatabase, SIGNAL(triggered()), this, SLOT(slotBrowseDatabase()));
71 connect(ui.actionAbout, SIGNAL(triggered()), this, SLOT(slotAbout()));
72 connect(ui.actionSettings, SIGNAL(triggered()), this, SLOT(slotSettings()));
73 connect(ui.actionUpload, SIGNAL(triggered()), this, SLOT(slotUpload()));
74 connect(ui.actionDevice, SIGNAL(triggered()), this, SLOT(slotShowDeviceOnline()));
75 connect(ui.pushButtonRefreshDataBase, SIGNAL(released()), this, SLOT(slotRefreshDatabase()));
76 connect(ui.listWidgetDatabaseDevices, SIGNAL(itemSelectionChanged()), this, SLOT(slotDatabaseDevicesItemChanged()));
77 connect(ui.tabWidget, SIGNAL(currentChanged(int)), this, SLOT(slotTabChanged(int)));
78 connect(ui.comboBoxDeviceVersions, SIGNAL(currentIndexChanged(int)), this, SLOT(slotDeviceVersionChanged(int)));
79
80 ui.tableWidgetDatabaseDeviceReport->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft);
81 ui.tableWidgetDatabaseDeviceReport->horizontalHeader()->setSectionResizeMode(QHeaderView::Fixed);
82
83 // Extension tree model and filter proxy
84 ui.treeViewExtensions->setModel(&extensionFilterProxy);
85 extensionFilterProxy.setSourceModel(&extensionTreeModel);
86 connect(ui.lineEditeExtensions, SIGNAL(textChanged(QString)), this, SLOT(slotFilterExtensions(QString)));
87
88 // Implementation tree model and filter proxy
89 ui.treeViewImplementation->setModel(&implementationFilterProxy);
90 implementationFilterProxy.setSourceModel(&implementationTreeModel);
91 connect(ui.lineEditImplementation, SIGNAL(textChanged(QString)), this, SLOT(slotFilterImplementation(QString)));
92
93 // Texture formats tree model and filter proxy
94 ui.listViewCompressedFormats->setModel(&texFormatFilterProxy);
95 texFormatFilterProxy.setSourceModel(&texFormatListModel);
96 connect(ui.lineEditTexFormats, SIGNAL(textChanged(QString)), this, SLOT(slotFilterTextureFormats(QString)));
97
98 // SPIRV extensions
99 ui.treeViewSPIRVExtensions->setModel(&SPIRVextensionFilterProxy);
100 SPIRVextensionFilterProxy.setSourceModel(&SPIRVextensionTreeModel);
101 connect(ui.lineEditSPIRVExtensions, SIGNAL(textChanged(QString)), this, SLOT(slotFilterSPIRVExtensions(QString)));
102
103 appSettings.restore();
104
105 if (!core.loadEnumList())
106 {
107 QMessageBox::warning(this, tr("Error"), tr("Could not load enum list!"));
108 // TODO : Instead of exiting, download from server and try to load again
109 exit(EXIT_FAILURE);
110 }
111
112 #ifdef DEVDATABASE
113 stringstream newTitle;
114 newTitle << this->windowTitle().toStdString() << " - ! Connected to development database !";
115 this->setWindowTitle(QString::fromStdString(newTitle.str()));
116 #endif
117 }
118
~glCapsViewer()119 glCapsViewer::~glCapsViewer()
120 {
121
122 }
123
124 /// <summary>
125 /// Updates the report status label
126 /// </summary>
updateReportState()127 void glCapsViewer::updateReportState()
128 {
129 glCapsViewerHttp glhttp;
130
131 ui.labelReportPresent->setText("<font color='#000000'>Connecting to database...</font>");
132 ui.labelReportPresent->setVisible(true);
133 ui.actionDevice->setEnabled(false);
134
135 QApplication::setOverrideCursor(Qt::WaitCursor);
136 if (!glhttp.checkServerConnection()) {
137 ui.labelReportPresent->setText("<font color='#FF0000'>Could not connect to the OpenGL hardware database!\n\nPlease check your internet connection and proxy settings!</font>");
138 ui.labelReportPresent->setVisible(true);
139 QApplication::restoreOverrideCursor();
140 return;
141 }
142
143 if (glhttp.checkReportPresent(core.description)) {
144 ui.actionDevice->setEnabled(true);
145 ui.labelReportPresent->setText("<font color='#00813e'>Device already present in database, all fields up-to-date</font>");
146 // Report present, check if it can be updated
147 int reportId = glhttp.getReportId(core.description);
148 if (canUpdateReport(reportId)) {
149 ui.labelReportPresent->setText("<font color='#0000FF'>Device already present in database, but can be updated with missing values!</font>");
150 }
151 }
152 else {
153 ui.labelReportPresent->setText("<font color='#bc0003'>Device not yet present in database</font>");
154 }
155 ui.labelReportPresent->setVisible(true);
156 QApplication::restoreOverrideCursor();
157 }
158
159 /// <summary>
160 /// Reads information about implementation-dependent support for internal formats
161 /// TODO : Create xml structure instead of hardcoding
162 /// TODO : Data structures for xml export (and database upload)
163 /// TODO : Move to core
164 /// </summary>
165
colorInternalFormatItem(QTreeWidgetItem * item,int column)166 void colorInternalFormatItem(QTreeWidgetItem *item, int column) {
167 if (item->text(column) == "GL_NONE")
168 item->setTextColor(column, QColor::fromRgb(255, 0, 0));
169 if (item->text(column) == "GL_CAVEAT_SUPPORT")
170 item->setTextColor(column, QColor::fromRgb(255, 150, 0));
171 if (item->text(column) == "GL_FULL_SUPPORT")
172 item->setTextColor(column, QColor::fromRgb(0, 128, 0));
173 }
174
displayCapabilities()175 void glCapsViewer::displayCapabilities()
176 {
177 QStandardItem *rootItem;
178 QStandardItem *parentItem;
179
180 // Implementation detail
181 rootItem = implementationTreeModel.invisibleRootItem();
182 QList<QStandardItem *> captionItems;
183 captionItems << new QStandardItem("Key");
184 captionItems << new QStandardItem("Value");
185 rootItem->appendRow(captionItems);
186
187 parentItem = new QStandardItem("Implementation details");
188 rootItem->appendRow(parentItem);
189
190 for (auto& s : core.implementation)
191 {
192 QList<QStandardItem *> rowItems;
193 rowItems << new QStandardItem(QString::fromStdString(s.first));
194 rowItems << new QStandardItem(QString::fromStdString(s.second));
195 parentItem->appendRow(rowItems);
196
197 }
198
199 // Capabilities
200 for (auto& group : core.capgroups)
201 {
202 if (!group.visible)
203 continue;
204 QTableWidgetItem *item = new QTableWidgetItem(QString::fromStdString(group.name));
205 item->setTextColor(QColor::fromRgb(0, 0, 255));
206
207 QList<QStandardItem *> rowItems;
208 rowItems << new QStandardItem(QString::fromStdString(group.name));
209
210 if (group.supported)
211 {
212 rowItems << new QStandardItem(QString::number(group.capabilities.size()));
213 for (auto& cap : group.capabilities)
214 {
215 QList<QStandardItem *> capsItems;
216 capsItems << new QStandardItem(QString::fromStdString(cap.first));
217 capsItems << new QStandardItem(QString::fromStdString(cap.second));
218 if (cap.second == "n/a")
219 {
220 capsItems[0]->setForeground(QColor::fromRgb(100, 100, 100));
221 capsItems[1]->setForeground(Qt::red);
222 }
223 rowItems[0]->appendRow(capsItems);
224 }
225 }
226 else
227 {
228 rowItems << new QStandardItem("not available");
229 rowItems[1]->setForeground(Qt::red);
230 }
231
232 rootItem->appendRow(rowItems);
233 }
234
235 ui.treeViewImplementation->expandAll();
236 ui.treeViewImplementation->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
237 }
238
239
displayExtensions()240 void glCapsViewer::displayExtensions()
241 {
242 QStandardItem *rootItem = extensionTreeModel.invisibleRootItem();
243 QStandardItem *parentItem;
244
245 parentItem = new QStandardItem("OpenGL extensions (" + QString::number(core.extensions.size()) + ")");
246 rootItem->appendRow(parentItem);
247 for (auto& s : core.extensions)
248 {
249 if (s == "") continue;
250 parentItem->appendRow(new QStandardItem(QString::fromStdString(s)));
251 }
252
253 parentItem = new QStandardItem("OS specific extensions (" + QString::number(core.osextensions.size()) + ")");
254 rootItem->appendRow(parentItem);
255 for (auto& s : core.osextensions)
256 {
257 if (s == "") continue;
258 parentItem->appendRow(new QStandardItem(QString::fromStdString(s)));
259 }
260 ui.treeViewExtensions->expandAll();
261 }
262
displayCompressedFormats()263 void glCapsViewer::displayCompressedFormats()
264 {
265 QStandardItem *rootItem = texFormatListModel.invisibleRootItem();
266 for (auto& compressedFormat : core.compressedFormats)
267 {
268 string formatString = core.getEnumName(compressedFormat);
269 rootItem->appendRow(new QStandardItem(QString::fromStdString(core.getEnumName(compressedFormat))));
270 }
271 }
272
displayInternalFormatInfo()273 void glCapsViewer::displayInternalFormatInfo()
274 {
275
276 QTreeWidget *tree = ui.treeWidgetInternalFormats;
277 tree->header()->resizeSection(0, 250);
278 tree->clear();
279
280 // Check if extension is supported
281 if (!core.extensionSupported("GL_ARB_internalformat_query")) {
282 QTreeWidgetItem *infoItem = new QTreeWidgetItem(tree);
283 infoItem->setText(0, "Extension not supported");
284 infoItem->setTextColor(0, QColor::fromRgb(255, 0, 0));
285 return;
286 }
287
288 for (auto& target : core.internalFormatTargets) {
289
290 QTreeWidgetItem *targetItem = new QTreeWidgetItem(tree);
291 targetItem->setText(0, QString::fromStdString(core.getEnumName(target.target)));
292
293 for (auto& textureFormat : target.textureFormats) {
294 QTreeWidgetItem *formatItem = new QTreeWidgetItem(targetItem);
295 formatItem->setText(0, QString::fromStdString(core.getEnumName(textureFormat.textureFormat)));
296 formatItem->addChild(targetItem);
297
298 if (!textureFormat.supported) {
299 formatItem->setText(1, "not supported");
300 formatItem->setTextColor(1, QColor::fromRgb(100, 100, 100));
301 formatItem->setTextColor(1, QColor::fromRgb(100, 100, 100));
302 continue;
303 }
304
305 for (auto& formatInfoValue : textureFormat.formatInfoValues) {
306 if (formatInfoValue.infoType == capsViewer::infoTypeValue) {
307 QTreeWidgetItem *paramItem = new QTreeWidgetItem(formatItem);
308 paramItem->setText(0, QString::fromStdString(formatInfoValue.infoString));
309 string enumString;
310 enumString = core.getEnumName(formatInfoValue.infoValue);
311 // Switch some values
312 if ((formatInfoValue.infoEnum == GL_INTERNALFORMAT_SUPPORTED) || (formatInfoValue.infoEnum == GL_TEXTURE_COMPRESSED)) {
313 enumString = (formatInfoValue.infoValue == 0) ? "GL_FALSE" : "GL_TRUE";
314 }
315 paramItem->setText(1, QString::fromStdString(enumString));
316 paramItem->addChild(formatItem);
317 colorInternalFormatItem(paramItem, 1);
318 }
319 }
320
321 QTreeWidgetItem *supportItem = new QTreeWidgetItem(formatItem);
322 supportItem->setText(0, "Shader support");
323
324 for (auto& formatInfoValue : textureFormat.formatInfoValues) {
325 if (formatInfoValue.infoType == capsViewer::infoTypeSupport) {
326 QTreeWidgetItem *valueItem = new QTreeWidgetItem(supportItem);
327 valueItem->setText(0, QString::fromStdString(formatInfoValue.infoString));
328 string enumString;
329 enumString = core.getEnumName(formatInfoValue.infoValue);
330 valueItem->setText(1, QString::fromStdString(enumString));
331 valueItem->addChild(formatItem);
332 colorInternalFormatItem(valueItem, 1);
333 }
334 }
335 }
336
337 targetItem->sortChildren(0, Qt::AscendingOrder);
338 }
339 }
340
displaySPIRVextensions()341 void glCapsViewer::displaySPIRVextensions()
342 {
343 QStandardItem *rootItem = SPIRVextensionTreeModel.invisibleRootItem();
344 for (auto& ext : core.SPIRVExtensions) {
345 rootItem->appendRow(new QStandardItem(QString::fromStdString(ext)));
346 }
347 ui.treeViewSPIRVExtensions->expandAll();
348 }
349
350 /// <summary>
351 /// Reads implementation details, extensions and capabilities
352 /// and displays the report
353 /// </summary>
generateReport()354 void glCapsViewer::generateReport()
355 {
356 QApplication::setOverrideCursor(Qt::WaitCursor);
357 ui.labelReportPresent->setText("Generating device report...");
358 ui.labelReportPresent->repaint();
359 qApp->processEvents();
360
361 extensionTreeModel.clear();
362 implementationTreeModel.clear();
363 texFormatListModel.clear();
364
365 core.readExtensions();
366 core.readOsExtensions();
367 core.readImplementation();
368 core.readCapabilities();
369 core.readCompressedFormats();
370 if (core.extensionSupported("GL_ARB_internalformat_query")) {
371 core.readInternalFormats();
372 }
373 if (core.extensionSupported("GL_ARB_spirv_extensions")) {
374 core.readSPIRVExtensions();
375 }
376
377 ui.labelDescription->setText(QString::fromStdString(core.description));
378
379 displayCapabilities();
380 displayExtensions();
381 displayCompressedFormats();
382 displayInternalFormatInfo();
383 displaySPIRVextensions();
384
385 updateReportState();
386
387 // Tab captions
388 stringstream tabText;
389 tabText << "Extensions (" << core.extensions.size() + core.osextensions.size() << ")";
390 ui.tabWidgetDevice->setTabText(1, QString::fromStdString(tabText.str()));
391 tabText.str("");
392 tabText << "Compressed formats (" << core.compressedFormats.size() << ")";
393 ui.tabWidgetDevice->setTabText(2, QString::fromStdString(tabText.str()));
394 tabText.str("");
395 tabText << "SPIR-V extensions (" << core.SPIRVExtensions.size() << ")";
396 ui.tabWidgetDevice->setTabText(3, QString::fromStdString(tabText.str()));
397
398 QApplication::restoreOverrideCursor();
399 }
400
contextTypeSelection()401 bool glCapsViewer::contextTypeSelection()
402 {
403 // Get available context types
404 core.availableContextTypes.clear();
405 core.availableContextTypes.push_back("OpenGL default");
406
407 #ifdef _WIN32
408 // Core context
409 if (wglewIsSupported("WGL_ARB_create_context_profile")) {
410 core.availableContextTypes.push_back("OpenGL core context");
411 }
412 // OpenGL ES context
413 // GLES 1.0
414 if (wglewIsSupported("WGL_EXT_create_context_es_profile")) {
415 core.availableContextTypes.push_back("OpenGL ES 1.0 context");
416 }
417 // GLES 2.0
418 if (wglewIsSupported("WGL_EXT_create_context_es2_profile")) {
419 core.availableContextTypes.push_back("OpenGL ES 2.0 context");
420 // GLES 3.0 (superset of ES 2.0)
421 if (glewIsSupported("GL_ARB_ES3_compatibility")) {
422 core.availableContextTypes.push_back("OpenGL ES 3.0 context");
423 }
424 }
425 #endif
426 #ifdef __DragonFly__
427 // Core context
428 if (glxewIsSupported("GLX_ARB_create_context_profile")) {
429 core.availableContextTypes.push_back("OpenGL core context");
430 }
431 // OpenGL ES context
432 // GLES 1.0
433 if (glxewIsSupported("GLX_EXT_create_context_es_profile")) {
434 core.availableContextTypes.push_back("OpenGL ES 1.0 context");
435 }
436 // GLES 2.0
437 if (glxewIsSupported("GLX_EXT_create_context_es2_profile")) {
438 core.availableContextTypes.push_back("OpenGL ES 2.0 context");
439 // GLES 3.0 (superset of ES 2.0)
440 if (glewIsSupported("GL_ARB_ES3_compatibility")) {
441 core.availableContextTypes.push_back("OpenGL ES 3.0 context");
442 }
443 }
444 #endif
445 core.contextType = "default";
446 if (core.availableContextTypes.size() > 1) {
447 QStringList items;
448 for (auto& s : core.availableContextTypes) {
449 items << QString::fromStdString(s);
450 }
451
452 bool ok;
453 QString item = QInputDialog::getItem(NULL, QObject::tr("Select render context to create"), QObject::tr("Context type:"), items, 0, false, &ok);
454 if (ok && !item.isEmpty()) {
455
456 if (item == "OpenGL core context") {
457 glewExperimental = GL_TRUE;
458 GLenum err = glewInit();
459 // Get max. supported OpenGL version for versioned core context
460 GLint glVersionMajor, glVersionMinor;
461 glGetIntegerv(GL_MAJOR_VERSION, &glVersionMajor);
462 glGetIntegerv(GL_MINOR_VERSION, &glVersionMinor);
463 // Create core context
464 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, glVersionMajor);
465 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, glVersionMinor);
466 glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
467 glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
468 core.contextType = "core";
469 }
470
471 if (item == "OpenGL ES 2.0 context") {
472 glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
473 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
474 core.contextType = "es2";
475 }
476
477 if (item == "OpenGL ES 3.0 context") {
478 glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
479 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
480 core.contextType = "es3";
481 }
482
483 return true;
484
485 }
486 else {
487 return false;
488 }
489 }
490 else {
491 return true;
492 }
493 }
494
slotRefreshReport()495 void glCapsViewer::slotRefreshReport()
496 {
497 QApplication::setOverrideCursor(Qt::WaitCursor);
498 core.clear();
499 core.contextType = "regular";
500 glfwMakeContextCurrent(window);
501 if (core.availableContextTypes.size() > 1) {
502 QStringList items;
503 for (auto& s : core.availableContextTypes) {
504 items << QString::fromStdString(s);
505 }
506
507 bool ok;
508 QString item = QInputDialog::getItem(NULL, QObject::tr("Select render context to create"), QObject::tr("Context type:"), items, 0, false, &ok);
509 if (ok && !item.isEmpty()) {
510
511 if (item == "OpenGL core context") {
512 glewExperimental = GL_TRUE;
513 GLenum err = glewInit();
514 // Get max. supported OpenGL version for versioned core context
515 GLint glVersionMajor, glVersionMinor;
516 glGetIntegerv(GL_MAJOR_VERSION, &glVersionMajor);
517 glGetIntegerv(GL_MINOR_VERSION, &glVersionMinor);
518 // Create core context
519 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, glVersionMajor);
520 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, glVersionMinor);
521 glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
522 glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
523 core.contextType = "core";
524 }
525
526 if (item == "OpenGL ES 2.0 context") {
527 glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
528 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
529 core.contextType = "es2";
530 }
531
532 glfwDestroyWindow(window);
533 };
534 }
535
536 glfwWindowHint(GLFW_VISIBLE, GL_FALSE);
537 window = glfwCreateWindow(320, 240, "glCapsViewer", NULL, NULL);
538 glfwMakeContextCurrent(window);
539 glewExperimental = GL_TRUE;
540 GLenum err = glewInit();
541 generateReport();
542
543 QApplication::restoreOverrideCursor();
544 }
545
refreshDeviceList()546 void glCapsViewer::refreshDeviceList()
547 {
548 QApplication::setOverrideCursor(Qt::WaitCursor);
549 glCapsViewerHttp glchttp;
550 vector<string> deviceList;
551 deviceList = glchttp.fetchDevices();
552 ui.listWidgetDatabaseDevices->clear();
553 for (auto& device : deviceList) {
554 QListWidgetItem *deviceItem = new QListWidgetItem(QString::fromStdString(device), ui.listWidgetDatabaseDevices);
555 deviceItem->setSizeHint(QSize(deviceItem->sizeHint().height(), 24));
556 deviceItem->setData(Qt::UserRole, QString::fromStdString(device));
557 // Highlight if same as current device
558 if (device == core.implementation["Renderer"]) {
559 stringstream ss;
560 ss << device << " (Your device)";
561 deviceItem->setText(QString::fromStdString(ss.str()));
562 deviceItem->setTextColor(QColor::fromRgb(50, 180, 50));
563 }
564 }
565 QApplication::restoreOverrideCursor();
566 }
567
canUpdateReport(int reportId)568 bool glCapsViewer::canUpdateReport(int reportId) {
569
570 // Download report and check against xml
571 glCapsViewerHttp glchttp;
572 string reportXml = glchttp.fetchReport(reportId);
573
574 bool capsMissing = false;
575 bool compressedFormatsMissing = false;
576 bool internalFormatsMissing = false;
577
578 // Gather client-side available caps
579 vector<string> capsList;
580 for (auto& capsGroup : core.capgroups) {
581 for (auto& cap : capsGroup.capabilities) {
582 if (cap.second != "n/a") {
583 capsList.push_back(cap.first);
584 }
585 }
586 }
587
588 QXmlStreamReader xmlReader(&reportXml[0]);
589
590 vector<string> capsMissingDatabase;
591 vector<int> compressedFormatsDatabase;
592 while (!xmlReader.atEnd()) {
593 if ((xmlReader.isStartElement()) && (xmlReader.name() == "implementation")) {
594 while (!xmlReader.atEnd()) {
595 xmlReader.readNext();
596 if (xmlReader.name() == "implementation") {
597 break;
598 }
599 QXmlStreamAttributes attrib = xmlReader.attributes();
600 QString nodeName = attrib.value("id").toString();
601 QString nodeValue = xmlReader.readElementText();
602 if (nodeValue == "")
603 {
604 capsMissingDatabase.push_back(nodeName.toStdString());
605 }
606
607 }
608 }
609 if ((xmlReader.isStartElement()) && (xmlReader.name() == "compressedtextureformats")) {
610 while (!xmlReader.atEnd()) {
611 xmlReader.readNext();
612 if (xmlReader.name() == "compressedtextureformats") {
613 break;
614 }
615 compressedFormatsDatabase.push_back(atoi(xmlReader.readElementText().toStdString().c_str()));
616 }
617 }
618 xmlReader.readNext();
619 }
620
621 // Check for missing caps
622 for (auto& capMissingDatabase : capsMissingDatabase) {
623 if (std::find(capsList.begin(), capsList.end(), capMissingDatabase) != capsList.end()) {
624 capsMissing = true;
625 break;
626 }
627 }
628
629 // Check for missing compressed formats
630 for (auto& compressedFormatClient : core.compressedFormats) {
631 if (std::find(compressedFormatsDatabase.begin(), compressedFormatsDatabase.end(), compressedFormatClient) == compressedFormatsDatabase.end()) {
632 compressedFormatsMissing = true;
633 break;
634 }
635 }
636
637 // TODO : Check internal formats
638
639 return (capsMissing || compressedFormatsMissing || internalFormatsMissing);
640 }
641
slotClose()642 void glCapsViewer::slotClose()
643 {
644 close();
645 }
646
slotUpload()647 void glCapsViewer::slotUpload()
648 {
649 glCapsViewerHttp glchttp;
650
651 if (!glchttp.checkServerConnection())
652 {
653 QMessageBox::warning(this, tr("Error"), tr("Could not connect to the OpenGL hardware database!\n\nPlease check your internet connection and proxy settings!"));
654 return;
655 }
656
657 if (!glchttp.checkReportPresent(core.description))
658 {
659 capsViewer::submitDialog dialog(appSettings.submitterName);
660 bool ok = (dialog.exec() == QDialog::Accepted);
661
662 if (ok)
663 {
664 core.submitter = dialog.getSubmitter();
665 core.comment = dialog.getComment();
666 QApplication::setOverrideCursor(Qt::WaitCursor);
667 string xml = core.reportToXml();
668 string reply = glchttp.postReport(xml);
669 QApplication::restoreOverrideCursor();
670 if (reply == "res_uploaded")
671 {
672 QMessageBox::information(this, tr("Report submitted"), tr("Your report has been uploaded to the database!\n\nThanks for your contribution!"));
673 updateReportState();
674 }
675 else
676 {
677 QMessageBox::warning(this, tr("Error"), "The report could not be uploaded : \n" + QString::fromStdString(reply));
678 }
679 }
680 }
681 else {
682 // Check if report can be updated
683 bool canUpdate = false;
684 int reportId = glchttp.getReportId(core.description);
685 canUpdate = canUpdateReport(reportId);
686 if (canUpdate) {
687 QMessageBox::StandardButton reply;
688 reply = QMessageBox::question(this, "Report outdated", "There is a report for your device present in the database, but it is missing some capabilities.\n\nDo you want to update the report?", QMessageBox::Yes | QMessageBox::No);
689 if (reply == QMessageBox::Yes) {
690 // Submitter name to be stored in report update log
691 bool ok;
692 QString text = QInputDialog::getText(this, tr("Submitter name"), tr("Submitter <i>(your name/nick, can be left empty)</i>:"), QLineEdit::Normal, appSettings.submitterName, &ok);
693 core.submitter = text.toStdString();
694 // TODO : Error handling
695 if (ok)
696 {
697 string xml = core.reportToXml();
698 string httpReply = glchttp.postReportForUpdate(xml);
699 QMessageBox::information(this, tr("Report updated"), QString::fromStdString(httpReply));
700 updateReportState();
701 }
702 }
703 }
704
705 if (!canUpdate) {
706 QMessageBox::StandardButton reply;
707 reply = QMessageBox::question(this, "Device already present", "A report for your device and OpenGL version is aleady present in the database.\n\nDo you want to open the report in your browser?", QMessageBox::Yes | QMessageBox::No);
708 if (reply == QMessageBox::Yes) {
709 reportId = glchttp.getReportId(core.description);
710 stringstream ss;
711 ss << glCapsViewerHttp::getBaseUrl() << "gl_generatereport.php?reportID=" << to_string(reportId);
712 QDesktopServices::openUrl(QUrl(QString::fromStdString(ss.str())));
713 }
714 }
715 }
716 }
717
slotExportXml()718 void glCapsViewer::slotExportXml(){
719 QString fileName = QFileDialog::getSaveFileName(this, tr("Save File"), "glCapsViewer_Report.xml", tr("xml (*.xml)"));
720 core.exportXml(fileName.toStdString());
721 }
722
slotBrowseDatabase()723 void glCapsViewer::slotBrowseDatabase() {
724 QString link = QString::fromStdString(glCapsViewerHttp::getBaseUrl());
725 QDesktopServices::openUrl(QUrl(link));
726 }
727
slotShowDeviceOnline()728 void glCapsViewer::slotShowDeviceOnline() {
729 glCapsViewerHttp glchttp;
730 int reportId = glchttp.getReportId(core.description);
731 stringstream ss;
732 ss << glchttp.getBaseUrl() << "gl_generatereport.php?reportID=" << to_string(reportId);
733 QDesktopServices::openUrl(QUrl(QString::fromStdString(ss.str())));
734 }
735
slotRefreshDatabase()736 void glCapsViewer::slotRefreshDatabase() {
737 refreshDeviceList();
738 }
739
slotAbout()740 void glCapsViewer::slotAbout() {
741 stringstream aboutText;
742 aboutText << "<p>OpenGL hardware capability viewer (glCapsViewer)<br/><br/>"
743 "Copyright (c) 2011-2019 by Sascha Willems<br/><br/>"
744 "This tool is <b>FREEWARE</b><br/><br/>"
745 "For usage and distribution details refer to the readme<br/><br/>"
746 "<a href='http://www.gpuinfo.org'>www.gpuinfo.org</a><br><br>"
747 "<a href='http://www.saschawillems.de'>www.saschawillems.de</a><br><br>";
748 aboutText << "GLFW : " << glfwGetVersionString() << "<br>";
749 aboutText << "GLEW : " << glewGetString(GLEW_VERSION);
750 aboutText << "</p>";
751 QMessageBox::about(this, tr("About the OpenGL hardware capability viewer"), QString::fromStdString(aboutText.str()));
752 }
753
slotSettings()754 void glCapsViewer::slotSettings()
755 {
756 capsViewer::settingsDialog dialog(appSettings);
757 dialog.setModal(true);
758 dialog.exec();
759 appSettings.restore();
760 }
761
slotTabChanged(int index)762 void glCapsViewer::slotTabChanged(int index)
763 {
764 if (index == 1) {
765 refreshDeviceList();
766 }
767 }
768
slotFilterExtensions(QString text)769 void glCapsViewer::slotFilterExtensions(QString text)
770 {
771 QRegExp regExp(text, Qt::CaseInsensitive, QRegExp::RegExp);
772 extensionFilterProxy.setFilterRegExp(regExp);
773 }
774
slotFilterImplementation(QString text)775 void glCapsViewer::slotFilterImplementation(QString text)
776 {
777 QRegExp regExp(text, Qt::CaseInsensitive, QRegExp::RegExp);
778 implementationFilterProxy.setFilterRegExp(regExp);
779 }
780
slotFilterTextureFormats(QString text)781 void glCapsViewer::slotFilterTextureFormats(QString text)
782 {
783 QRegExp regExp(text, Qt::CaseInsensitive, QRegExp::RegExp);
784 texFormatFilterProxy.setFilterRegExp(regExp);
785 }
786
slotFilterSPIRVExtensions(QString text)787 void glCapsViewer::slotFilterSPIRVExtensions(QString text)
788 {
789 QRegExp regExp(text, Qt::CaseInsensitive, QRegExp::RegExp);
790 SPIRVextensionFilterProxy.setFilterRegExp(regExp);
791 }
792
793
794 /// <summary>
795 /// Fetches a list of available report version for currently selected device
796 /// </summary>
slotDatabaseDevicesItemChanged()797 void glCapsViewer::slotDatabaseDevicesItemChanged() {
798 // TODO : Check if ui.listWidgetDatabaseDevices->currentItem()->text() is assigned
799 glCapsViewerHttp glchttp;
800 vector<reportInfo> reportList;
801
802 ui.comboBoxDeviceVersions->clear();
803 QVariant data = ui.listWidgetDatabaseDevices->currentItem()->data(Qt::UserRole);
804 QString deviceName = data.toString();
805 reportList = glchttp.fetchDeviceReports(deviceName.toStdString());
806
807 for (auto& report : reportList) {
808 stringstream ss;
809 ss << report.version << " (" << report.operatingSystem << ")";
810 ui.comboBoxDeviceVersions->addItem(QString::fromStdString(ss.str()), QVariant(report.reportId));
811 }
812 }
813
814 /// <summary>
815 /// Fetches the report for the currently selected device and report version
816 /// Displays it in table form
817 /// </summary>
slotDeviceVersionChanged(int index)818 void glCapsViewer::slotDeviceVersionChanged(int index) {
819 // TODO : Error checking
820 if (index < 0) {
821 return;
822 }
823 QApplication::setOverrideCursor(Qt::WaitCursor);
824 ui.labelDatabaseDeviceExtensions->setText("Extensions");
825 int reportId = ui.comboBoxDeviceVersions->itemData(index).toInt();
826 glCapsViewerHttp glchttp;
827 string reportXml = glchttp.fetchReport(reportId);
828 // Generate simple report
829
830 QTableWidget *table = ui.tableWidgetDatabaseDeviceReport;
831 table->setRowCount(0);
832 table->setColumnWidth(0, 250);
833 table->horizontalHeader()->setStretchLastSection(true);
834 table->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft);
835 table->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed);
836 table->verticalHeader()->setDefaultSectionSize(24);
837 table->verticalHeader()->setVisible(false);
838
839 QXmlStreamReader xmlReader(&reportXml[0]);
840
841 while (!xmlReader.atEnd()) {
842
843 if ((xmlReader.isStartElement()) && (xmlReader.name() == "implementation")) {
844 while (!xmlReader.atEnd()) {
845 xmlReader.readNext();
846 if (xmlReader.name() == "implementation") {
847 break;
848 }
849 table->insertRow(table->rowCount());
850 QXmlStreamAttributes attrib = xmlReader.attributes();
851 QString nodeName = attrib.value("id").toString();
852 QString nodeValue = xmlReader.readElementText();
853 table->setItem(table->rowCount() - 1, 0, new QTableWidgetItem(nodeName));
854 if (nodeValue == "") {
855 table->setItem(table->rowCount() - 1, 1, new QTableWidgetItem("n/a"));
856 table->item(table->rowCount() - 1, 0)->setTextColor(QColor::fromRgb(100, 100, 100));
857 table->item(table->rowCount() - 1, 1)->setTextColor(QColor::fromRgb(100, 100, 100));
858 }
859 else {
860 table->setItem(table->rowCount() - 1, 1, new QTableWidgetItem(nodeValue));
861 }
862 }
863 }
864
865 int extCount = 0;
866 if ((xmlReader.isStartElement()) && (xmlReader.name() == "extensions")) {
867 while (!xmlReader.atEnd()) {
868 xmlReader.readNext();
869 if (xmlReader.name() != "extension") {
870 break;
871 }
872 QListWidgetItem *deviceItem = new QListWidgetItem(xmlReader.readElementText(), ui.listWidgetDatabaseDeviceExtensions);
873 deviceItem->setSizeHint(QSize(deviceItem->sizeHint().height(), 24));
874 extCount++;
875 }
876 stringstream ss;
877 ss << "Extensions (" << extCount << ")";
878 ui.labelDatabaseDeviceExtensions->setText(QString::fromStdString(ss.str()));
879 }
880
881 xmlReader.readNext();
882 }
883
884 QApplication::restoreOverrideCursor();
885 }
886