From a3a19af3ec12b1fc10a4e46ce87970d415d76f4a Mon Sep 17 00:00:00 2001 From: Dimitri Lozeve Date: Tue, 23 Feb 2021 19:14:00 +0100 Subject: [PATCH] Initial commit --- .gitignore | 3 + README.org | 3 + log.c | 168 +++++++++++++++++++++++++++++++ log.h | 49 ++++++++++ main.c | 277 ++++++++++++++++++++++++++++++++++++++++++++++++++++ main.frag | 86 ++++++++++++++++ main.h | 12 +++ meson.build | 20 ++++ 8 files changed, 618 insertions(+) create mode 100644 .gitignore create mode 100644 README.org create mode 100644 log.c create mode 100644 log.h create mode 100644 main.c create mode 100644 main.frag create mode 100644 main.h create mode 100644 meson.build diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..376e902 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*\~ +.cache/ +build/ diff --git a/README.org b/README.org new file mode 100644 index 0000000..9a165b6 --- /dev/null +++ b/README.org @@ -0,0 +1,3 @@ +* ShaderTool + +Live tool for developing OpenGL shaders interactively. diff --git a/log.c b/log.c new file mode 100644 index 0000000..1a7626e --- /dev/null +++ b/log.c @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2020 rxi + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "log.h" + +#define MAX_CALLBACKS 32 + +typedef struct { + log_LogFn fn; + void *udata; + int level; +} Callback; + +static struct { + void *udata; + log_LockFn lock; + int level; + bool quiet; + Callback callbacks[MAX_CALLBACKS]; +} L; + + +static const char *level_strings[] = { + "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL" +}; + +#ifdef LOG_USE_COLOR +static const char *level_colors[] = { + "\x1b[94m", "\x1b[36m", "\x1b[32m", "\x1b[33m", "\x1b[31m", "\x1b[35m" +}; +#endif + + +static void stdout_callback(log_Event *ev) { + char buf[16]; + buf[strftime(buf, sizeof(buf), "%H:%M:%S", ev->time)] = '\0'; +#ifdef LOG_USE_COLOR + fprintf( + ev->udata, "%s %s%-5s\x1b[0m \x1b[90m%s:%d:\x1b[0m ", + buf, level_colors[ev->level], level_strings[ev->level], + ev->file, ev->line); +#else + fprintf( + ev->udata, "%s %-5s %s:%d: ", + buf, level_strings[ev->level], ev->file, ev->line); +#endif + vfprintf(ev->udata, ev->fmt, ev->ap); + fprintf(ev->udata, "\n"); + fflush(ev->udata); +} + + +static void file_callback(log_Event *ev) { + char buf[64]; + buf[strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", ev->time)] = '\0'; + fprintf( + ev->udata, "%s %-5s %s:%d: ", + buf, level_strings[ev->level], ev->file, ev->line); + vfprintf(ev->udata, ev->fmt, ev->ap); + fprintf(ev->udata, "\n"); + fflush(ev->udata); +} + + +static void lock(void) { + if (L.lock) { L.lock(true, L.udata); } +} + + +static void unlock(void) { + if (L.lock) { L.lock(false, L.udata); } +} + + +const char* log_level_string(int level) { + return level_strings[level]; +} + + +void log_set_lock(log_LockFn fn, void *udata) { + L.lock = fn; + L.udata = udata; +} + + +void log_set_level(int level) { + L.level = level; +} + + +void log_set_quiet(bool enable) { + L.quiet = enable; +} + + +int log_add_callback(log_LogFn fn, void *udata, int level) { + for (int i = 0; i < MAX_CALLBACKS; i++) { + if (!L.callbacks[i].fn) { + L.callbacks[i] = (Callback) { fn, udata, level }; + return 0; + } + } + return -1; +} + + +int log_add_fp(FILE *fp, int level) { + return log_add_callback(file_callback, fp, level); +} + + +static void init_event(log_Event *ev, void *udata) { + if (!ev->time) { + time_t t = time(NULL); + ev->time = localtime(&t); + } + ev->udata = udata; +} + + +void log_log(int level, const char *file, int line, const char *fmt, ...) { + log_Event ev = { + .fmt = fmt, + .file = file, + .line = line, + .level = level, + }; + + lock(); + + if (!L.quiet && level >= L.level) { + init_event(&ev, stderr); + va_start(ev.ap, fmt); + stdout_callback(&ev); + va_end(ev.ap); + } + + for (int i = 0; i < MAX_CALLBACKS && L.callbacks[i].fn; i++) { + Callback *cb = &L.callbacks[i]; + if (level >= cb->level) { + init_event(&ev, cb->udata); + va_start(ev.ap, fmt); + cb->fn(&ev); + va_end(ev.ap); + } + } + + unlock(); +} diff --git a/log.h b/log.h new file mode 100644 index 0000000..b1fae24 --- /dev/null +++ b/log.h @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2020 rxi + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the MIT license. See `log.c` for details. + */ + +#ifndef LOG_H +#define LOG_H + +#include +#include +#include +#include + +#define LOG_VERSION "0.1.0" + +typedef struct { + va_list ap; + const char *fmt; + const char *file; + struct tm *time; + void *udata; + int line; + int level; +} log_Event; + +typedef void (*log_LogFn)(log_Event *ev); +typedef void (*log_LockFn)(bool lock, void *udata); + +enum { LOG_TRACE, LOG_DEBUG, LOG_INFO, LOG_WARN, LOG_ERROR, LOG_FATAL }; + +#define log_trace(...) log_log(LOG_TRACE, __FILE__, __LINE__, __VA_ARGS__) +#define log_debug(...) log_log(LOG_DEBUG, __FILE__, __LINE__, __VA_ARGS__) +#define log_info(...) log_log(LOG_INFO, __FILE__, __LINE__, __VA_ARGS__) +#define log_warn(...) log_log(LOG_WARN, __FILE__, __LINE__, __VA_ARGS__) +#define log_error(...) log_log(LOG_ERROR, __FILE__, __LINE__, __VA_ARGS__) +#define log_fatal(...) log_log(LOG_FATAL, __FILE__, __LINE__, __VA_ARGS__) + +const char* log_level_string(int level); +void log_set_lock(log_LockFn fn, void *udata); +void log_set_level(int level); +void log_set_quiet(bool enable); +int log_add_callback(log_LogFn fn, void *udata, int level); +int log_add_fp(FILE *fp, int level); + +void log_log(int level, const char *file, int line, const char *fmt, ...); + +#endif diff --git a/main.c b/main.c new file mode 100644 index 0000000..b8f46bc --- /dev/null +++ b/main.c @@ -0,0 +1,277 @@ +#include +#include +#include +#include +#include + +#include "log.h" +#include "main.h" + +#define UNUSED(a) (void)a + +#define WINDOW_WIDTH 800 +#define WINDOW_HEIGHT 600 + +#define FRAGMENT_SHADER_FILE "main.frag" + +int main() { + /* Initialize GLFW */ + if (!glfwInit()) { + log_error("[GLFW] Failed to init"); + return EXIT_FAILURE; + } + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + + /* Create window */ + GLFWwindow *window = + glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "OpenGL Test", NULL, NULL); + if (window == NULL) { + log_error("[GLFW] Failed to create window"); + glfwTerminate(); + return EXIT_FAILURE; + } + glfwMakeContextCurrent(window); + log_debug("[GLFW] Created window of size %d, %d", WINDOW_WIDTH, + WINDOW_HEIGHT); + + glViewport(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT); + glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); + + /* Initialize OpenGL */ + if (glewInit() != GLEW_OK) { + log_error("[GLEW] Failed to initialize"); + return EXIT_FAILURE; + } + log_debug("[GLEW] Initialized successfully"); + + /* Define vertices */ + float vertices[] = { + -1.0, -1.0, 0.0, // bottom left + -1.0, 1.0, 0.0, // top left + 1.0, -1.0, 0.0, // top right + 1.0, 1.0, 0.0 // bottom right + }; + unsigned int indices[] = { + 0, 1, 2, // first triangle + 1, 2, 3 // second triangle + }; + + unsigned int VBO = 0; + glGenBuffers(1, &VBO); + unsigned int EBO = 0; + glGenBuffers(1, &EBO); + unsigned int VAO = 0; + glGenVertexArrays(1, &VAO); + + glBindVertexArray(VAO); + glBindBuffer(GL_ARRAY_BUFFER, VBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, + GL_STATIC_DRAW); + + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void *)0); + glEnableVertexAttribArray(0); + log_debug("Vertex data initialized successfully"); + + unsigned int shader_program = glCreateProgram(); + int result = compile_shaders(&shader_program, FRAGMENT_SHADER_FILE); + if (!shader_program || result) { + log_error("Could not compile shaders"); + return EXIT_FAILURE; + } + + /* Drawing loop */ + size_t frame = 0; + while (!glfwWindowShouldClose(window)) { + process_input(window, &shader_program); + + /* Background */ + glClearColor(0, 0, 0, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + + glUseProgram(shader_program); + + float time = glfwGetTime(); + int frag_time_location = glGetUniformLocation(shader_program, "u_time"); + glUniform1f(frag_time_location, time); + + int frag_frame_location = glGetUniformLocation(shader_program, "u_frame"); + glUniform1ui(frag_frame_location, frame); + + int viewport[4] = {0}; + glGetIntegerv(GL_VIEWPORT, viewport); + int frag_resolution_location = + glGetUniformLocation(shader_program, "u_resolution"); + glUniform2f(frag_resolution_location, viewport[2], viewport[3]); + + double mouse_x = 0, mouse_y = 0; + glfwGetCursorPos(window, &mouse_x, &mouse_y); + int frag_mouse_resolution_location = + glGetUniformLocation(shader_program, "u_mouse"); + glUniform2f(frag_mouse_resolution_location, mouse_x, mouse_y); + + // glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); /* For wireframe mode */ + glBindVertexArray(VAO); + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); + glBindVertexArray(0); + + glfwSwapBuffers(window); + glfwPollEvents(); + frame++; + } + + glfwTerminate(); + return EXIT_SUCCESS; +} + +/** + * @brief Compile shaders from source files. + * + * This function reads the source files of the vertex and fragment + * shaders, compiles them, and links them together in a shader + * program. + * + * @param shader_program Pointer to the ID of the shader program + * where the shaders will be stored. + * @param fragment_shader_file File name of the fragment shader. + * @return 0 on success, 1 on error. + */ +int compile_shaders(unsigned int *shader_program, + const char *const fragment_shader_file) { + /* Compile vertex shader */ + const char *const vertex_shader_source = + "#version 330 core\n" + "layout (location = 0) in vec3 aPos;\n" + "void main()\n" + "{\n" + " gl_Position = vec4(aPos, 1.0);\n" + "}\n"; + + unsigned int vertex_shader = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(vertex_shader, 1, &vertex_shader_source, NULL); + glCompileShader(vertex_shader); + int success = 0; + char info_log[512]; + glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &success); + if (!success) { + glGetShaderInfoLog(vertex_shader, 512, NULL, info_log); + log_error("Vertex shader compilation failed: %s", info_log); + } + + /* Compile fragment shader */ + const char *const fragment_shader_source = read_file(fragment_shader_file); + if (fragment_shader_source == NULL) { + log_error("Could not load fragment shader from file %s", + fragment_shader_file); + return 1; + } + unsigned int fragment_shader = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(fragment_shader, 1, &fragment_shader_source, NULL); + glCompileShader(fragment_shader); + glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &success); + if (!success) { + glGetShaderInfoLog(fragment_shader, 512, NULL, info_log); + log_error("Fragment shader compilation failed: %s", info_log); + return 1; + } + + /* Link shaders */ + unsigned int new_shader_program = glCreateProgram(); + glAttachShader(new_shader_program, vertex_shader); + glAttachShader(new_shader_program, fragment_shader); + glLinkProgram(new_shader_program); + glGetShaderiv(new_shader_program, GL_LINK_STATUS, &success); + if (!success) { + glGetShaderInfoLog(new_shader_program, 512, NULL, info_log); + log_error("Shader program linking failed: %s", info_log); + return 1; + } + + glDeleteProgram(*shader_program); + *shader_program = new_shader_program; + + free((void *)fragment_shader_source); + glDeleteShader(vertex_shader); + glDeleteShader(fragment_shader); + + log_debug("Shaders compiled successfully"); + + return 0; +} + +/** + * @brief Callback to adjust the size of the viewport when the window + * is resized. + * + * @param window The current window. + * @param width The new width. + * @param height The new height. + */ +void framebuffer_size_callback(GLFWwindow *window, int width, int height) { + UNUSED(window); + glViewport(0, 0, width, height); +} + +/** + * @brief Ensure the window is closed when the user presses the escape + * key. + * + * @param window The current window. + * @param shader_program Pointer to the shader program to update if needed. + */ +void process_input(GLFWwindow *window, unsigned int *shader_program) { + if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) { + log_debug("Quitting"); + glfwSetWindowShouldClose(window, true); + } else if (glfwGetKey(window, GLFW_KEY_R) == GLFW_PRESS) { + compile_shaders(shader_program, FRAGMENT_SHADER_FILE); + } +} + +/** + * @brief Reads a file in a heap-allocated buffer. + * + * This function computes the length of the file, allocate a buffer of + * the correct size, and reads the file in the buffer. Returns `NULL` + * on error. + * + * @param filename The file to read. + * @return A buffer containing the contents of the file, or `NULL` if + * there was an error. + */ +char *read_file(const char *const filename) { + FILE *fd = fopen(filename, "r"); + if (fd == NULL) { + log_error("Could not open file %s", filename); + return NULL; + } + + if (fseek(fd, 0, SEEK_END) == -1) { + perror("fseek"); + return NULL; + } + long size = ftell(fd); + if (size == -1) { + perror("ftell"); + return NULL; + } + rewind(fd); + char *buf = calloc(size + 1, 1); + if (buf == NULL) { + log_error("Failed to allocate memory to read file %s", filename); + return NULL; + } + + long bytes_read = fread(buf, 1, size, fd); + if (bytes_read != size) { + log_error("Failed to read file %s (%ld bytes read out of %ld total)", + filename, bytes_read, size); + return NULL; + } + + fclose(fd); + return buf; +} diff --git a/main.frag b/main.frag new file mode 100644 index 0000000..161b68f --- /dev/null +++ b/main.frag @@ -0,0 +1,86 @@ +#version 330 core + +out vec4 fragColor; + +uniform float u_time; +uniform uint u_frame; +uniform vec2 u_resolution; +uniform vec2 u_mouse; + +// Some useful functions +vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; } +vec2 mod289(vec2 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; } +vec3 permute(vec3 x) { return mod289(((x * 34.0) + 1.0) * x); } + +// +// Description : GLSL 2D simplex noise function +// Author : Ian McEwan, Ashima Arts +// Maintainer : ijm +// Lastmod : 20110822 (ijm) +// License : +// Copyright (C) 2011 Ashima Arts. All rights reserved. +// Distributed under the MIT License. See LICENSE file. +// https://github.com/ashima/webgl-noise +// +float snoise(vec2 v) { + + // Precompute values for skewed triangular grid + const vec4 C = vec4(0.211324865405187, // (3.0-sqrt(3.0))/6.0 + 0.366025403784439, // 0.5*(sqrt(3.0)-1.0) + -0.577350269189626, // -1.0 + 2.0 * C.x + 0.024390243902439); // 1.0 / 41.0 + + // First corner (x0) + vec2 i = floor(v + dot(v, C.yy)); + vec2 x0 = v - i + dot(i, C.xx); + + // Other two corners (x1, x2) + vec2 i1 = vec2(0.0); + i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0); + vec2 x1 = x0.xy + C.xx - i1; + vec2 x2 = x0.xy + C.zz; + + // Do some permutations to avoid + // truncation effects in permutation + i = mod289(i); + vec3 p = + permute(permute(i.y + vec3(0.0, i1.y, 1.0)) + i.x + vec3(0.0, i1.x, 1.0)); + + vec3 m = max(0.5 - vec3(dot(x0, x0), dot(x1, x1), dot(x2, x2)), 0.0); + + m = m * m; + m = m * m; + + // Gradients: + // 41 pts uniformly over a line, mapped onto a diamond + // The ring size 17*17 = 289 is close to a multiple + // of 41 (41*7 = 287) + vec3 x = 2.0 * fract(p * C.www) - 1.0; + vec3 h = abs(x) - 0.5; + vec3 ox = floor(x + 0.5); + vec3 a0 = x - ox; + + // Normalise gradients implicitly by scaling m + // Approximation of: m *= inversesqrt(a0*a0 + h*h); + m *= 1.79284291400159 - 0.85373472095314 * (a0 * a0 + h * h); + + // Compute final noise value at P + vec3 g = vec3(0.0); + g.x = a0.x * x0.x + h.x * x0.y; + g.yz = a0.yz * vec2(x1.x, x2.x) + h.yz * vec2(x1.y, x2.y); + return 130.0 * dot(m, g); +} + +void main() { + vec2 st = gl_FragCoord.xy / u_resolution.xy; + st.x *= u_resolution.x / u_resolution.y; + + vec3 color = vec3(0.0); + + // Scale the space in order to see the function + st *= 10.0; + + color = vec3(snoise(st) * .5 + .5); + + fragColor = vec4(color, 1.0); +} diff --git a/main.h b/main.h new file mode 100644 index 0000000..966f0ed --- /dev/null +++ b/main.h @@ -0,0 +1,12 @@ +#ifndef MAIN_H +#define MAIN_H + +#include + +char *read_file(const char *const filename); +int compile_shaders(unsigned int *shader_program, + const char *const fragment_shader_file); +void process_input(GLFWwindow *window, unsigned int *shader_program); +void framebuffer_size_callback(GLFWwindow *window, int width, int height); + +#endif /* MAIN_H */ diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..c4ea4d4 --- /dev/null +++ b/meson.build @@ -0,0 +1,20 @@ +project( + 'shadertool', 'c', + version: '0.1', + default_options: [ + 'buildtype=debugoptimized', + 'warning_level=3', + 'c_std=c99', + 'werror=true', + ] +) + +glfw_dep = dependency('glfw3') +glew_dep = dependency('glew') + +executable( + 'shadertool', + sources: ['main.c', 'log.c'], + dependencies: [glfw_dep, glew_dep], + c_args: '-DLOG_USE_COLOR', +)