#if !defined(LUMENARIUM_EDITOR_GRAPHICS_H) #define LUMENARIUM_EDITOR_GRAPHICS_H #define os_gl_no_error() os_gl_no_error_((char*)__FILE__, (u32)__LINE__) void os_gl_no_error_(char* file, u32 line); #define GL_NULL (u32)0 #define SHADER_MAX_ATTRS 8 #define SHADER_ATTR_LAST (u32)(1 << 31) typedef struct Shader Shader; struct Shader { u32 id; u32 attrs[SHADER_MAX_ATTRS]; u32 uniforms[SHADER_MAX_ATTRS]; }; typedef struct XPlatform_Shader_Program_Src XPlatform_Shader_Program_Src; struct XPlatform_Shader_Program_Src { String win32_vert; String win32_frag; String osx_vert; String osx_frag; String wasm_vert; String wasm_frag; }; String xplatform_shader_program_get_vert(XPlatform_Shader_Program_Src src); String xplatform_shader_program_get_frag(XPlatform_Shader_Program_Src src); typedef struct Geometry_Buffer Geometry_Buffer; struct Geometry_Buffer { u32 buffer_id_vao; u32 buffer_id_vertices; u32 buffer_id_indices; u32 vertices_len; u32 indices_len; }; typedef struct Texture_Desc Texture_Desc; struct Texture_Desc { u32 w, h, s; u32 mag_filter, min_filter, fmt_internal, fmt_data; }; typedef struct Texture Texture; struct Texture { u32 id; Texture_Desc desc; }; typedef struct Graphics_Frame_Desc Graphics_Frame_Desc; struct Graphics_Frame_Desc { v4 clear_color; v2 viewport_min; v2 viewport_max; }; void frame_begin(Graphics_Frame_Desc desc); void frame_clear(); // Geometry Geometry_Buffer geometry_buffer_create(r32* vertices, u32 vertices_len, u32* indices, u32 indices_len); Shader shader_create(String code_vert, String code_frag, String* attribs, u32 attribs_len, String* uniforms, u32 uniforms_len); void geometry_buffer_update(Geometry_Buffer* buffer, r32* verts, u32 verts_offset, u32 verts_len, u32* indices, u32 indices_offset, u32 indices_len); Geometry_Buffer unit_quad_create(); // Shaders void geometry_bind(Geometry_Buffer geo); void shader_bind(Shader shader); void geometry_drawi(Geometry_Buffer geo, u32 indices, u32 offset); void geometry_draw(Geometry_Buffer geo); void vertex_attrib_pointer(Geometry_Buffer geo, Shader shader, u32 count, u32 attr_index, u32 stride, u32 offset); void set_uniform(Shader shader, u32 index, m44 u); // Textures Texture texture_create(Texture_Desc desc, u8* pixels); void texture_bind(Texture tex); void texture_update(Texture tex, u8* new_pixels, u32 width, u32 height, u32 stride); ////////////////////////////////////////// ////////////////////////////////////////// // IMPLEMENTATION ////////////////////////////////////////// ////////////////////////////////////////// Geometry_Buffer geometry_buffer_create( r32* vertices, u32 vertices_len, u32* indices, u32 indices_len ){ Geometry_Buffer result = {}; gl.glGenVertexArrays(1, &result.buffer_id_vao); os_gl_no_error(); GLuint buffers[2]; gl.glGenBuffers(2, (GLuint*)buffers); os_gl_no_error(); result.buffer_id_vertices = buffers[0]; result.buffer_id_indices = buffers[1]; // Vertices gl.glBindVertexArray(result.buffer_id_vao); gl.glBindBuffer(GL_ARRAY_BUFFER, result.buffer_id_vertices); os_gl_no_error(); gl.glBufferData(GL_ARRAY_BUFFER, sizeof(r32) * vertices_len, vertices, GL_STATIC_DRAW); os_gl_no_error(); result.vertices_len = vertices_len; // Indices gl.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, result.buffer_id_indices); os_gl_no_error(); gl.glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(u32) * indices_len, indices, GL_STATIC_DRAW); os_gl_no_error(); result.indices_len = indices_len; gl.glBindBuffer(GL_ARRAY_BUFFER, GL_NULL); gl.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, GL_NULL); return result; } void geometry_buffer_update( Geometry_Buffer* buffer, r32* verts, u32 verts_offset, u32 verts_len, u32* indices, u32 indices_offset, u32 indices_len ){ gl.glBindVertexArray(buffer->buffer_id_vao); gl.glBindBuffer(GL_ARRAY_BUFFER, buffer->buffer_id_vertices); os_gl_no_error(); if (verts_len > buffer->vertices_len) { // NOTE(PS): this is because we're going to delete the old buffer and // create a new one. In order to do that and not lose data, the update // function needs to have been passed all the buffer's data assert(verts_offset == 0); gl.glBufferData(GL_ARRAY_BUFFER, verts_len * sizeof(r32), (void*)verts, GL_STATIC_DRAW); } else { gl.glBufferSubData(GL_ARRAY_BUFFER, verts_offset * sizeof(r32), verts_len * sizeof(r32), (void*)verts); } os_gl_no_error(); gl.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer->buffer_id_indices); os_gl_no_error(); if (indices_len > buffer->indices_len) { // NOTE(PS): this is because we're going to delete the old buffer and // create a new one. In order to do that and not lose data, the update // function needs to have been passed all the buffer's data assert(indices_offset == 0); gl.glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices_len * sizeof(u32), (void*)indices, GL_STATIC_DRAW); } else { gl.glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, indices_offset * sizeof(u32), indices_len * sizeof(u32), (void*)indices); } os_gl_no_error(); } Geometry_Buffer unit_quad_create() { r32 z = 0; r32 r = 1; r32 verts[] = { // pos uv -r, -r, z, 0, 0, r, -r, z, 1, 0, r, r, z, 1, 1, -1, r, z, 0, 1, }; u32 indices[] = { 0, 1, 2, 0, 2, 3 }; return geometry_buffer_create(verts, sizeof(verts) / sizeof(r32), indices, 6); } Shader shader_create(String code_vert, String code_frag, String* attrs, u32 attrs_len, String* uniforms, u32 uniforms_len){ Shader result = {}; GLuint shader_vert = gl.glCreateShader(GL_VERTEX_SHADER); s32* code_vert_len = (s32*)&code_vert.len; gl.glShaderSource(shader_vert, 1, (const char**)&code_vert.str, code_vert_len); gl.glCompileShader(shader_vert); { // errors GLint shader_vert_compiled; gl.glGetShaderiv(shader_vert, GL_COMPILE_STATUS, &shader_vert_compiled); if (!shader_vert_compiled) { GLsizei log_length = 0; GLchar message[1024]; gl.glGetShaderInfoLog(shader_vert, 1024, &log_length, message); printf("GLSL Error: %s\n", message); invalid_code_path; } } GLuint shader_frag = gl.glCreateShader(GL_FRAGMENT_SHADER); s32* code_frag_len = (s32*)&code_frag.len; gl.glShaderSource(shader_frag, 1, (const char**)&code_frag.str, code_frag_len); gl.glCompileShader(shader_frag); { // errors GLint shader_frag_compiled; gl.glGetShaderiv(shader_frag, GL_COMPILE_STATUS, &shader_frag_compiled); if (!shader_frag_compiled) { GLsizei log_length = 0; GLchar message[1024]; gl.glGetShaderInfoLog(shader_frag, 1024, &log_length, message); printf("GLSL Error: %s\n", message); printf("%.*s\n", str_varg(code_frag)); invalid_code_path; } } result.id = (u32)gl.glCreateProgram(); gl.glAttachShader(result.id, shader_vert); gl.glAttachShader(result.id, shader_frag); gl.glLinkProgram(result.id); GLint program_linked; gl.glGetProgramiv(result.id, GL_LINK_STATUS, &program_linked); if (program_linked != GL_TRUE) { GLsizei log_length = 0; GLchar message[1024]; gl.glGetProgramInfoLog(result.id, 1024, &log_length, message); printf("GLSL Error: %s\n", message); invalid_code_path; } gl.glUseProgram(result.id); // TODO(PS): delete the vert and frag programs assert(attrs_len < SHADER_MAX_ATTRS); for (u32 i = 0; i < attrs_len; i++) { result.attrs[i] = gl.glGetAttribLocation(result.id, (char*)attrs[i].str); os_gl_no_error(); } result.attrs[attrs_len] = SHADER_ATTR_LAST; assert(uniforms_len < SHADER_MAX_ATTRS); for (GLuint i = 0; i < uniforms_len; i++) { s32 len = (s32)uniforms[i].len; result.uniforms[i] = gl.glGetUniformLocation(result.id, (char*)uniforms[i].str); } result.uniforms[uniforms_len] = SHADER_ATTR_LAST; return result; } void set_uniform(Shader shader, u32 index, m44 u) { gl.glUniformMatrix4fv(shader.uniforms[index], 1, GL_FALSE, (r32*)u.Elements); } void geometry_bind(Geometry_Buffer geo) { gl.glBindVertexArray(geo.buffer_id_vao); os_gl_no_error(); gl.glBindBuffer(GL_ARRAY_BUFFER, geo.buffer_id_vertices); os_gl_no_error(); gl.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, geo.buffer_id_indices); os_gl_no_error(); } void shader_bind(Shader shader) { gl.glUseProgram(shader.id); os_gl_no_error(); for (u32 i = 0; ((i < SHADER_MAX_ATTRS) && (shader.attrs[i] != SHADER_ATTR_LAST)); i++) { gl.glEnableVertexAttribArray(shader.attrs[i]); os_gl_no_error(); } } void geometry_drawi(Geometry_Buffer geo, u32 indices, u32 offset){ glDrawElements(GL_TRIANGLES, indices, GL_UNSIGNED_INT, (void*)(offset * sizeof(u32))); os_gl_no_error(); } void geometry_draw(Geometry_Buffer geo){ geometry_drawi(geo, geo.indices_len, 0); } void vertex_attrib_pointer(Geometry_Buffer geo, Shader shader, GLuint count, GLuint attr_index, GLuint stride, GLuint offset){ geometry_bind(geo); gl.glVertexAttribPointer(shader.attrs[attr_index], count, GL_FLOAT, false, stride * sizeof(float), (void*)(offset * sizeof(float))); os_gl_no_error(); gl.glEnableVertexAttribArray(shader.attrs[attr_index]); os_gl_no_error(); } Texture_Desc texture_desc_default(u32 width, u32 height) { return (Texture_Desc){ .w = width, .h = height, .s = width, .min_filter = GL_NEAREST, .mag_filter = GL_LINEAR, .fmt_internal = GL_RGBA, .fmt_data = GL_RGBA }; } Texture texture_create(Texture_Desc desc, u8* pixels) { Texture result = {}; glGenTextures(1, &result.id); os_gl_no_error(); glBindTexture(GL_TEXTURE_2D, result.id); os_gl_no_error(); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, desc.min_filter); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, desc.mag_filter); os_gl_no_error(); glTexImage2D( GL_TEXTURE_2D, 0, desc.fmt_internal, desc.w, desc.h, 0, desc.fmt_data, GL_UNSIGNED_BYTE, pixels ); os_gl_no_error(); result.desc = desc; glBindTexture(GL_TEXTURE_2D, 0); return result; } void texture_update(Texture tex, u8* new_pixels, u32 width, u32 height, u32 stride) { // NOTE(PS): this function simply replaces the entire image // we can write a more granular version if we need it assert(tex.desc.w == width && tex.desc.h == height && tex.desc.s == stride); texture_bind(tex); glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, // offset width, height, GL_RGBA, GL_UNSIGNED_BYTE, new_pixels ); os_gl_no_error(); } void texture_bind(Texture tex) { glBindTexture(GL_TEXTURE_2D, tex.id); os_gl_no_error(); } void frame_begin(Graphics_Frame_Desc desc) { v4 cc = desc.clear_color; glClearColor(cc.r, cc.g, cc.b, cc.a); v2 vmin = desc.viewport_min; v2 vdim = HMM_SubtractVec2(desc.viewport_max, desc.viewport_min); glViewport((GLsizei)vmin.x, (GLsizei)vmin.y, (GLint)vdim.x, (GLint)vdim.y); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glDisable(GL_CULL_FACE); glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LESS); os_gl_no_error(); } void frame_clear() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } String xplatform_shader_program_get_vert(XPlatform_Shader_Program_Src src) { #if defined(PLATFORM_win32) return src.win32_vert; #elif defined(PLATFORM_osx) return src.osx_vert; #elif defined(PLATFORM_wasm) return src.wasm_vert; #endif } String xplatform_shader_program_get_frag(XPlatform_Shader_Program_Src src) { #if defined(PLATFORM_win32) return src.win32_frag; #elif defined(PLATFORM_osx) return src.osx_frag; #elif defined(PLATFORM_wasm) return src.wasm_frag; #endif } #endif // LUMENARIUM_EDITOR_GRAPHICS_H