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