src/platform/qt/ShaderSelector.cpp (view raw)
1/* Copyright (c) 2013-2015 Jeffrey Pfau
2 *
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6#include "ShaderSelector.h"
7
8#include "ConfigController.h"
9#include "GBAApp.h"
10#include "Display.h"
11#include "VFileDevice.h"
12
13#include <QCheckBox>
14#include <QDoubleSpinBox>
15#include <QFileDialog>
16#include <QFormLayout>
17#include <QGridLayout>
18#include <QSpinBox>
19
20#include <mgba/core/version.h>
21#include <mgba-util/vfs.h>
22#include "platform/video-backend.h"
23
24#if !defined(_WIN32) || defined(USE_EPOXY)
25#include "platform/opengl/gles2.h"
26#endif
27
28using namespace QGBA;
29
30ShaderSelector::ShaderSelector(Display* display, ConfigController* config, QWidget* parent)
31 : QDialog(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint)
32 , m_display(display)
33 , m_config(config)
34{
35 m_ui.setupUi(this);
36
37 refreshShaders();
38
39 connect(m_ui.load, &QAbstractButton::clicked, this, &ShaderSelector::selectShader);
40 connect(m_ui.unload, &QAbstractButton::clicked, this, &ShaderSelector::clearShader);
41 connect(m_ui.buttonBox, &QDialogButtonBox::clicked, this, &ShaderSelector::buttonPressed);
42}
43
44ShaderSelector::~ShaderSelector() {
45 clear();
46}
47
48void ShaderSelector::clear() {
49 m_ui.shaderName->setText(tr("No shader active"));
50 m_ui.description->clear();
51 m_ui.author->clear();
52
53 while (QWidget* page = m_ui.passes->widget(0)) {
54 m_ui.passes->removeTab(0);
55 delete page;
56 }
57}
58
59void ShaderSelector::selectShader() {
60 QString path(GBAApp::dataDir());
61 path += QLatin1String("/shaders");
62 QFileDialog dialog(nullptr, tr("Load shader"), path, tr("%1 Shader (%.shader)").arg(projectName));
63 dialog.setFileMode(QFileDialog::Directory);
64 dialog.exec();
65 QStringList names = dialog.selectedFiles();
66 if (names.count() == 1) {
67 loadShader(names[0]);
68 refreshShaders();
69 }
70}
71
72void ShaderSelector::loadShader(const QString& path) {
73 VDir* shader = VFileDevice::openDir(path);
74 if (!shader) {
75 shader = VFileDevice::openArchive(path);
76 }
77 if (!shader) {
78 return;
79 }
80 m_display->setShaders(shader);
81 shader->close(shader);
82 m_shaderPath = path;
83}
84
85void ShaderSelector::clearShader() {
86 m_display->clearShaders();
87 refreshShaders();
88 m_shaderPath = "";
89 m_config->setOption("shader", nullptr);
90}
91
92void ShaderSelector::refreshShaders() {
93 clear();
94 m_shaders = m_display->shaders();
95 if (!m_shaders) {
96 return;
97 }
98 if (m_shaders->name) {
99 m_ui.shaderName->setText(m_shaders->name);
100 } else {
101 m_ui.shaderName->setText(tr("No shader loaded"));
102 }
103 if (m_shaders->description) {
104 m_ui.description->setText(m_shaders->description);
105 } else {
106 m_ui.description->clear();
107 }
108 if (m_shaders->author) {
109 m_ui.author->setText(tr("by %1").arg(m_shaders->author));
110 } else {
111 m_ui.author->clear();
112 }
113
114 disconnect(this, &ShaderSelector::saved, 0, 0);
115 disconnect(this, &ShaderSelector::reset, 0, 0);
116 disconnect(this, &ShaderSelector::resetToDefault, 0, 0);
117
118#if !defined(_WIN32) || defined(USE_EPOXY)
119 if (m_shaders->preprocessShader) {
120 m_ui.passes->addTab(makePage(static_cast<mGLES2Shader*>(m_shaders->preprocessShader), "default", 0), tr("Preprocessing"));
121 }
122 mGLES2Shader* shaders = static_cast<mGLES2Shader*>(m_shaders->passes);
123 QFileInfo fi(m_shaderPath);
124 for (size_t p = 0; p < m_shaders->nPasses; ++p) {
125 QWidget* page = makePage(&shaders[p], fi.baseName(), p);
126 if (page) {
127 m_ui.passes->addTab(page, tr("Pass %1").arg(p + 1));
128 }
129 }
130#endif
131}
132
133void ShaderSelector::addUniform(QGridLayout* settings, const QString& section, const QString& name, float* value, float min, float max, int y, int x) {
134 QDoubleSpinBox* f = new QDoubleSpinBox;
135 f->setDecimals(3);
136 if (min < max) {
137 f->setMinimum(min);
138 f->setMaximum(max);
139 }
140 float def = *value;
141 bool ok = false;
142 float v = m_config->getQtOption(name, section).toFloat(&ok);
143 if (ok) {
144 *value = v;
145 }
146 f->setValue(*value);
147 f->setSingleStep(0.001);
148 f->setAccelerated(true);
149 settings->addWidget(f, y, x);
150 connect(f, static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), [value](double v) {
151 *value = v;
152 });
153 connect(this, &ShaderSelector::saved, [this, section, name, f]() {
154 m_config->setQtOption(name, f->value(), section);
155 });
156 connect(this, &ShaderSelector::reset, [this, section, name, f]() {
157 bool ok = false;
158 float v = m_config->getQtOption(name, section).toFloat(&ok);
159 if (ok) {
160 f->setValue(v);
161 }
162 });
163 connect(this, &ShaderSelector::resetToDefault, [def, section, name, f]() {
164 f->setValue(def);
165 });
166}
167
168void ShaderSelector::addUniform(QGridLayout* settings, const QString& section, const QString& name, int* value, int min, int max, int y, int x) {
169 QSpinBox* i = new QSpinBox;
170 if (min < max) {
171 i->setMinimum(min);
172 i->setMaximum(max);
173 }
174 int def = *value;
175 bool ok = false;
176 int v = m_config->getQtOption(name, section).toInt(&ok);
177 if (ok) {
178 *value = v;
179 }
180 i->setValue(*value);
181 i->setSingleStep(1);
182 i->setAccelerated(true);
183 settings->addWidget(i, y, x);
184 connect(i, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), [value](int v) {
185 *value = v;
186 });
187 connect(this, &ShaderSelector::saved, [this, section, name, i]() {
188 m_config->setQtOption(name, i->value(), section);
189 });
190 connect(this, &ShaderSelector::reset, [this, section, name, i]() {
191 bool ok = false;
192 int v = m_config->getQtOption(name, section).toInt(&ok);
193 if (ok) {
194 i->setValue(v);
195 }
196 });
197 connect(this, &ShaderSelector::resetToDefault, [def, section, name, i]() {
198 i->setValue(def);
199 });
200}
201
202QWidget* ShaderSelector::makePage(mGLES2Shader* shader, const QString& name, int pass) {
203#if !defined(_WIN32) || defined(USE_EPOXY)
204 if (!shader->nUniforms) {
205 return nullptr;
206 }
207 QWidget* page = new QWidget;
208 QFormLayout* layout = new QFormLayout;
209 page->setLayout(layout);
210 for (size_t u = 0 ; u < shader->nUniforms; ++u) {
211 QGridLayout* settings = new QGridLayout;
212 mGLES2Uniform* uniform = &shader->uniforms[u];
213 QString section = QString("shader.%1.%2").arg(name).arg(pass);
214 QString name = QLatin1String(uniform->name);
215 switch (uniform->type) {
216 case GL_FLOAT:
217 addUniform(settings, section, name, &uniform->value.f, uniform->min.f, uniform->max.f, 0, 0);
218 break;
219 case GL_FLOAT_VEC2:
220 addUniform(settings, section, name + "[0]", &uniform->value.fvec2[0], uniform->min.fvec2[0], uniform->max.fvec2[0], 0, 0);
221 addUniform(settings, section, name + "[1]", &uniform->value.fvec2[1], uniform->min.fvec2[1], uniform->max.fvec2[1], 0, 1);
222 break;
223 case GL_FLOAT_VEC3:
224 addUniform(settings, section, name + "[0]", &uniform->value.fvec3[0], uniform->min.fvec3[0], uniform->max.fvec3[0], 0, 0);
225 addUniform(settings, section, name + "[1]", &uniform->value.fvec3[1], uniform->min.fvec3[1], uniform->max.fvec3[1], 0, 1);
226 addUniform(settings, section, name + "[2]", &uniform->value.fvec3[2], uniform->min.fvec3[2], uniform->max.fvec3[2], 0, 2);
227 break;
228 case GL_FLOAT_VEC4:
229 addUniform(settings, section, name + "[0]", &uniform->value.fvec4[0], uniform->min.fvec4[0], uniform->max.fvec4[0], 0, 0);
230 addUniform(settings, section, name + "[1]", &uniform->value.fvec4[1], uniform->min.fvec4[1], uniform->max.fvec4[1], 0, 1);
231 addUniform(settings, section, name + "[2]", &uniform->value.fvec4[2], uniform->min.fvec4[2], uniform->max.fvec4[2], 0, 2);
232 addUniform(settings, section, name + "[3]", &uniform->value.fvec4[3], uniform->min.fvec4[3], uniform->max.fvec4[3], 0, 3);
233 break;
234 case GL_INT:
235 addUniform(settings, section, name, &uniform->value.i, uniform->min.i, uniform->max.i, 0, 0);
236 break;
237 case GL_INT_VEC2:
238 addUniform(settings, section, name + "[0]", &uniform->value.ivec2[0], uniform->min.ivec2[0], uniform->max.ivec2[0], 0, 0);
239 addUniform(settings, section, name + "[1]", &uniform->value.ivec2[1], uniform->min.ivec2[1], uniform->max.ivec2[1], 0, 1);
240 break;
241 case GL_INT_VEC3:
242 addUniform(settings, section, name + "[0]", &uniform->value.ivec3[0], uniform->min.ivec3[0], uniform->max.ivec3[0], 0, 0);
243 addUniform(settings, section, name + "[1]", &uniform->value.ivec3[1], uniform->min.ivec3[1], uniform->max.ivec3[1], 0, 1);
244 addUniform(settings, section, name + "[2]", &uniform->value.ivec3[2], uniform->min.ivec3[2], uniform->max.ivec3[2], 0, 2);
245 break;
246 case GL_INT_VEC4:
247 addUniform(settings, section, name + "[0]", &uniform->value.ivec4[0], uniform->min.ivec4[0], uniform->max.ivec4[0], 0, 0);
248 addUniform(settings, section, name + "[1]", &uniform->value.ivec4[1], uniform->min.ivec4[1], uniform->max.ivec4[1], 0, 1);
249 addUniform(settings, section, name + "[2]", &uniform->value.ivec4[2], uniform->min.ivec4[2], uniform->max.ivec4[2], 0, 2);
250 addUniform(settings, section, name + "[3]", &uniform->value.ivec4[3], uniform->min.ivec4[3], uniform->max.ivec4[3], 0, 3);
251 break;
252 }
253 layout->addRow(shader->uniforms[u].readableName, settings);
254 }
255 return page;
256#else
257 return nullptr;
258#endif
259}
260
261void ShaderSelector::buttonPressed(QAbstractButton* button) {
262 switch (m_ui.buttonBox->standardButton(button)) {
263 case QDialogButtonBox::Reset:
264 emit reset();
265 break;
266 case QDialogButtonBox::Ok:
267 m_config->setOption("shader", m_shaderPath);
268 emit saved();
269 close();
270 break;
271 case QDialogButtonBox::RestoreDefaults:
272 emit resetToDefault();
273 break;
274 default:
275 break;
276 }
277}