diff --git a/Makefile b/Makefile index 28076d9..03be6bb 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,8 @@ -# Makefile CC = gcc CFLAGS = -Wall -Wextra -O2 $(shell pkg-config --cflags glfw3) LDFLAGS = $(shell pkg-config --libs glfw3) -ldl -SRC = main.c glad.c +SRC = glad.c gl_utils.c main.c OBJ = $(SRC:.c=.o) BIN = fallingCand diff --git a/fallingCand b/fallingCand index a2ffcfd..250e73b 100755 Binary files a/fallingCand and b/fallingCand differ diff --git a/gl_utils.c b/gl_utils.c new file mode 100644 index 0000000..284926e --- /dev/null +++ b/gl_utils.c @@ -0,0 +1,152 @@ +// gl_utils.c +#include "gl_utils.h" + +#include +#include +#include +#include + +void glfw_error_callback(int error, const char* description) { + fprintf(stderr, "GLFW error %d: %s\n", error, description); +} + +char* load_text_file(const char* path) { + FILE* f = fopen(path, "rb"); + if (!f) { + fprintf(stderr, "Failed to open '%s': %s\n", path, strerror(errno)); + return NULL; + } + + if (fseek(f, 0, SEEK_END) != 0) { + fprintf(stderr, "fseek failed on '%s'\n", path); + fclose(f); + return NULL; + } + + long size = ftell(f); + if (size < 0) { + fprintf(stderr, "ftell failed on '%s'\n", path); + fclose(f); + return NULL; + } + + rewind(f); + + char* buffer = malloc((size_t)size + 1); + if (!buffer) { + fprintf(stderr, "Out of memory reading '%s'\n", path); + fclose(f); + return NULL; + } + + size_t read = fread(buffer, 1, (size_t)size, f); + fclose(f); + + if (read != (size_t)size) { + fprintf(stderr, "Short read on '%s'\n", path); + free(buffer); + return NULL; + } + + buffer[size] = '\0'; + return buffer; +} + +GLuint compile_shader(GLenum type, const char* source, const char* debugName) { + GLuint shader = glCreateShader(type); + glShaderSource(shader, 1, &source, NULL); + glCompileShader(shader); + + GLint success = 0; + glGetShaderiv(shader, GL_COMPILE_STATUS, &success); + if (!success) { + char log[1024]; + glGetShaderInfoLog(shader, sizeof(log), NULL, log); + fprintf(stderr, "Shader compile failed (%s):\n%s\n", debugName ? debugName : "unknown", log); + glDeleteShader(shader); + exit(EXIT_FAILURE); + } + + return shader; +} + +GLuint create_program_from_files(const char* vsPath, const char* fsPath) { + char* vsSource = load_text_file(vsPath); + if (!vsSource) { + fprintf(stderr, "Could not load vertex shader from '%s'\n", vsPath); + exit(EXIT_FAILURE); + } + + char* fsSource = load_text_file(fsPath); + if (!fsSource) { + fprintf(stderr, "Could not load fragment shader from '%s'\n", fsPath); + free(vsSource); + exit(EXIT_FAILURE); + } + + GLuint vs = compile_shader(GL_VERTEX_SHADER, vsSource, vsPath); + GLuint fs = compile_shader(GL_FRAGMENT_SHADER, fsSource, fsPath); + + free(vsSource); + free(fsSource); + + GLuint prog = glCreateProgram(); + glAttachShader(prog, vs); + glAttachShader(prog, fs); + glLinkProgram(prog); + + glDeleteShader(vs); + glDeleteShader(fs); + + GLint linked = 0; + glGetProgramiv(prog, GL_LINK_STATUS, &linked); + if (!linked) { + char log[1024]; + glGetProgramInfoLog(prog, sizeof(log), NULL, log); + fprintf(stderr, "Program link failed:\n%s\n", log); + glDeleteProgram(prog); + exit(EXIT_FAILURE); + } + + return prog; +} + +GLFWwindow* init_glfw_glad(const char* title, int width, int height) { + glfwSetErrorCallback(glfw_error_callback); + + // Prefer Wayland if this GLFW build supports it + if (glfwPlatformSupported(GLFW_PLATFORM_WAYLAND)) { + glfwInitHint(GLFW_PLATFORM, GLFW_PLATFORM_WAYLAND); + fprintf(stderr, "[info] Requesting Wayland platform via glfwInitHint\n"); + } else { + fprintf(stderr, "[warn] Wayland platform not supported by this GLFW build, using default\n"); + } + + if (!glfwInit()) { + fprintf(stderr, "[fatal] glfwInit failed\n"); + return NULL; + } + + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE); + + GLFWwindow* window = glfwCreateWindow(width, height, title, NULL, NULL); + if (!window) { + fprintf(stderr, "[fatal] glfwCreateWindow failed\n"); + glfwTerminate(); + return NULL; + } + + glfwMakeContextCurrent(window); + + if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { + fprintf(stderr, "[fatal] Failed to initialize GLAD\n"); + glfwDestroyWindow(window); + glfwTerminate(); + return NULL; + } + + return window; +} diff --git a/gl_utils.h b/gl_utils.h new file mode 100644 index 0000000..7de4523 --- /dev/null +++ b/gl_utils.h @@ -0,0 +1,27 @@ +// gl_utils.h +#ifndef GL_UTILS_H +#define GL_UTILS_H + + +#include "glad/glad.h" +#define GLFW_INCLUDE_NONE +#include +// Error callback for GLFW +void glfw_error_callback(int error, const char* description); + +// Read an entire text file into a null-terminated buffer. +// Caller must free() the returned pointer. +char* load_text_file(const char* path); + +// Compile a single shader from source. +GLuint compile_shader(GLenum type, const char* source, const char* debugName); + +// Compile & link a vertex+fragment program from files. +GLuint create_program_from_files(const char* vsPath, const char* fsPath); + +// Minimal GLFW+GLAD init: sets error callback, (optionally) hints Wayland, +// calls glfwInit, creates window, makes context current, loads GLAD. +// Returns the created window or NULL on fatal error. +GLFWwindow* init_glfw_glad(const char* title, int width, int height); + +#endif // GL_UTILS_H diff --git a/main.c b/main.c index a1ebd39..cf693c6 100644 --- a/main.c +++ b/main.c @@ -1,142 +1,81 @@ // main.c #include #include -#include -#include "glad/glad.h" -#define GLFW_INCLUDE_NONE -#include - -static void glfw_error_callback(int error, const char* description) { - fprintf(stderr, "GLFW error %d: %s\n", error, description); -} - -static GLuint create_compute_program(const char* source) { - GLint success = 0; - GLchar log[1024]; - - GLuint shader = glCreateShader(GL_COMPUTE_SHADER); - glShaderSource(shader, 1, &source, NULL); - glCompileShader(shader); - - glGetShaderiv(shader, GL_COMPILE_STATUS, &success); - if (!success) { - glGetShaderInfoLog(shader, sizeof(log), NULL, log); - fprintf(stderr, "Compute shader compilation failed:\n%s\n", log); - exit(EXIT_FAILURE); - } - - GLuint program = glCreateProgram(); - glAttachShader(program, shader); - glLinkProgram(program); - glDeleteShader(shader); - - glGetProgramiv(program, GL_LINK_STATUS, &success); - if (!success) { - glGetProgramInfoLog(program, sizeof(log), NULL, log); - fprintf(stderr, "Program link failed:\n%s\n", log); - exit(EXIT_FAILURE); - } - - return program; -} +#include "gl_utils.h" int main(void) { - // --- Init GLFW & create GL context --- - glfwSetErrorCallback(glfw_error_callback); + int winW = 800; + int winH = 600; - if (!glfwInit()) { - fprintf(stderr, "Failed to init GLFW\n"); - return EXIT_FAILURE; - } - - // Request OpenGL 4.3 core (needed for compute shaders) - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); - glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); - // We don't actually *need* to show the window - GLFWwindow* window = glfwCreateWindow(640, 480, "GPU Hello", NULL, NULL); + GLFWwindow* window = init_glfw_glad("Falling Sand - Fullscreen Quad", winW, winH); if (!window) { - fprintf(stderr, "Failed to create GLFW window\n"); - glfwTerminate(); return EXIT_FAILURE; } - glfwMakeContextCurrent(window); + printf("Renderer: %s\n", (const char*)glGetString(GL_RENDERER)); + printf("OpenGL: %s\n", (const char*)glGetString(GL_VERSION)); + printf("GLSL: %s\n", (const char*)glGetString(GL_SHADING_LANGUAGE_VERSION)); - // --- Load GL symbols via GLAD --- - if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { - fprintf(stderr, "Failed to initialize GLAD\n"); - glfwDestroyWindow(window); - glfwTerminate(); - return EXIT_FAILURE; - } + // --- Create fullscreen triangle geometry (VAO + VBO) --- + float vertices[] = { + // x, y + -1.0f, -1.0f, + 3.0f, -1.0f, + -1.0f, 3.0f + }; - printf("OpenGL version: %s\n", glGetString(GL_VERSION)); - printf("GLSL version: %s\n", glGetString(GL_SHADING_LANGUAGE_VERSION)); + GLuint vao = 0, vbo = 0; + glGenVertexArrays(1, &vao); + glGenBuffers(1, &vbo); - // --- Prepare data buffer on GPU (SSBO) --- - const GLuint N = 16; - GLuint initial[N]; - for (GLuint i = 0; i < N; ++i) { - initial[i] = i; - } + glBindVertexArray(vao); - GLuint ssbo = 0; - glGenBuffers(1, &ssbo); - glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo); - glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(initial), initial, GL_DYNAMIC_COPY); - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, ssbo); // binding = 0 in shader + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); - // --- Compute shader source: data[i] *= 2 --- - const char* compute_src = - "#version 430 core\n" - "layout(local_size_x = 16) in;\n" - "layout(std430, binding = 0) buffer Data {\n" - " uint data[];\n" - "};\n" - "void main() {\n" - " uint idx = gl_GlobalInvocationID.x;\n" - " // N == 16 and local_size_x == 16, so we know idx < 16\n" - " data[idx] *= 2u;\n" - "}\n"; - - GLuint compute_program = create_compute_program(compute_src); - - // --- Run the compute shader --- - glUseProgram(compute_program); - - // One workgroup of 16 threads (matches local_size_x) - glDispatchCompute(1, 1, 1); - - // Make sure writes to the SSBO are visible to the CPU - glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT); - - // --- Read back the result --- - glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo); - GLuint* ptr = (GLuint*)glMapBufferRange( - GL_SHADER_STORAGE_BUFFER, + // layout(location = 0) in vec2 aPos; + glEnableVertexAttribArray(0); + glVertexAttribPointer( 0, - sizeof(initial), - GL_MAP_READ_BIT + 2, + GL_FLOAT, + GL_FALSE, + 2 * sizeof(float), + (void*)0 ); - if (!ptr) { - fprintf(stderr, "Failed to map SSBO\n"); - } else { - printf("GPU computed values:\n"); - for (GLuint i = 0; i < N; ++i) { - printf(" %2u -> %2u\n", i, ptr[i]); - } - glUnmapBuffer(GL_SHADER_STORAGE_BUFFER); + glBindVertexArray(0); + + // --- Create shader program --- + GLuint program = create_program_from_files( + "shaders/fullscreen.vert", + "shaders/gradient.frag" + ); + + glUseProgram(program); + + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + + // --- Main loop --- + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + + glClear(GL_COLOR_BUFFER_BIT); + + glUseProgram(program); + glBindVertexArray(vao); + glDrawArrays(GL_TRIANGLES, 0, 3); + + glfwSwapBuffers(window); } // --- Cleanup --- - glDeleteProgram(compute_program); - glDeleteBuffers(1, &ssbo); + glDeleteProgram(program); + glDeleteBuffers(1, &vbo); + glDeleteVertexArrays(1, &vao); glfwDestroyWindow(window); glfwTerminate(); - return EXIT_SUCCESS; } diff --git a/sand_hello b/sand_hello new file mode 100755 index 0000000..250e73b Binary files /dev/null and b/sand_hello differ diff --git a/shaders/double.comp b/shaders/double.comp new file mode 100644 index 0000000..6c506e8 --- /dev/null +++ b/shaders/double.comp @@ -0,0 +1,12 @@ +#version 460 core + +layout(local_size_x = 16) in; + +layout(std430, binding = 0) buffer Data { + uint data[]; +}; + +void main() { + uint idx = gl_GlobalInvocationID.x; + data[idx] *= 2u; +} diff --git a/shaders/fullscreen.vert b/shaders/fullscreen.vert new file mode 100644 index 0000000..c0d22c3 --- /dev/null +++ b/shaders/fullscreen.vert @@ -0,0 +1,8 @@ +#version 430 core + +layout(location = 0) in vec2 aPos; + +void main() +{ + gl_Position = vec4(aPos, 0.0, 1.0); +} diff --git a/shaders/gradient.frag b/shaders/gradient.frag new file mode 100644 index 0000000..6f8726d --- /dev/null +++ b/shaders/gradient.frag @@ -0,0 +1,11 @@ +#version 430 core + +out vec4 FragColor; + +void main() +{ + // Normalized coordinates in [0,1] + vec2 uv = gl_FragCoord.xy / vec2(800.0, 600.0); // we’ll fix this later with a uniform + vec3 color = vec3(uv.x, uv.y, 0.2); + FragColor = vec4(color, 1.0); +}