#include #include #include #include #include #ifndef _WIN32 # define APIENTRY #endif #define CGLTF_IMPLEMENTATION #include "cgltf.h" #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" #define CGLM_FORCE_DEPTH_ZERO_TO_ONE #include "cglm/cglm.h" #include "glad/gl.h" #include "gl.c" #include "SDL3/SDL.h" #include "SDL3/SDL_main.h" #include "SDL3/SDL_opengl.h" typedef int8_t i8 ; typedef int16_t i16; typedef int32_t i32; typedef int64_t i64; typedef uint8_t u8 ; typedef uint16_t u16; typedef uint32_t u32; typedef uint64_t u64; typedef float f32; typedef double f64; typedef uintptr_t uintptr; typedef intptr_t intptr; #ifdef __linux__ typedef ssize_t isize; typedef size_t usize; #endif #if _WIN64 typedef i64 isize; typedef u64 usize; #elif _WIN32 typedef i32 isize; typedef u32 usize; #endif typedef struct String { u8 *data; u64 length; } String; template struct Array { T *ptr; u64 length; T& operator[](u64 index) const { assert(index < length); return ptr[index]; } Array operator[](u64 start, u64 end) const { assert(start < length && end <= length); Array array; array.ptr = ptr+start; array.length = end-start; return array; } operator bool() const { assert(ptr == NULL || (ptr != NULL && length)); return ptr != NULL && length; } }; #define PrintVarArgs(...) __VA_ARGS__ #define _JoinArgs(x, y) x##y #define JoinArgs(x, y) _JoinArgs(x, y) #define Length(ptr) (sizeof(ptr)/sizeof(ptr[0])) #define ArrayLit(name, T, ...) \ std::array JoinArgs(array_lit_, __LINE__){PrintVarArgs(__VA_ARGS__)}; \ Array name = { .ptr = JoinArgs(array_lit_, __LINE__).data(), .length = JoinArgs(array_lit_, __LINE__).size() } struct String8 { union { u8 *ptr; char *ch_ptr; }; u64 length; bool operator==(const String8 &rhs) const { bool result = false; if(length == rhs.length) { result = strncmp((const char *)ptr, (const char *)(rhs.ptr), length) == 0; } return result; } u8& operator[](u64 index) const { assert(index < length); return ptr[index]; } String8 operator[](u64 start, u64 end) const { assert(start < length && end <= length); return (String8){ .ptr = ptr+start, .length = end-start }; } operator bool() const { assert(ptr == NULL || (ptr != NULL && length)); return ptr != NULL && length; } }; #define String8Lit(str) (String8){ .ptr = (u8 *)str, .length = sizeof(str) } #define String8(str) (String8){ .ptr = (u8 *)str, .length = strlen((const char *)str) } template union Vector2 { T v[2]; struct { T x, y; }; struct { T r, g; }; struct { T w, h; }; }; template union Vector3 { T v[3]; struct { T x, y, z; }; struct { T r, g, b; }; }; template union Vector4 { T v[4]; struct { T x, y, z, w; }; struct { T r, g, b, a; }; }; struct Vec2 { union { vec2 v; struct { f32 x, y; }; struct { f32 r, g; }; struct { f32 w, h; }; }; }; struct Vec3 { union { vec3 v; struct { f32 x, y, z; }; struct { f32 r, g, b; }; }; }; struct Vec4 { union { vec4 v; struct { f32 x, y, z, w; }; struct { f32 r, g, b, a; }; }; }; struct Mat4 { union { f32 v[4*4]; mat4 matrix; }; Vec4 operator*(const Vec4 &vec) { Vec4 result; glm_mat4_mulv(matrix, (f32 *)vec.v, result.v); return result; } f32 &operator[](u64 i, u64 j) { assert(i < 4 && j < 4); return v[(i*4) + j]; } }; Vec4 MakeVec4(f32 x, f32 y, f32 z, f32 w) { return (Vec4){ .x = x, .y = y, .z = z, .w = w }; } Vec4 MakeVec4(f32 v) { return (Vec4){ .x = v, .y = v, .z = v, .w = v }; } Vec4 MakeVec4(Vec3 v, f32 w) { return (Vec4){ .x = v.x, .y = v.y, .z = v.z, .w = w }; } Vec3 MakeVec3(f32 x, f32 y, f32 z) { return (Vec3){ .x = x, .y = y, .z = z }; } Vec3 MakeVec3(f32 v) { return (Vec3){ .x = v, .y = v, .z = v }; } Vec3 MakeVec3(Vec2 v, f32 z) { return (Vec3){ .x = v.x, .y = v.y, .z = z }; } Vec2 MakeVec2(f32 x, f32 y) { return (Vec2){ .x = x, .y = y }; } Vec2 MakeVec2(f32 v) { return (Vec2){ .x = v, .y = v }; } Mat4 MakeMat4(f32 matrix[16]) { Mat4 mat; memcpy(mat.v, matrix, sizeof(f32)*16); return mat; } #define Vec2(...) MakeVec2(__VA_ARGS__) #define Vec3(...) MakeVec3(__VA_ARGS__) #define Vec4(...) MakeVec4(__VA_ARGS__) #define Mat4(...) MakeMat4(__VA_ARGS__) Vec3 Transform(Vec3 &vec, Mat4 &mat, f32 last = 1.0) { Vec3 dst = {}; glm_mat4_mulv3(mat.matrix, vec.v, last, dst.v); return dst; } typedef Vector2 UVec2; typedef Vector2 IVec2; typedef Vector3 UVec3; typedef Vector3 IVec3; typedef Vector4 UVec4; typedef Vector4 IVec4; typedef GLuint TextureID; typedef GLuint PipelineID; typedef GLuint ShaderID; typedef GLuint BufferID; struct ImageBuffer { Array data; u32 w, h, ch; }; struct ModelBuffers { BufferID vertex_array; BufferID vertex_buffer; BufferID index_buffer; }; enum MaterialMapIndex { MMI_Albedo, MMI_Normal, MMI_Metallic, MMI_Roughness, MMI_Occlusion, MMI_Emission, MMI_Height, MMI_Cubemap, MMI_Irradiance, MMI_Prefilter, MMI_BRDF, MMI_Max, }; #define AlignPow2(x, align) (((usize)(x) + align - 1) & ~(align - 1)) #define PtrSub(x, y) ((usize)(x) - (usize)(y)) #define PtrAdd(x, y) ((usize)(x) + (usize)(y)) #define MovePtrForward(ptr, x) (void *)AlignPow2(PtrAdd(ptr, x), DEFAULT_ALIGNMENT) #define Max(x, y) (x > y ? x : y) #define Min(x, y) (x < y ? x : y) TextureID CreateTexture(ImageBuffer image_buffer); template BufferID CreateBuffer(T *data); ModelBuffers CreateModelBuffers(); struct Arena; struct ShaderGlobals { Mat4 projection; Mat4 view; Mat4 model_matrix; }; struct Renderer { SDL_Window* window; SDL_GLContext gl_context; Arena *arena; IVec2 resolution; PipelineID pipeline_id; u32 texture_locations[MMI_Max]; TextureID default_texture; BufferID default_material; BufferID globals_buffer_id; ShaderGlobals globals; }; Renderer g_renderer; #include "alloc.cpp" #include "util.cpp" #include "assets.cpp" #define InitCheckError(cond, msg) \ if(cond) \ { \ error = msg; \ goto InitFailure; \ } ModelBuffers CreateModelBuffers() { ModelBuffers model_buffers; glGenVertexArrays(1, &model_buffers.vertex_array); glGenBuffers(1, &model_buffers.vertex_buffer); glGenBuffers(1, &model_buffers.index_buffer); return model_buffers; } ShaderID CreateShader(GLenum type, char *src) { ShaderID shader_id = glCreateShader(type); if(shader_id) { GLint success; glShaderSource(shader_id, 1, (const char **)&src, NULL); glCompileShader(shader_id); glGetShaderiv(shader_id, GL_COMPILE_STATUS, &success); if(!success) { GLint message_length; glGetShaderiv(shader_id, GL_INFO_LOG_LENGTH, &message_length); if(message_length) { char message_buffer[512]; glGetShaderInfoLog(shader_id, message_length, NULL, message_buffer); printf("Error compiling %s shader: %s\n", type == GL_VERTEX_SHADER ? "vertex" : "fragment", message_buffer); } shader_id = 0; } } return shader_id; } PipelineID CreatePipeline(Array vertex_shader_src, Array frag_shader_src) { PipelineID pipeline_id; GLint success; ShaderID vertex_shader_id = CreateShader(GL_VERTEX_SHADER, (char *)vertex_shader_src.ptr); ShaderID fragment_shader_id = CreateShader(GL_FRAGMENT_SHADER, (char *)frag_shader_src.ptr); if(vertex_shader_id && fragment_shader_id) { pipeline_id = glCreateProgram(); } if(pipeline_id) { glAttachShader(pipeline_id, vertex_shader_id); glAttachShader(pipeline_id, fragment_shader_id); glLinkProgram(pipeline_id); glGetProgramiv(pipeline_id, GL_LINK_STATUS, &success); if(!success) { GLint message_length; glGetProgramiv(pipeline_id, GL_INFO_LOG_LENGTH, &message_length); if(message_length) { char message_buffer[512]; glGetProgramInfoLog(pipeline_id, message_length, NULL, message_buffer); printf("Error linking program: %s\n", message_buffer); } pipeline_id = 0; } } glDeleteShader(vertex_shader_id); glDeleteShader(fragment_shader_id); return pipeline_id; } TextureID CreateTexture(ImageBuffer image_buffer) { TextureID texture_id; glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glGenTextures(1, &texture_id); glBindTexture(GL_TEXTURE_2D, texture_id); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image_buffer.w, image_buffer.h, 0, GL_RGBA, GL_UNSIGNED_BYTE, image_buffer.data.ptr); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); return texture_id; } template void UpdateBuffer(BufferID target, T *data) { glBindBuffer(GL_UNIFORM_BUFFER, target); glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(T), data); glBindBuffer(GL_UNIFORM_BUFFER, 0); } template BufferID CreateBuffer(T *data) { BufferID buffer; glGenBuffers(1, &buffer); glBindBuffer(GL_UNIFORM_BUFFER, buffer); glBufferData(GL_UNIFORM_BUFFER, sizeof(T), data, GL_STATIC_DRAW); glBindBuffer(GL_UNIFORM_BUFFER, 0); return buffer; } #ifdef BUILD_DEBUG void APIENTRY glDebugOutput(GLenum source, GLenum type, unsigned int id, GLenum severity, GLsizei length, const char *message, const void *userParam) { // ignore non-significant error/warning codes if(id == 131169 || id == 131185 || id == 131218 || id == 131204) return; printf("---------------\n"); switch (source) { case GL_DEBUG_SOURCE_API: printf("Source: API\n"); break; case GL_DEBUG_SOURCE_WINDOW_SYSTEM: printf("Source: Window System\n"); break; case GL_DEBUG_SOURCE_SHADER_COMPILER: printf("Source: Shader Compiler\n"); break; case GL_DEBUG_SOURCE_THIRD_PARTY: printf("Source: Third Party\n"); break; case GL_DEBUG_SOURCE_APPLICATION: printf("Source: Application\n"); break; case GL_DEBUG_SOURCE_OTHER: printf("Source: Other\n"); break; } switch (type) { case GL_DEBUG_TYPE_ERROR: printf("Type: Error\n"); break; case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: printf("Type: Deprecated Behaviour\n"); break; case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: printf("Type: Undefined Behaviour\n"); break; case GL_DEBUG_TYPE_PORTABILITY: printf("Type: Portability\n"); break; case GL_DEBUG_TYPE_PERFORMANCE: printf("Type: Performance\n"); break; case GL_DEBUG_TYPE_MARKER: printf("Type: Marker\n"); break; case GL_DEBUG_TYPE_PUSH_GROUP: printf("Type: Push Group\n"); break; case GL_DEBUG_TYPE_POP_GROUP: printf("Type: Pop Group\n"); break; case GL_DEBUG_TYPE_OTHER: printf("Type: Other\n"); break; } switch (severity) { case GL_DEBUG_SEVERITY_HIGH: printf("Severity: high\n\n"); break; case GL_DEBUG_SEVERITY_MEDIUM: printf("Severity: medium\n\n"); break; case GL_DEBUG_SEVERITY_LOW: printf("Severity: low\n\n"); break; case GL_DEBUG_SEVERITY_NOTIFICATION: printf("Severity: notification\n\n"); break; } printf("Debug message (%llu): %s\n", id, message); } #endif bool Init(Renderer *renderer) { const char* error = NULL; BeginScratch(MB(1)); renderer->arena = CreateArena(MB(4)); SDL_SetAppMetadata("Renderer", "0.0", "com.sleepyday.renderer"); InitCheckError(!SDL_Init(SDL_INIT_VIDEO), SDL_GetError()); #ifdef BUILD_DEBUG SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG); #else SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0); #endif SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 6); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); { f32 main_scale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay()); SDL_WindowFlags window_flags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIDDEN | SDL_WINDOW_HIGH_PIXEL_DENSITY; renderer->window = SDL_CreateWindow("Graphics Programming", (int)(1920*main_scale), (int)(1080*main_scale), window_flags); InitCheckError(!renderer->window, SDL_GetError()); } { renderer->gl_context = SDL_GL_CreateContext(renderer->window); InitCheckError(!renderer->gl_context, SDL_GetError()); } { int version = gladLoadGL((GLADloadfunc)SDL_GL_GetProcAddress); InitCheckError(!version, "Unable to load OpenGL Functions"); } #ifdef BUILD_DEBUG { glEnable(GL_DEBUG_OUTPUT); glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); glDebugMessageCallback(glDebugOutput, NULL); glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, NULL, GL_TRUE); } #endif { SDL_GL_MakeCurrent(renderer->window, renderer->gl_context); SDL_GL_SetSwapInterval(1); // Vsync SDL_SetWindowPosition(renderer->window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); SDL_ShowWindow(renderer->window); Array vertex_src = OpenFile(String8Lit("./shaders/vert.glsl")); Array fragment_src = OpenFile(String8Lit("./shaders/frag.glsl")); if(vertex_src.ptr && fragment_src.ptr) { renderer->pipeline_id = CreatePipeline(vertex_src, fragment_src); Free(&vertex_src); Free(&fragment_src); } InitCheckError(!renderer->pipeline_id, "Unable to create pipeline"); } { renderer->default_texture = CreateTexture(DEFAULT_TEXTURE); renderer->default_material = CreateBuffer(&DEFAULT_MATERIAL); } { for(u64 i = 0; i < MMI_Max; i += 1) { u8 buffer[64]; String8 texture_uniform = SPrintf(buffer, "u_texture%llu", i); renderer->texture_locations[i] = glGetUniformLocation(renderer->pipeline_id, texture_uniform.ch_ptr); } glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LESS); renderer->globals.projection = IdentityMatrix(); renderer->globals.view = LookAt(Vec3(0.0f), Vec3(0.0f, 5.0f, 0.0f), Vec3(0.0f, 1.0f, 0.0f)); renderer->globals.model_matrix = Translate(IdentityMatrix(), Vec3(0.0f, 0.0f, -3.0f)); renderer->globals_buffer_id = CreateBuffer(&renderer->globals); glBindBufferBase(GL_UNIFORM_BUFFER, 0, g_renderer.globals_buffer_id); } return true; InitFailure: printf("Failed to initialize: [%s]", error); return false; } int main(int argc, char** argv) { bool running = Init(&g_renderer); Model tree_model = {}; if(running) { running = LoadGLTF(g_renderer.arena, &tree_model, String8Lit("./assets/StylizedNature/glTF/CherryBlossom_1.gltf")); if(!running) { Logf("Failed to open GLTF file"); } } while(running) { BeginScratch(); SDL_Event event; while(SDL_PollEvent(&event)) { if(event.type == SDL_EVENT_QUIT) { running = false; } if(event.type == SDL_EVENT_WINDOW_CLOSE_REQUESTED && event.window.windowID == SDL_GetWindowID(g_renderer.window)) { running = false; } } i32 w, h; SDL_GetWindowSize(g_renderer.window, &w, &h); if(g_renderer.resolution.w != w || g_renderer.resolution.h != h) { glViewport(0, 0, (int)w, (int)h); g_renderer.resolution.w = w; g_renderer.resolution.h = h; g_renderer.globals.projection = Perspective(Radians(90.0f), (f32)(w)/(f32)(h), 10000.0f, 0.1f); UpdateBuffer(g_renderer.globals_buffer_id, &g_renderer.globals); glBindBufferBase(GL_UNIFORM_BUFFER, 0, g_renderer.globals_buffer_id); } /* if(SDL_GetWindowFlags(renderer.window) && SDL_WINDOW_MINIMIZED) { SDL_Delay(10); continue; } */ glClearColor(0.2, 0.3, 0.7, 1.0); glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); glUseProgram(g_renderer.pipeline_id); glBindVertexArray(tree_model.buffers.vertex_array); for(u64 i = 0; i < tree_model.meshes.length; i += 1) { Material *material = tree_model.materials.ptr + tree_model.meshes[i].material_index; for(u64 j = 0; j < MMI_Max; j += 1) { glActiveTexture(GL_TEXTURE0+j); glUniform1i(g_renderer.texture_locations[j], j); glBindTexture(GL_TEXTURE_2D, material->textures[j]); } glDrawElements(GL_TRIANGLES, tree_model.meshes[i].index_length, GL_UNSIGNED_INT, (void *)(u64)(tree_model.meshes[i].index_start*sizeof(u32))); glActiveTexture(GL_TEXTURE0); } glBindVertexArray(0); SDL_GL_SwapWindow(g_renderer.window); } }