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