ACMX 2.139.0
Dual-Backend Real-Time GPU Video Synthesis
Loading...
Searching...
No Matches
shader.cpp
Go to the documentation of this file.
1#include "shader.hpp"
3#include "custom_style.hpp"
4#include "shader-manifest.hpp"
5#include <QDir>
6#include <QFileInfo>
7#include <QRegularExpression>
8#include <QSettings>
9
10ShaderDialog::ShaderDialog(acmx2::Backend selectedBackend, QWidget *parent) : QDialog(parent), backend(selectedBackend) { init(); }
11
13 QVBoxLayout *layout = new QVBoxLayout(this);
14
15 QLabel *instructionLabel = new QLabel("Enter the name of the shader file:", this);
16 layout->addWidget(instructionLabel);
17
18 shaderNameEdit = new QLineEdit(this);
19 shaderNameEdit->setPlaceholderText(backend == acmx2::Backend::Acmxvk ? "Shader name (e.g., myshader.frag)" : "Shader name (e.g., myshader.glsl)");
20 layout->addWidget(shaderNameEdit);
21
22 QLabel *shaderTypeLabel = new QLabel("Shader type:", this);
23 layout->addWidget(shaderTypeLabel);
24
25 shaderTypeComboBox = new QComboBox(this);
26 shaderTypeComboBox->addItem(backend == acmx2::Backend::Acmxvk ? "Fragment shader (.frag)" : "Fragment shader (.glsl)");
27 shaderTypeComboBox->addItem("Compute shader (.comp)");
28 layout->addWidget(shaderTypeComboBox);
29
30 defaultCodeCheckBox = new QCheckBox("Include default shader code", this);
31 defaultCodeCheckBox->setChecked(true);
32 layout->addWidget(defaultCodeCheckBox);
33
34 cacheShaderCheckBox = new QCheckBox("Create as cache shader (_cache.glsl)", this);
35 cacheShaderCheckBox->setToolTip("Create a texture-cache shader starter with frame-cache and spectrum-history sampling.");
36 layout->addWidget(cacheShaderCheckBox);
37
38 connect(shaderTypeComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int index) {
39 const bool createComputeShader = index == 1;
40 cacheShaderCheckBox->setText(createComputeShader ? "Create as cache shader (_cache.comp)" : (backend == acmx2::Backend::Acmxvk ? "Create as cache shader (_cache.frag)" : "Create as cache shader (_cache.glsl)"));
41 shaderNameEdit->setPlaceholderText(createComputeShader ? "Shader name (e.g., myshader.comp)" : (backend == acmx2::Backend::Acmxvk ? "Shader name (e.g., myshader.frag)" : "Shader name (e.g., myshader.glsl)"));
42 });
43
44 QHBoxLayout *buttonLayout = new QHBoxLayout();
45 okButton = new QPushButton("OK", this);
46 connect(okButton, &QPushButton::clicked, this, &ShaderDialog::onOkButtonClicked);
47 buttonLayout->addWidget(okButton);
48
49 cancelButton = new QPushButton("Cancel", this);
50 connect(cancelButton, &QPushButton::clicked, this, &ShaderDialog::onCancelButtonClicked);
51 buttonLayout->addWidget(cancelButton);
52
53 layout->addLayout(buttonLayout);
54
55 setLayout(layout);
56 setWindowTitle("Create New Shader");
57 resize(440, 230);
59}
60
62 QString shaderName = shaderNameEdit->text().trimmed();
63 if (shaderName.isEmpty()) {
64 QMessageBox::warning(this, "Warning", "Please enter a shader name.");
65 return;
66 }
67
68 if (shaderName.endsWith(".glsl", Qt::CaseInsensitive) || shaderName.endsWith(".frag", Qt::CaseInsensitive)) {
69 shaderName.chop(5);
70 } else if (shaderName.endsWith(".comp", Qt::CaseInsensitive)) {
71 shaderName.chop(5);
72 }
73
74 if (shaderName.isEmpty()) {
75 QMessageBox::warning(this, "Warning", "Please enter a shader name.");
76 return;
77 }
78
79 static const QRegularExpression safeName(QStringLiteral("^[A-Za-z0-9][A-Za-z0-9_.-]*$"));
80 if (!safeName.match(shaderName).hasMatch() || shaderName == "." || shaderName == "..") {
81 QMessageBox::warning(this, "Warning", "Shader names may contain only letters, numbers, '.', '_', and '-'.");
82 return;
83 }
84
85 const bool createComputeShader = shaderTypeComboBox->currentIndex() == 1;
86 const bool createCacheShader = cacheShaderCheckBox->isChecked();
87 if (createCacheShader && !shaderName.endsWith("_cache", Qt::CaseInsensitive)) {
88 shaderName += "_cache";
89 }
90 shaderName += createComputeShader ? ".comp" : (backend == acmx2::Backend::Acmxvk ? ".frag" : ".glsl");
91
92 QString relativeShaderName = shaderName;
93 if (backend == acmx2::Backend::Acmxvk && createComputeShader)
94 relativeShaderName = QStringLiteral("compute/") + shaderName;
95 const QString absoluteShaderName = QDir(shaderPath).filePath(relativeShaderName);
96 if (QFileInfo::exists(absoluteShaderName)) {
97 QMessageBox::warning(this, "Shader Already Exists", QString("The shader already exists and was not changed:\n%1").arg(absoluteShaderName));
98 return;
99 }
100 if (!QDir().mkpath(QFileInfo(absoluteShaderName).absolutePath())) {
101 QMessageBox::critical(this, "Error", "Failed to create the shader directory.");
102 return;
103 }
104
105 const bool includeDefaultCode = defaultCodeCheckBox->isChecked();
106 if (!createShaderFile(absoluteShaderName, includeDefaultCode, createCacheShader, createComputeShader)) {
107 return;
108 }
109
110 QString manifestError;
111 bool manifestCreated = false;
113 QStringList manifestShaders;
114 if (!acmx2::load_shader_manifest(shaderPath, manifestShaders, manifestError)) {
115 QFile::remove(absoluteShaderName);
116 QMessageBox::critical(this, "Error", manifestError);
117 return;
118 }
119 if (!manifestShaders.contains(relativeShaderName, Qt::CaseInsensitive)) {
120 manifestShaders.append(relativeShaderName);
121 }
123 manifestCreated = acmx2::create_acmxvk_source_manifest_for_shaders(shaderPath, manifestShaders, QString(), result, manifestError);
124 } else {
125 manifestCreated = acmx2::append_shader_manifest(shaderPath, relativeShaderName, manifestError);
126 }
127 if (!manifestCreated) {
128 QFile::remove(absoluteShaderName);
129 QMessageBox::critical(this, "Error", manifestError);
130 return;
131 }
132 QMessageBox::information(this, "Success", "Shader file created successfully.");
133 accept();
134}
135
136void ShaderDialog::setShaderPath(const QString &path) { shaderPath = path; }
137
139
140namespace {
141 constexpr const char *DEFAULT_SHADER_CODE = R"(#version 330 core
142in vec2 tc;
143out vec4 color;
144
145uniform sampler2D samp;
146
147void main() {
148 color = texture(samp, tc);
149}
150)";
151
152 constexpr const char *CACHE_SHADER_CODE = R"(#version 330 core
153in vec2 tc;
154out vec4 color;
155
156uniform sampler2D samp;
157uniform sampler2DArray history;
158uniform int history_head;
159#ifndef SIZE
160#define SIZE 8
161#endif
162#ifndef CACHE_HISTORY_LAYER
163#define CACHE_HISTORY_LAYER(index) ((history_head + (index)) % SIZE)
164#endif
165
166uniform sampler1D spectrum0;
167uniform sampler1DArray spectrum_history;
168uniform int spectrum_history_head;
169uniform int spectrum_history_size;
170#ifndef SPECTRUM_HISTORY_LAYER
171#define SPECTRUM_HISTORY_LAYER(index) ((spectrum_history_head - ((index) % max(spectrum_history_size, 1)) + max(spectrum_history_size, 1)) % max(spectrum_history_size, 1))
172#endif
173
174vec4 sample_cache(int index, vec2 uv) {
175 int cache_index = clamp(index, 0, SIZE - 1);
176 return texture(history,
177 vec3(uv, float(CACHE_HISTORY_LAYER(cache_index))));
178}
179
180float sample_spectrum_history(int index, float frequency) {
181 int maximum_age = max(min(SIZE, spectrum_history_size) - 1, 0);
182 int spectrum_age = min(max(index, 0) + 1, maximum_age);
183 return texture(spectrum_history,
184 vec2(clamp(frequency, 0.0, 1.0),
185 float(SPECTRUM_HISTORY_LAYER(spectrum_age))))
186 .r;
187}
188
189void main() {
190 vec4 live_frame = texture(samp, tc);
191 vec4 cached_frame = sample_cache(SIZE - 1, tc);
192 float live_energy = texture(spectrum0, 0.08).r;
193 float history_energy = sample_spectrum_history(0, 0.08);
194 float cache_mix = clamp(0.25 + history_energy * 0.5, 0.0, 0.75);
195
196 color = mix(live_frame, cached_frame, cache_mix);
197 color.rgb *= 1.0 + live_energy * 0.2;
198}
199)";
200
201 constexpr const char *COMPUTE_SHADER_CODE = R"(#version 430 core
202layout(local_size_x = 16, local_size_y = 16) in;
203layout(rgba16f, binding = 0) writeonly uniform image2D outputImage;
204
205uniform sampler2D samp;
206uniform vec2 iResolution;
207
208void main() {
209 ivec2 pixel = ivec2(gl_GlobalInvocationID.xy);
210 if (pixel.x >= int(iResolution.x) || pixel.y >= int(iResolution.y))
211 return;
212
213 vec2 uv = (vec2(pixel) + 0.5) / iResolution;
214 imageStore(outputImage, pixel, textureLod(samp, uv, 0.0));
215}
216)";
217
218 constexpr const char *COMPUTE_CACHE_SHADER_CODE = R"(#version 430 core
219layout(local_size_x = 16, local_size_y = 16) in;
220layout(rgba16f, binding = 0) writeonly uniform image2D outputImage;
221
222uniform sampler2D samp;
223uniform vec2 iResolution;
224
225#ifndef SIZE
226#define SIZE 8
227#endif
228#ifndef USE_HISTORY_TEXTURE_ARRAY
229#define USE_HISTORY_TEXTURE_ARRAY 0
230#endif
231
232#if USE_HISTORY_TEXTURE_ARRAY
233uniform sampler2DArray history;
234uniform int history_head;
235
236vec4 sample_oldest_frame(vec2 uv) {
237 return textureLod(history, vec3(uv, float(history_head)), 0.0);
238}
239#else
240uniform sampler2D textures[SIZE];
241
242vec4 sample_oldest_frame(vec2 uv) {
243 return textureLod(textures[0], uv, 0.0);
244}
245#endif
246
247void main() {
248 ivec2 pixel = ivec2(gl_GlobalInvocationID.xy);
249 if (pixel.x >= int(iResolution.x) || pixel.y >= int(iResolution.y))
250 return;
251
252 vec2 uv = (vec2(pixel) + 0.5) / iResolution;
253 vec4 live_frame = textureLod(samp, uv, 0.0);
254 vec4 cached_frame = sample_oldest_frame(uv);
255 imageStore(outputImage, pixel, mix(live_frame, cached_frame, 0.5));
256}
257)";
258
259 constexpr const char *ACMXVK_FRAGMENT_SHADER_CODE = R"(#version 450
260
261layout(location = 0) in vec2 tc;
262layout(location = 0) out vec4 color;
263layout(set = 0, binding = 0) uniform sampler2D samp;
264
265layout(set = 0, binding = 1, std140) uniform SpriteExtended {
266 vec4 mouse;
267 vec4 u0;
268 vec4 u1;
269 vec4 u2;
270 vec4 u3;
271 vec4 custom_uniforms[16];
272 vec4 audio_bands;
273 vec4 audio_history;
274} ext;
275
276#define iResolution ext.u0.zw
277#define time_f ext.u2.y
278
279void main() {
280 color = texture(samp, tc);
281}
282)";
283
284 constexpr const char *ACMXVK_FRAGMENT_CACHE_SHADER_CODE = R"(#version 450
285
286layout(location = 0) in vec2 tc;
287layout(location = 0) out vec4 color;
288layout(set = 0, binding = 0) uniform sampler2D samp;
289layout(set = 0, binding = 2) uniform sampler2DArray history;
290
291layout(set = 0, binding = 1, std140) uniform SpriteExtended {
292 vec4 mouse;
293 vec4 u0;
294 vec4 u1;
295 vec4 u2;
296 vec4 u3;
297 vec4 custom_uniforms[16];
298 vec4 audio_bands;
299 vec4 audio_history;
300} ext;
301
302#define history_head int(ext.u3.x + 0.5)
303#define history_size max(int(ext.u3.y), 1)
304
305void main() {
306 vec4 live_frame = texture(samp, tc);
307 int cache_layer = history_head % history_size;
308 vec4 cached_frame = texture(history, vec3(tc, float(cache_layer)));
309 color = mix(live_frame, cached_frame, 0.5);
310}
311)";
312
313 constexpr const char *ACMXVK_COMPUTE_SHADER_CODE = R"(#version 450
314
315layout(local_size_x = 16, local_size_y = 16) in;
316layout(set = 0, binding = 5, rgba8) writeonly uniform image2D outputImage;
317layout(set = 0, binding = 0) uniform sampler2D samp;
318
319layout(set = 0, binding = 1, std140) uniform SpriteExtended {
320 vec4 mouse;
321 vec4 u0;
322 vec4 u1;
323 vec4 u2;
324 vec4 u3;
325 vec4 custom_uniforms[16];
326 vec4 audio_bands;
327 vec4 audio_history;
328} ext;
329
330void main() {
331 ivec2 pixel = ivec2(gl_GlobalInvocationID.xy);
332 ivec2 size = imageSize(outputImage);
333 if (any(greaterThanEqual(pixel, size)))
334 return;
335 vec2 uv = (vec2(pixel) + 0.5) / vec2(size);
336 imageStore(outputImage, pixel, textureLod(samp, uv, 0.0));
337}
338)";
339
340 constexpr const char *ACMXVK_COMPUTE_CACHE_SHADER_CODE = R"(#version 450
341
342layout(local_size_x = 16, local_size_y = 16) in;
343layout(set = 0, binding = 5, rgba8) writeonly uniform image2D outputImage;
344layout(set = 0, binding = 0) uniform sampler2D samp;
345layout(set = 0, binding = 2) uniform sampler2DArray history;
346
347layout(set = 0, binding = 1, std140) uniform SpriteExtended {
348 vec4 mouse;
349 vec4 u0;
350 vec4 u1;
351 vec4 u2;
352 vec4 u3;
353 vec4 custom_uniforms[16];
354 vec4 audio_bands;
355 vec4 audio_history;
356} ext;
357
358#define history_head int(ext.u3.x + 0.5)
359#define history_size max(int(ext.u3.y), 1)
360
361void main() {
362 ivec2 pixel = ivec2(gl_GlobalInvocationID.xy);
363 ivec2 size = imageSize(outputImage);
364 if (any(greaterThanEqual(pixel, size)))
365 return;
366 vec2 uv = (vec2(pixel) + 0.5) / vec2(size);
367 vec4 live_frame = textureLod(samp, uv, 0.0);
368 int cache_layer = history_head % history_size;
369 vec4 cached_frame =
370 textureLod(history, vec3(uv, float(cache_layer)), 0.0);
371 imageStore(outputImage, pixel, mix(live_frame, cached_frame, 0.5));
372}
373)";
374} // namespace
375
376bool ShaderDialog::createShaderFile(const QString &shaderName, bool includeDefaultCode, bool createCacheShader, bool createComputeShader) {
377 QFile file(shaderName);
378 if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
379 QTextStream out(&file);
380 if (includeDefaultCode) {
382 if (createComputeShader) {
383 out << (createCacheShader ? ACMXVK_COMPUTE_CACHE_SHADER_CODE : ACMXVK_COMPUTE_SHADER_CODE);
384 } else {
385 out << (createCacheShader ? ACMXVK_FRAGMENT_CACHE_SHADER_CODE : ACMXVK_FRAGMENT_SHADER_CODE);
386 }
387 } else if (createComputeShader) {
388 out << (createCacheShader ? COMPUTE_CACHE_SHADER_CODE : COMPUTE_SHADER_CODE);
389 } else {
390 out << (createCacheShader ? CACHE_SHADER_CODE : DEFAULT_SHADER_CODE);
391 }
392 out << "\n";
393 }
394 file.close();
395 return true;
396 } else {
397 QMessageBox::critical(this, "Error", "Failed to create shader file.");
398 return false;
399 }
400}
QPushButton * okButton
Definition shader.hpp:34
void onOkButtonClicked()
Definition shader.cpp:61
QCheckBox * defaultCodeCheckBox
Definition shader.hpp:36
ShaderDialog(acmx2::Backend backend, QWidget *parent=nullptr)
Definition shader.cpp:10
void setShaderPath(const QString &path)
Set output directory used for generated shader files.
Definition shader.cpp:136
void init()
Definition shader.cpp:12
QPushButton * cancelButton
Definition shader.hpp:35
acmx2::Backend backend
Definition shader.hpp:40
bool createShaderFile(const QString &shaderName, bool includeDefaultCode, bool createCacheShader, bool createComputeShader)
Definition shader.cpp:376
QLineEdit * shaderNameEdit
Definition shader.hpp:33
QCheckBox * cacheShaderCheckBox
Definition shader.hpp:37
QString shaderPath
Definition shader.hpp:39
void onCancelButtonClicked()
Definition shader.cpp:138
QComboBox * shaderTypeComboBox
Definition shader.hpp:38
bool create_acmxvk_source_manifest_for_shaders(const QString &rootDirectory, const QStringList &shaderFiles, const QString &outputPath, AcmxvkSourceManifestResult &result, QString &error)
Generate a source manifest from an explicit subset of files below root.
Backend
Definition backend.hpp:8
void applyCustomStyleIfEnabled(QWidget *widget)
bool load_shader_manifest(const QString &directory, QStringList &shaders, QString &error)
Load shader filenames from the preferred manifest in a directory.
bool append_shader_manifest(const QString &directory, const QString &shader, QString &error)
Add one shader to the preferred manifest if it is not already present.
constexpr const char * ACMXVK_COMPUTE_SHADER_CODE
Definition shader.cpp:313
constexpr const char * COMPUTE_CACHE_SHADER_CODE
Definition shader.cpp:218
constexpr const char * ACMXVK_FRAGMENT_CACHE_SHADER_CODE
Definition shader.cpp:284
constexpr const char * ACMXVK_FRAGMENT_SHADER_CODE
Definition shader.cpp:259
constexpr const char * COMPUTE_SHADER_CODE
Definition shader.cpp:201
constexpr const char * ACMXVK_COMPUTE_CACHE_SHADER_CODE
Definition shader.cpp:340
constexpr const char * CACHE_SHADER_CODE
Definition shader.cpp:152
constexpr const char * DEFAULT_SHADER_CODE
Definition shader.cpp:141
Optional JSON and legacy text shader-library manifests.
Dialog for creating a new shader file.