Browse Source

Introduce render.conf file

rexim 3 years ago
parent
commit
9c3283c3ba
5 changed files with 419 additions and 28 deletions
  1. BIN
      assets/tsodinSleep.png
  2. BIN
      assets/tsodinW.png
  3. 108 28
      main.c
  4. 4 0
      render.conf
  5. 307 0
      sv.h

BIN
assets/tsodinSleep.png


BIN
assets/tsodinW.png


+ 108 - 28
main.c

@@ -13,6 +13,9 @@
 #define LA_IMPLEMENTATION
 #include "la.h"
 
+#define SV_IMPLEMENTATION
+#include "sv.h"
+
 #define DEFAULT_SCREEN_WIDTH 1600
 #define DEFAULT_SCREEN_HEIGHT 900
 #define MANUAL_TIME_STEP 0.1
@@ -170,6 +173,7 @@ typedef struct {
     GLint uniforms[COUNT_UNIFORMS];
     Vertex vertex_buf[VERTEX_BUF_CAP];
     size_t vertex_buf_sz;
+    GLuint texture;
 } Renderer;
 
 // Global variables (fragile people with CS degree look away)
@@ -246,6 +250,103 @@ bool load_shader_program(const char *vertex_file_path,
     return true;
 }
 
+static char *render_conf = NULL;
+const char *vert_path = NULL;
+const char *frag_path = NULL;
+const char *texture_path = NULL;
+
+void reload_render_conf(const char *render_conf_path)
+{
+    if (render_conf) free(render_conf);
+
+    render_conf = slurp_file_into_malloced_cstr(render_conf_path);
+    if (render_conf == NULL) {
+        fprintf(stderr, "ERROR: could not load %s: %s\n", render_conf_path, strerror(errno));
+        exit(1);
+    }
+
+    String_View content = sv_from_cstr(render_conf);
+
+    vert_path = NULL;
+    frag_path = NULL;
+    texture_path = NULL;
+    for (int row = 0; content.count > 0; row++) {
+        String_View line = sv_chop_by_delim(&content, '\n');
+        const char *line_start = line.data;
+        line = sv_trim_left(line);
+
+        if (line.count > 0 && line.data[0] != '#') {
+            String_View key = sv_trim(sv_chop_by_delim(&line, '='));
+            String_View value = sv_trim_left(line);
+
+            ((char*)value.data)[value.count] = '\0';
+            // ^^^SAFETY NOTES: this is needed so we can use `value` as a NULL-terminated C-string.
+            // This should not cause any problems because the original string `render_conf`
+            // that we are processing the `value` from is mutable, NULL-terminated and we are splitting
+            // it by newlines which garantees that there is always a character after 
+            // the end of `value`.
+            //
+            // Let's consider an example where `render_conf` is equal to this:
+            //
+            // ```
+            // key = value\n
+            // key = value\n
+            // key = value\0
+            // ```
+            //
+            // There is always something after `value`. It's either `\n` or `\0`. With all of these 
+            // invariats in place writing to `value.data[value.count]` should be safe.
+
+            if (sv_eq(key, SV("vert"))) {
+                vert_path = value.data;
+                printf("Vertex Path: %s\n", vert_path);
+            } else if (sv_eq(key, SV("frag"))) {
+                frag_path = value.data;
+                printf("Fragment Path: %s\n", frag_path);
+            } else if (sv_eq(key, SV("texture"))) {
+                texture_path = value.data;
+                printf("Texture Path: %s\n", texture_path);
+            } else {
+                printf("%s:%d:%ld: ERROR: unsupported key `"SV_Fmt"`\n",
+                       render_conf_path, row, key.data - line_start, 
+                       SV_Arg(key));
+            }
+        }
+    }
+}
+
+void renderer_reload_textures(Renderer *r)
+{
+    int texture_width, texture_height;
+    unsigned char *texture_pixels = stbi_load(texture_path, &texture_width, &texture_height, NULL, 4);
+    if (texture_pixels == NULL) {
+        fprintf(stderr, "ERROR: could not load image %s: %s\n",
+                texture_path, strerror(errno));
+        return;
+    }
+
+    glDeleteTextures(1, &r->texture);
+    glGenTextures(1, &r->texture);
+    glBindTexture(GL_TEXTURE_2D, r->texture);
+
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
+
+    glTexImage2D(GL_TEXTURE_2D,
+                 0,
+                 GL_RGBA,
+                 texture_width,
+                 texture_height,
+                 0,
+                 GL_RGBA,
+                 GL_UNSIGNED_BYTE,
+                 texture_pixels);
+
+    stbi_image_free(texture_pixels);
+}
+
 void renderer_reload_shaders(Renderer *r)
 {
     glDeleteProgram(r->program);
@@ -254,7 +355,7 @@ void renderer_reload_shaders(Renderer *r)
     glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
 
     {
-        if (!load_shader_program("shaders/main.vert", "shaders/main.frag", &r->program)) {
+        if (!load_shader_program(vert_path, frag_path, &r->program)) {
             return;
         }
 
@@ -280,6 +381,8 @@ void key_callback(GLFWwindow* window, int key, int scancode, int action, int mod
 
     if (action == GLFW_PRESS) {
         if (key == GLFW_KEY_F5) {
+            reload_render_conf("render.conf");
+            renderer_reload_textures(&global_renderer);
             renderer_reload_shaders(&global_renderer);
         } else if (key == GLFW_KEY_SPACE) {
             pause = !pause;
@@ -322,6 +425,7 @@ void MessageCallback(GLenum source,
 
 void renderer_init(Renderer *r)
 {
+
     glGenVertexArrays(1, &r->vao);
     glBindVertexArray(r->vao);
 
@@ -356,6 +460,8 @@ void renderer_init(Renderer *r)
 
 int main(void)
 {
+    reload_render_conf("render.conf");
+
     if (!glfwInit()) {
         fprintf(stderr, "ERROR: could not initialize GLFW\n");
         exit(1);
@@ -398,33 +504,6 @@ int main(void)
     glEnable(GL_BLEND);
     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
 
-    const char *texture_filename = "./assets/tsodinFlushed.png";
-    int texture_width, texture_height;
-    unsigned char *texture_pixels = stbi_load(texture_filename, &texture_width, &texture_height, NULL, 4);
-    if (texture_pixels == NULL) {
-        fprintf(stderr, "ERROR: could not load image %s: %s\n",
-                texture_filename, strerror(errno));
-        exit(1);
-    }
-
-    GLuint texture;
-    glGenTextures(1, &texture);
-    glBindTexture(GL_TEXTURE_2D, texture);
-
-    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
-    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
-
-    glTexImage2D(GL_TEXTURE_2D,
-                 0,
-                 GL_RGBA,
-                 texture_width,
-                 texture_height,
-                 0,
-                 GL_RGBA,
-                 GL_UNSIGNED_BYTE,
-                 texture_pixels);
 
     renderer_init(&global_renderer);
 
@@ -432,6 +511,7 @@ int main(void)
         0
     });
     renderer_sync(&global_renderer);
+    renderer_reload_textures(&global_renderer);
     renderer_reload_shaders(&global_renderer);
 
     glfwSetKeyCallback(window, key_callback);

+ 4 - 0
render.conf

@@ -0,0 +1,4 @@
+vert = shaders/main.vert
+frag = shaders/box-muller.frag
+#frag = shaders/main.frag
+texture = assets/tsodinFlushed.png

+ 307 - 0
sv.h

@@ -0,0 +1,307 @@
+// Copyright 2021 Alexey Kutepov <[email protected]>
+
+// 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.
+
+#ifndef SV_H_
+#define SV_H_
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.h>
+
+#ifndef SVDEF
+#define SVDEF
+#endif // SVDEF
+
+typedef struct {
+    size_t count;
+    const char *data;
+} String_View;
+
+#define SV(cstr_lit) sv_from_parts(cstr_lit, sizeof(cstr_lit) - 1)
+#define SV_STATIC(cstr_lit)   \
+    {                         \
+        sizeof(cstr_lit) - 1, \
+        (cstr_lit)            \
+    }
+
+#define SV_NULL sv_from_parts(NULL, 0)
+
+// printf macros for String_View
+#define SV_Fmt "%.*s"
+#define SV_Arg(sv) (int) (sv).count, (sv).data
+// USAGE:
+//   String_View name = ...;
+//   printf("Name: "SV_Fmt"\n", SV_Arg(name));
+
+SVDEF String_View sv_from_parts(const char *data, size_t count);
+SVDEF String_View sv_from_cstr(const char *cstr);
+SVDEF String_View sv_trim_left(String_View sv);
+SVDEF String_View sv_trim_right(String_View sv);
+SVDEF String_View sv_trim(String_View sv);
+SVDEF String_View sv_take_left_while(String_View sv, bool (*predicate)(char x));
+SVDEF String_View sv_chop_by_delim(String_View *sv, char delim);
+SVDEF String_View sv_chop_by_sv(String_View *sv, String_View thicc_delim);
+SVDEF bool sv_try_chop_by_delim(String_View *sv, char delim, String_View *chunk);
+SVDEF String_View sv_chop_left(String_View *sv, size_t n);
+SVDEF String_View sv_chop_right(String_View *sv, size_t n);
+SVDEF String_View sv_chop_left_while(String_View *sv, bool (*predicate)(char x));
+SVDEF bool sv_index_of(String_View sv, char c, size_t *index);
+SVDEF bool sv_eq(String_View a, String_View b);
+SVDEF bool sv_eq_ignorecase(String_View a, String_View b);
+SVDEF bool sv_starts_with(String_View sv, String_View prefix);
+SVDEF bool sv_ends_with(String_View sv, String_View suffix);
+SVDEF uint64_t sv_to_u64(String_View sv);
+
+#endif  // SV_H_
+
+#ifdef SV_IMPLEMENTATION
+
+SVDEF String_View sv_from_parts(const char *data, size_t count)
+{
+    String_View sv;
+    sv.count = count;
+    sv.data = data;
+    return sv;
+}
+
+SVDEF String_View sv_from_cstr(const char *cstr)
+{
+    return sv_from_parts(cstr, strlen(cstr));
+}
+
+SVDEF String_View sv_trim_left(String_View sv)
+{
+    size_t i = 0;
+    while (i < sv.count && isspace(sv.data[i])) {
+        i += 1;
+    }
+
+    return sv_from_parts(sv.data + i, sv.count - i);
+}
+
+SVDEF String_View sv_trim_right(String_View sv)
+{
+    size_t i = 0;
+    while (i < sv.count && isspace(sv.data[sv.count - 1 - i])) {
+        i += 1;
+    }
+
+    return sv_from_parts(sv.data, sv.count - i);
+}
+
+SVDEF String_View sv_trim(String_View sv)
+{
+    return sv_trim_right(sv_trim_left(sv));
+}
+
+SVDEF String_View sv_chop_left(String_View *sv, size_t n)
+{
+    if (n > sv->count) {
+        n = sv->count;
+    }
+
+    String_View result = sv_from_parts(sv->data, n);
+
+    sv->data  += n;
+    sv->count -= n;
+
+    return result;
+}
+
+SVDEF String_View sv_chop_right(String_View *sv, size_t n)
+{
+    if (n > sv->count) {
+        n = sv->count;
+    }
+
+    String_View result = sv_from_parts(sv->data + sv->count - n, n);
+
+    sv->count -= n;
+
+    return result;
+}
+
+SVDEF bool sv_index_of(String_View sv, char c, size_t *index)
+{
+    size_t i = 0;
+    while (i < sv.count && sv.data[i] != c) {
+        i += 1;
+    }
+
+    if (i < sv.count) {
+        if (index) {
+            *index = i;
+        }
+        return true;
+    } else {
+        return false;
+    }
+}
+
+SVDEF bool sv_try_chop_by_delim(String_View *sv, char delim, String_View *chunk)
+{
+    size_t i = 0;
+    while (i < sv->count && sv->data[i] != delim) {
+        i += 1;
+    }
+
+    String_View result = sv_from_parts(sv->data, i);
+
+    if (i < sv->count) {
+        sv->count -= i + 1;
+        sv->data  += i + 1;
+        if (chunk) {
+            *chunk = result;
+        }
+        return true;
+    }
+
+    return false;
+}
+
+SVDEF String_View sv_chop_by_delim(String_View *sv, char delim)
+{
+    size_t i = 0;
+    while (i < sv->count && sv->data[i] != delim) {
+        i += 1;
+    }
+
+    String_View result = sv_from_parts(sv->data, i);
+
+    if (i < sv->count) {
+        sv->count -= i + 1;
+        sv->data  += i + 1;
+    } else {
+        sv->count -= i;
+        sv->data  += i;
+    }
+
+    return result;
+}
+
+SVDEF String_View sv_chop_by_sv(String_View *sv, String_View thicc_delim)
+{
+    String_View window = sv_from_parts(sv->data, thicc_delim.count);
+    size_t i = 0;
+    while (i + thicc_delim.count < sv->count 
+        && !(sv_eq(window, thicc_delim))) 
+    {
+        i++;
+        window.data++;
+    }
+
+    String_View result = sv_from_parts(sv->data, i);
+
+    if (i + thicc_delim.count == sv->count) {
+        // include last <thicc_delim.count> characters if they aren't 
+        //  equal to thicc_delim
+        result.count += thicc_delim.count; 
+    }
+    
+    // Chop!
+    sv->data  += i + thicc_delim.count;
+    sv->count -= i + thicc_delim.count;
+
+    return result;
+}
+
+SVDEF bool sv_starts_with(String_View sv, String_View expected_prefix)
+{
+    if (expected_prefix.count <= sv.count) {
+        String_View actual_prefix = sv_from_parts(sv.data, expected_prefix.count);
+        return sv_eq(expected_prefix, actual_prefix);
+    }
+
+    return false;
+}
+
+SVDEF bool sv_ends_with(String_View sv, String_View expected_suffix)
+{
+    if (expected_suffix.count <= sv.count) {
+        String_View actual_suffix = sv_from_parts(sv.data + sv.count - expected_suffix.count, expected_suffix.count);
+        return sv_eq(expected_suffix, actual_suffix);
+    }
+
+    return false;
+}
+
+SVDEF bool sv_eq(String_View a, String_View b)
+{
+    if (a.count != b.count) {
+        return false;
+    } else {
+        return memcmp(a.data, b.data, a.count) == 0;
+    }
+}
+
+SVDEF bool sv_eq_ignorecase(String_View a, String_View b)
+{
+    if (a.count != b.count) {
+        return false;
+    }
+    
+    char x, y;
+    for (size_t i = 0; i < a.count; i++) {
+        x = 'A' <= a.data[i] && a.data[i] <= 'Z'
+              ? a.data[i] + 32
+              : a.data[i];
+        
+        y = 'A' <= b.data[i] && b.data[i] <= 'Z'
+              ? b.data[i] + 32
+              : b.data[i];
+
+        if (x != y) return false;
+    } 
+    return true;
+}
+
+SVDEF uint64_t sv_to_u64(String_View sv)
+{
+    uint64_t result = 0;
+
+    for (size_t i = 0; i < sv.count && isdigit(sv.data[i]); ++i) {
+        result = result * 10 + (uint64_t) sv.data[i] - '0';
+    }
+
+    return result;
+}
+
+SVDEF String_View sv_chop_left_while(String_View *sv, bool (*predicate)(char x))
+{
+    size_t i = 0;
+    while (i < sv->count && predicate(sv->data[i])) {
+        i += 1;
+    }
+    return sv_chop_left(sv, i);
+}
+
+SVDEF String_View sv_take_left_while(String_View sv, bool (*predicate)(char x))
+{
+    size_t i = 0;
+    while (i < sv.count && predicate(sv.data[i])) {
+        i += 1;
+    }
+    return sv_from_parts(sv.data, i);
+}
+
+#endif // SV_IMPLEMENTATION