OOP for Shader and ShaderProgram v_0
authorFrantišek Kučera <franta-hg@frantovo.cz>
Fri, 01 Dec 2023 20:25:49 +0100
branchv_0
changeset 5 ee4ba9f5a053
parent 4 9aba96f0b001
child 6 fd93a46db15b
OOP for Shader and ShaderProgram
CLIParser.h
Configuration.h
Makefile
Program.cpp
Program.h
Shader.cpp
Shader.h
Shark.cpp
Shark.h
bash-completion.sh
nbproject/configurations.xml
opengl.h
--- a/CLIParser.h	Wed Nov 29 01:27:05 2023 +0100
+++ b/CLIParser.h	Fri Dec 01 20:25:49 2023 +0100
@@ -26,6 +26,7 @@
 private:
 
 	static const std::string OPTION_TEXTURE;
+	static const std::string OPTION_SHADER;
 	static const std::string OPTION_BACKGROUND_COLOR;
 	static const std::string OPTION_ROOT_WINDOW;
 
@@ -70,6 +71,12 @@
 				Configuration::Texture tex;
 				tex.fileName = readNext(arguments, i);
 				c.textures.push_back(tex);
+			} else if (option == OPTION_SHADER) {
+				const auto type = readNext(arguments, i);
+				const auto file = readNext(arguments, i);
+				if (type == "fragment") c.fragmentShaders.push_back({file});
+				else if (type == "vertex") c.vertexShaders .push_back({file});
+				else throw std::invalid_argument("unsupported shader type");
 			} else if (option == OPTION_BACKGROUND_COLOR) {
 				c.backgroundColor = parseHexColor(readNext(arguments, i));
 			} else if (option == OPTION_ROOT_WINDOW) {
@@ -82,5 +89,6 @@
 };
 
 const std::string CLIParser::OPTION_TEXTURE = "--texture";
+const std::string CLIParser::OPTION_SHADER = "--shader";
 const std::string CLIParser::OPTION_BACKGROUND_COLOR = "--background-color";
 const std::string CLIParser::OPTION_ROOT_WINDOW = "--root-window";
--- a/Configuration.h	Wed Nov 29 01:27:05 2023 +0100
+++ b/Configuration.h	Fri Dec 01 20:25:49 2023 +0100
@@ -43,8 +43,8 @@
 
 	// TODO: support loading whole directory and monitoring using inotify
 	std::vector<Texture> textures;
-	VertexShader vertexShader;
-	FragmentShader fragmentShader;
+	std::vector<VertexShader> vertexShaders;
+	std::vector<FragmentShader> fragmentShaders;
 
 	unsigned long backgroundColor = (0x33 << 16 | 0x33 << 8 | 0x33);
 	Window rootWindow = 0;
--- a/Makefile	Wed Nov 29 01:27:05 2023 +0100
+++ b/Makefile	Fri Dec 01 20:25:49 2023 +0100
@@ -26,7 +26,7 @@
 build:
 	mkdir -p $(@)
 	
-SRC=Shark.cpp shader-shark.cpp ImageLoader.cpp
+SRC=Shark.cpp shader-shark.cpp ImageLoader.cpp Shader.cpp Program.cpp
 
 build/shader-shark: $(SRC) build *.h
 	$(CXX) -std=c++20 -g -o $(@) $(SRC) $$(pkg-config --cflags --libs \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Program.cpp	Fri Dec 01 20:25:49 2023 +0100
@@ -0,0 +1,76 @@
+/**
+ * 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 <string>
+#include <exception>
+
+#include "Program.h"
+
+class Program::Impl {
+public:
+	GLuint id;
+};
+
+Program::Program() : impl(new Impl()) {
+	impl->id = glCreateProgram();
+}
+
+Program::~Program() {
+	glDeleteProgram(impl->id);
+	delete impl;
+}
+
+void Program::attachShader(const Shader& shader) {
+	glAttachShader(impl->id, shader.getId());
+}
+
+void Program::detachShader(const Shader& shader) {
+	glDetachShader(impl->id, shader.getId());
+}
+
+void Program::link() {
+	glLinkProgram(impl->id);
+	GLint linkStatus;
+	glGetProgramiv(impl->id, GL_LINK_STATUS, &linkStatus);
+	if (linkStatus != GL_TRUE) {
+		char error[512];
+		glGetProgramInfoLog(impl->id, sizeof (error), NULL, error);
+		throw std::logic_error(
+				std::string("GLSL: program failed to link: ") + error);
+	}
+}
+
+void Program::use() {
+	glUseProgram(impl->id);
+	checkError();
+}
+
+GLint Program::getAttribLocation(const std::string& name) {
+	return glGetAttribLocation(impl->id, name.c_str());
+}
+
+GLint Program::getUniformLocation(const std::string& name) {
+	return glGetUniformLocation(impl->id, name.c_str());
+}
+
+GLint Program::getFragDataLocation(const std::string& name) {
+	return glGetFragDataLocation(impl->id, name.c_str());
+}
+
+void Program::bindFragDataLocation(const std::string& name, const GLint color) {
+	glBindFragDataLocation(impl->id, color, name.c_str());
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Program.h	Fri Dec 01 20:25:49 2023 +0100
@@ -0,0 +1,40 @@
+/**
+ * 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 "opengl.h"
+#include "Shader.h"
+
+class Program {
+public:
+	class Impl;
+	Program();
+	virtual ~Program();
+	void attachShader(const Shader& shader);
+	void detachShader(const Shader& shader);
+	void link();
+	void use();
+	GLint getAttribLocation(const std::string& name);
+	GLint getUniformLocation(const std::string& name);
+	GLint getFragDataLocation(const std::string& name);
+	void bindFragDataLocation(const std::string& name, const GLint color);
+private:
+	Impl* impl;
+	Program(const Program&) = delete;
+	Program& operator=(const Program&) = delete;
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Shader.cpp	Fri Dec 01 20:25:49 2023 +0100
@@ -0,0 +1,68 @@
+/**
+ * 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 <string>
+#include <stdexcept>
+
+#include "Shader.h"
+
+class Shader::Impl {
+public:
+	Shader::Type type;
+	GLuint id;
+	std::string fileName;
+};
+
+Shader::Shader(
+		const Type type,
+		const Buffer& source,
+		const std::string& fileName) : impl(new Impl()) {
+	impl->type = type;
+	impl->fileName = fileName;
+	impl->id = glCreateShader((GLenum) impl->type);
+	update(source);
+}
+
+Shader::~Shader() {
+	glDeleteShader(impl->id);
+	delete impl;
+}
+
+GLuint Shader::getId() const {
+	return impl->id;
+}
+
+const std::string Shader::getFileName() const {
+	return impl->fileName;
+}
+
+void Shader::update(const Buffer& source) {
+	auto fileData = source.getData();
+	GLint fileSize = source.getSize();
+	glShaderSource(impl->id, 1, &fileData, &fileSize);
+	glCompileShader(impl->id);
+
+	GLint compileStatus;
+	glGetShaderiv(impl->id, GL_COMPILE_STATUS, &compileStatus);
+
+	if (compileStatus != GL_TRUE) {
+		char error[512];
+		glGetShaderInfoLog(impl->id, sizeof (error), NULL, error);
+		throw std::logic_error(
+				std::string("GLSL: shader failed to compile: ") + error);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Shader.h	Fri Dec 01 20:25:49 2023 +0100
@@ -0,0 +1,45 @@
+/**
+ * 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 <string>
+
+#include <epoxy/gl.h>
+#include <epoxy/glx.h>
+
+#include "Buffer.h"
+
+class Shader {
+public:
+	class Impl;
+
+	enum class Type : GLenum {
+		FRAGMENT = GL_FRAGMENT_SHADER,
+		VERTEX = GL_VERTEX_SHADER,
+	};
+
+	Shader(Type type, const Buffer& source, const std::string& fileName);
+	virtual ~Shader();
+	GLuint getId() const;
+	const std::string getFileName() const;
+	void update(const Buffer& source);
+private:
+	Impl* impl;
+	Shader(const Shader&) = delete;
+	Shader& operator=(const Shader&) = delete;
+};
--- a/Shark.cpp	Wed Nov 29 01:27:05 2023 +0100
+++ b/Shark.cpp	Fri Dec 01 20:25:49 2023 +0100
@@ -24,15 +24,25 @@
 }
 
 Shark::~Shark() {
+	// TODO: more SBRM
+	shaders.clear();
+	shaderProgram = nullptr;
+	XFree(vi);
+	// for (auto page : pdfTextures) glDeleteTextures(1, &page.texture);
+	glXMakeCurrent(dpy, None, NULL);
+	glXDestroyContext(dpy, glc);
+	XDestroyWindow(dpy, win);
+	XCloseDisplay(dpy);
+	// std::cerr << "~Shark()" << std::endl;
 }
 
 void Shark::run() {
-	Display* dpy = XOpenDisplay(NULL);
+	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};
-	XVisualInfo* vi = glXChooseVisual(dpy, 0, att);
+	vi = glXChooseVisual(dpy, 0, att);
 	Window root = DefaultRootWindow(dpy);
 	Window parent = cfg.rootWindow ? cfg.rootWindow : root;
 
@@ -52,7 +62,7 @@
 		height = parentAttr.height;
 	}
 
-	Window win = XCreateWindow(
+	win = XCreateWindow(
 			dpy, parent, 0, 0, width, height, 0,
 			vi->depth, InputOutput, vi->visual,
 			CWColormap | CWEventMask, &swa);
@@ -62,7 +72,7 @@
 	setX11PID(dpy, win);
 	// XSetWindowBackground(dpy, win, 0) vs. glClearColor()
 
-	GLXContext glc = glXCreateContext(dpy, vi, NULL, GL_TRUE);
+	glc = glXCreateContext(dpy, vi, NULL, GL_TRUE);
 	glXMakeCurrent(dpy, win, glc);
 
 	clear();
@@ -209,14 +219,6 @@
 			}
 		}
 	}
-
-	XFree(vi);
-	// for (auto page : pdfTextures) glDeleteTextures(1, &page.texture);
-
-	glXMakeCurrent(dpy, None, NULL);
-	glXDestroyContext(dpy, glc);
-	XDestroyWindow(dpy, win);
-	XCloseDisplay(dpy);
 }
 
 void Shark::clear() {
@@ -229,7 +231,7 @@
 }
 
 void Shark::runShaders() {
-	glUseProgram(shaderProgram);
+	shaderProgram->use();
 	checkError(&std::cerr);
 
 	clear();
@@ -358,7 +360,7 @@
 	}
 }
 
-GLuint Shark::loadShaders() {
+std::shared_ptr<Program> Shark::loadShaders() {
 	try {
 		// Vertex Array Object (VAO)
 		GLuint vao;
@@ -379,36 +381,17 @@
 			"shaders/first.frag",
 		};
 
-		std::vector<GLuint> shaders;
-
-		GLuint program = glCreateProgram();
+		std::shared_ptr<Program> program = std::make_shared<Program>();
 
 		// glBindFragDataLocation(program, 0, "outColor");
 		// glBindAttribLocation(program, LOC.input, "vertices");
 
 		for (const std::string& fileName : fileNames) {
 			MappedFile file(fileName);
-			GLuint shader = glCreateShader(toShaderType(fileName));
-			auto fileData = file.getData();
-			GLint fileSize = file.getSize();
-			glShaderSource(shader, 1, &fileData, &fileSize);
-			glCompileShader(shader);
+			std::shared_ptr<Shader> shader = std::make_shared<Shader>(
+					toShaderType(fileName), file, fileName);
 
-			GLint compileStatus;
-			glGetShaderiv(shader, GL_COMPILE_STATUS, &compileStatus);
-			std::cerr << "GLSL shader compile status: "
-					<< compileStatus
-					<< (compileStatus == GL_TRUE ? " = OK" : " = ERROR")
-					<< std::endl;
-
-			if (compileStatus != GL_TRUE) {
-				char error[512];
-				glGetShaderInfoLog(shader, sizeof (error), NULL, error);
-				std::cerr << "GLSL shader error: " << error;
-				throw std::logic_error("GLSL: shader failed to compile");
-			}
-
-			glAttachShader(program, shader);
+			program->attachShader(*shader.get());
 			shaders.push_back(shader);
 			std::cerr << "GLSL loaded: " << fileName.c_str() << std::endl;
 		}
@@ -419,27 +402,18 @@
 		// the variable is still deleted and considered „inactive“.
 		// Functions glGetAttribLocation() and glGetUniformLocation() return -1.
 
-		glLinkProgram(program);
+		program->link();
 
-		ProgAttr.aVertexXYZ = glGetAttribLocation(program, "aVertexXYZ");
-		ProgAttr.aTextureXY = glGetAttribLocation(program, "aTextureXY");
-		ProgAttr.uModel = glGetUniformLocation(program, "uModel");
-		ProgAttr.uView = glGetUniformLocation(program, "uView");
-		ProgAttr.uProjection = glGetUniformLocation(program, "uProjection");
-		ProgAttr.uTexture = glGetUniformLocation(program, "uTexture");
-		ProgAttr.fColor = glGetFragDataLocation(program, "fColor");
-		glBindFragDataLocation(program, ProgAttr.fColor, "fColor");
+		ProgAttr.aVertexXYZ = program->getAttribLocation("aVertexXYZ");
+		ProgAttr.aTextureXY = program->getAttribLocation("aTextureXY");
+		ProgAttr.uModel = program->getUniformLocation("uModel");
+		ProgAttr.uView = program->getUniformLocation("uView");
+		ProgAttr.uProjection = program->getUniformLocation("uProjection");
+		ProgAttr.uTexture = program->getUniformLocation("uTexture");
+		ProgAttr.fColor = program->getFragDataLocation("fColor");
+		program->bindFragDataLocation("fColor", ProgAttr.fColor);
 		// listVariables(program);
-		GLint linkStatus;
-		glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
-		if (linkStatus != GL_TRUE) {
-			char error[512];
-			glGetProgramInfoLog(program, sizeof (error), NULL, error);
-			std::cerr << "GLSL program error: " << error;
-			throw std::logic_error("GLSL: program failed to link");
-		}
 		std::cerr << "GLSL shader count: " << shaders.size() << std::endl;
-
 		return program;
 	} catch (const std::exception& e) {
 		std::cerr << "Error while loading shaders: " << e.what() << std::endl;
--- a/Shark.h	Wed Nov 29 01:27:05 2023 +0100
+++ b/Shark.h	Fri Dec 01 20:25:49 2023 +0100
@@ -35,6 +35,8 @@
 #include "Logger.h"
 #include "MappedFile.h"
 #include "ImageLoader.h"
+#include "Shader.h"
+#include "Program.h"
 
 class Shark {
 private:
@@ -167,10 +169,15 @@
 			return (GLfloat) width / (GLfloat) height;
 		}
 	};
+
+	Display* dpy;
+	Window win;
+	XVisualInfo* vi;
+	GLXContext glc;
 	
 	ImageLoader imageLoader;
-	
-	GLuint shaderProgram;
+	std::vector<std::shared_ptr<Shader>> shaders;
+	std::shared_ptr<Program> shaderProgram;
 	std::vector<Texture> textures;
 
 	Configuration cfg;
@@ -184,7 +191,7 @@
 	void loadVertices();
 	Texture loadTexture(const std::string& fileName);
 	void loadTextures();
-	GLuint loadShaders();
+	std::shared_ptr<Program> loadShaders();
 public:
 	Shark(const Configuration& cfg);
 	virtual ~Shark();
--- a/bash-completion.sh	Wed Nov 29 01:27:05 2023 +0100
+++ b/bash-completion.sh	Fri Dec 01 20:25:49 2023 +0100
@@ -28,13 +28,23 @@
 		"147258"
 	)
 
+	local SHADER_TYPE=(
+		"vertex"
+		"fragment"
+	)
+
 	if   [[ "$w1" == "--texture" ]]; then
 		COMPREPLY=($(compgen -f -- "$w0"; compgen -d -S / -- "$w0"))
+	elif [[ "$w1" == "--shader" ]]; then
+		COMPREPLY=($(compgen -W "${SHADER_TYPE[*]}" -- "$w0"))
+	elif [[ "$w2" == "--shader" ]]; then
+		COMPREPLY=($(compgen -f -- "$w0"; compgen -d -S / -- "$w0"))
 	elif [[ "$w1" == "--background-color" ]]; then
 		COMPREPLY=($(compgen -W "${COLOR[*]}" -- "$w0"))
 	else
 		local OPTIONS=(
 			"--texture"
+			"--shader"
 			"--background-color"
 			"--root-window"
 		)
--- a/nbproject/configurations.xml	Wed Nov 29 01:27:05 2023 +0100
+++ b/nbproject/configurations.xml	Fri Dec 01 20:25:49 2023 +0100
@@ -3,6 +3,8 @@
   <logicalFolder name="root" displayName="root" projectFiles="true" kind="ROOT">
     <df root="." name="0">
       <in>ImageLoader.cpp</in>
+      <in>Program.cpp</in>
+      <in>Shader.cpp</in>
       <in>Shark.cpp</in>
       <in>shader-shark.cpp</in>
     </df>
@@ -57,6 +59,14 @@
         <ccTool flags="0">
         </ccTool>
       </item>
+      <item path="Program.cpp" ex="false" tool="1" flavor2="0">
+        <ccTool flags="0">
+        </ccTool>
+      </item>
+      <item path="Shader.cpp" ex="false" tool="1" flavor2="0">
+        <ccTool flags="0">
+        </ccTool>
+      </item>
       <item path="Shark.cpp" ex="false" tool="1" flavor2="0">
         <ccTool flags="0">
         </ccTool>
--- a/opengl.h	Wed Nov 29 01:27:05 2023 +0100
+++ b/opengl.h	Fri Dec 01 20:25:49 2023 +0100
@@ -17,6 +17,7 @@
 
 #pragma once
 
+#include <iostream>
 #include <string>
 #include <stdexcept>
 
@@ -27,6 +28,8 @@
 #include <glm/glm.hpp>
 #include <glm/ext.hpp>
 
+#include "Shader.h"
+
 inline
 void quads(GLfloat tx, GLfloat ty, GLfloat x, GLfloat y, GLfloat z) {
 	glTexCoord2f(tx, ty);
@@ -34,7 +37,7 @@
 }
 
 inline
-void checkError(std::ostream* out) {
+void checkError(std::ostream* out = nullptr) {
 	GLenum code = glGetError();
 	if (code == GL_NO_ERROR) {
 		if (out) *out << "GL check: OK" << std::endl;
@@ -46,9 +49,9 @@
 }
 
 inline
-GLenum toShaderType(const std::string& fileName) {
-	if (fileName.ends_with(".vert")) return GL_VERTEX_SHADER;
-	else if (fileName.ends_with(".frag")) return GL_FRAGMENT_SHADER;
+Shader::Type toShaderType(const std::string& fileName) {
+	if (fileName.ends_with(".vert")) return Shader::Type::VERTEX;
+	else if (fileName.ends_with(".frag")) return Shader::Type::FRAGMENT;
 	else throw std::invalid_argument("Expecting *.vert or *.frag file");
 }