diff options
Diffstat (limited to 'shaders/shaders.cc')
| -rw-r--r-- | shaders/shaders.cc | 626 |
1 files changed, 626 insertions, 0 deletions
diff --git a/shaders/shaders.cc b/shaders/shaders.cc new file mode 100644 index 0000000..214d598 --- /dev/null +++ b/shaders/shaders.cc @@ -0,0 +1,626 @@ +// SPDX-License-Identifier: 0BSD + +#define GLAD_GL_IMPLEMENTATION +#include "shaders.hh" + +#define SDL_MAIN_USE_CALLBACKS +#include <SDL3/SDL.h> +#include <SDL3/SDL_main.h> +#include <SDL3/SDL_opengl.h> + +#define IMGUI_IMPL_OPENGL_LOADER_CUSTOM +#include "imgui_impl_opengl3.cpp" +#include "imgui_impl_sdl3.cpp" + +#include <cctype> +#include <cmath> +#include <cstddef> + +// -------------------------------------------------------------------------------- +// Geometry +// -------------------------------------------------------------------------------- + +constexpr float Pi = 3.1415926535f; + +template <typename T> +static inline T Min(T lhs, T rhs) +{ + return (lhs < rhs) ? lhs : rhs; +} + +template <typename T> +static inline T Max(T lhs, T rhs) +{ + return (lhs > rhs) ? lhs : rhs; +} + +template <typename T> +static inline T Clamp(T val, T vmin, T vmax) +{ + return Min(Max(val, vmin), vmax); +} + +static inline float Radians(float degrees) +{ + return degrees * Pi / 180.0f; +} + +class Vec2 +{ +public: + float x = 0.0f; + float y = 0.0f; +public: + Vec2() = default; + Vec2(float x, float y) : x(x), y(y) { } +}; + +class Vec3 +{ +public: + float x = 0.0f; + float y = 0.0f; + float z = 0.0f; +public: + Vec3() = default; + Vec3(float x, float y, float z) : x(x), y(y), z(z) { } + + inline Vec3 operator+(const Vec3& rhs) const + { + return Vec3(x + rhs.x, y + rhs.y, z + rhs.z); + } + + inline Vec3 operator-(const Vec3& rhs) const + { + return Vec3(x - rhs.x, y - rhs.y, z - rhs.z); + } + + inline Vec3 operator*(float scalar) const + { + return Vec3(x * scalar, y * scalar, z * scalar); + } + + inline float Length() const + { + return std::sqrtf(x * x + y * y + z * z); + } + + inline Vec3 Normalize() const + { + float l = Length(); + if (l == 0.0f) { l = 1.0f; } + return Vec3(x / l, y / l, z / l); + } + + inline float Dot(const Vec3& rhs) const + { + return x * rhs.x + y * rhs.y + z * rhs.z; + } + + inline Vec3 Cross(const Vec3& rhs) const + { + return Vec3( + y * rhs.z - z * rhs.y, + z * rhs.x - x * rhs.z, + x * rhs.y - y * rhs.x + ); + } +public: + static Vec3 FromEuler(Vec2 angles) + { + const float rx = Radians(angles.x); + const float ry = Radians(angles.y); + const float x = cosf(ry) * sinf(rx); + const float y = sinf(ry); + const float z = cosf(ry) * cosf(rx); + return Vec3(x, y, z); + } +}; + +class Vec4 +{ +public: + float x = 0.0f; + float y = 0.0f; + float z = 0.0f; + float w = 1.0f; +public: + Vec4() = default; + Vec4(float x, float y, float z) : x(x), y(y), z(z), w(1.0f) { } + Vec4(float x, float y, float z, float w) : x(x), y(y), z(z), w(w) { } +}; + +class Mat4x4 +{ +private: + float m[4 * 4]; +public: + inline float& operator[](std::size_t s) { return m[s]; } + inline float operator[](std::size_t s) const { return m[s]; } + + inline float* Base() { return m; } + + inline Mat4x4 operator*(const Mat4x4& rhs) + { + Mat4x4 r = { }; + for (int col = 0; col < 4; ++col) { + for (int row = 0; row < 4; ++row) { + r[col * 4 + row] = + m[0 * 4 + row] * rhs[col * 4 + 0] + + m[1 * 4 + row] * rhs[col * 4 + 1] + + m[2 * 4 + row] * rhs[col * 4 + 2] + + m[3 * 4 + row] * rhs[col * 4 + 3]; + } + } + return r; + } +public: + static Mat4x4 LookAt(const Vec3& eye, const Vec3& at, const Vec3& up) + { + Vec3 f = (at - eye).Normalize(); + Vec3 s = f.Cross(up).Normalize(); + Vec3 u = s.Cross(f); + Vec3 t = Vec3(-s.Dot(eye), -u.Dot(eye), f.Dot(eye)); + + Mat4x4 m; + m[ 0] = s.x; m[ 1] = u.x; m[ 2] = -f.x; m[ 3] = 0.0f; + m[ 4] = s.y; m[ 5] = u.y; m[ 6] = -f.y; m[ 7] = 0.0f; + m[ 8] = s.z; m[ 9] = u.z; m[10] = -f.z; m[11] = 0.0f; + m[12] = t.x; m[13] = t.y; m[14] = t.z; m[15] = 1.0f; + + return m; + } + + static Mat4x4 Perspective(float fov_deg, float aspect, float z_near, float z_far) + { + const float fov_cot = 1.0f / std::tanf(Radians(fov_deg) / 2.0f); + + Mat4x4 m = { }; + + m[0*4+0] = fov_cot / aspect; + m[1*4+1] = fov_cot; + m[2*4+3] = -1.0f; + + m[2*4+2] = (z_far + z_near) / (z_near - z_far); + m[3*4+2] = (2.0f * z_near * z_far) / (z_near - z_far); + + return m; + } +}; + +// -------------------------------------------------------------------------------- +// Application +// -------------------------------------------------------------------------------- + +typedef struct Model Model; +struct Model +{ + GLuint vao; + GLuint vbo; + GLuint ibo; + GLuint vs; + int ibo_len; + bool is_3d; +}; + +static struct +{ + SDL_Window* wnd; + bool ui_hidden; + bool pause; + + int shader_index = 0; + + float cam_dist = 3.5f; + Vec2 cam_ang = Vec2(0, 0); + + Model models[_MODEL_COUNT]; +} G = { }; + +// Helper: Report errors via glGetError() after every OpenGL function call. +// macOS does not support glDebugMessageCallback +void AssertGL(GLenum error, const char* expr, int line) +{ + if (error != GL_NO_ERROR) { + const char* error_string; + switch (error) { +#define BIND_ERROR(name) case name: { error_string = #name; }; break; + BIND_ERROR(GL_INVALID_ENUM); + BIND_ERROR(GL_INVALID_VALUE); + BIND_ERROR(GL_INVALID_OPERATION); + BIND_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION); + BIND_ERROR(GL_OUT_OF_MEMORY); +#undef BIND_ERROR + default: SDL_assert(0 && "Invalid GL_ERROR enum"); + } + SDL_Log("[Line #%u] %s caused %s\n", line, expr, error_string); + } +} + +std::vector<Shader*>& GetShaders() +{ + static std::vector<Shader*> s_shaders = { }; + return s_shaders; +} + +SDL_AppResult SDLCALL SDL_AppInit(void**, int, char**) +{ + if (!SDL_Init(SDL_INIT_VIDEO)) { + SDL_Log("Failed to initialize SDL: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4); + + Uint32 wflags = SDL_WINDOW_OPENGL | + SDL_WINDOW_HIGH_PIXEL_DENSITY | + SDL_WINDOW_RESIZABLE; + G.wnd = SDL_CreateWindow("shaders", 1024, 768, wflags); + if (!G.wnd) { + SDL_Log("Failed to create window: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + + if (!SDL_GL_CreateContext(G.wnd)) { + SDL_Log("Failed to create OpenGL context: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + SDL_GL_SetSwapInterval(1); + + if (!gladLoadGL(SDL_GL_GetProcAddress)) { + SDL_Log("Failed to load OpenGL functions"); + return SDL_APP_FAILURE; + } + + // Quad mesh + // 0 1 + // +------------+ + // | | + // | | + // | | + // | | + // | 3 | 2 + // +------------+ + // + { + Model* m = &G.models[MODEL_QUAD]; + + GL(glGenVertexArrays(1, &m->vao)); + GL(glBindVertexArray(m->vao)); + + GL(glGenBuffers(1, &m->vbo)); + GL(glBindBuffer(GL_ARRAY_BUFFER, m->vbo)); + + GL(glGenBuffers(1, &m->ibo)); + GL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m->ibo)); + + struct V { Vec2 p; Vec2 t; }; + const V vdata[] = { + { Vec2(-1.0f, 1.0f), Vec2(0, 1) }, + { Vec2( 1.0f, 1.0f), Vec2(1, 1) }, + { Vec2( 1.0f, -1.0f), Vec2(1, 0) }, + { Vec2(-1.0f, -1.0f), Vec2(0, 0) }, + }; + const uint16_t idata[] = { + 0, 1, 2, + 0, 2, 3, + }; + GL(glBufferData(GL_ARRAY_BUFFER, sizeof(vdata), vdata, GL_STATIC_DRAW)); + GL(glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(idata), idata, GL_STATIC_DRAW)); + GL(glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(V), (void*)offsetof(V, p))); + GL(glEnableVertexAttribArray(0)); + GL(glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(V), (void*)offsetof(V, t))); + GL(glEnableVertexAttribArray(1)); + m->ibo_len = COUNTOF(idata); + m->is_3d = false; + } + + // Cube mesh + // +------------+ + // /| /| + // / | / | + // +------------+ | + // | | | | + // | | | | + // | +---------|--+ + // | / | / + // |/ |/ + // +------------+ + // + { + Model* m = &G.models[MODEL_CUBE]; + + GL(glGenVertexArrays(1, &m->vao)); + GL(glBindVertexArray(m->vao)); + + GL(glGenBuffers(1, &m->vbo)); + GL(glBindBuffer(GL_ARRAY_BUFFER, m->vbo)); + + GL(glGenBuffers(1, &m->ibo)); + GL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m->ibo)); + + struct V { Vec3 p; Vec2 t; Vec3 n; }; + const V vdata[] = { + // Front + { Vec3(-1, 1, 1), Vec2(0, 1), Vec3(0, 0, 1) }, + { Vec3( 1, 1, 1), Vec2(1, 1), Vec3(0, 0, 1) }, + { Vec3( 1, -1, 1), Vec2(1, 0), Vec3(0, 0, 1) }, + { Vec3(-1, -1, 1), Vec2(0, 0), Vec3(0, 0, 1) }, + // Right + { Vec3( 1, 1, 1), Vec2(0, 1), Vec3(1, 0, 0) }, + { Vec3( 1, 1, -1), Vec2(1, 1), Vec3(1, 0, 0) }, + { Vec3( 1, -1, -1), Vec2(1, 0), Vec3(1, 0, 0) }, + { Vec3( 1, -1, 1), Vec2(0, 0), Vec3(1, 0, 0) }, + // Back + { Vec3( 1, 1, -1), Vec2(0, 1), Vec3(0, 0, -1) }, + { Vec3(-1, 1, -1), Vec2(1, 1), Vec3(0, 0, -1) }, + { Vec3(-1, -1, -1), Vec2(1, 0), Vec3(0, 0, -1) }, + { Vec3( 1, -1, -1), Vec2(0, 0), Vec3(0, 0, -1) }, + // Left + { Vec3(-1, 1, -1), Vec2(0, 1), Vec3(-1, 0, 0) }, + { Vec3(-1, 1, 1), Vec2(1, 1), Vec3(-1, 0, 0) }, + { Vec3(-1, -1, 1), Vec2(1, 0), Vec3(-1, 0, 0) }, + { Vec3(-1, -1, -1), Vec2(0, 0), Vec3(-1, 0, 0) }, + // Top + { Vec3(-1, 1, -1), Vec2(0, 1), Vec3(0, 1, 0) }, + { Vec3( 1, 1, -1), Vec2(1, 1), Vec3(0, 1, 0) }, + { Vec3( 1, 1, 1), Vec2(1, 0), Vec3(0, 1, 0) }, + { Vec3(-1, 1, 1), Vec2(0, 0), Vec3(0, 1, 0) }, + // Bottom + { Vec3(-1, -1, 1), Vec2(0, 1), Vec3(0, -1, 0) }, + { Vec3( 1, -1, 1), Vec2(1, 1), Vec3(0, -1, 0) }, + { Vec3( 1, -1, -1), Vec2(1, 0), Vec3(0, -1, 0) }, + { Vec3(-1, -1, -1), Vec2(0, 0), Vec3(0, -1, 0) }, + }; + const uint16_t idata[] = { + 0, 1, 2, + 0, 2, 3, + 4, 5, 6, + 4, 6, 7, + 8, 9, 10, + 8, 10, 11, + 12, 13, 14, + 12, 14, 15, + 16, 17, 18, + 16, 18, 19, + 20, 21, 22, + 20, 22, 23, + }; + GL(glBufferData(GL_ARRAY_BUFFER, sizeof(vdata), vdata, GL_STATIC_DRAW)); + GL(glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(idata), idata, GL_STATIC_DRAW)); + GL(glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(V), (void*)offsetof(V, p))); + GL(glEnableVertexAttribArray(0)); + GL(glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(V), (void*)offsetof(V, t))); + GL(glEnableVertexAttribArray(1)); + GL(glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(V), (void*)offsetof(V, n))); + GL(glEnableVertexAttribArray(2)); + m->ibo_len = COUNTOF(idata); + m->is_3d = true; + } + + const char* vshaders[] = { + "vert_quad.glsl", + "vert_cube.glsl" + }; + static_assert(COUNTOF(vshaders) == _MODEL_COUNT); + for (size_t i = 0; i < COUNTOF(vshaders); ++i) { + char* vs_src = (char*)SDL_LoadFile(vshaders[i], NULL); + if (!vs_src) { + SDL_Log("Failed to load vertex shader %s: %s", vshaders[i], SDL_GetError()); + return SDL_APP_FAILURE; + } + + GLuint shader = GL(glCreateShader(GL_VERTEX_SHADER)); + GL(glShaderSource(shader, 1, &vs_src, NULL)); + GL(glCompileShader(shader)); + + GLint compile_status = 0; + GL(glGetShaderiv(shader, GL_COMPILE_STATUS, &compile_status)); + if (compile_status != GL_TRUE) { + char error[1024] = { 0 }; + GL(glGetShaderInfoLog(shader, sizeof(error), NULL, error)); + SDL_Log("Error compiling shader %s: %s", vshaders[i], error); + return SDL_APP_FAILURE; + } + + G.models[i].vs = shader; + SDL_Log("Compiled vertex shader %s", vshaders[i]); + + SDL_free(vs_src); + } + + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGui_ImplSDL3_Init(G.wnd, 0, 0); + ImGui_ImplOpenGL3_Init(); + ImGui::GetIO().IniFilename = NULL; + + return SDL_APP_CONTINUE; +} + +SDL_AppResult SDLCALL SDL_AppIterate(void*) +{ + int wnd_x = 0; + int wnd_y = 0; + SDL_GetWindowSizeInPixels(G.wnd, &wnd_x, &wnd_y); + + GL(glViewport(0, 0, wnd_x, wnd_y)); + GL(glEnable(GL_CULL_FACE)); + GL(glCullFace(GL_FRONT)); + GL(glEnable(GL_DEPTH_TEST)); + GL(glEnable(GL_BLEND)); + GL(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + GL(glClearColor(0.1f, 0.1f, 0.1f, 1.0f)); + GL(glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); + + Shader* shader = GetShaders()[G.shader_index]; + Model* model = &G.models[shader->model]; + + if (!shader->ready) { + shader->program = GL(glCreateProgram()); + + char* fs_src = (char*)SDL_LoadFile(shader->path, NULL); + if (!fs_src) { + SDL_Log("Failed to load fragment shader %s: %s", shader->path, SDL_GetError()); + return SDL_APP_FAILURE; + } + + GLuint fs = GL(glCreateShader(GL_FRAGMENT_SHADER)); + GL(glShaderSource(fs, 1, &fs_src, NULL)); + GL(glCompileShader(fs)); + + GLint compile_status = 0; + GL(glGetShaderiv(fs, GL_COMPILE_STATUS, &compile_status)); + if (compile_status != GL_TRUE) { + char error[1024] = { 0 }; + GL(glGetShaderInfoLog(fs, sizeof(error), NULL, error)); + SDL_Log("Error compiling shader %s: %s", shader->path, error); + return SDL_APP_FAILURE; + } + + SDL_Log("Compiled fragment shader %s", shader->path); + + SDL_free(fs_src); + + GL(glAttachShader(shader->program, model->vs)); + GL(glAttachShader(shader->program, fs)); + GL(glLinkProgram(shader->program)); + + GLint link_status = 0; + GL(glGetProgramiv(shader->program, GL_LINK_STATUS, &link_status)); + if (link_status != GL_TRUE) { + char error[1024] = { 0 }; + GL(glGetProgramInfoLog(shader->program, sizeof(error), NULL, error)); + SDL_Log("Error linking program %s: %s", shader->path, error); + return SDL_APP_FAILURE; + } + + SDL_Log("Linked program"); + + shader->ready = true; + } + + GL(glBindVertexArray(model->vao)); + GL(glUseProgram(shader->program)); + + GLint u_id = -1; + if ((u_id = glGetUniformLocation(shader->program, "u_res")) != -1) { + GL(glUniform2f(u_id, (float)wnd_x, (float)wnd_y)); + } + if ((u_id = glGetUniformLocation(shader->program, "u_time")) != -1) { + if (!G.pause) { + GL(glUniform1f(u_id, SDL_GetTicks() / 1e3f)); + } + } + + if (model->is_3d) { + const Vec3 cam = Vec3::FromEuler(G.cam_ang) * G.cam_dist; + const Vec3 tgt = Vec3(0, 0, 0); + const float fov = 75.0f; + + Mat4x4 m_look = Mat4x4::LookAt(cam, tgt, Vec3(0, 1, 0)); + Mat4x4 m_proj = Mat4x4::Perspective(fov, (float)wnd_x / (float)wnd_y, 0.1f, 100.0f); + Mat4x4 m_view = m_proj * m_look; + + if ((u_id = glGetUniformLocation(shader->program, "u_vmat")) == -1) { + SDL_Log("Warning: u_vmat uniform not found"); + } else { + GL(glUniformMatrix4fv(u_id, 1, GL_FALSE, m_view.Base())); + } + + if ((u_id = glGetUniformLocation(shader->program, "u_cam")) != -1) { + GL(glUniform3f(u_id, cam.x, cam.y, cam.z)); + } + } + + if (shader->uf_fn) { + shader->uf_fn(shader); + } + + GL(glDrawElements(GL_TRIANGLES, model->ibo_len, GL_UNSIGNED_SHORT, NULL)); + + ImGui_ImplSDL3_NewFrame(); + ImGui_ImplOpenGL3_NewFrame(); + ImGui::NewFrame(); + + int wnd_height = 0; + SDL_GetWindowSize(G.wnd, NULL, &wnd_height); + if (!G.ui_hidden) { + ImGui::SetNextWindowPos(ImVec2(15.0f, 15.0f), ImGuiCond_Once); + ImGui::SetNextWindowSize(ImVec2(300.0f, wnd_height - 30.0f), ImGuiCond_Once); + ImGuiWindowFlags flags = + ImGuiWindowFlags_NoTitleBar | + ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoResize; + if (ImGui::Begin("ControlsWindow", NULL, flags)) { + if (ImGui::BeginCombo("Shader", GetShaders()[G.shader_index]->path)) { + for (int i = 0; i < (int)GetShaders().size(); ++i) { + if (ImGui::Selectable(GetShaders()[i]->path, i == G.shader_index)) { + G.shader_index = i; + } + } + ImGui::EndCombo(); + } + ImGui::SeparatorText("Controls"); + ImGui::Text("Tab: Toggle UI"); + ImGui::Checkbox("Pause time", &G.pause); + + if (shader->ui_fn) { + ImGui::SeparatorText("Uniforms"); + shader->ui_fn(); + } + } + ImGui::End(); + } + + ImGui::Render(); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + + if (!SDL_GL_SwapWindow(G.wnd)) { + SDL_Log("Failed to present window: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + + return SDL_APP_CONTINUE; +} + +SDL_AppResult SDLCALL SDL_AppEvent(void*, SDL_Event* event) +{ + bool is_3d = G.models[GetShaders()[G.shader_index]->model].is_3d; + + ImGui_ImplSDL3_ProcessEvent(event); + switch (event->type) { + case SDL_EVENT_KEY_DOWN: { + if (event->key.key == SDLK_TAB) { + G.ui_hidden = !G.ui_hidden; + } + } break; + case SDL_EVENT_MOUSE_MOTION: { + if (is_3d && (SDL_GetMouseState(NULL, NULL) & SDL_BUTTON_MASK(SDL_BUTTON_LEFT))) { + const float sens = 0.4f; + G.cam_ang.x -= event->motion.xrel * sens; + G.cam_ang.y += event->motion.yrel * sens; + G.cam_ang.y = Clamp(G.cam_ang.y, -89.0f, 89.0f); + } + } break; + case SDL_EVENT_MOUSE_WHEEL: { + if (is_3d) { + G.cam_dist -= event->wheel.y * 0.1f; + G.cam_dist = Max(G.cam_dist, 1.0f); + } + } break; + case SDL_EVENT_QUIT: { + return SDL_APP_SUCCESS; + } break; + } + return SDL_APP_CONTINUE; +} + +void SDLCALL SDL_AppQuit(void*, SDL_AppResult) +{ + SDL_DestroyWindow(G.wnd); +} |