diff --git a/.github/workflows/fedora.yml.off b/.github/workflows/fedora.yml.off new file mode 100644 index 0000000..cc8ff8e --- /dev/null +++ b/.github/workflows/fedora.yml.off @@ -0,0 +1,24 @@ + +name: Fedora + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +on: + pull_request: + +jobs: + build: + name: Fedora + runs-on: ubuntu-latest + container: fedora:43 + steps: + - uses: actions/checkout@v6 + + - run: | + sudo dnf install -y libXcursor-devel libXrandr-devel libXinerama-devel libXi-devel + curl -Lo mkn https://github.com/mkn/mkn/releases/download/latest/mkn_nix + chmod +x mkn + PATH="$PWD:$PATH" ./mkn.sh + ./mkn clean build -dtOa -fPIC -p all diff --git a/.github/workflows/build_nix.yml b/.github/workflows/ubuntu.yml similarity index 69% rename from .github/workflows/build_nix.yml rename to .github/workflows/ubuntu.yml index 9c3a4fd..7b05571 100644 --- a/.github/workflows/build_nix.yml +++ b/.github/workflows/ubuntu.yml @@ -1,5 +1,9 @@ -name: Build on Ubuntu +name: Ubuntu + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true on: pull_request: @@ -10,9 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - with: - submodules: true + - uses: actions/checkout@v6 - run: | sudo apt update && sudo apt install -y libxcursor-dev libxrandr-dev libxinerama-dev libxi-dev diff --git a/.gitignore b/.gitignore index 5465747..62038cf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -inc bin dep lib diff --git a/inc/.gitignore b/inc/.gitignore new file mode 100644 index 0000000..ac43837 --- /dev/null +++ b/inc/.gitignore @@ -0,0 +1,3 @@ +GL* +glm +gli diff --git a/inc/mkn/gl.hpp b/inc/mkn/gl.hpp new file mode 100644 index 0000000..9624a9b --- /dev/null +++ b/inc/mkn/gl.hpp @@ -0,0 +1,13 @@ +#ifndef _MKN_GL_HPP_ +#define _MKN_GL_HPP_ + +#include "mkn/gl/fw.hpp" +#include "mkn/gl/fw/window.hpp" + +#include "mkn/gl/sl/program.hpp" + +#include "mkn/gl/dds.hpp" +#include "mkn/gl/view.hpp" +#include "mkn/gl/shaders.hpp" + +#endif // _MKN_GL_HPP_ diff --git a/inc/mkn/gl/dds.hpp b/inc/mkn/gl/dds.hpp new file mode 100644 index 0000000..a20a56d --- /dev/null +++ b/inc/mkn/gl/dds.hpp @@ -0,0 +1,106 @@ +#ifndef _MKN_GL_DDS_HPP_ +#define _MKN_GL_DDS_HPP_ + +#include + +#include +#include +#include +#include +#include + +namespace mkn::gl::dds { + +GLuint inline static load(const char *imagepath) { + auto constexpr static FOURCC_DXT1 = 0x31545844; // Equivalent to "DXT1" in ASCII + auto constexpr static FOURCC_DXT3 = 0x33545844; // Equivalent to "DXT3" in ASCII + auto constexpr static FOURCC_DXT5 = 0x35545844; // Equivalent to "DXT5" in ASCII + + unsigned char header[124]; + + FILE *fp = fopen(imagepath, "rb"); + if (fp == NULL) { + printf( + "%s could not be opened. Are you in the right directory ? Don't forget to read the FAQ !\n", + imagepath); + getchar(); + return 0; + } + + /* verify the type of file */ + char filecode[4]; + fread(filecode, 1, 4, fp); + if (strncmp(filecode, "DDS ", 4) != 0) { + fclose(fp); + return 0; + } + + /* get the surface desc */ + fread(&header, 124, 1, fp); + + unsigned int height = *(unsigned int *)&(header[8]); + unsigned int width = *(unsigned int *)&(header[12]); + unsigned int linearSize = *(unsigned int *)&(header[16]); + unsigned int mipMapCount = *(unsigned int *)&(header[24]); + unsigned int fourCC = *(unsigned int *)&(header[80]); + + unsigned char *buffer; + unsigned int bufsize; + /* how big is it going to be including all mipmaps? */ + bufsize = mipMapCount > 1 ? linearSize * 2 : linearSize; + buffer = (unsigned char *)malloc(bufsize * sizeof(unsigned char)); + fread(buffer, 1, bufsize, fp); + /* close the file pointer */ + fclose(fp); + + unsigned int components = (fourCC == FOURCC_DXT1) ? 3 : 4; + unsigned int format; + switch (fourCC) { + case FOURCC_DXT1: + format = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; + break; + case FOURCC_DXT3: + format = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; + break; + case FOURCC_DXT5: + format = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + break; + default: + free(buffer); + return 0; + } + + // Create one OpenGL texture + GLuint textureID; + glGenTextures(1, &textureID); + + // "Bind" the newly created texture : all future texture functions will modify this texture + glBindTexture(GL_TEXTURE_2D, textureID); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + unsigned int blockSize = (format == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT) ? 8 : 16; + unsigned int offset = 0; + + /* load the mipmaps */ + for (unsigned int level = 0; level < mipMapCount && (width || height); ++level) { + unsigned int size = ((width + 3) / 4) * ((height + 3) / 4) * blockSize; + glCompressedTexImage2D(GL_TEXTURE_2D, level, format, width, height, 0, size, buffer + offset); + + offset += size; + width /= 2; + height /= 2; + + // Deal with Non-Power-Of-Two textures. This code is not included in the webpage to reduce + // clutter. + if (width < 1) width = 1; + if (height < 1) height = 1; + } + + free(buffer); + + return textureID; +} + +} // namespace mkn::gl::dds + +#endif //_MKN_GL_DDS_HPP_ diff --git a/inc/mkn/gl/ew.hpp b/inc/mkn/gl/ew.hpp new file mode 100644 index 0000000..d1439d6 --- /dev/null +++ b/inc/mkn/gl/ew.hpp @@ -0,0 +1 @@ +glew.hpp \ No newline at end of file diff --git a/inc/mkn/gl/ew/vertex.hpp b/inc/mkn/gl/ew/vertex.hpp new file mode 100644 index 0000000..e69de29 diff --git a/inc/mkn/gl/fw.hpp b/inc/mkn/gl/fw.hpp new file mode 100644 index 0000000..a0983b6 --- /dev/null +++ b/inc/mkn/gl/fw.hpp @@ -0,0 +1,24 @@ +#ifndef _MKN_GL_FW_HPP_ +#define _MKN_GL_FW_HPP_ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "mkn/kul/except.hpp" + +namespace mkn::gl::fw { + +auto static inline init() { + if (!glfwInit()) throw std::runtime_error("glfwInit() failed!"); +} + +} // namespace mkn::gl::fw + +#endif // _MKN_GL_FW_HPP_ diff --git a/inc/mkn/gl/fw/mouse.hpp b/inc/mkn/gl/fw/mouse.hpp new file mode 100644 index 0000000..d86a7ce --- /dev/null +++ b/inc/mkn/gl/fw/mouse.hpp @@ -0,0 +1,35 @@ +#ifndef _MKN_GL_FW_MOUSE_HPP_ +#define _MKN_GL_FW_MOUSE_HPP_ + +#include +#include + +namespace mkn::gl::fw { + +struct WindowParams {}; + +static inline GLFWwindow* create_window(WindowParams const& wp = {}) { + glfwWindowHint(GLFW_SAMPLES, 4); + glfwWindowHint(GLFW_RESIZABLE, GL_FALSE); + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // To make macOS happy; should not be needed + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + + // Open a window and create its OpenGL context + GLFWwindow* window = glfwCreateWindow(1024, 768, "Tutorial 18 - Particles", NULL, NULL); + if (window == NULL) { + fprintf(stderr, + "Failed to open GLFW window. If you have an Intel GPU, they are not 3.3 compatible. " + "Try the 2.1 version of the tutorials.\n"); + getchar(); + glfwTerminate(); + return nullptr; + } + glfwMakeContextCurrent(window); + return window; +} + +} // namespace mkn::gl::fw + +#endif // _MKN_GL_FW_MOUSE_HPP_ diff --git a/inc/mkn/gl/fw/window.hpp b/inc/mkn/gl/fw/window.hpp new file mode 100644 index 0000000..a9e2635 --- /dev/null +++ b/inc/mkn/gl/fw/window.hpp @@ -0,0 +1,35 @@ +#ifndef _MKN_GLFW_WINDOW_HPP_ +#define _MKN_GLFW_WINDOW_HPP_ + +#include +#include + +namespace mkn::gl::fw { + +struct WindowParams {}; + +static inline GLFWwindow* create_window(WindowParams const& wp = {}) { + glfwWindowHint(GLFW_SAMPLES, 4); + glfwWindowHint(GLFW_RESIZABLE, GL_FALSE); + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // To make macOS happy; should not be needed + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + + // Open a window and create its OpenGL context + GLFWwindow* window = glfwCreateWindow(1024, 768, "Tutorial 18 - Particles", NULL, NULL); + if (window == NULL) { + fprintf(stderr, + "Failed to open GLFW window. If you have an Intel GPU, they are not 3.3 compatible. " + "Try the 2.1 version of the tutorials.\n"); + getchar(); + glfwTerminate(); + return nullptr; + } + glfwMakeContextCurrent(window); + return window; +} + +} // namespace mkn::gl::fw + +#endif // _MKN_GLFW_WINDOW_HPP_ diff --git a/inc/mkn/gl/keys.hpp b/inc/mkn/gl/keys.hpp new file mode 100644 index 0000000..0912a68 --- /dev/null +++ b/inc/mkn/gl/keys.hpp @@ -0,0 +1,79 @@ +#ifndef _MKN_GL_KEYS_HPP_ +#define _MKN_GL_KEYS_HPP_ + +// #include +// #include +// #include + +// #include +// #include +// #include +// #include +// #include + +// void computeMatricesFromInputs(GLFWwindow* window) { +// // glfwGetTime is called only once, the first time this function is called +// static double lastTime = glfwGetTime(); + +// // Compute time difference between current and last frame +// double currentTime = glfwGetTime(); +// float deltaTime = float(currentTime - lastTime); + +// // Get mouse position +// double xpos, ypos; +// glfwGetCursorPos(window, &xpos, &ypos); + +// // Reset mouse position for next frame +// glfwSetCursorPos(window, 1024 / 2, 768 / 2); + +// // Compute new orientation +// horizontalAngle += mouseSpeed * float(1024 / 2 - xpos); +// verticalAngle += mouseSpeed * float(768 / 2 - ypos); + +// // Direction : Spherical coordinates to Cartesian coordinates conversion +// glm::vec3 direction(cos(verticalAngle) * sin(horizontalAngle), sin(verticalAngle), +// cos(verticalAngle) * cos(horizontalAngle)); + +// // Right vector +// glm::vec3 right = +// glm::vec3(sin(horizontalAngle - 3.14f / 2.0f), 0, cos(horizontalAngle - 3.14f / 2.0f)); + +// // Up vector +// glm::vec3 up = glm::cross(right, direction); + +// // Move forward +// if (glfwGetKey(window, GLFW_KEY_UP) == GLFW_PRESS) { +// position += direction * deltaTime * speed; +// } +// // Move backward +// if (glfwGetKey(window, GLFW_KEY_DOWN) == GLFW_PRESS) { +// position -= direction * deltaTime * speed; +// } +// // Strafe right +// if (glfwGetKey(window, GLFW_KEY_RIGHT) == GLFW_PRESS) { +// position += right * deltaTime * speed; +// } +// // Strafe left +// if (glfwGetKey(window, GLFW_KEY_LEFT) == GLFW_PRESS) { +// position -= right * deltaTime * speed; +// } + +// float FoV = initialFoV; // - 5 * glfwGetMouseWheel(); // Now GLFW 3 requires setting up a +// // callback for this. It's a bit too complicated for this beginner's +// // tutorial, so it's disabled instead. + +// // Projection matrix : 45° Field of View, 4:3 ratio, display range : 0.1 unit <-> 100 units +// ProjectionMatrix = glm::perspective(glm::radians(FoV), 4.0f / 3.0f, 0.1f, 100.0f); +// // Camera matrix +// ViewMatrix = +// glm::lookAt(position, // Camera is here +// position + direction, // and looks here : at the same position, plus +// "direction" up // Head is up (set to 0,-1,0 to look +// upside-down) +// ); + +// // For the next frame, the "last time" will be "now" +// lastTime = currentTime; +// } + +#endif // _MKN_GL_KEYS_HPP_ diff --git a/inc/mkn/gl/shaders.hpp b/inc/mkn/gl/shaders.hpp new file mode 100644 index 0000000..eb91a9a --- /dev/null +++ b/inc/mkn/gl/shaders.hpp @@ -0,0 +1,109 @@ +#ifndef _MKN_GL_SHADERS_HPP_ +#define _MKN_GL_SHADERS_HPP_ + +#include + +#include +#include +#include +#include + +namespace mkn::gl::shaders { + +std::optional file_to_string(auto const &file_path) { + std::string file_contents; + std::ifstream ifs(file_path, std::ios::in); + if (ifs.is_open()) { + std::stringstream sstr; + sstr << ifs.rdbuf(); + file_contents = sstr.str(); + ifs.close(); + return file_contents; + } + printf( + "Impossible to open %s. Are you in the right directory ? Don't " + "forget to read the FAQ !\n", + file_path); + getchar(); + + return std::nullopt; +} + +auto static inline load(char const *vertex_file_path, char const *fragment_file_path) { + decltype(glCreateProgram()) ret = 0; + // Create the shaders + auto VertexShaderID = glCreateShader(GL_VERTEX_SHADER); + auto FragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER); + + auto optional_vtx = file_to_string(vertex_file_path); + if (!optional_vtx) return ret; + auto VertexShaderCode = *optional_vtx; + + auto optional_fragement = file_to_string(fragment_file_path); + auto FragmentShaderCode = optional_fragement ? *optional_fragement : ""; + + auto Result = GL_FALSE; + int InfoLogLength; + + // Compile Vertex Shader + printf("Compiling shader : %s\n", vertex_file_path); + char const *VertexSourcePointer = VertexShaderCode.c_str(); + glShaderSource(VertexShaderID, 1, &VertexSourcePointer, NULL); + glCompileShader(VertexShaderID); + + // Check Vertex Shader + glGetShaderiv(VertexShaderID, GL_COMPILE_STATUS, &Result); + glGetShaderiv(VertexShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength); + if (InfoLogLength > 0) { + std::vector VertexShaderErrorMessage(InfoLogLength + 1); + glGetShaderInfoLog(VertexShaderID, InfoLogLength, NULL, &VertexShaderErrorMessage[0]); + printf("%s\n", &VertexShaderErrorMessage[0]); + } + + // Compile Fragment Shader + printf("Compiling shader : %s\n", fragment_file_path); + char const *FragmentSourcePointer = FragmentShaderCode.c_str(); + glShaderSource(FragmentShaderID, 1, &FragmentSourcePointer, NULL); + glCompileShader(FragmentShaderID); + + // Check Fragment Shader + glGetShaderiv(FragmentShaderID, GL_COMPILE_STATUS, &Result); + glGetShaderiv(FragmentShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength); + if (InfoLogLength > 0) { + std::vector FragmentShaderErrorMessage(InfoLogLength + 1); + glGetShaderInfoLog(FragmentShaderID, InfoLogLength, NULL, &FragmentShaderErrorMessage[0]); + printf("%s\n", &FragmentShaderErrorMessage[0]); + } + + // Link the program + printf("Linking program\n"); + auto ProgramID = glCreateProgram(); + glAttachShader(ProgramID, VertexShaderID); + glAttachShader(ProgramID, FragmentShaderID); + glLinkProgram(ProgramID); + + // Check the program + glGetProgramiv(ProgramID, GL_LINK_STATUS, &Result); + glGetProgramiv(ProgramID, GL_INFO_LOG_LENGTH, &InfoLogLength); + if (InfoLogLength > 0) { + std::vector ProgramErrorMessage(InfoLogLength + 1); + glGetProgramInfoLog(ProgramID, InfoLogLength, NULL, &ProgramErrorMessage[0]); + printf("%s\n", &ProgramErrorMessage[0]); + } + + glDetachShader(ProgramID, VertexShaderID); + glDetachShader(ProgramID, FragmentShaderID); + + glDeleteShader(VertexShaderID); + glDeleteShader(FragmentShaderID); + + return ProgramID; +} + +auto static inline load(std::string const &vfp, std::string const &ffp) { + return load(vfp.c_str(), ffp.c_str()); +} + +} // namespace mkn::gl::shaders + +#endif //_MKN_GL_SHADERS_HPP_ diff --git a/inc/mkn/gl/sl/program.hpp b/inc/mkn/gl/sl/program.hpp new file mode 100644 index 0000000..f2d3b5f --- /dev/null +++ b/inc/mkn/gl/sl/program.hpp @@ -0,0 +1,33 @@ +#ifndef _MKN_GL_SL_PROGRAM_HPP_ +#define _MKN_GL_SL_PROGRAM_HPP_ + +#include +#include +#include +#include + +#include "mkn/gl/shaders.hpp" + +namespace mkn::gl::sl { + +struct Program { + void tick() { glUseProgram(id); } + ~Program() { glDeleteProgram(id); } + + GLuint id = 0; +}; + +struct VertexFragmentProgram : Program { + // + + // + VertexFragmentProgram(std::string const& vfp, std::string const& ffp) + : Program{mkn::gl::shaders::load(vfp, ffp)}, vertex_file_path{vfp}, fragment_file_path{ffp} {} + + std::string const vertex_file_path; + std::string const fragment_file_path; +}; + +} // namespace mkn::gl::sl + +#endif // _MKN_GL_SL_PROGRAM_HPP_ diff --git a/inc/mkn/gl/view.hpp b/inc/mkn/gl/view.hpp new file mode 100644 index 0000000..0fefe1d --- /dev/null +++ b/inc/mkn/gl/view.hpp @@ -0,0 +1,109 @@ +#ifndef _MKN_GL_VIEW_HPP_ +#define _MKN_GL_VIEW_HPP_ + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace mkn::gl { + +struct Viewer { + Viewer(GLFWwindow* w) : window{w} { glfwSetScrollCallback(window, scroll_callback); } + + void tick(); + static void scroll_callback(GLFWwindow* window, double xoffset, double yoffset) {} + + GLFWwindow* window; + glm::mat4 view_mat; + glm::mat4 proj_mat; + + // Initial position : on +Z + glm::vec3 position = glm::vec3(0, 0, 5); + // Initial horizontal angle : toward -Z + float horizontalAngle = 3.14f; + // Initial vertical angle : none + float verticalAngle = 0.0f; + // Initial Field of View + float initialFoV = 45.0f; + + float speed = 3.0f; // 3 units / second + float mouseSpeed = 0.005f; +}; + +void Viewer::tick() { + // glfwGetTime is called only once, the first time this function is called + static double lastTime = glfwGetTime(); + + // Compute time difference between current and last frame + double currentTime = glfwGetTime(); + float deltaTime = float(currentTime - lastTime); + + // Get mouse position + double xpos, ypos; + glfwGetCursorPos(window, &xpos, &ypos); + + // Reset mouse position for next frame + glfwSetCursorPos(window, 1024.f / 2, 768.f / 2); + + // Compute new orientation + horizontalAngle += mouseSpeed * float(1024.f / 2 - xpos); + verticalAngle += mouseSpeed * float(768.f / 2 - ypos); + + // Direction : Spherical coordinates to Cartesian coordinates conversion + glm::vec3 direction(cos(verticalAngle) * sin(horizontalAngle), sin(verticalAngle), + cos(verticalAngle) * cos(horizontalAngle)); + + // Right vector + glm::vec3 right = + glm::vec3(sin(horizontalAngle - 3.14f / 2.0f), 0, cos(horizontalAngle - 3.14f / 2.0f)); + + // Up vector + glm::vec3 up = glm::cross(right, direction); + + // Move forward + if (glfwGetKey(window, GLFW_KEY_UP) == GLFW_PRESS || + glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) { + position += direction * deltaTime * speed; + } + // Move backward + if (glfwGetKey(window, GLFW_KEY_DOWN) == GLFW_PRESS || + glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) { + position -= direction * deltaTime * speed; + } + // Strafe right + if (glfwGetKey(window, GLFW_KEY_RIGHT) == GLFW_PRESS || + glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) { + position += right * deltaTime * speed; + } + // Strafe left + if (glfwGetKey(window, GLFW_KEY_LEFT) == GLFW_PRESS || + glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) { + position -= right * deltaTime * speed; + } + + float FoV = initialFoV; // - 5 * glfwGetMouseWheel(); // Now GLFW 3 requires setting up a + // callback for this. It's a bit too complicated for this beginner's + // tutorial, so it's disabled instead. + + // Projection matrix : 45° Field of View, 4:3 ratio, display range : 0.1 unit <-> 100 units + proj_mat = glm::perspective(glm::radians(FoV), 4.0f / 3.0f, 0.1f, 100.0f); + // Camera matrix + view_mat = + glm::lookAt(position, // Camera is here + position + direction, // and looks here : at the same position, plus "direction" + up // Head is up (set to 0,-1,0 to look upside-down) + ); + + // For the next frame, the "last time" will be "now" + lastTime = currentTime; +} + +} // namespace mkn::gl + +#endif // _MKN_GL_VIEW_HPP_ diff --git a/mkn.sh b/mkn.sh index 419e0fb..e5314fa 100755 --- a/mkn.sh +++ b/mkn.sh @@ -8,8 +8,9 @@ do_incs() { cp -r dep/gl/inc/GL/* inc/GL/ cp -r dep/glew/g/include/GL/* inc/GL/ cp -r dep/glu/g/include/GL/* inc/GL - cp -r dep/glfw/g/include/* inc/ + cp -r dep/glfw/g/include/* inc cp -r dep/glm/g/glm inc/ + cp -r dep/gli/g/gli inc/ } -[ ! -d "inc" ] && do_incs +[ ! -d "inc/GL" ] && do_incs diff --git a/mkn.yaml b/mkn.yaml index 65a318d..a788fac 100644 --- a/mkn.yaml +++ b/mkn.yaml @@ -1,6 +1,6 @@ -#! clean build test run -tOqp rocm -x res/mkn/hipcc -W +#! clean build -dtOa -fPIC -p all -name: mkn.gpu +name: mkn.gl parent: all profile: @@ -8,6 +8,7 @@ profile: inc: inc - name: all + dep: mkn.kul parent: inc if_arg: bsd: -DMACOSX @@ -28,3 +29,4 @@ profile: gl.glew&dep/glew gl.glfw&dep/glfw gl.glm&dep/glm + gl.gli&dep/gli