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 | |
17 | static 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 | |
59 | DEFINE_SCOPED_BINDING(ScopedTextureBinding, GL_TEXTURE_BINDING_2D, glBindTexture, GL_TEXTURE_2D); |
60 | DEFINE_SCOPED_BINDING(ScopedFrameBufferBinding, GL_DRAW_FRAMEBUFFER_BINDING, glBindFramebuffer, |
61 | GL_DRAW_FRAMEBUFFER); |
62 | DEFINE_SCOPED_BINDING(ScopedVBOBinding, GL_ARRAY_BUFFER_BINDING, glBindBuffer, GL_ARRAY_BUFFER); |
63 | |
64 | struct 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 | |
78 | struct 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 | |
140 | class DemoRenderer |
141 | { |
142 | public: |
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 | |
171 | private: |
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 | |
374 | int 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 | |