--- 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
--- 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
--- 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
--- 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
--- 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
--- 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
--- 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
--- 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
--- 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
--- 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
--- 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 <http://www.gnu.org/licenses/>.
-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++)
--- 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
--- /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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <iostream>
+#include <iomanip>
+#include <string>
+#include <charconv>
+#include <memory>
+#include <functional>
+#include <sstream>
+#include <vector>
+
+#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<WatchedFile> watchedFiles;
+ ImageLoader imageLoader;
+ std::vector<std::shared_ptr<Shader>> shaders;
+ std::shared_ptr<Program> shaderProgram;
+ std::vector<std::shared_ptr<Texture>> 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<Texture> 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<Texture> tex = textures[i];
+ // TODO: draw a rectangle for each texture
+ GLfloat ratio = tex->getRatio();
+ const std::vector<GLfloat> 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<Texture> 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<ImageLoader::ImageBuffer>
+ img(imageLoader.loadImage(MappedFile(tex.fileName)));
+ textures.push_back(std::make_shared<Texture>(
+ 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<Texture> tex : textures) {
+ if (tex->getFileName() == fileName) {
+ std::shared_ptr<ImageLoader::ImageBuffer>
+ 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<Program>();
+
+ // 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> shader = std::make_shared<Shader>(
+ 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;
+}
--- /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 <http://www.gnu.org/licenses/>.
+ */
+
+#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;
+};
--- 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
--- 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
--- 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
--- 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
--- 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 <http://www.gnu.org/licenses/>.
- */
-
-#include <iostream>
-#include <iomanip>
-#include <string>
-#include <charconv>
-#include <memory>
-#include <functional>
-#include <sstream>
-#include <vector>
-
-#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<WatchedFile> watchedFiles;
- ImageLoader imageLoader;
- std::vector<std::shared_ptr<Shader>> shaders;
- std::shared_ptr<Program> shaderProgram;
- std::vector<std::shared_ptr<Texture>> 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<Texture> 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<Texture> tex = textures[i];
- // TODO: draw a rectangle for each texture
- GLfloat ratio = tex->getRatio();
- const std::vector<GLfloat> 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<Texture> 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<ImageLoader::ImageBuffer>
- img(imageLoader.loadImage(MappedFile(tex.fileName)));
- textures.push_back(std::make_shared<Texture>(
- 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<Texture> tex : textures) {
- if (tex->getFileName() == fileName) {
- std::shared_ptr<ImageLoader::ImageBuffer>
- 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<Program>();
-
- // 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> shader = std::make_shared<Shader>(
- 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;
-}
--- 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 <http://www.gnu.org/licenses/>.
- */
-
-#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;
-};
--- 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
--- 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
--- 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
--- 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
--- 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 <http://www.gnu.org/licenses/>.
-_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
--- 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 @@
<in>ImageLoader.cpp</in>
<in>Program.cpp</in>
<in>Shader.cpp</in>
- <in>Shark.cpp</in>
+ <in>OHP3D.cpp</in>
<in>Texture.cpp</in>
<in>XAttrs.cpp</in>
- <in>shader-shark.cpp</in>
+ <in>ohp3d.cpp</in>
</df>
<logicalFolder name="ExternalFiles"
displayName="Important Files"
@@ -41,7 +41,7 @@
<buildCommandWorkingDir>.</buildCommandWorkingDir>
<buildCommand>${MAKE} -f Makefile</buildCommand>
<cleanCommand>${MAKE} -f Makefile clean</cleanCommand>
- <executablePath>build/shader-shark</executablePath>
+ <executablePath>build/ohp3d</executablePath>
<ccTool>
<incDir>
<pElem>/usr/include/x86_64-linux-gnu/ImageMagick-6</pElem>
@@ -74,7 +74,7 @@
<ccTool flags="0">
</ccTool>
</item>
- <item path="Shark.cpp" ex="false" tool="1" flavor2="0">
+ <item path="OHP3D.cpp" ex="false" tool="1" flavor2="0">
<ccTool flags="0">
</ccTool>
</item>
@@ -86,7 +86,7 @@
<ccTool flags="0">
</ccTool>
</item>
- <item path="shader-shark.cpp" ex="false" tool="1" flavor2="0">
+ <item path="ohp3d.cpp" ex="false" tool="1" flavor2="0">
<ccTool flags="0">
</ccTool>
</item>
--- 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 @@
<type>org.netbeans.modules.cnd.makeproject</type>
<configuration>
<data xmlns="http://www.netbeans.org/ns/make-project/1">
- <name>ShaderShark</name>
+ <name>OHP3D</name>
<c-extensions/>
<cpp-extensions>cpp</cpp-extensions>
<header-extensions>h</header-extensions>
--- /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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <iostream>
+#include <locale>
+#include <stdexcept>
+#include <vector>
+#include <string>
+
+#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<std::string> 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;
+}
--- 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
--- 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 <http://www.gnu.org/licenses/>.
- */
-
-#include <iostream>
-#include <locale>
-#include <stdexcept>
-#include <vector>
-#include <string>
-
-#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<std::string> 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;
-}
Binary file textures/default.png has changed
--- 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