# HG changeset patch # User František Kučera # Date 1703638238 -3600 # Node ID dc3c102e12648c01777fc2c5e15742208dbc5283 # Parent 4cbd9c0beb4ca56100ab722ea3c8b64ee21b2e0a derive OHP3D from ShaderShark diff -r 4cbd9c0beb4c -r dc3c102e1264 AllocatedBuffer.h --- a/AllocatedBuffer.h Tue Dec 26 23:46:45 2023 +0100 +++ b/AllocatedBuffer.h Wed Dec 27 01:50:38 2023 +0100 @@ -1,5 +1,5 @@ /** - * ShaderShark + * OHP3D * Copyright © 2023 František Kučera (Frantovo.cz, GlobalCode.info) * * This program is free software: you can redistribute it and/or modify diff -r 4cbd9c0beb4c -r dc3c102e1264 Buffer.h --- a/Buffer.h Tue Dec 26 23:46:45 2023 +0100 +++ b/Buffer.h Wed Dec 27 01:50:38 2023 +0100 @@ -1,5 +1,5 @@ /** - * ShaderShark + * OHP3D * Copyright © 2023 František Kučera (Frantovo.cz, GlobalCode.info) * * This program is free software: you can redistribute it and/or modify diff -r 4cbd9c0beb4c -r dc3c102e1264 CLIParser.h --- a/CLIParser.h Tue Dec 26 23:46:45 2023 +0100 +++ b/CLIParser.h Wed Dec 27 01:50:38 2023 +0100 @@ -1,5 +1,5 @@ /** - * ShaderShark + * OHP3D * Copyright © 2023 František Kučera (Frantovo.cz, GlobalCode.info) * * This program is free software: you can redistribute it and/or modify diff -r 4cbd9c0beb4c -r dc3c102e1264 Configuration.h --- a/Configuration.h Tue Dec 26 23:46:45 2023 +0100 +++ b/Configuration.h Wed Dec 27 01:50:38 2023 +0100 @@ -1,5 +1,5 @@ /** - * ShaderShark + * OHP3D * Copyright © 2023 František Kučera (Frantovo.cz, GlobalCode.info) * * This program is free software: you can redistribute it and/or modify diff -r 4cbd9c0beb4c -r dc3c102e1264 EPoll.h --- a/EPoll.h Tue Dec 26 23:46:45 2023 +0100 +++ b/EPoll.h Wed Dec 27 01:50:38 2023 +0100 @@ -1,5 +1,5 @@ /** - * ShaderShark + * OHP3D * Copyright © 2023 František Kučera (Frantovo.cz, GlobalCode.info) * * This program is free software: you can redistribute it and/or modify diff -r 4cbd9c0beb4c -r dc3c102e1264 FileMonitor.cpp --- a/FileMonitor.cpp Tue Dec 26 23:46:45 2023 +0100 +++ b/FileMonitor.cpp Wed Dec 27 01:50:38 2023 +0100 @@ -1,5 +1,5 @@ /** - * ShaderShark + * OHP3D * Copyright © 2023 František Kučera (Frantovo.cz, GlobalCode.info) * * This program is free software: you can redistribute it and/or modify diff -r 4cbd9c0beb4c -r dc3c102e1264 FileMonitor.h --- a/FileMonitor.h Tue Dec 26 23:46:45 2023 +0100 +++ b/FileMonitor.h Wed Dec 27 01:50:38 2023 +0100 @@ -1,5 +1,5 @@ /** - * ShaderShark + * OHP3D * Copyright © 2023 František Kučera (Frantovo.cz, GlobalCode.info) * * This program is free software: you can redistribute it and/or modify diff -r 4cbd9c0beb4c -r dc3c102e1264 ImageLoader.cpp --- a/ImageLoader.cpp Tue Dec 26 23:46:45 2023 +0100 +++ b/ImageLoader.cpp Wed Dec 27 01:50:38 2023 +0100 @@ -1,5 +1,5 @@ /** - * ShaderShark + * OHP3D * Copyright © 2023 František Kučera (Frantovo.cz, GlobalCode.info) * * This program is free software: you can redistribute it and/or modify diff -r 4cbd9c0beb4c -r dc3c102e1264 ImageLoader.h --- a/ImageLoader.h Tue Dec 26 23:46:45 2023 +0100 +++ b/ImageLoader.h Wed Dec 27 01:50:38 2023 +0100 @@ -1,5 +1,5 @@ /** - * ShaderShark + * OHP3D * Copyright © 2023 František Kučera (Frantovo.cz, GlobalCode.info) * * This program is free software: you can redistribute it and/or modify diff -r 4cbd9c0beb4c -r dc3c102e1264 Logger.h --- a/Logger.h Tue Dec 26 23:46:45 2023 +0100 +++ b/Logger.h Wed Dec 27 01:50:38 2023 +0100 @@ -1,5 +1,5 @@ /** - * ShaderShark + * OHP3D * Copyright © 2023 František Kučera (Frantovo.cz, GlobalCode.info) * * This program is free software: you can redistribute it and/or modify diff -r 4cbd9c0beb4c -r dc3c102e1264 Makefile --- a/Makefile Tue Dec 26 23:46:45 2023 +0100 +++ b/Makefile Wed Dec 27 01:50:38 2023 +0100 @@ -1,4 +1,4 @@ -# ShaderShark +# OHP3D # Copyright © 2023 František Kučera (Frantovo.cz, GlobalCode.info) # # This program is free software: you can redistribute it and/or modify @@ -13,19 +13,19 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -all: build/shader-shark +all: build/ohp3d .PHONY: all clean run clean: rm -rf build -run: build/shader-shark - SHADER_SHARK_DATA_DIR=. $(<) +run: build/ohp3d + OHP3D_DATA_DIR=. $(<) SRC= \ - Shark.cpp \ - shader-shark.cpp \ + OHP3D.cpp \ + ohp3d.cpp \ ImageLoader.cpp \ Texture.cpp \ Shader.cpp \ @@ -33,7 +33,7 @@ XAttrs.cpp \ FileMonitor.cpp -build/shader-shark: $(SRC) *.h +build/ohp3d: $(SRC) *.h mkdir -p build $(CXX) -std=c++20 -g -o $(@) $(SRC) $$(pkg-config --cflags --libs \ epoxy x11 glu glm Magick++) diff -r 4cbd9c0beb4c -r dc3c102e1264 MappedFile.h --- a/MappedFile.h Tue Dec 26 23:46:45 2023 +0100 +++ b/MappedFile.h Wed Dec 27 01:50:38 2023 +0100 @@ -1,5 +1,5 @@ /** - * ShaderShark + * OHP3D * Copyright © 2023 František Kučera (Frantovo.cz, GlobalCode.info) * * This program is free software: you can redistribute it and/or modify diff -r 4cbd9c0beb4c -r dc3c102e1264 OHP3D.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OHP3D.cpp Wed Dec 27 01:50:38 2023 +0100 @@ -0,0 +1,694 @@ +/** + * OHP3D + * Copyright © 2023 František Kučera (Frantovo.cz, GlobalCode.info) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "x11.h" +#include "opengl.h" +#include "EPoll.h" +#include "Logger.h" +#include "MappedFile.h" +#include "ImageLoader.h" +#include "Texture.h" +#include "Shader.h" +#include "Program.h" +#include "FileMonitor.h" +#include "XAttrs.h" + +#include "OHP3D.h" + +class OHP3D::Impl { +public: + + struct { + GLint aVertexXYZ = -2; + GLint aTextureXY = -2; + + GLint fColor = -2; + + GLint uModel = -2; + GLint uView = -2; + GLint uProjection = -2; + GLint uTexture = -2; + GLint uTextureScale = -2; + } ProgAttr; + + struct { + float yaw = -90.f; + float pitch = 0.f; + float roll = 0.f; + float fov = 45.0f; + glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f); + glm::vec3 cameraFront = glm::vec3(0.0f, 0.0f, -1.0f); + glm::vec3 cameraUp = glm::vec3(0.0f, 1.0f, 0.0f); + + void adjustFov(float diff) { + fov += diff; + if (fov < 1.0f) fov = 1.0f; + else if (fov > 120.0f) fov = 120.0f; + std::cerr << "field of view: " << fov << " °" << std::endl; + } + + void moveForward(const float cameraSpeed) { + cameraPos += cameraSpeed * cameraFront; + } + + void moveBackward(const float cameraSpeed) { + cameraPos -= cameraSpeed * cameraFront; + } + + void moveLeft(const float cameraSpeed) { + cameraPos -= glm::normalize( + glm::cross(cameraFront, cameraUp)) * cameraSpeed; + } + + void moveRight(const float cameraSpeed) { + cameraPos += glm::normalize( + glm::cross(cameraFront, cameraUp)) * cameraSpeed; + } + + void moveUp(const float cameraSpeed) { + cameraPos += cameraSpeed * glm::normalize(cameraUp); + } + + void moveDown(const float cameraSpeed) { + cameraPos -= cameraSpeed * glm::normalize(cameraUp); + } + + void updateCameraFrontAndUp() { + std::cerr << "--- updateCameraFrontAndUp() --------" << std::endl; + dump("pitch, yaw, roll", glm::vec3(pitch, yaw, roll)); + dump("cameraPos", cameraPos); + dump("cameraFront", cameraFront); + const auto pitchR = glm::radians(pitch); // around X axis + const auto yawR = glm::radians(yaw); // around Y axis + const auto rollR = glm::radians(roll); // around Z axis + + cameraFront.x = cos(pitchR) * cos(yawR); + cameraFront.y = sin(pitchR); + cameraFront.z = cos(pitchR) * sin(yawR); + cameraFront = glm::normalize(cameraFront); + dump("cameraFront", cameraFront); + dump("cameraUp", cameraUp); + + // TODO: review ROLL rotation and default angle + glm::mat4 rollMatrix = glm::rotate( + glm::mat4(1.0f), rollR, cameraFront); + cameraUp = glm::mat3(rollMatrix) * glm::vec3(0., 1., 0.); + dump("cameraUp", cameraUp); + std::cerr << "-------------------------------------" << std::endl; + } + + void limitPitch() { + if (pitch > +89.0f) pitch = +89.0f; + if (pitch < -89.0f) pitch = -89.0f; + } + + void turnLeft(const float angleSpeed) { + yaw -= angleSpeed; + updateCameraFrontAndUp(); + } + + void turnRight(const float angleSpeed) { + yaw += angleSpeed; + updateCameraFrontAndUp(); + } + + void turnUp(const float angleSpeed) { + pitch += angleSpeed; + limitPitch(); + updateCameraFrontAndUp(); + } + + void turnDown(const float angleSpeed) { + pitch -= angleSpeed; + limitPitch(); + updateCameraFrontAndUp(); + } + + void rollLeft(const float angleSpeed) { + roll += angleSpeed; + updateCameraFrontAndUp(); + } + + void rollRight(const float angleSpeed) { + roll -= angleSpeed; + updateCameraFrontAndUp(); + } + + } initialCtx, ctx; + + Display* dpy; + Window win; + XVisualInfo* vi; + GLXContext glc; + + FileMonitor fileMonitor; + std::vector watchedFiles; + ImageLoader imageLoader; + std::vector> shaders; + std::shared_ptr shaderProgram; + std::vector> textures; + + Configuration cfg; + std::ostream& logOutput = std::cerr; + + Impl(Configuration cfg) : cfg(cfg) { + } + + void run(); + void clear(); + void runShaders(); + Window getRootWindow(Window defaultValue); + void log(LogLevel level, std::string message); + int setNonBlocking(int fd); + void loadVertices(); + void parametrizeTexture(std::shared_ptr tex); + bool reloadTexture(const std::string& fileName); + void loadTextures(); + void loadShaders(); + void updateVariableLocations(); + bool reloadShader(const std::string& fileName); + void setTitle(const std::string& suffix = ""); + static const std::string getDefaultFile(const std::string& relativePath); + +}; + +OHP3D::OHP3D(const Configuration& configuration) : +impl(new Impl(configuration)) { +} + +OHP3D::~OHP3D() { + impl->textures.clear(); + impl->shaders.clear(); + impl->shaderProgram = nullptr; + XFree(impl->vi); + glXMakeCurrent(impl->dpy, None, NULL); + glXDestroyContext(impl->dpy, impl->glc); + XDestroyWindow(impl->dpy, impl->win); + XCloseDisplay(impl->dpy); + delete impl; + // std::cerr << "~OHP3D()" << std::endl; +} + +void OHP3D::Impl::setTitle(const std::string& suffix) { + std::stringstream title; + title << "OHP3D"; + if (suffix.size()) title << ": " << suffix.c_str(); + XStoreName(dpy, win, title.str().c_str()); + XFlush(dpy); +} + +void OHP3D::run() { + impl->run(); +} + +void OHP3D::Impl::run() { + dpy = XOpenDisplay(NULL); + + if (dpy == NULL) throw std::logic_error("Unable to connect to X server"); + + GLint att[] = {GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None}; + vi = glXChooseVisual(dpy, 0, att); + Window root = DefaultRootWindow(dpy); + Window parent = cfg.rootWindow ? cfg.rootWindow : root; + + XSetWindowAttributes swa; + swa.colormap = XCreateColormap(dpy, parent, vi->visual, AllocNone); + swa.event_mask = ExposureMask | KeyPressMask | PointerMotionMask + | ButtonPressMask + | StructureNotifyMask; + + bool full = false; + unsigned int width = 1600; + unsigned int height = 1200; + if (parent != root) { + XWindowAttributes parentAttr; + XGetWindowAttributes(dpy, parent, &parentAttr); + width = parentAttr.width; + height = parentAttr.height; + } + + win = XCreateWindow( + dpy, parent, 0, 0, width, height, 0, + vi->depth, InputOutput, vi->visual, + CWColormap | CWEventMask, &swa); + + XMapWindow(dpy, win); + setTitle(); + setX11PID(dpy, win); + // XSetWindowBackground(dpy, win, 0) vs. glClearColor() + + glc = glXCreateContext(dpy, vi, NULL, GL_TRUE); + glXMakeCurrent(dpy, win, glc); + + glEnable(GL_DEPTH_TEST); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + clear(); + glXSwapBuffers(dpy, win); + + + // Load GLSL shaders: + loadShaders(); + loadTextures(); + loadVertices(); + + auto toggleFullscreen = [&]() { + full = setFullscreen(dpy, win, !full); + }; + + auto resetView = [&]() { + ctx = initialCtx; + ctx.updateCameraFrontAndUp(); + }; + + // root can reize our window + // or we can listen to root resize and then resize our window ourselves + bool listenToRootResizes = true; + if (listenToRootResizes) XSelectInput(dpy, parent, StructureNotifyMask); + + bool keepRunningX11 = true; + int x11fd = XConnectionNumber(dpy); + EPoll epoll; + epoll.add(x11fd); + epoll.add(fileMonitor.getFD()); + try { + epoll.add(setNonBlocking(STDIN_FILENO)); + } catch (const EPoll::Exception& e) { + logOutput << "Will not monitor events on STDIN: " << e.what() << "\n"; + } + + // rended the 3D scene even before the first event: + runShaders(); + glXSwapBuffers(dpy, win); + + for (XEvent xev; keepRunningX11;) { + int epollEventCount = epoll.wait(); + //std::cout << "trace: epoll.wait() = " << epollEventCount << std::endl; + for (int epollEvent = 0; epollEvent < epollEventCount; epollEvent++) { + bool redraw = false; + if (epoll[epollEvent].data.fd == x11fd) { + if (!XPending(dpy)) { + // otherwise STDIN events are held until the first X11 event + logOutput << "trace: no pending X11 event" << std::endl; + break; + } +process_x11_event: + XWindowAttributes gwa; + XNextEvent(dpy, &xev); + + if (xev.type == Expose) { + std::cout << "XEvent: Expose" << std::endl; + XGetWindowAttributes(dpy, win, &gwa); + glViewport(0, 0, gwa.width, gwa.height); + redraw = true; + } else if (xev.type == KeyPress) { + DecodedKey key = decodeKeycode(dpy, xev.xkey.keycode); + std::cout << "XEvent: KeyPress:" + << " keycode=" << key.code + << " key=" << key.name + << std::endl; + + const float cSp = 0.05f; // camera speed + const float aSp = 5.f; // angle speed + + if (key.matches(XK_q, XK_Escape)) keepRunningX11 = false; + else if (key.matches(XK_Left, XK_s)) ctx.turnLeft(aSp); + else if (key.matches(XK_Right, XK_f)) ctx.turnRight(aSp); + else if (key.matches(XK_Up, XK_e)) ctx.moveForward(cSp); + else if (key.matches(XK_Down, XK_d)) ctx.moveBackward(cSp); + else if (key.matches(XK_w)) ctx.rollLeft(aSp); + else if (key.matches(XK_r)) ctx.rollRight(aSp); + else if (key.matches(XK_t)) ctx.turnUp(aSp); + else if (key.matches(XK_g)) ctx.turnDown(aSp); + else if (key.matches(XK_m)) ctx.moveLeft(cSp); + else if (key.matches(XK_comma)) ctx.moveRight(cSp); + else if (key.matches(XK_l)) ctx.moveUp(cSp); + else if (key.matches(XK_period)) ctx.moveDown(cSp); + else if (key.matches(XK_j)) ctx.moveLeft(cSp * 5); + else if (key.matches(XK_k)) ctx.moveRight(cSp * 5); + else if (key.matches(XK_u)) ctx.moveLeft(cSp * 10); + else if (key.matches(XK_i)) ctx.moveRight(cSp * 10); + else if (key.matches(XK_x)) resetView(); + else if (key.matches(XK_F11, XK_y)) toggleFullscreen(); + redraw = true; + } else if (xev.type == ButtonPress) { + std::cout << "XEvent: ButtonPress:" + << " button=" << xev.xbutton.button + << std::endl; + if (xev.xbutton.button == 1); + else if (xev.xbutton.button == 4) ctx.adjustFov(-1.0); + else if (xev.xbutton.button == 5) ctx.adjustFov(+1.0); + else if (xev.xbutton.button == 8) resetView(); + else if (xev.xbutton.button == 9) keepRunningX11 = false; + redraw = true; + } else if (xev.type == MotionNotify) { + // printCursorInfo(xev.xmotion); + } else if (xev.type == ConfigureNotify) { + std::cout << "XEvent: ConfigureNotify:" + << " window=" << xev.xconfigure.window + << " height=" << xev.xconfigure.height + << " width=" << xev.xconfigure.width + << std::endl; + if (listenToRootResizes + && xev.xconfigure.window == parent) { + XResizeWindow(dpy, win, + xev.xconfigure.width, xev.xconfigure.height); + } + } else if (xev.type == UnmapNotify) { + std::cout << "XEvent: UnmapNotify" << std::endl; + } else if (xev.type == DestroyNotify) { + std::cout << "XEvent: DestroyNotify → finish" << std::endl; + break; + } else { + std::cout << "XEvent: type=" << xev.type << std::endl; + } + if (XPending(dpy)) goto process_x11_event; + } else if (epoll[epollEvent].data.fd == STDIN_FILENO) { + int epollFD = epoll[epollEvent].data.fd; + logOutput << "other event: fd=" << epollFD << " data="; + for (char ch; read(epollFD, &ch, 1) > 0;) { + std::stringstream msg; + msg + << std::hex + << std::setfill('0') + << std::setw(2) + << (int) ch; + logOutput << msg.str(); + } + logOutput << std::endl; + + } else if (epoll[epollEvent].data.fd == fileMonitor.getFD()) { + std::cout << "FileMonitor event:" << std::endl; + for (FileEvent fe; fileMonitor.readEvent(fe);) { + logOutput << " " + << " file=" << fe.fileName + << " mask=" << fe.mask + << std::endl; + try { + redraw |= reloadTexture(fe.fileName); + redraw |= reloadShader(fe.fileName); + setTitle(); + } catch (const std::exception& e) { + setTitle("[ERROR]"); + logOutput << "error while reloading '" + << fe.fileName.c_str() + << "': " << e.what() << std::endl; + } + } + } else { + logOutput + << "error: event on an unexpected FD: " + << epoll[epollEvent].data.fd + << std::endl; + } + + if (redraw) { + runShaders(); + glXSwapBuffers(dpy, win); + } + } + } +} + +void OHP3D::Impl::clear() { + glClearColor( + (cfg.backgroundColor >> 16 & 0xFF) / 256., + (cfg.backgroundColor >> 8 & 0xFF) / 256., + (cfg.backgroundColor & 0xFF) / 256., + 1.0); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); +} + +void OHP3D::Impl::runShaders() { + shaderProgram->use(); + checkError(&std::cerr); + + clear(); + + GLint viewport[4]; + glGetIntegerv(GL_VIEWPORT, viewport); + GLfloat width = viewport[2]; + GLfloat height = viewport[3]; + + glm::mat4 projection = glm::perspective( + glm::radians(ctx.fov), + width / height, + 0.1f, 100.0f); + glUniformMatrix4fv(ProgAttr.uProjection, 1, GL_FALSE, &projection[0][0]); + + glm::mat4 view = glm::lookAt( + ctx.cameraPos, + ctx.cameraPos + ctx.cameraFront, + ctx.cameraUp); + glUniformMatrix4fv(ProgAttr.uView, 1, GL_FALSE, &view[0][0]); + + // glBindVertexArray(vao); + + glm::mat4 model = glm::mat4(1.0f); // identity matrix + glUniformMatrix4fv(ProgAttr.uModel, 1, GL_FALSE, &model[0][0]); + + // TODO: draw a rectangle for each texture + glUniform1f(ProgAttr.uTextureScale, textures[0]->getScale()); + + glDrawArrays(GL_TRIANGLES, 0, 2 * 3); // see loadVertices() + std::cerr << "GLSL: glDrawArrays()" << std::endl; +} + +void OHP3D::Impl::log(LogLevel level, std::string message) { + ::log(logOutput, level, message); +} + +int OHP3D::Impl::setNonBlocking(int fd) { + int flags = fcntl(fd, F_GETFL, 0); + fcntl(fd, F_SETFL, flags | O_NONBLOCK); + return fd; +} + +void OHP3D::Impl::loadVertices() { + for (int i = 0; i < textures.size(); i++) { + std::shared_ptr tex = textures[i]; + // TODO: draw a rectangle for each texture + GLfloat ratio = tex->getRatio(); + const std::vector vertices = { + // Vertex XYZ Texture XY + -0.80f * ratio, +0.80f, +0.0, /**/ 0.0, 0.0, + +0.80f * ratio, +0.80f, +0.0, /**/ 1.0, 0.0, + -0.80f * ratio, -0.80f, +0.0, /**/ 0.0, 1.0, + + -0.80f * ratio, -0.80f, +0.0, /**/ 0.0, 1.0, + +0.80f * ratio, -0.80f, +0.0, /**/ 1.0, 1.0, + +0.80f * ratio, +0.80f, +0.0, /**/ 1.0, 0.0, + + // see glDrawArrays(), where we set start offset and count + }; + + // Vertex data: + glVertexAttribPointer(ProgAttr.aVertexXYZ, 3, // vertex items + GL_FLOAT, GL_FALSE, 5 * sizeof (float), + (void*) 0); + glEnableVertexAttribArray(ProgAttr.aVertexXYZ); + + // Texture positions: + glVertexAttribPointer(ProgAttr.aTextureXY, 2, // texture items + GL_FLOAT, GL_FALSE, 5 * sizeof (float), + (void*) (3 * sizeof (float))); + glEnableVertexAttribArray(ProgAttr.aTextureXY); + + glBufferData(GL_ARRAY_BUFFER, + vertices.size() * sizeof (vertices[0]), + vertices.data(), + GL_STATIC_DRAW); + // GL_STATIC_DRAW: + // The vertex data will be uploaded once + // and drawn many times(e.g. the world). + // GL_DYNAMIC_DRAW: + // The vertex data will be created once, changed from + // time to time, but drawn many times more than that. + // GL_STREAM_DRAW: + // The vertex data will be uploaded once and drawn once. + + // see also glBindBuffer(GL_ARRAY_BUFFER, vbo); where we set current VBO + } +} + +const std::string +OHP3D::Impl::getDefaultFile(const std::string& relativePath) { + const char* envName = "OHP3D_DATA_DIR"; + const char* envValue = ::getenv(envName); + if (envValue) { + return std::string(envValue) + "/" + relativePath; + } else { + throw std::invalid_argument(std::string("Configure $") + envName + + " in order to use defaults" + " or specify textures and shaders as parameters"); + } +} + +void OHP3D::Impl::parametrizeTexture(std::shared_ptr tex) { + XAttrs xa(tex->getFileName()); + std::string magf = xa["ohp3d.texture.mag-filter"]; + std::string minf = xa["ohp3d.texture.min-filter"]; + std::string scale = xa["ohp3d.texture.scale"]; + + auto GLT2D = GL_TEXTURE_2D; + auto MAG = GL_TEXTURE_MAG_FILTER; + auto MIN = GL_TEXTURE_MIN_FILTER; + + if (magf == "linear") glTexParameteri(GLT2D, MAG, GL_LINEAR); + else if (magf == "nearest") glTexParameteri(GLT2D, MAG, GL_NEAREST); + + if (minf == "linear") glTexParameteri(GLT2D, MIN, GL_LINEAR); + else if (minf == "nearest") glTexParameteri(GLT2D, MIN, GL_NEAREST); + + if (scale.size()) { + float sc; + if (std::from_chars(scale.data(), scale.data() + scale.size(), sc).ec + == std::errc{}) tex->setScale(sc); + else std::cerr << "Invalid texture scale value - expecting float\n"; + // tex->setScale(std::stof(scale)); // locale-dependent (. vs ,) + } +} + +void OHP3D::Impl::loadTextures() { + // Load default texture if there is no configured: + if (cfg.textures.empty()) + cfg.textures.push_back({getDefaultFile("textures/default.png")}); + + for (const Configuration::Texture& tex : cfg.textures) { + std::shared_ptr + img(imageLoader.loadImage(MappedFile(tex.fileName))); + textures.push_back(std::make_shared( + img->width, img->height, *img, tex.fileName)); + parametrizeTexture(textures.back()); + // static const uint32_t watchMask = IN_CLOSE_WRITE | IN_ATTRIB; + // watchedFiles.push_back(fileMonitor.watch(tex.fileName, watchMask)); + watchedFiles.push_back(fileMonitor.watch(tex.fileName)); + // TODO: review texture loading and binding + // works even without this - default texture + // glUniform1i(..., ...); + // checkError(&std::cerr); + } +} + +bool OHP3D::Impl::reloadTexture(const std::string& fileName) { + for (std::shared_ptr tex : textures) { + if (tex->getFileName() == fileName) { + std::shared_ptr + img(imageLoader.loadImage(MappedFile(fileName))); + tex->update(img->width, img->height, *img); + parametrizeTexture(tex); + loadVertices(); + return true; + } + } + return false; +} + +void OHP3D::Impl::loadShaders() { + // Vertex Array Object (VAO) + GLuint vao; + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + // VAO - something like context for bound data/variables + // We can switch multiple VAOs. VAO can contain multiple VBOs. + // what-are-vertex-array-objects + + // Vertex Buffer Object (VBO): + GLuint vbo; + glGenBuffers(1, &vbo); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + + { + // Load default shaders if there are no configured: + int vc = 0; + int fc = 0; + auto& ss = cfg.shaders; + for (const auto& s : ss) if (s.type == "vertex") vc++; + for (const auto& s : ss) if (s.type == "fragment") fc++; + auto& d = getDefaultFile; + if (vc == 0) ss.push_back({d("shaders/default.vert"), "vertex"}); + if (fc == 0) ss.push_back({d("shaders/default.frag"), "fragment"}); + } + + shaderProgram = std::make_shared(); + + // glBindFragDataLocation(program, 0, "outColor"); + // glBindAttribLocation(program, LOC.input, "vertices"); + + for (const Configuration::Shader definition : cfg.shaders) { + Shader::Type type; + std::string fileName = definition.fileName; + if (definition.type == "fragment") type = Shader::Type::FRAGMENT; + else if (definition.type == "vertex") type = Shader::Type::VERTEX; + else throw std::invalid_argument("unsupported shader type"); + + MappedFile file(fileName); + std::shared_ptr shader = std::make_shared( + type, file, fileName); + + shaderProgram->attachShader(*shader.get()); + shaders.push_back(shader); + watchedFiles.push_back(fileMonitor.watch(fileName)); + std::cerr << "GLSL loaded: " << fileName.c_str() << std::endl; + // We may detach and delete shaders, + // but our shaders are small, so we keep them for later reloading. + } + + shaderProgram->link(); + updateVariableLocations(); + // listVariables(program); + std::cerr << "GLSL shader count: " << shaders.size() << std::endl; +} + +void OHP3D::Impl::updateVariableLocations() { + // GLSL compiler does very efficient / aggressive optimization. + // Attributes and uniforms that are not used in the shader are deleted. + // And even if we e.g. read color from a texture and overwrite it, + // the variable is still deleted and considered „inactive“. + // Functions glGetAttribLocation() and glGetUniformLocation() return -1. + ProgAttr.aVertexXYZ = shaderProgram->getAttribLocation("aVertexXYZ"); + ProgAttr.aTextureXY = shaderProgram->getAttribLocation("aTextureXY"); + ProgAttr.uModel = shaderProgram->getUniformLocation("uModel"); + ProgAttr.uView = shaderProgram->getUniformLocation("uView"); + ProgAttr.uProjection = shaderProgram->getUniformLocation("uProjection"); + ProgAttr.uTexture = shaderProgram->getUniformLocation("uTexture"); + ProgAttr.uTextureScale = shaderProgram->getUniformLocation("uTextureScale"); + ProgAttr.fColor = shaderProgram->getFragDataLocation("fColor"); + shaderProgram->bindFragDataLocation("fColor", ProgAttr.fColor); +} + +bool OHP3D::Impl::reloadShader(const std::string& fileName) { + for (auto shader : shaders) { + if (shader->getFileName() == fileName) { + shader->update(MappedFile(fileName)); + shaderProgram->link(); + updateVariableLocations(); + return true; + } + } + return false; +} diff -r 4cbd9c0beb4c -r dc3c102e1264 OHP3D.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OHP3D.h Wed Dec 27 01:50:38 2023 +0100 @@ -0,0 +1,32 @@ +/** + * OHP3D + * Copyright © 2023 František Kučera (Frantovo.cz, GlobalCode.info) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "Configuration.h" + +class OHP3D { +public: + OHP3D(const Configuration& cfg); + virtual ~OHP3D(); + void run(); +private: + class Impl; + Impl* impl; + OHP3D(const OHP3D&) = delete; + OHP3D& operator=(const OHP3D&) = delete; +}; diff -r 4cbd9c0beb4c -r dc3c102e1264 Program.cpp --- a/Program.cpp Tue Dec 26 23:46:45 2023 +0100 +++ b/Program.cpp Wed Dec 27 01:50:38 2023 +0100 @@ -1,5 +1,5 @@ /** - * ShaderShark + * OHP3D * Copyright © 2023 František Kučera (Frantovo.cz, GlobalCode.info) * * This program is free software: you can redistribute it and/or modify diff -r 4cbd9c0beb4c -r dc3c102e1264 Program.h --- a/Program.h Tue Dec 26 23:46:45 2023 +0100 +++ b/Program.h Wed Dec 27 01:50:38 2023 +0100 @@ -1,5 +1,5 @@ /** - * ShaderShark + * OHP3D * Copyright © 2023 František Kučera (Frantovo.cz, GlobalCode.info) * * This program is free software: you can redistribute it and/or modify diff -r 4cbd9c0beb4c -r dc3c102e1264 Shader.cpp --- a/Shader.cpp Tue Dec 26 23:46:45 2023 +0100 +++ b/Shader.cpp Wed Dec 27 01:50:38 2023 +0100 @@ -1,5 +1,5 @@ /** - * ShaderShark + * OHP3D * Copyright © 2023 František Kučera (Frantovo.cz, GlobalCode.info) * * This program is free software: you can redistribute it and/or modify diff -r 4cbd9c0beb4c -r dc3c102e1264 Shader.h --- a/Shader.h Tue Dec 26 23:46:45 2023 +0100 +++ b/Shader.h Wed Dec 27 01:50:38 2023 +0100 @@ -1,5 +1,5 @@ /** - * ShaderShark + * OHP3D * Copyright © 2023 František Kučera (Frantovo.cz, GlobalCode.info) * * This program is free software: you can redistribute it and/or modify diff -r 4cbd9c0beb4c -r dc3c102e1264 Shark.cpp --- a/Shark.cpp Tue Dec 26 23:46:45 2023 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,694 +0,0 @@ -/** - * ShaderShark - * Copyright © 2023 František Kučera (Frantovo.cz, GlobalCode.info) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "x11.h" -#include "opengl.h" -#include "EPoll.h" -#include "Logger.h" -#include "MappedFile.h" -#include "ImageLoader.h" -#include "Texture.h" -#include "Shader.h" -#include "Program.h" -#include "FileMonitor.h" -#include "XAttrs.h" - -#include "Shark.h" - -class Shark::Impl { -public: - - struct { - GLint aVertexXYZ = -2; - GLint aTextureXY = -2; - - GLint fColor = -2; - - GLint uModel = -2; - GLint uView = -2; - GLint uProjection = -2; - GLint uTexture = -2; - GLint uTextureScale = -2; - } ProgAttr; - - struct { - float yaw = -90.f; - float pitch = 0.f; - float roll = 0.f; - float fov = 45.0f; - glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f); - glm::vec3 cameraFront = glm::vec3(0.0f, 0.0f, -1.0f); - glm::vec3 cameraUp = glm::vec3(0.0f, 1.0f, 0.0f); - - void adjustFov(float diff) { - fov += diff; - if (fov < 1.0f) fov = 1.0f; - else if (fov > 120.0f) fov = 120.0f; - std::cerr << "field of view: " << fov << " °" << std::endl; - } - - void moveForward(const float cameraSpeed) { - cameraPos += cameraSpeed * cameraFront; - } - - void moveBackward(const float cameraSpeed) { - cameraPos -= cameraSpeed * cameraFront; - } - - void moveLeft(const float cameraSpeed) { - cameraPos -= glm::normalize( - glm::cross(cameraFront, cameraUp)) * cameraSpeed; - } - - void moveRight(const float cameraSpeed) { - cameraPos += glm::normalize( - glm::cross(cameraFront, cameraUp)) * cameraSpeed; - } - - void moveUp(const float cameraSpeed) { - cameraPos += cameraSpeed * glm::normalize(cameraUp); - } - - void moveDown(const float cameraSpeed) { - cameraPos -= cameraSpeed * glm::normalize(cameraUp); - } - - void updateCameraFrontAndUp() { - std::cerr << "--- updateCameraFrontAndUp() --------" << std::endl; - dump("pitch, yaw, roll", glm::vec3(pitch, yaw, roll)); - dump("cameraPos", cameraPos); - dump("cameraFront", cameraFront); - const auto pitchR = glm::radians(pitch); // around X axis - const auto yawR = glm::radians(yaw); // around Y axis - const auto rollR = glm::radians(roll); // around Z axis - - cameraFront.x = cos(pitchR) * cos(yawR); - cameraFront.y = sin(pitchR); - cameraFront.z = cos(pitchR) * sin(yawR); - cameraFront = glm::normalize(cameraFront); - dump("cameraFront", cameraFront); - dump("cameraUp", cameraUp); - - // TODO: review ROLL rotation and default angle - glm::mat4 rollMatrix = glm::rotate( - glm::mat4(1.0f), rollR, cameraFront); - cameraUp = glm::mat3(rollMatrix) * glm::vec3(0., 1., 0.); - dump("cameraUp", cameraUp); - std::cerr << "-------------------------------------" << std::endl; - } - - void limitPitch() { - if (pitch > +89.0f) pitch = +89.0f; - if (pitch < -89.0f) pitch = -89.0f; - } - - void turnLeft(const float angleSpeed) { - yaw -= angleSpeed; - updateCameraFrontAndUp(); - } - - void turnRight(const float angleSpeed) { - yaw += angleSpeed; - updateCameraFrontAndUp(); - } - - void turnUp(const float angleSpeed) { - pitch += angleSpeed; - limitPitch(); - updateCameraFrontAndUp(); - } - - void turnDown(const float angleSpeed) { - pitch -= angleSpeed; - limitPitch(); - updateCameraFrontAndUp(); - } - - void rollLeft(const float angleSpeed) { - roll += angleSpeed; - updateCameraFrontAndUp(); - } - - void rollRight(const float angleSpeed) { - roll -= angleSpeed; - updateCameraFrontAndUp(); - } - - } initialCtx, ctx; - - Display* dpy; - Window win; - XVisualInfo* vi; - GLXContext glc; - - FileMonitor fileMonitor; - std::vector watchedFiles; - ImageLoader imageLoader; - std::vector> shaders; - std::shared_ptr shaderProgram; - std::vector> textures; - - Configuration cfg; - std::ostream& logOutput = std::cerr; - - Impl(Configuration cfg) : cfg(cfg) { - } - - void run(); - void clear(); - void runShaders(); - Window getRootWindow(Window defaultValue); - void log(LogLevel level, std::string message); - int setNonBlocking(int fd); - void loadVertices(); - void parametrizeTexture(std::shared_ptr tex); - bool reloadTexture(const std::string& fileName); - void loadTextures(); - void loadShaders(); - void updateVariableLocations(); - bool reloadShader(const std::string& fileName); - void setTitle(const std::string& suffix = ""); - static const std::string getDefaultFile(const std::string& relativePath); - -}; - -Shark::Shark(const Configuration& configuration) : -impl(new Impl(configuration)) { -} - -Shark::~Shark() { - impl->textures.clear(); - impl->shaders.clear(); - impl->shaderProgram = nullptr; - XFree(impl->vi); - glXMakeCurrent(impl->dpy, None, NULL); - glXDestroyContext(impl->dpy, impl->glc); - XDestroyWindow(impl->dpy, impl->win); - XCloseDisplay(impl->dpy); - delete impl; - // std::cerr << "~Shark()" << std::endl; -} - -void Shark::Impl::setTitle(const std::string& suffix) { - std::stringstream title; - title << "ShaderShark"; - if (suffix.size()) title << ": " << suffix.c_str(); - XStoreName(dpy, win, title.str().c_str()); - XFlush(dpy); -} - -void Shark::run() { - impl->run(); -} - -void Shark::Impl::run() { - dpy = XOpenDisplay(NULL); - - if (dpy == NULL) throw std::logic_error("Unable to connect to X server"); - - GLint att[] = {GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None}; - vi = glXChooseVisual(dpy, 0, att); - Window root = DefaultRootWindow(dpy); - Window parent = cfg.rootWindow ? cfg.rootWindow : root; - - XSetWindowAttributes swa; - swa.colormap = XCreateColormap(dpy, parent, vi->visual, AllocNone); - swa.event_mask = ExposureMask | KeyPressMask | PointerMotionMask - | ButtonPressMask - | StructureNotifyMask; - - bool full = false; - unsigned int width = 1600; - unsigned int height = 1200; - if (parent != root) { - XWindowAttributes parentAttr; - XGetWindowAttributes(dpy, parent, &parentAttr); - width = parentAttr.width; - height = parentAttr.height; - } - - win = XCreateWindow( - dpy, parent, 0, 0, width, height, 0, - vi->depth, InputOutput, vi->visual, - CWColormap | CWEventMask, &swa); - - XMapWindow(dpy, win); - setTitle(); - setX11PID(dpy, win); - // XSetWindowBackground(dpy, win, 0) vs. glClearColor() - - glc = glXCreateContext(dpy, vi, NULL, GL_TRUE); - glXMakeCurrent(dpy, win, glc); - - glEnable(GL_DEPTH_TEST); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - clear(); - glXSwapBuffers(dpy, win); - - - // Load GLSL shaders: - loadShaders(); - loadTextures(); - loadVertices(); - - auto toggleFullscreen = [&]() { - full = setFullscreen(dpy, win, !full); - }; - - auto resetView = [&]() { - ctx = initialCtx; - ctx.updateCameraFrontAndUp(); - }; - - // root can reize our window - // or we can listen to root resize and then resize our window ourselves - bool listenToRootResizes = true; - if (listenToRootResizes) XSelectInput(dpy, parent, StructureNotifyMask); - - bool keepRunningX11 = true; - int x11fd = XConnectionNumber(dpy); - EPoll epoll; - epoll.add(x11fd); - epoll.add(fileMonitor.getFD()); - try { - epoll.add(setNonBlocking(STDIN_FILENO)); - } catch (const EPoll::Exception& e) { - logOutput << "Will not monitor events on STDIN: " << e.what() << "\n"; - } - - // rended the 3D scene even before the first event: - runShaders(); - glXSwapBuffers(dpy, win); - - for (XEvent xev; keepRunningX11;) { - int epollEventCount = epoll.wait(); - //std::cout << "trace: epoll.wait() = " << epollEventCount << std::endl; - for (int epollEvent = 0; epollEvent < epollEventCount; epollEvent++) { - bool redraw = false; - if (epoll[epollEvent].data.fd == x11fd) { - if (!XPending(dpy)) { - // otherwise STDIN events are held until the first X11 event - logOutput << "trace: no pending X11 event" << std::endl; - break; - } -process_x11_event: - XWindowAttributes gwa; - XNextEvent(dpy, &xev); - - if (xev.type == Expose) { - std::cout << "XEvent: Expose" << std::endl; - XGetWindowAttributes(dpy, win, &gwa); - glViewport(0, 0, gwa.width, gwa.height); - redraw = true; - } else if (xev.type == KeyPress) { - DecodedKey key = decodeKeycode(dpy, xev.xkey.keycode); - std::cout << "XEvent: KeyPress:" - << " keycode=" << key.code - << " key=" << key.name - << std::endl; - - const float cSp = 0.05f; // camera speed - const float aSp = 5.f; // angle speed - - if (key.matches(XK_q, XK_Escape)) keepRunningX11 = false; - else if (key.matches(XK_Left, XK_s)) ctx.turnLeft(aSp); - else if (key.matches(XK_Right, XK_f)) ctx.turnRight(aSp); - else if (key.matches(XK_Up, XK_e)) ctx.moveForward(cSp); - else if (key.matches(XK_Down, XK_d)) ctx.moveBackward(cSp); - else if (key.matches(XK_w)) ctx.rollLeft(aSp); - else if (key.matches(XK_r)) ctx.rollRight(aSp); - else if (key.matches(XK_t)) ctx.turnUp(aSp); - else if (key.matches(XK_g)) ctx.turnDown(aSp); - else if (key.matches(XK_m)) ctx.moveLeft(cSp); - else if (key.matches(XK_comma)) ctx.moveRight(cSp); - else if (key.matches(XK_l)) ctx.moveUp(cSp); - else if (key.matches(XK_period)) ctx.moveDown(cSp); - else if (key.matches(XK_j)) ctx.moveLeft(cSp * 5); - else if (key.matches(XK_k)) ctx.moveRight(cSp * 5); - else if (key.matches(XK_u)) ctx.moveLeft(cSp * 10); - else if (key.matches(XK_i)) ctx.moveRight(cSp * 10); - else if (key.matches(XK_x)) resetView(); - else if (key.matches(XK_F11, XK_y)) toggleFullscreen(); - redraw = true; - } else if (xev.type == ButtonPress) { - std::cout << "XEvent: ButtonPress:" - << " button=" << xev.xbutton.button - << std::endl; - if (xev.xbutton.button == 1); - else if (xev.xbutton.button == 4) ctx.adjustFov(-1.0); - else if (xev.xbutton.button == 5) ctx.adjustFov(+1.0); - else if (xev.xbutton.button == 8) resetView(); - else if (xev.xbutton.button == 9) keepRunningX11 = false; - redraw = true; - } else if (xev.type == MotionNotify) { - // printCursorInfo(xev.xmotion); - } else if (xev.type == ConfigureNotify) { - std::cout << "XEvent: ConfigureNotify:" - << " window=" << xev.xconfigure.window - << " height=" << xev.xconfigure.height - << " width=" << xev.xconfigure.width - << std::endl; - if (listenToRootResizes - && xev.xconfigure.window == parent) { - XResizeWindow(dpy, win, - xev.xconfigure.width, xev.xconfigure.height); - } - } else if (xev.type == UnmapNotify) { - std::cout << "XEvent: UnmapNotify" << std::endl; - } else if (xev.type == DestroyNotify) { - std::cout << "XEvent: DestroyNotify → finish" << std::endl; - break; - } else { - std::cout << "XEvent: type=" << xev.type << std::endl; - } - if (XPending(dpy)) goto process_x11_event; - } else if (epoll[epollEvent].data.fd == STDIN_FILENO) { - int epollFD = epoll[epollEvent].data.fd; - logOutput << "other event: fd=" << epollFD << " data="; - for (char ch; read(epollFD, &ch, 1) > 0;) { - std::stringstream msg; - msg - << std::hex - << std::setfill('0') - << std::setw(2) - << (int) ch; - logOutput << msg.str(); - } - logOutput << std::endl; - - } else if (epoll[epollEvent].data.fd == fileMonitor.getFD()) { - std::cout << "FileMonitor event:" << std::endl; - for (FileEvent fe; fileMonitor.readEvent(fe);) { - logOutput << " " - << " file=" << fe.fileName - << " mask=" << fe.mask - << std::endl; - try { - redraw |= reloadTexture(fe.fileName); - redraw |= reloadShader(fe.fileName); - setTitle(); - } catch (const std::exception& e) { - setTitle("[ERROR]"); - logOutput << "error while reloading '" - << fe.fileName.c_str() - << "': " << e.what() << std::endl; - } - } - } else { - logOutput - << "error: event on an unexpected FD: " - << epoll[epollEvent].data.fd - << std::endl; - } - - if (redraw) { - runShaders(); - glXSwapBuffers(dpy, win); - } - } - } -} - -void Shark::Impl::clear() { - glClearColor( - (cfg.backgroundColor >> 16 & 0xFF) / 256., - (cfg.backgroundColor >> 8 & 0xFF) / 256., - (cfg.backgroundColor & 0xFF) / 256., - 1.0); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); -} - -void Shark::Impl::runShaders() { - shaderProgram->use(); - checkError(&std::cerr); - - clear(); - - GLint viewport[4]; - glGetIntegerv(GL_VIEWPORT, viewport); - GLfloat width = viewport[2]; - GLfloat height = viewport[3]; - - glm::mat4 projection = glm::perspective( - glm::radians(ctx.fov), - width / height, - 0.1f, 100.0f); - glUniformMatrix4fv(ProgAttr.uProjection, 1, GL_FALSE, &projection[0][0]); - - glm::mat4 view = glm::lookAt( - ctx.cameraPos, - ctx.cameraPos + ctx.cameraFront, - ctx.cameraUp); - glUniformMatrix4fv(ProgAttr.uView, 1, GL_FALSE, &view[0][0]); - - // glBindVertexArray(vao); - - glm::mat4 model = glm::mat4(1.0f); // identity matrix - glUniformMatrix4fv(ProgAttr.uModel, 1, GL_FALSE, &model[0][0]); - - // TODO: draw a rectangle for each texture - glUniform1f(ProgAttr.uTextureScale, textures[0]->getScale()); - - glDrawArrays(GL_TRIANGLES, 0, 2 * 3); // see loadVertices() - std::cerr << "GLSL: glDrawArrays()" << std::endl; -} - -void Shark::Impl::log(LogLevel level, std::string message) { - ::log(logOutput, level, message); -} - -int Shark::Impl::setNonBlocking(int fd) { - int flags = fcntl(fd, F_GETFL, 0); - fcntl(fd, F_SETFL, flags | O_NONBLOCK); - return fd; -} - -void Shark::Impl::loadVertices() { - for (int i = 0; i < textures.size(); i++) { - std::shared_ptr tex = textures[i]; - // TODO: draw a rectangle for each texture - GLfloat ratio = tex->getRatio(); - const std::vector vertices = { - // Vertex XYZ Texture XY - -0.80f * ratio, +0.80f, +0.0, /**/ 0.0, 0.0, - +0.80f * ratio, +0.80f, +0.0, /**/ 1.0, 0.0, - -0.80f * ratio, -0.80f, +0.0, /**/ 0.0, 1.0, - - -0.80f * ratio, -0.80f, +0.0, /**/ 0.0, 1.0, - +0.80f * ratio, -0.80f, +0.0, /**/ 1.0, 1.0, - +0.80f * ratio, +0.80f, +0.0, /**/ 1.0, 0.0, - - // see glDrawArrays(), where we set start offset and count - }; - - // Vertex data: - glVertexAttribPointer(ProgAttr.aVertexXYZ, 3, // vertex items - GL_FLOAT, GL_FALSE, 5 * sizeof (float), - (void*) 0); - glEnableVertexAttribArray(ProgAttr.aVertexXYZ); - - // Texture positions: - glVertexAttribPointer(ProgAttr.aTextureXY, 2, // texture items - GL_FLOAT, GL_FALSE, 5 * sizeof (float), - (void*) (3 * sizeof (float))); - glEnableVertexAttribArray(ProgAttr.aTextureXY); - - glBufferData(GL_ARRAY_BUFFER, - vertices.size() * sizeof (vertices[0]), - vertices.data(), - GL_STATIC_DRAW); - // GL_STATIC_DRAW: - // The vertex data will be uploaded once - // and drawn many times(e.g. the world). - // GL_DYNAMIC_DRAW: - // The vertex data will be created once, changed from - // time to time, but drawn many times more than that. - // GL_STREAM_DRAW: - // The vertex data will be uploaded once and drawn once. - - // see also glBindBuffer(GL_ARRAY_BUFFER, vbo); where we set current VBO - } -} - -const std::string -Shark::Impl::getDefaultFile(const std::string& relativePath) { - const char* envName = "SHADER_SHARK_DATA_DIR"; - const char* envValue = ::getenv(envName); - if (envValue) { - return std::string(envValue) + "/" + relativePath; - } else { - throw std::invalid_argument(std::string("Configure $") + envName - + " in order to use defaults" - " or specify textures and shaders as parameters"); - } -} - -void Shark::Impl::parametrizeTexture(std::shared_ptr tex) { - XAttrs xa(tex->getFileName()); - std::string magf = xa["shader-shark.texture.mag-filter"]; - std::string minf = xa["shader-shark.texture.min-filter"]; - std::string scale = xa["shader-shark.texture.scale"]; - - auto GLT2D = GL_TEXTURE_2D; - auto MAG = GL_TEXTURE_MAG_FILTER; - auto MIN = GL_TEXTURE_MIN_FILTER; - - if (magf == "linear") glTexParameteri(GLT2D, MAG, GL_LINEAR); - else if (magf == "nearest") glTexParameteri(GLT2D, MAG, GL_NEAREST); - - if (minf == "linear") glTexParameteri(GLT2D, MIN, GL_LINEAR); - else if (minf == "nearest") glTexParameteri(GLT2D, MIN, GL_NEAREST); - - if (scale.size()) { - float sc; - if (std::from_chars(scale.data(), scale.data() + scale.size(), sc).ec - == std::errc{}) tex->setScale(sc); - else std::cerr << "Invalid texture scale value - expecting float\n"; - // tex->setScale(std::stof(scale)); // locale-dependent (. vs ,) - } -} - -void Shark::Impl::loadTextures() { - // Load default texture if there is no configured: - if (cfg.textures.empty()) - cfg.textures.push_back({getDefaultFile("textures/default.png")}); - - for (const Configuration::Texture& tex : cfg.textures) { - std::shared_ptr - img(imageLoader.loadImage(MappedFile(tex.fileName))); - textures.push_back(std::make_shared( - img->width, img->height, *img, tex.fileName)); - parametrizeTexture(textures.back()); - // static const uint32_t watchMask = IN_CLOSE_WRITE | IN_ATTRIB; - // watchedFiles.push_back(fileMonitor.watch(tex.fileName, watchMask)); - watchedFiles.push_back(fileMonitor.watch(tex.fileName)); - // TODO: review texture loading and binding - // works even without this - default texture - // glUniform1i(..., ...); - // checkError(&std::cerr); - } -} - -bool Shark::Impl::reloadTexture(const std::string& fileName) { - for (std::shared_ptr tex : textures) { - if (tex->getFileName() == fileName) { - std::shared_ptr - img(imageLoader.loadImage(MappedFile(fileName))); - tex->update(img->width, img->height, *img); - parametrizeTexture(tex); - loadVertices(); - return true; - } - } - return false; -} - -void Shark::Impl::loadShaders() { - // Vertex Array Object (VAO) - GLuint vao; - glGenVertexArrays(1, &vao); - glBindVertexArray(vao); - // VAO - something like context for bound data/variables - // We can switch multiple VAOs. VAO can contain multiple VBOs. - // what-are-vertex-array-objects - - // Vertex Buffer Object (VBO): - GLuint vbo; - glGenBuffers(1, &vbo); - glBindBuffer(GL_ARRAY_BUFFER, vbo); - - { - // Load default shaders if there are no configured: - int vc = 0; - int fc = 0; - auto& ss = cfg.shaders; - for (const auto& s : ss) if (s.type == "vertex") vc++; - for (const auto& s : ss) if (s.type == "fragment") fc++; - auto& d = getDefaultFile; - if (vc == 0) ss.push_back({d("shaders/default.vert"), "vertex"}); - if (fc == 0) ss.push_back({d("shaders/default.frag"), "fragment"}); - } - - shaderProgram = std::make_shared(); - - // glBindFragDataLocation(program, 0, "outColor"); - // glBindAttribLocation(program, LOC.input, "vertices"); - - for (const Configuration::Shader definition : cfg.shaders) { - Shader::Type type; - std::string fileName = definition.fileName; - if (definition.type == "fragment") type = Shader::Type::FRAGMENT; - else if (definition.type == "vertex") type = Shader::Type::VERTEX; - else throw std::invalid_argument("unsupported shader type"); - - MappedFile file(fileName); - std::shared_ptr shader = std::make_shared( - type, file, fileName); - - shaderProgram->attachShader(*shader.get()); - shaders.push_back(shader); - watchedFiles.push_back(fileMonitor.watch(fileName)); - std::cerr << "GLSL loaded: " << fileName.c_str() << std::endl; - // We may detach and delete shaders, - // but our shaders are small, so we keep them for later reloading. - } - - shaderProgram->link(); - updateVariableLocations(); - // listVariables(program); - std::cerr << "GLSL shader count: " << shaders.size() << std::endl; -} - -void Shark::Impl::updateVariableLocations() { - // GLSL compiler does very efficient / aggressive optimization. - // Attributes and uniforms that are not used in the shader are deleted. - // And even if we e.g. read color from a texture and overwrite it, - // the variable is still deleted and considered „inactive“. - // Functions glGetAttribLocation() and glGetUniformLocation() return -1. - ProgAttr.aVertexXYZ = shaderProgram->getAttribLocation("aVertexXYZ"); - ProgAttr.aTextureXY = shaderProgram->getAttribLocation("aTextureXY"); - ProgAttr.uModel = shaderProgram->getUniformLocation("uModel"); - ProgAttr.uView = shaderProgram->getUniformLocation("uView"); - ProgAttr.uProjection = shaderProgram->getUniformLocation("uProjection"); - ProgAttr.uTexture = shaderProgram->getUniformLocation("uTexture"); - ProgAttr.uTextureScale = shaderProgram->getUniformLocation("uTextureScale"); - ProgAttr.fColor = shaderProgram->getFragDataLocation("fColor"); - shaderProgram->bindFragDataLocation("fColor", ProgAttr.fColor); -} - -bool Shark::Impl::reloadShader(const std::string& fileName) { - for (auto shader : shaders) { - if (shader->getFileName() == fileName) { - shader->update(MappedFile(fileName)); - shaderProgram->link(); - updateVariableLocations(); - return true; - } - } - return false; -} diff -r 4cbd9c0beb4c -r dc3c102e1264 Shark.h --- a/Shark.h Tue Dec 26 23:46:45 2023 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,32 +0,0 @@ -/** - * ShaderShark - * Copyright © 2023 František Kučera (Frantovo.cz, GlobalCode.info) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#pragma once - -#include "Configuration.h" - -class Shark { -public: - Shark(const Configuration& cfg); - virtual ~Shark(); - void run(); -private: - class Impl; - Impl* impl; - Shark(const Shark&) = delete; - Shark& operator=(const Shark&) = delete; -}; diff -r 4cbd9c0beb4c -r dc3c102e1264 Texture.cpp --- a/Texture.cpp Tue Dec 26 23:46:45 2023 +0100 +++ b/Texture.cpp Wed Dec 27 01:50:38 2023 +0100 @@ -1,5 +1,5 @@ /** - * ShaderShark + * OHP3D * Copyright © 2023 František Kučera (Frantovo.cz, GlobalCode.info) * * This program is free software: you can redistribute it and/or modify diff -r 4cbd9c0beb4c -r dc3c102e1264 Texture.h --- a/Texture.h Tue Dec 26 23:46:45 2023 +0100 +++ b/Texture.h Wed Dec 27 01:50:38 2023 +0100 @@ -1,5 +1,5 @@ /** - * ShaderShark + * OHP3D * Copyright © 2023 František Kučera (Frantovo.cz, GlobalCode.info) * * This program is free software: you can redistribute it and/or modify diff -r 4cbd9c0beb4c -r dc3c102e1264 XAttrs.cpp --- a/XAttrs.cpp Tue Dec 26 23:46:45 2023 +0100 +++ b/XAttrs.cpp Wed Dec 27 01:50:38 2023 +0100 @@ -1,5 +1,5 @@ /** - * ShaderShark + * OHP3D * Copyright © 2023 František Kučera (Frantovo.cz, GlobalCode.info) * * This program is free software: you can redistribute it and/or modify diff -r 4cbd9c0beb4c -r dc3c102e1264 XAttrs.h --- a/XAttrs.h Tue Dec 26 23:46:45 2023 +0100 +++ b/XAttrs.h Wed Dec 27 01:50:38 2023 +0100 @@ -1,5 +1,5 @@ /** - * ShaderShark + * OHP3D * Copyright © 2023 František Kučera (Frantovo.cz, GlobalCode.info) * * This program is free software: you can redistribute it and/or modify diff -r 4cbd9c0beb4c -r dc3c102e1264 bash-completion.sh --- a/bash-completion.sh Tue Dec 26 23:46:45 2023 +0100 +++ b/bash-completion.sh Wed Dec 27 01:50:38 2023 +0100 @@ -1,4 +1,4 @@ -# ShaderShark +# OHP3D # Copyright © 2023 František Kučera (Frantovo.cz, GlobalCode.info) # # This program is free software: you can redistribute it and/or modify @@ -13,7 +13,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -_shader_shark_completion() { +_ohp3d_completion() { local w0 w1 w2 w3 COMPREPLY=() @@ -52,4 +52,4 @@ fi } -complete -F _shader_shark_completion shader-shark +complete -F _ohp3d_completion ohp3d diff -r 4cbd9c0beb4c -r dc3c102e1264 nbproject/configurations.xml --- a/nbproject/configurations.xml Tue Dec 26 23:46:45 2023 +0100 +++ b/nbproject/configurations.xml Wed Dec 27 01:50:38 2023 +0100 @@ -6,10 +6,10 @@ ImageLoader.cpp Program.cpp Shader.cpp - Shark.cpp + OHP3D.cpp Texture.cpp XAttrs.cpp - shader-shark.cpp + ohp3d.cpp . ${MAKE} -f Makefile ${MAKE} -f Makefile clean - build/shader-shark + build/ohp3d /usr/include/x86_64-linux-gnu/ImageMagick-6 @@ -74,7 +74,7 @@ - + @@ -86,7 +86,7 @@ - + diff -r 4cbd9c0beb4c -r dc3c102e1264 nbproject/project.xml --- a/nbproject/project.xml Tue Dec 26 23:46:45 2023 +0100 +++ b/nbproject/project.xml Wed Dec 27 01:50:38 2023 +0100 @@ -3,7 +3,7 @@ org.netbeans.modules.cnd.makeproject - ShaderShark + OHP3D cpp h diff -r 4cbd9c0beb4c -r dc3c102e1264 ohp3d.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ohp3d.cpp Wed Dec 27 01:50:38 2023 +0100 @@ -0,0 +1,50 @@ +/** + * OHP3D + * Copyright © 2023 František Kučera (Frantovo.cz, GlobalCode.info) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include + +#include "CLIParser.h" +#include "OHP3D.h" + +int main(int argc, char** argv) { + setlocale(LC_ALL, ""); + std::cout << "OHP3D for OpenGL" << std::endl; + int result = 0; + + try { + std::vector arguments; + for (int i = 1; i < argc; i++) arguments.push_back(argv[i]); + + CLIParser cliParser; + Configuration cfg = cliParser.parse(arguments); + + OHP3D shaderOHP3D(cfg); + shaderOHP3D.run(); + } catch (const std::exception& e) { + std::cerr << "Exception: " << e.what() << std::endl; + result = 1; + } catch (...) { + std::cerr << "Exception: unexpected" << std::endl; + result = 2; + } + + return result; +} diff -r 4cbd9c0beb4c -r dc3c102e1264 opengl.h --- a/opengl.h Tue Dec 26 23:46:45 2023 +0100 +++ b/opengl.h Wed Dec 27 01:50:38 2023 +0100 @@ -1,5 +1,5 @@ /** - * ShaderShark + * OHP3D * Copyright © 2023 František Kučera (Frantovo.cz, GlobalCode.info) * * This program is free software: you can redistribute it and/or modify diff -r 4cbd9c0beb4c -r dc3c102e1264 shader-shark.cpp --- a/shader-shark.cpp Tue Dec 26 23:46:45 2023 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,50 +0,0 @@ -/** - * ShaderShark - * Copyright © 2023 František Kučera (Frantovo.cz, GlobalCode.info) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include -#include -#include -#include -#include - -#include "CLIParser.h" -#include "Shark.h" - -int main(int argc, char** argv) { - setlocale(LC_ALL, ""); - std::cout << "ShaderShark for OpenGL" << std::endl; - int result = 0; - - try { - std::vector arguments; - for (int i = 1; i < argc; i++) arguments.push_back(argv[i]); - - CLIParser cliParser; - Configuration cfg = cliParser.parse(arguments); - - Shark shaderShark(cfg); - shaderShark.run(); - } catch (const std::exception& e) { - std::cerr << "Exception: " << e.what() << std::endl; - result = 1; - } catch (...) { - std::cerr << "Exception: unexpected" << std::endl; - result = 2; - } - - return result; -} diff -r 4cbd9c0beb4c -r dc3c102e1264 textures/default.png Binary file textures/default.png has changed diff -r 4cbd9c0beb4c -r dc3c102e1264 x11.h --- a/x11.h Tue Dec 26 23:46:45 2023 +0100 +++ b/x11.h Wed Dec 27 01:50:38 2023 +0100 @@ -1,5 +1,5 @@ /** - * ShaderShark + * OHP3D * Copyright © 2023 František Kučera (Frantovo.cz, GlobalCode.info) * * This program is free software: you can redistribute it and/or modify