Shark.cpp
branchv_0
changeset 29 dc3c102e1264
parent 28 4cbd9c0beb4c
child 30 02972f051744
equal deleted inserted replaced
28:4cbd9c0beb4c 29:dc3c102e1264
     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 }