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