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