1 /** |
|
2 * ShaderShark |
|
3 * Copyright © 2023 František Kučera (Frantovo.cz, GlobalCode.info) |
|
4 * |
|
5 * This program is free software: you can redistribute it and/or modify |
|
6 * it under the terms of the GNU General Public License as published by |
|
7 * the Free Software Foundation, version 3 of the License. |
|
8 * |
|
9 * This program is distributed in the hope that it will be useful, |
|
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 * GNU General Public License for more details. |
|
13 * |
|
14 * You should have received a copy of the GNU General Public License |
|
15 * along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 */ |
|
17 |
|
18 #include <iostream> |
|
19 #include <iomanip> |
|
20 #include <string> |
|
21 #include <charconv> |
|
22 #include <memory> |
|
23 #include <functional> |
|
24 #include <sstream> |
|
25 #include <vector> |
|
26 |
|
27 #include "x11.h" |
|
28 #include "opengl.h" |
|
29 #include "EPoll.h" |
|
30 #include "Logger.h" |
|
31 #include "MappedFile.h" |
|
32 #include "ImageLoader.h" |
|
33 #include "Texture.h" |
|
34 #include "Shader.h" |
|
35 #include "Program.h" |
|
36 #include "FileMonitor.h" |
|
37 #include "XAttrs.h" |
|
38 |
|
39 #include "Shark.h" |
|
40 |
|
41 class Shark::Impl { |
|
42 public: |
|
43 |
|
44 struct { |
|
45 GLint aVertexXYZ = -2; |
|
46 GLint aTextureXY = -2; |
|
47 |
|
48 GLint fColor = -2; |
|
49 |
|
50 GLint uModel = -2; |
|
51 GLint uView = -2; |
|
52 GLint uProjection = -2; |
|
53 GLint uTexture = -2; |
|
54 GLint uTextureScale = -2; |
|
55 } ProgAttr; |
|
56 |
|
57 struct { |
|
58 float yaw = -90.f; |
|
59 float pitch = 0.f; |
|
60 float roll = 0.f; |
|
61 float fov = 45.0f; |
|
62 glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f); |
|
63 glm::vec3 cameraFront = glm::vec3(0.0f, 0.0f, -1.0f); |
|
64 glm::vec3 cameraUp = glm::vec3(0.0f, 1.0f, 0.0f); |
|
65 |
|
66 void adjustFov(float diff) { |
|
67 fov += diff; |
|
68 if (fov < 1.0f) fov = 1.0f; |
|
69 else if (fov > 120.0f) fov = 120.0f; |
|
70 std::cerr << "field of view: " << fov << " °" << std::endl; |
|
71 } |
|
72 |
|
73 void moveForward(const float cameraSpeed) { |
|
74 cameraPos += cameraSpeed * cameraFront; |
|
75 } |
|
76 |
|
77 void moveBackward(const float cameraSpeed) { |
|
78 cameraPos -= cameraSpeed * cameraFront; |
|
79 } |
|
80 |
|
81 void moveLeft(const float cameraSpeed) { |
|
82 cameraPos -= glm::normalize( |
|
83 glm::cross(cameraFront, cameraUp)) * cameraSpeed; |
|
84 } |
|
85 |
|
86 void moveRight(const float cameraSpeed) { |
|
87 cameraPos += glm::normalize( |
|
88 glm::cross(cameraFront, cameraUp)) * cameraSpeed; |
|
89 } |
|
90 |
|
91 void moveUp(const float cameraSpeed) { |
|
92 cameraPos += cameraSpeed * glm::normalize(cameraUp); |
|
93 } |
|
94 |
|
95 void moveDown(const float cameraSpeed) { |
|
96 cameraPos -= cameraSpeed * glm::normalize(cameraUp); |
|
97 } |
|
98 |
|
99 void updateCameraFrontAndUp() { |
|
100 std::cerr << "--- updateCameraFrontAndUp() --------" << std::endl; |
|
101 dump("pitch, yaw, roll", glm::vec3(pitch, yaw, roll)); |
|
102 dump("cameraPos", cameraPos); |
|
103 dump("cameraFront", cameraFront); |
|
104 const auto pitchR = glm::radians(pitch); // around X axis |
|
105 const auto yawR = glm::radians(yaw); // around Y axis |
|
106 const auto rollR = glm::radians(roll); // around Z axis |
|
107 |
|
108 cameraFront.x = cos(pitchR) * cos(yawR); |
|
109 cameraFront.y = sin(pitchR); |
|
110 cameraFront.z = cos(pitchR) * sin(yawR); |
|
111 cameraFront = glm::normalize(cameraFront); |
|
112 dump("cameraFront", cameraFront); |
|
113 dump("cameraUp", cameraUp); |
|
114 |
|
115 // TODO: review ROLL rotation and default angle |
|
116 glm::mat4 rollMatrix = glm::rotate( |
|
117 glm::mat4(1.0f), rollR, cameraFront); |
|
118 cameraUp = glm::mat3(rollMatrix) * glm::vec3(0., 1., 0.); |
|
119 dump("cameraUp", cameraUp); |
|
120 std::cerr << "-------------------------------------" << std::endl; |
|
121 } |
|
122 |
|
123 void limitPitch() { |
|
124 if (pitch > +89.0f) pitch = +89.0f; |
|
125 if (pitch < -89.0f) pitch = -89.0f; |
|
126 } |
|
127 |
|
128 void turnLeft(const float angleSpeed) { |
|
129 yaw -= angleSpeed; |
|
130 updateCameraFrontAndUp(); |
|
131 } |
|
132 |
|
133 void turnRight(const float angleSpeed) { |
|
134 yaw += angleSpeed; |
|
135 updateCameraFrontAndUp(); |
|
136 } |
|
137 |
|
138 void turnUp(const float angleSpeed) { |
|
139 pitch += angleSpeed; |
|
140 limitPitch(); |
|
141 updateCameraFrontAndUp(); |
|
142 } |
|
143 |
|
144 void turnDown(const float angleSpeed) { |
|
145 pitch -= angleSpeed; |
|
146 limitPitch(); |
|
147 updateCameraFrontAndUp(); |
|
148 } |
|
149 |
|
150 void rollLeft(const float angleSpeed) { |
|
151 roll += angleSpeed; |
|
152 updateCameraFrontAndUp(); |
|
153 } |
|
154 |
|
155 void rollRight(const float angleSpeed) { |
|
156 roll -= angleSpeed; |
|
157 updateCameraFrontAndUp(); |
|
158 } |
|
159 |
|
160 } initialCtx, ctx; |
|
161 |
|
162 Display* dpy; |
|
163 Window win; |
|
164 XVisualInfo* vi; |
|
165 GLXContext glc; |
|
166 |
|
167 FileMonitor fileMonitor; |
|
168 std::vector<WatchedFile> watchedFiles; |
|
169 ImageLoader imageLoader; |
|
170 std::vector<std::shared_ptr<Shader>> shaders; |
|
171 std::shared_ptr<Program> shaderProgram; |
|
172 std::vector<std::shared_ptr<Texture>> textures; |
|
173 |
|
174 Configuration cfg; |
|
175 std::ostream& logOutput = std::cerr; |
|
176 |
|
177 Impl(Configuration cfg) : cfg(cfg) { |
|
178 } |
|
179 |
|
180 void run(); |
|
181 void clear(); |
|
182 void runShaders(); |
|
183 Window getRootWindow(Window defaultValue); |
|
184 void log(LogLevel level, std::string message); |
|
185 int setNonBlocking(int fd); |
|
186 void loadVertices(); |
|
187 void parametrizeTexture(std::shared_ptr<Texture> tex); |
|
188 bool reloadTexture(const std::string& fileName); |
|
189 void loadTextures(); |
|
190 void loadShaders(); |
|
191 void updateVariableLocations(); |
|
192 bool reloadShader(const std::string& fileName); |
|
193 void setTitle(const std::string& suffix = ""); |
|
194 static const std::string getDefaultFile(const std::string& relativePath); |
|
195 |
|
196 }; |
|
197 |
|
198 Shark::Shark(const Configuration& configuration) : |
|
199 impl(new Impl(configuration)) { |
|
200 } |
|
201 |
|
202 Shark::~Shark() { |
|
203 impl->textures.clear(); |
|
204 impl->shaders.clear(); |
|
205 impl->shaderProgram = nullptr; |
|
206 XFree(impl->vi); |
|
207 glXMakeCurrent(impl->dpy, None, NULL); |
|
208 glXDestroyContext(impl->dpy, impl->glc); |
|
209 XDestroyWindow(impl->dpy, impl->win); |
|
210 XCloseDisplay(impl->dpy); |
|
211 delete impl; |
|
212 // std::cerr << "~Shark()" << std::endl; |
|
213 } |
|
214 |
|
215 void Shark::Impl::setTitle(const std::string& suffix) { |
|
216 std::stringstream title; |
|
217 title << "ShaderShark"; |
|
218 if (suffix.size()) title << ": " << suffix.c_str(); |
|
219 XStoreName(dpy, win, title.str().c_str()); |
|
220 XFlush(dpy); |
|
221 } |
|
222 |
|
223 void Shark::run() { |
|
224 impl->run(); |
|
225 } |
|
226 |
|
227 void Shark::Impl::run() { |
|
228 dpy = XOpenDisplay(NULL); |
|
229 |
|
230 if (dpy == NULL) throw std::logic_error("Unable to connect to X server"); |
|
231 |
|
232 GLint att[] = {GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None}; |
|
233 vi = glXChooseVisual(dpy, 0, att); |
|
234 Window root = DefaultRootWindow(dpy); |
|
235 Window parent = cfg.rootWindow ? cfg.rootWindow : root; |
|
236 |
|
237 XSetWindowAttributes swa; |
|
238 swa.colormap = XCreateColormap(dpy, parent, vi->visual, AllocNone); |
|
239 swa.event_mask = ExposureMask | KeyPressMask | PointerMotionMask |
|
240 | ButtonPressMask |
|
241 | StructureNotifyMask; |
|
242 |
|
243 bool full = false; |
|
244 unsigned int width = 1600; |
|
245 unsigned int height = 1200; |
|
246 if (parent != root) { |
|
247 XWindowAttributes parentAttr; |
|
248 XGetWindowAttributes(dpy, parent, &parentAttr); |
|
249 width = parentAttr.width; |
|
250 height = parentAttr.height; |
|
251 } |
|
252 |
|
253 win = XCreateWindow( |
|
254 dpy, parent, 0, 0, width, height, 0, |
|
255 vi->depth, InputOutput, vi->visual, |
|
256 CWColormap | CWEventMask, &swa); |
|
257 |
|
258 XMapWindow(dpy, win); |
|
259 setTitle(); |
|
260 setX11PID(dpy, win); |
|
261 // XSetWindowBackground(dpy, win, 0) vs. glClearColor() |
|
262 |
|
263 glc = glXCreateContext(dpy, vi, NULL, GL_TRUE); |
|
264 glXMakeCurrent(dpy, win, glc); |
|
265 |
|
266 glEnable(GL_DEPTH_TEST); |
|
267 glEnable(GL_BLEND); |
|
268 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); |
|
269 |
|
270 clear(); |
|
271 glXSwapBuffers(dpy, win); |
|
272 |
|
273 |
|
274 // Load GLSL shaders: |
|
275 loadShaders(); |
|
276 loadTextures(); |
|
277 loadVertices(); |
|
278 |
|
279 auto toggleFullscreen = [&]() { |
|
280 full = setFullscreen(dpy, win, !full); |
|
281 }; |
|
282 |
|
283 auto resetView = [&]() { |
|
284 ctx = initialCtx; |
|
285 ctx.updateCameraFrontAndUp(); |
|
286 }; |
|
287 |
|
288 // root can reize our window |
|
289 // or we can listen to root resize and then resize our window ourselves |
|
290 bool listenToRootResizes = true; |
|
291 if (listenToRootResizes) XSelectInput(dpy, parent, StructureNotifyMask); |
|
292 |
|
293 bool keepRunningX11 = true; |
|
294 int x11fd = XConnectionNumber(dpy); |
|
295 EPoll epoll; |
|
296 epoll.add(x11fd); |
|
297 epoll.add(fileMonitor.getFD()); |
|
298 try { |
|
299 epoll.add(setNonBlocking(STDIN_FILENO)); |
|
300 } catch (const EPoll::Exception& e) { |
|
301 logOutput << "Will not monitor events on STDIN: " << e.what() << "\n"; |
|
302 } |
|
303 |
|
304 // rended the 3D scene even before the first event: |
|
305 runShaders(); |
|
306 glXSwapBuffers(dpy, win); |
|
307 |
|
308 for (XEvent xev; keepRunningX11;) { |
|
309 int epollEventCount = epoll.wait(); |
|
310 //std::cout << "trace: epoll.wait() = " << epollEventCount << std::endl; |
|
311 for (int epollEvent = 0; epollEvent < epollEventCount; epollEvent++) { |
|
312 bool redraw = false; |
|
313 if (epoll[epollEvent].data.fd == x11fd) { |
|
314 if (!XPending(dpy)) { |
|
315 // otherwise STDIN events are held until the first X11 event |
|
316 logOutput << "trace: no pending X11 event" << std::endl; |
|
317 break; |
|
318 } |
|
319 process_x11_event: |
|
320 XWindowAttributes gwa; |
|
321 XNextEvent(dpy, &xev); |
|
322 |
|
323 if (xev.type == Expose) { |
|
324 std::cout << "XEvent: Expose" << std::endl; |
|
325 XGetWindowAttributes(dpy, win, &gwa); |
|
326 glViewport(0, 0, gwa.width, gwa.height); |
|
327 redraw = true; |
|
328 } else if (xev.type == KeyPress) { |
|
329 DecodedKey key = decodeKeycode(dpy, xev.xkey.keycode); |
|
330 std::cout << "XEvent: KeyPress:" |
|
331 << " keycode=" << key.code |
|
332 << " key=" << key.name |
|
333 << std::endl; |
|
334 |
|
335 const float cSp = 0.05f; // camera speed |
|
336 const float aSp = 5.f; // angle speed |
|
337 |
|
338 if (key.matches(XK_q, XK_Escape)) keepRunningX11 = false; |
|
339 else if (key.matches(XK_Left, XK_s)) ctx.turnLeft(aSp); |
|
340 else if (key.matches(XK_Right, XK_f)) ctx.turnRight(aSp); |
|
341 else if (key.matches(XK_Up, XK_e)) ctx.moveForward(cSp); |
|
342 else if (key.matches(XK_Down, XK_d)) ctx.moveBackward(cSp); |
|
343 else if (key.matches(XK_w)) ctx.rollLeft(aSp); |
|
344 else if (key.matches(XK_r)) ctx.rollRight(aSp); |
|
345 else if (key.matches(XK_t)) ctx.turnUp(aSp); |
|
346 else if (key.matches(XK_g)) ctx.turnDown(aSp); |
|
347 else if (key.matches(XK_m)) ctx.moveLeft(cSp); |
|
348 else if (key.matches(XK_comma)) ctx.moveRight(cSp); |
|
349 else if (key.matches(XK_l)) ctx.moveUp(cSp); |
|
350 else if (key.matches(XK_period)) ctx.moveDown(cSp); |
|
351 else if (key.matches(XK_j)) ctx.moveLeft(cSp * 5); |
|
352 else if (key.matches(XK_k)) ctx.moveRight(cSp * 5); |
|
353 else if (key.matches(XK_u)) ctx.moveLeft(cSp * 10); |
|
354 else if (key.matches(XK_i)) ctx.moveRight(cSp * 10); |
|
355 else if (key.matches(XK_x)) resetView(); |
|
356 else if (key.matches(XK_F11, XK_y)) toggleFullscreen(); |
|
357 redraw = true; |
|
358 } else if (xev.type == ButtonPress) { |
|
359 std::cout << "XEvent: ButtonPress:" |
|
360 << " button=" << xev.xbutton.button |
|
361 << std::endl; |
|
362 if (xev.xbutton.button == 1); |
|
363 else if (xev.xbutton.button == 4) ctx.adjustFov(-1.0); |
|
364 else if (xev.xbutton.button == 5) ctx.adjustFov(+1.0); |
|
365 else if (xev.xbutton.button == 8) resetView(); |
|
366 else if (xev.xbutton.button == 9) keepRunningX11 = false; |
|
367 redraw = true; |
|
368 } else if (xev.type == MotionNotify) { |
|
369 // printCursorInfo(xev.xmotion); |
|
370 } else if (xev.type == ConfigureNotify) { |
|
371 std::cout << "XEvent: ConfigureNotify:" |
|
372 << " window=" << xev.xconfigure.window |
|
373 << " height=" << xev.xconfigure.height |
|
374 << " width=" << xev.xconfigure.width |
|
375 << std::endl; |
|
376 if (listenToRootResizes |
|
377 && xev.xconfigure.window == parent) { |
|
378 XResizeWindow(dpy, win, |
|
379 xev.xconfigure.width, xev.xconfigure.height); |
|
380 } |
|
381 } else if (xev.type == UnmapNotify) { |
|
382 std::cout << "XEvent: UnmapNotify" << std::endl; |
|
383 } else if (xev.type == DestroyNotify) { |
|
384 std::cout << "XEvent: DestroyNotify → finish" << std::endl; |
|
385 break; |
|
386 } else { |
|
387 std::cout << "XEvent: type=" << xev.type << std::endl; |
|
388 } |
|
389 if (XPending(dpy)) goto process_x11_event; |
|
390 } else if (epoll[epollEvent].data.fd == STDIN_FILENO) { |
|
391 int epollFD = epoll[epollEvent].data.fd; |
|
392 logOutput << "other event: fd=" << epollFD << " data="; |
|
393 for (char ch; read(epollFD, &ch, 1) > 0;) { |
|
394 std::stringstream msg; |
|
395 msg |
|
396 << std::hex |
|
397 << std::setfill('0') |
|
398 << std::setw(2) |
|
399 << (int) ch; |
|
400 logOutput << msg.str(); |
|
401 } |
|
402 logOutput << std::endl; |
|
403 |
|
404 } else if (epoll[epollEvent].data.fd == fileMonitor.getFD()) { |
|
405 std::cout << "FileMonitor event:" << std::endl; |
|
406 for (FileEvent fe; fileMonitor.readEvent(fe);) { |
|
407 logOutput << " " |
|
408 << " file=" << fe.fileName |
|
409 << " mask=" << fe.mask |
|
410 << std::endl; |
|
411 try { |
|
412 redraw |= reloadTexture(fe.fileName); |
|
413 redraw |= reloadShader(fe.fileName); |
|
414 setTitle(); |
|
415 } catch (const std::exception& e) { |
|
416 setTitle("[ERROR]"); |
|
417 logOutput << "error while reloading '" |
|
418 << fe.fileName.c_str() |
|
419 << "': " << e.what() << std::endl; |
|
420 } |
|
421 } |
|
422 } else { |
|
423 logOutput |
|
424 << "error: event on an unexpected FD: " |
|
425 << epoll[epollEvent].data.fd |
|
426 << std::endl; |
|
427 } |
|
428 |
|
429 if (redraw) { |
|
430 runShaders(); |
|
431 glXSwapBuffers(dpy, win); |
|
432 } |
|
433 } |
|
434 } |
|
435 } |
|
436 |
|
437 void Shark::Impl::clear() { |
|
438 glClearColor( |
|
439 (cfg.backgroundColor >> 16 & 0xFF) / 256., |
|
440 (cfg.backgroundColor >> 8 & 0xFF) / 256., |
|
441 (cfg.backgroundColor & 0xFF) / 256., |
|
442 1.0); |
|
443 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
|
444 } |
|
445 |
|
446 void Shark::Impl::runShaders() { |
|
447 shaderProgram->use(); |
|
448 checkError(&std::cerr); |
|
449 |
|
450 clear(); |
|
451 |
|
452 GLint viewport[4]; |
|
453 glGetIntegerv(GL_VIEWPORT, viewport); |
|
454 GLfloat width = viewport[2]; |
|
455 GLfloat height = viewport[3]; |
|
456 |
|
457 glm::mat4 projection = glm::perspective( |
|
458 glm::radians(ctx.fov), |
|
459 width / height, |
|
460 0.1f, 100.0f); |
|
461 glUniformMatrix4fv(ProgAttr.uProjection, 1, GL_FALSE, &projection[0][0]); |
|
462 |
|
463 glm::mat4 view = glm::lookAt( |
|
464 ctx.cameraPos, |
|
465 ctx.cameraPos + ctx.cameraFront, |
|
466 ctx.cameraUp); |
|
467 glUniformMatrix4fv(ProgAttr.uView, 1, GL_FALSE, &view[0][0]); |
|
468 |
|
469 // glBindVertexArray(vao); |
|
470 |
|
471 glm::mat4 model = glm::mat4(1.0f); // identity matrix |
|
472 glUniformMatrix4fv(ProgAttr.uModel, 1, GL_FALSE, &model[0][0]); |
|
473 |
|
474 // TODO: draw a rectangle for each texture |
|
475 glUniform1f(ProgAttr.uTextureScale, textures[0]->getScale()); |
|
476 |
|
477 glDrawArrays(GL_TRIANGLES, 0, 2 * 3); // see loadVertices() |
|
478 std::cerr << "GLSL: glDrawArrays()" << std::endl; |
|
479 } |
|
480 |
|
481 void Shark::Impl::log(LogLevel level, std::string message) { |
|
482 ::log(logOutput, level, message); |
|
483 } |
|
484 |
|
485 int Shark::Impl::setNonBlocking(int fd) { |
|
486 int flags = fcntl(fd, F_GETFL, 0); |
|
487 fcntl(fd, F_SETFL, flags | O_NONBLOCK); |
|
488 return fd; |
|
489 } |
|
490 |
|
491 void Shark::Impl::loadVertices() { |
|
492 for (int i = 0; i < textures.size(); i++) { |
|
493 std::shared_ptr<Texture> tex = textures[i]; |
|
494 // TODO: draw a rectangle for each texture |
|
495 GLfloat ratio = tex->getRatio(); |
|
496 const std::vector<GLfloat> vertices = { |
|
497 // Vertex XYZ Texture XY |
|
498 -0.80f * ratio, +0.80f, +0.0, /**/ 0.0, 0.0, |
|
499 +0.80f * ratio, +0.80f, +0.0, /**/ 1.0, 0.0, |
|
500 -0.80f * ratio, -0.80f, +0.0, /**/ 0.0, 1.0, |
|
501 |
|
502 -0.80f * ratio, -0.80f, +0.0, /**/ 0.0, 1.0, |
|
503 +0.80f * ratio, -0.80f, +0.0, /**/ 1.0, 1.0, |
|
504 +0.80f * ratio, +0.80f, +0.0, /**/ 1.0, 0.0, |
|
505 |
|
506 // see glDrawArrays(), where we set start offset and count |
|
507 }; |
|
508 |
|
509 // Vertex data: |
|
510 glVertexAttribPointer(ProgAttr.aVertexXYZ, 3, // vertex items |
|
511 GL_FLOAT, GL_FALSE, 5 * sizeof (float), |
|
512 (void*) 0); |
|
513 glEnableVertexAttribArray(ProgAttr.aVertexXYZ); |
|
514 |
|
515 // Texture positions: |
|
516 glVertexAttribPointer(ProgAttr.aTextureXY, 2, // texture items |
|
517 GL_FLOAT, GL_FALSE, 5 * sizeof (float), |
|
518 (void*) (3 * sizeof (float))); |
|
519 glEnableVertexAttribArray(ProgAttr.aTextureXY); |
|
520 |
|
521 glBufferData(GL_ARRAY_BUFFER, |
|
522 vertices.size() * sizeof (vertices[0]), |
|
523 vertices.data(), |
|
524 GL_STATIC_DRAW); |
|
525 // GL_STATIC_DRAW: |
|
526 // The vertex data will be uploaded once |
|
527 // and drawn many times(e.g. the world). |
|
528 // GL_DYNAMIC_DRAW: |
|
529 // The vertex data will be created once, changed from |
|
530 // time to time, but drawn many times more than that. |
|
531 // GL_STREAM_DRAW: |
|
532 // The vertex data will be uploaded once and drawn once. |
|
533 |
|
534 // see also glBindBuffer(GL_ARRAY_BUFFER, vbo); where we set current VBO |
|
535 } |
|
536 } |
|
537 |
|
538 const std::string |
|
539 Shark::Impl::getDefaultFile(const std::string& relativePath) { |
|
540 const char* envName = "SHADER_SHARK_DATA_DIR"; |
|
541 const char* envValue = ::getenv(envName); |
|
542 if (envValue) { |
|
543 return std::string(envValue) + "/" + relativePath; |
|
544 } else { |
|
545 throw std::invalid_argument(std::string("Configure $") + envName |
|
546 + " in order to use defaults" |
|
547 " or specify textures and shaders as parameters"); |
|
548 } |
|
549 } |
|
550 |
|
551 void Shark::Impl::parametrizeTexture(std::shared_ptr<Texture> tex) { |
|
552 XAttrs xa(tex->getFileName()); |
|
553 std::string magf = xa["shader-shark.texture.mag-filter"]; |
|
554 std::string minf = xa["shader-shark.texture.min-filter"]; |
|
555 std::string scale = xa["shader-shark.texture.scale"]; |
|
556 |
|
557 auto GLT2D = GL_TEXTURE_2D; |
|
558 auto MAG = GL_TEXTURE_MAG_FILTER; |
|
559 auto MIN = GL_TEXTURE_MIN_FILTER; |
|
560 |
|
561 if (magf == "linear") glTexParameteri(GLT2D, MAG, GL_LINEAR); |
|
562 else if (magf == "nearest") glTexParameteri(GLT2D, MAG, GL_NEAREST); |
|
563 |
|
564 if (minf == "linear") glTexParameteri(GLT2D, MIN, GL_LINEAR); |
|
565 else if (minf == "nearest") glTexParameteri(GLT2D, MIN, GL_NEAREST); |
|
566 |
|
567 if (scale.size()) { |
|
568 float sc; |
|
569 if (std::from_chars(scale.data(), scale.data() + scale.size(), sc).ec |
|
570 == std::errc{}) tex->setScale(sc); |
|
571 else std::cerr << "Invalid texture scale value - expecting float\n"; |
|
572 // tex->setScale(std::stof(scale)); // locale-dependent (. vs ,) |
|
573 } |
|
574 } |
|
575 |
|
576 void Shark::Impl::loadTextures() { |
|
577 // Load default texture if there is no configured: |
|
578 if (cfg.textures.empty()) |
|
579 cfg.textures.push_back({getDefaultFile("textures/default.png")}); |
|
580 |
|
581 for (const Configuration::Texture& tex : cfg.textures) { |
|
582 std::shared_ptr<ImageLoader::ImageBuffer> |
|
583 img(imageLoader.loadImage(MappedFile(tex.fileName))); |
|
584 textures.push_back(std::make_shared<Texture>( |
|
585 img->width, img->height, *img, tex.fileName)); |
|
586 parametrizeTexture(textures.back()); |
|
587 // static const uint32_t watchMask = IN_CLOSE_WRITE | IN_ATTRIB; |
|
588 // watchedFiles.push_back(fileMonitor.watch(tex.fileName, watchMask)); |
|
589 watchedFiles.push_back(fileMonitor.watch(tex.fileName)); |
|
590 // TODO: review texture loading and binding |
|
591 // works even without this - default texture |
|
592 // glUniform1i(..., ...); |
|
593 // checkError(&std::cerr); |
|
594 } |
|
595 } |
|
596 |
|
597 bool Shark::Impl::reloadTexture(const std::string& fileName) { |
|
598 for (std::shared_ptr<Texture> tex : textures) { |
|
599 if (tex->getFileName() == fileName) { |
|
600 std::shared_ptr<ImageLoader::ImageBuffer> |
|
601 img(imageLoader.loadImage(MappedFile(fileName))); |
|
602 tex->update(img->width, img->height, *img); |
|
603 parametrizeTexture(tex); |
|
604 loadVertices(); |
|
605 return true; |
|
606 } |
|
607 } |
|
608 return false; |
|
609 } |
|
610 |
|
611 void Shark::Impl::loadShaders() { |
|
612 // Vertex Array Object (VAO) |
|
613 GLuint vao; |
|
614 glGenVertexArrays(1, &vao); |
|
615 glBindVertexArray(vao); |
|
616 // VAO - something like context for bound data/variables |
|
617 // We can switch multiple VAOs. VAO can contain multiple VBOs. |
|
618 // what-are-vertex-array-objects |
|
619 |
|
620 // Vertex Buffer Object (VBO): |
|
621 GLuint vbo; |
|
622 glGenBuffers(1, &vbo); |
|
623 glBindBuffer(GL_ARRAY_BUFFER, vbo); |
|
624 |
|
625 { |
|
626 // Load default shaders if there are no configured: |
|
627 int vc = 0; |
|
628 int fc = 0; |
|
629 auto& ss = cfg.shaders; |
|
630 for (const auto& s : ss) if (s.type == "vertex") vc++; |
|
631 for (const auto& s : ss) if (s.type == "fragment") fc++; |
|
632 auto& d = getDefaultFile; |
|
633 if (vc == 0) ss.push_back({d("shaders/default.vert"), "vertex"}); |
|
634 if (fc == 0) ss.push_back({d("shaders/default.frag"), "fragment"}); |
|
635 } |
|
636 |
|
637 shaderProgram = std::make_shared<Program>(); |
|
638 |
|
639 // glBindFragDataLocation(program, 0, "outColor"); |
|
640 // glBindAttribLocation(program, LOC.input, "vertices"); |
|
641 |
|
642 for (const Configuration::Shader definition : cfg.shaders) { |
|
643 Shader::Type type; |
|
644 std::string fileName = definition.fileName; |
|
645 if (definition.type == "fragment") type = Shader::Type::FRAGMENT; |
|
646 else if (definition.type == "vertex") type = Shader::Type::VERTEX; |
|
647 else throw std::invalid_argument("unsupported shader type"); |
|
648 |
|
649 MappedFile file(fileName); |
|
650 std::shared_ptr<Shader> shader = std::make_shared<Shader>( |
|
651 type, file, fileName); |
|
652 |
|
653 shaderProgram->attachShader(*shader.get()); |
|
654 shaders.push_back(shader); |
|
655 watchedFiles.push_back(fileMonitor.watch(fileName)); |
|
656 std::cerr << "GLSL loaded: " << fileName.c_str() << std::endl; |
|
657 // We may detach and delete shaders, |
|
658 // but our shaders are small, so we keep them for later reloading. |
|
659 } |
|
660 |
|
661 shaderProgram->link(); |
|
662 updateVariableLocations(); |
|
663 // listVariables(program); |
|
664 std::cerr << "GLSL shader count: " << shaders.size() << std::endl; |
|
665 } |
|
666 |
|
667 void Shark::Impl::updateVariableLocations() { |
|
668 // GLSL compiler does very efficient / aggressive optimization. |
|
669 // Attributes and uniforms that are not used in the shader are deleted. |
|
670 // And even if we e.g. read color from a texture and overwrite it, |
|
671 // the variable is still deleted and considered „inactive“. |
|
672 // Functions glGetAttribLocation() and glGetUniformLocation() return -1. |
|
673 ProgAttr.aVertexXYZ = shaderProgram->getAttribLocation("aVertexXYZ"); |
|
674 ProgAttr.aTextureXY = shaderProgram->getAttribLocation("aTextureXY"); |
|
675 ProgAttr.uModel = shaderProgram->getUniformLocation("uModel"); |
|
676 ProgAttr.uView = shaderProgram->getUniformLocation("uView"); |
|
677 ProgAttr.uProjection = shaderProgram->getUniformLocation("uProjection"); |
|
678 ProgAttr.uTexture = shaderProgram->getUniformLocation("uTexture"); |
|
679 ProgAttr.uTextureScale = shaderProgram->getUniformLocation("uTextureScale"); |
|
680 ProgAttr.fColor = shaderProgram->getFragDataLocation("fColor"); |
|
681 shaderProgram->bindFragDataLocation("fColor", ProgAttr.fColor); |
|
682 } |
|
683 |
|
684 bool Shark::Impl::reloadShader(const std::string& fileName) { |
|
685 for (auto shader : shaders) { |
|
686 if (shader->getFileName() == fileName) { |
|
687 shader->update(MappedFile(fileName)); |
|
688 shaderProgram->link(); |
|
689 updateVariableLocations(); |
|
690 return true; |
|
691 } |
|
692 } |
|
693 return false; |
|
694 } |
|