1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: MIT
3
4#include "scene.h"
5
6#include <cstdlib>
7#include <iostream>
8#include <stdlib.h>
9#include <stdio.h>
10#include <chrono>
11#include <cassert>
12#include <concepts>
13
14#include <GLES3/gl3.h>
15#include <GLES3/gl3platform.h>
16
17static GLint compile_shader(GLuint program, GLuint shader_type, const GLchar *const *source)
18{
19 auto shader_id = glCreateShader(type: shader_type);
20 glShaderSource(shader: shader_id, count: 1, string: source, length: nullptr);
21 glCompileShader(shader: shader_id);
22
23 GLint compiled = 0;
24 glGetShaderiv(shader: shader_id, GL_COMPILE_STATUS, params: &compiled);
25 if (!compiled) {
26 GLint infoLen = 0;
27 glGetShaderiv(shader: shader_id, GL_INFO_LOG_LENGTH, params: &infoLen);
28 if (infoLen > 1) {
29 char *infoLog = reinterpret_cast<char *>(malloc(size: sizeof(char) * infoLen));
30 glGetShaderInfoLog(shader: shader_id, bufSize: infoLen, NULL, infoLog);
31 fprintf(stderr, format: "Error compiling %s shader:\n%s\n",
32 shader_type == GL_FRAGMENT_SHADER ? "fragment shader" : "vertex shader",
33 infoLog);
34 free(ptr: infoLog);
35 }
36 glDeleteShader(shader: shader_id);
37 exit(status: 1);
38 }
39 glAttachShader(program, shader: shader_id);
40
41 return shader_id;
42}
43
44#define DEFINE_SCOPED_BINDING(StructName, ParamName, BindingFn, TargetName) \
45 struct StructName \
46 { \
47 GLuint saved_value = {}; \
48 StructName() = delete; \
49 StructName(const StructName &) = delete; \
50 StructName &operator=(const StructName &) = delete; \
51 StructName(GLuint new_value) \
52 { \
53 glGetIntegerv(ParamName, (GLint *)&saved_value); \
54 BindingFn(TargetName, new_value); \
55 } \
56 ~StructName() { BindingFn(TargetName, saved_value); } \
57 }
58
59DEFINE_SCOPED_BINDING(ScopedTextureBinding, GL_TEXTURE_BINDING_2D, glBindTexture, GL_TEXTURE_2D);
60DEFINE_SCOPED_BINDING(ScopedFrameBufferBinding, GL_DRAW_FRAMEBUFFER_BINDING, glBindFramebuffer,
61 GL_DRAW_FRAMEBUFFER);
62DEFINE_SCOPED_BINDING(ScopedVBOBinding, GL_ARRAY_BUFFER_BINDING, glBindBuffer, GL_ARRAY_BUFFER);
63
64struct ScopedVAOBinding
65{
66 GLuint saved_value = {};
67 ScopedVAOBinding() = delete;
68 ScopedVAOBinding(const ScopedVAOBinding &) = delete;
69 ScopedVAOBinding &operator=(const ScopedVAOBinding &) = delete;
70 ScopedVAOBinding(GLuint new_value)
71 {
72 glGetIntegerv(GL_VERTEX_ARRAY_BINDING, data: (GLint *)&saved_value);
73 glBindVertexArray(array: new_value);
74 }
75 ~ScopedVAOBinding() { glBindVertexArray(array: saved_value); }
76};
77
78struct DemoTexture
79{
80 GLuint texture;
81 int width;
82 int height;
83 GLuint fbo;
84
85 DemoTexture(int width, int height) : width(width), height(height)
86 {
87 glGenFramebuffers(n: 1, framebuffers: &fbo);
88 glGenTextures(n: 1, textures: &texture);
89
90 ScopedTextureBinding activeTexture(texture);
91
92 GLint old_unpack_alignment;
93 glGetIntegerv(GL_UNPACK_ALIGNMENT, data: &old_unpack_alignment);
94 GLint old_unpack_row_length;
95 glGetIntegerv(GL_UNPACK_ROW_LENGTH, data: &old_unpack_row_length);
96 GLint old_unpack_skip_pixels;
97 glGetIntegerv(GL_UNPACK_SKIP_PIXELS, data: &old_unpack_skip_pixels);
98 GLint old_unpack_skip_rows;
99 glGetIntegerv(GL_UNPACK_SKIP_ROWS, data: &old_unpack_skip_rows);
100
101 glPixelStorei(GL_UNPACK_ALIGNMENT, param: 1);
102 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
103 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
104 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
105 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
106 glPixelStorei(GL_UNPACK_ROW_LENGTH, param: width);
107 glPixelStorei(GL_UNPACK_SKIP_PIXELS, param: 0);
108 glPixelStorei(GL_UNPACK_SKIP_ROWS, param: 0);
109
110 glTexImage2D(GL_TEXTURE_2D, level: 0, GL_RGBA, width, height, border: 0, GL_RGBA, GL_UNSIGNED_BYTE,
111 pixels: nullptr);
112
113 ScopedFrameBufferBinding activeFBO(fbo);
114
115 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, level: 0);
116
117 assert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
118
119 glPixelStorei(GL_UNPACK_ALIGNMENT, param: old_unpack_alignment);
120 glPixelStorei(GL_UNPACK_ROW_LENGTH, param: old_unpack_row_length);
121 glPixelStorei(GL_UNPACK_SKIP_PIXELS, param: old_unpack_skip_pixels);
122 glPixelStorei(GL_UNPACK_SKIP_ROWS, param: old_unpack_skip_rows);
123 }
124 DemoTexture(const DemoTexture &) = delete;
125 DemoTexture &operator=(const DemoTexture &) = delete;
126 ~DemoTexture()
127 {
128 glDeleteFramebuffers(n: 1, framebuffers: &fbo);
129 glDeleteTextures(n: 1, textures: &texture);
130 }
131
132 template<std::invocable<> Callback>
133 void with_active_fbo(Callback callback)
134 {
135 ScopedFrameBufferBinding activeFBO(fbo);
136 callback();
137 }
138};
139
140class DemoRenderer
141{
142public:
143 DemoRenderer(slint::ComponentWeakHandle<App> app) : app_weak(app) { }
144
145 void operator()(slint::RenderingState state, slint::GraphicsAPI)
146 {
147 switch (state) {
148 case slint::RenderingState::RenderingSetup:
149 setup();
150 break;
151 case slint::RenderingState::BeforeRendering:
152 if (auto app = app_weak.lock()) {
153 auto red = (*app)->get_selected_red();
154 auto green = (*app)->get_selected_green();
155 auto blue = (*app)->get_selected_blue();
156 auto width = (*app)->get_requested_texture_width();
157 auto height = (*app)->get_requested_texture_height();
158 auto texture = render(red, green, blue, width, height);
159 (*app)->set_texture(texture);
160 (*app)->window().request_redraw();
161 }
162 break;
163 case slint::RenderingState::AfterRendering:
164 break;
165 case slint::RenderingState::RenderingTeardown:
166 teardown();
167 break;
168 }
169 }
170
171private:
172 void setup()
173 {
174 program = glCreateProgram();
175
176 const GLchar *const fragment_shader =
177 R"(#version 100
178 precision highp float;
179 varying vec2 frag_position;
180 uniform vec3 selected_light_color;
181 uniform float iTime;
182
183 float sdRoundBox(vec3 p, vec3 b, float r)
184 {
185 vec3 q = abs(p) - b;
186 return length(max(q,0.0)) + min(max(q.x,max(q.y,q.z)),0.0) - r;
187 }
188
189 vec3 rotateY(vec3 r, float angle)
190 {
191 mat3 rotation_matrix = mat3(cos(angle), 0, sin(angle), 0, 1, 0, -sin(angle), 0, cos(angle));
192 return rotation_matrix * r;
193 }
194
195 vec3 rotateZ(vec3 r, float angle) {
196 mat3 rotation_matrix = mat3(cos(angle), -sin(angle), 0, sin(angle), cos(angle), 0, 0, 0, 1);
197 return rotation_matrix * r;
198 }
199
200 // Distance from the scene
201 float scene(vec3 r)
202 {
203 vec3 pos = rotateZ(rotateY(r + vec3(-1.0, -1.0, 4.0), iTime), iTime);
204 vec3 cube = vec3(0.5, 0.5, 0.5);
205 float edge = 0.1;
206 return sdRoundBox(pos, cube, edge);
207 }
208
209 // https://iquilezles.org/articles/normalsSDF
210 vec3 normal( in vec3 pos )
211 {
212 vec2 e = vec2(1.0,-1.0)*0.5773;
213 const float eps = 0.0005;
214 return normalize( e.xyy*scene( pos + e.xyy*eps ) +
215 e.yyx*scene( pos + e.yyx*eps ) +
216 e.yxy*scene( pos + e.yxy*eps ) +
217 e.xxx*scene( pos + e.xxx*eps ) );
218 }
219
220 #define ITERATIONS 90
221 #define EPS 0.0001
222
223 vec4 render(vec2 fragCoord, vec3 light_color)
224 {
225 vec4 color = vec4(0, 0, 0, 1);
226
227 vec3 camera = vec3(1.0, 2.0, 1.0);
228 vec3 p = vec3(fragCoord.x, fragCoord.y + 1.0, -1.0);
229 vec3 dir = normalize(p - camera);
230
231 for(int i=0; i < ITERATIONS; i++)
232 {
233 float dist = scene(p);
234 if(dist < EPS) {
235 break;
236 }
237 p = p + dir * dist;
238 }
239
240 vec3 surf_normal = normal(p);
241
242 vec3 light_position = vec3(2.0, 4.0, -0.5);
243 float light = 7.0 + 2.0 * dot(surf_normal, light_position);
244 light /= 0.2 * pow(length(light_position - p), 3.5);
245
246 return vec4(light * light_color.x, light * light_color.y, light * light_color.z, 1.0) * 2.0;
247 }
248
249 /*
250 void mainImage(out vec4 fragColor, in vec2 fragCoord)
251 {
252 vec2 r = fragCoord.xy / iResolution.xy;
253 r.x *= (iResolution.x / iResolution.y);
254 fragColor = render(r, vec3(0.2, 0.5, 0.9));
255 }
256 */
257
258 void main() {
259 vec2 r = vec2(0.5 * frag_position.x + 1.0, 0.5 - 0.5 * frag_position.y);
260 gl_FragColor = render(r, selected_light_color);
261 })";
262
263 const GLchar *const vertex_shader = "#version 100\n"
264 "attribute vec2 position;\n"
265 "varying vec2 frag_position;\n"
266 "void main() {\n"
267 " frag_position = position;\n"
268 " gl_Position = vec4(position, 0.0, 1.0);\n"
269 "}\n";
270
271 auto fragment_shader_id = compile_shader(program, GL_FRAGMENT_SHADER, source: &fragment_shader);
272 auto vertex_shader_id = compile_shader(program, GL_VERTEX_SHADER, source: &vertex_shader);
273
274 GLint linked = 0;
275 glLinkProgram(program);
276 glGetProgramiv(program, GL_LINK_STATUS, params: &linked);
277
278 if (!linked) {
279 GLint infoLen = 0;
280 glGetProgramiv(program, GL_INFO_LOG_LENGTH, params: &infoLen);
281 if (infoLen > 1) {
282 char *infoLog = reinterpret_cast<char *>(malloc(size: sizeof(char) * infoLen));
283 glGetProgramInfoLog(program, bufSize: infoLen, NULL, infoLog);
284 fprintf(stderr, format: "Error linking shader:\n%s\n", infoLog);
285 free(ptr: infoLog);
286 }
287 glDeleteProgram(program);
288 exit(status: 1);
289 }
290 glDetachShader(program, shader: fragment_shader_id);
291 glDetachShader(program, shader: vertex_shader_id);
292
293 GLuint position_location = glGetAttribLocation(program, name: "position");
294 effect_time_location = glGetUniformLocation(program, name: "iTime");
295 selected_light_color_position = glGetUniformLocation(program, name: "selected_light_color");
296
297 displayed_texture = std::make_unique<DemoTexture>(320, 200);
298 next_texture = std::make_unique<DemoTexture>(320, 200);
299
300 glGenVertexArrays(n: 1, arrays: &vao);
301 glGenBuffers(n: 1, buffers: &vbo);
302
303 ScopedVBOBinding savedVBO(vbo);
304 ScopedVAOBinding savedVAO(vao);
305
306 const float vertices[] = { -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0, -1.0 };
307 glBufferData(GL_ARRAY_BUFFER, size: sizeof(vertices) * sizeof(vertices[0]), data: &vertices,
308 GL_STATIC_DRAW);
309
310 glEnableVertexAttribArray(index: position_location);
311 glVertexAttribPointer(index: position_location, size: 2, GL_FLOAT, normalized: false, stride: 8, pointer: 0);
312 }
313
314 slint::Image render(float red, float green, float blue, int width, int height)
315 {
316 ScopedVBOBinding savedVBO(vbo);
317 ScopedVAOBinding savedVAO(vao);
318
319 glUseProgram(program);
320
321 if (next_texture->width != width || next_texture->height != height) {
322 auto new_texture = std::make_unique<DemoTexture>(width, height);
323 std::swap(next_texture, new_texture);
324 }
325
326 next_texture->with_active_fbo([&]() {
327 GLint saved_viewport[4];
328 glGetIntegerv(GL_VIEWPORT, &saved_viewport[0]);
329
330 glViewport(0, 0, next_texture->width, next_texture->height);
331
332 auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
333 std::chrono::steady_clock::now() - start_time)
334 / 500.;
335 glUniform1f(effect_time_location, elapsed.count());
336 glUniform3f(selected_light_color_position, red, green, blue);
337
338 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
339
340 glViewport(saved_viewport[0], saved_viewport[1], saved_viewport[2], saved_viewport[3]);
341 });
342
343 glUseProgram(program: 0);
344
345 auto resultTexture = slint::Image::create_from_borrowed_gl_2d_rgba_texture(
346 next_texture->texture,
347 { static_cast<uint32_t>(next_texture->width),
348 static_cast<uint32_t>(next_texture->height) });
349
350 std::swap(next_texture, displayed_texture);
351
352 return resultTexture;
353 }
354
355 void teardown()
356 {
357 glDeleteProgram(program);
358 glDeleteVertexArrays(n: 1, arrays: &vao);
359 glDeleteBuffers(n: 1, buffers: &vbo);
360 }
361
362 slint::ComponentWeakHandle<App> app_weak;
363 GLuint vbo;
364 GLuint vao;
365 GLuint program = 0;
366 GLuint effect_time_location = 0;
367 GLuint selected_light_color_position = 0;
368 std::chrono::time_point<std::chrono::steady_clock> start_time =
369 std::chrono::steady_clock::now();
370 std::unique_ptr<DemoTexture> displayed_texture;
371 std::unique_ptr<DemoTexture> next_texture;
372};
373
374int main()
375{
376 auto app = App::create();
377
378 if (auto error = app->window().set_rendering_notifier(DemoRenderer(app))) {
379 if (*error == slint::SetRenderingNotifierError::Unsupported) {
380 fprintf(stderr,
381 format: "This example requires the use of a GL renderer. Please run with the "
382 "environment variable SLINT_BACKEND=winit set.\n");
383 } else {
384 fprintf(stderr, format: "Unknown error calling set_rendering_notifier\n");
385 }
386 exit(EXIT_FAILURE);
387 }
388
389 app->run();
390}
391

source code of slint/examples/opengl_texture/main.cpp