#include #define GLM_FORCE_XYZW_ONLY #include #include "tangerine.h" #define UTIL_IMPLEMENTATION #include "util.h" #define GL_DEBUG_IMPLEMENTATION #include "GLDebug.h" // forward declarations bool initGraphics(SDLHandles* handles, const char* title, uvec2 dims); LightsBuffer* initLights(RenderState* rs, u32 max_lights, vec4 ambient_color); // interface RenderState* initRenderState(const char* window_title, uvec2 window_dims, u32 SDL_flags, GLClearColor clear_col, vec4 ambient_color, u32 max_models, u32 max_textures, u32 max_shaders, u32 max_render_groups, u32 max_ubos, u32 max_lights) { LOGF(Info, "Initializing Renderer\n"); RenderState* rs = UTIL_ALLOC(1, RenderState); if (rs) { rs->clear_col = clear_col; rs->assets.arena = arenaInit(DEFAULT_ARENA_SIZE); rs->assets.max_models = max_models; rs->assets.models = ARENA_ALLOC(rs->assets.arena, Model, max_models); rs->assets.max_textures = max_textures; rs->assets.textures = ARENA_ALLOC(rs->assets.arena, Texture, max_textures); rs->rg_arena = arenaInit(DEFAULT_ARENA_SIZE); rs->max_render_groups = DEFAULT_RENDER_GROUP_COUNT; rs->render_groups = ARENA_ALLOC(rs->rg_arena, RenderGroup, DEFAULT_RENDER_GROUP_COUNT); rs->window_dims = window_dims; rs->handles.SDL_flags = SDL_flags; if (!initGraphics(&rs->handles, window_title, window_dims)) { LOGF(Error, "error initializing renderer\n"); return nullptr; } rs->gl_ctx = initGLContext(rs->assets.arena, max_shaders, max_textures, max_ubos); rs->camera = UTIL_ALLOC(1, Camera); GLBuffer* xforms_ubo = initGLBackingBuffer(rs->gl_ctx, rs->assets.arena, "matrices", GL_FLOAT, sizeof(Transforms), &rs->camera->xforms); assert(xforms_ubo); // FIXME: should this be an interface function? rs->lights_buf = initLights(rs, max_lights, ambient_color); // FIXME: should have an error message instead of assert here, and // clean up arenas/allocations before returning bool ret = loadDefaultShaders(rs); assert(ret); } return rs; } void freeRenderState(RenderState*& rs) { if (rs) { SDL_GL_DeleteContext(rs->handles.sdl_gl_ctx); SDL_DestroyWindow(rs->handles.window); arenaFree(rs->assets.arena); arenaFree(rs->rg_arena); utilSafeFree(rs); rs = nullptr; } SDL_Quit(); } void initRenderGroup(RenderGroup* rg, MemoryArena* arena, ShaderProgram* shader, u32 num_entities, const char* name) { rg->max_entities = num_entities; rg->shader = shader; rg->name = arenaCopyCStr(arena, name); rg->entities = ARENA_ALLOC(arena, Entity, num_entities); } void freeRenderGroup(RenderGroup* rg, MemoryArena* arena) { LOGF(Info, "should probably look into freeing arena memory?\n"); assert(0); } RenderGroup* getFreeRenderGroup(RenderState* rs) { if (rs->num_render_groups < rs->max_render_groups) return &rs->render_groups[rs->num_render_groups++]; LOGF(Error, "no free render group\n"); return nullptr; } RenderGroup* getRenderGroupByName(RenderState* rs, const char* name) { RenderGroup* rg_out = nullptr; for (u32 i = 0; i < rs->num_render_groups; i++) { RenderGroup* rg = &rs->render_groups[i]; if (utilCStrMatch(name, rg->name)) rg_out = rg; } if (rg_out == nullptr) LOGF(Error, "render group with name, \"%s\", not found\n", name); return rg_out; } Entity* getFreeEntity(RenderGroup* rg) { if (rg->num_entities < rg->max_entities) return &rg->entities[rg->num_entities++]; LOGF(Error, "render group full\n"); return nullptr; } Entity* getEntityByName(RenderGroup* rg, const char* name) { Entity* e_out = nullptr; for (u32 i = 0; i < rg->num_entities; i++) { Entity* e = &rg->entities[i]; if (utilCStrMatch(name, e->name)) e_out = e; } if (e_out == nullptr) LOGF(Error, "Entity with name, \"%s\", not found\n", name); return e_out; } void doRenderLoop(RenderState* rs, u32 framerate, frame_callback_fn cb_func_pre, frame_callback_fn cb_func_post, void* user_data) { u32 delay = (framerate > 0) ? 1 / framerate : 0; u32 frameStart, frameTime; rs->running = true; SDL_Event e; while (rs->running) { frameStart = SDL_GetTicks(); if (cb_func_pre != nullptr) { cb_func_pre(rs, user_data); } else { while (SDL_PollEvent(&e)) { if (e.type == SDL_QUIT || (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_ESCAPE)) { rs->running = false; break; } } } renderFrame(rs, rs->clear_col); if (cb_func_post != nullptr) cb_func_post(rs, user_data); SDL_GL_SwapWindow(rs->handles.window); frameTime = SDL_GetTicks() - frameStart; if (delay > frameTime) SDL_Delay(delay - frameTime); } } void renderFrame(RenderState* rs, const GLClearColor& clear_col) { glClearColor(clear_col.R, clear_col.G, clear_col.B, clear_col.A); glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); for (u32 i = 0; i < rs->num_render_groups; i++) { RenderGroup* rg = &rs->render_groups[i]; for (u32 j = 0; j < rg->num_entities; j++) { Entity* e = &rg->entities[j]; for (u32 k = 0; k < e->num_meshes; k++) { GLMesh& glm = e->meshes[k]; renderVAO(&glm, e->model_xform, rg->shader, e->diffuse_texture, e->draw_mode); } } } } bool loadDefaultShaders(RenderState* rs, u32 num_shaders, const ShaderInit shaders[]) { for (u32 i = 0; i < num_shaders; i++) { const ShaderInit& si = shaders[i]; if (!addShaderProgram(rs->assets.arena, rs->gl_ctx, si.vert_path, si.frag_path, si.name)) { LOG(Error) << "failed to load shader " << si.name << "\n"; return false; } ShaderProgram* shader = getShaderByName(si.name, rs->gl_ctx); assert(shader); // NOTE: not every buffer will be available for every shader, so we // enumerate them all, and store the ones that are present u32 attrib_idx = 0; for (u32 i = 0; i < MESH_BUFFER_TYPE_COUNT; i++) { MeshBufferType buf_type = (MeshBufferType) i; GLVertexAttrib* attrib = getVertexAttribByType(shader, buf_type); if (attrib) shader->attrib_mappings[attrib_idx++] = { attrib, buf_type }; } } return true; } GLVertexAttrib* getVertexAttribByType(ShaderProgram* shader, MeshBufferType buf_type) { switch (buf_type) { case VERTEX: return getVertexAttribByName(shader, "position"); case NORMAL: return getVertexAttribByName(shader, "normal"); case UV: return getVertexAttribByName(shader, "uv"); case COLOR: return getVertexAttribByName(shader, "color"); default: return nullptr; } } // internal bool initGraphics(SDLHandles* handles, const char* title, uvec2 dims) { handles->window = SDL_CreateWindow( title, SDL_WINDOWPOS_CENTERED_DISPLAY(0), SDL_WINDOWPOS_CENTERED_DISPLAY(0), dims.x, dims.y, SDL_WINDOW_OPENGL|SDL_WINDOW_RESIZABLE); if (SDL_Init(handles->SDL_flags) != 0) { std::cout << "error, sdl init: " << SDL_GetError() << "\n"; return false; } SDL_GL_SetSwapInterval(1); SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); SDL_GetCurrentDisplayMode(0, &handles->display_mode); handles->sdl_gl_ctx = SDL_GL_CreateContext(handles->window); if (!handles->sdl_gl_ctx) { std::cout << "error creating context\n"; return false; } if (glewInit()) { std::cout << "error initializing opengl\n"; return false; } std::cout << "opengl vendor: " << glGetString(GL_VENDOR) << "\n"; std::cout << "opengl renderer: " << glGetString(GL_RENDERER) << "\n"; std::cout << "opengl version: " << glGetString(GL_VERSION) << "\n"; glEnable(GL_DEPTH_TEST); glEnable(GL_LINE_SMOOTH); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glEnable (GL_DEBUG_OUTPUT); glDebugMessageCallback((GLDEBUGPROC) openglDebugCallback, 0); return handles->window != nullptr; } LightsBuffer* initLights(RenderState* rs, u32 max_lights, vec4 ambient_color) { // FIXME: revisit for 'Scene' abstraction // FIXME: see if we can simplify this with the use of offsetof() // https://en.cppreference.com/w/cpp/types/offsetof LightsBuffer* lb = ARENA_ALLOC(rs->assets.arena, LightsBuffer, 1); lb->buf_size = 8 * sizeof(u32) // NOTE: 'header' + sizeof(vec4) // NOTE: ambient color + 6 * max_lights * sizeof(vec4); // NOTE: vector arrays LOGF(Debug, "buf_size: %d\n", lb->buf_size); lb->buffer = ARENA_ALLOC(rs->assets.arena, u8, lb->buf_size); lb->max_p_lights = (u32*) lb->buffer; lb->active_p_lights = (u32*) arenaGetAddressOffset(lb->max_p_lights, sizeof(u32)); lb->max_d_lights = (u32*) arenaGetAddressOffset(lb->active_p_lights, sizeof(u32)); lb->active_d_lights = (u32*) arenaGetAddressOffset(lb->max_d_lights, sizeof(u32)); *lb->max_p_lights = max_lights; *lb->max_d_lights = max_lights; // NOTE: add padding, we're not actually using this since 4 * u32 is on a // 16 byte boundary, but will be helpful if we need to add more 'headers' // in the future void* arr_start = arenaGetAddressOffset(lb->buffer, 8 * sizeof(u32)); // NOTE: ambient color lb->ambient_color = (vec4*) arr_start; *lb->ambient_color = ambient_color; // NOTE: set offsets for array pointers u32 arr_size = max_lights * sizeof(vec4); lb->pl_positions = //(vec4*) arr_start; (vec4*) arenaGetAddressOffset(arr_start, sizeof(vec4)); lb->pl_colors = (vec4*) arenaGetAddressOffset(lb->pl_positions, arr_size); lb->pl_intensities = (uvec4*) arenaGetAddressOffset(lb->pl_colors, arr_size); lb->dl_directions = (vec4*) arenaGetAddressOffset(lb->pl_intensities, arr_size); lb->dl_colors = (vec4*) arenaGetAddressOffset(lb->dl_directions, arr_size); lb->dl_intensities = (uvec4*) arenaGetAddressOffset(lb->dl_colors, arr_size); initGLBackingBuffer(rs->gl_ctx, rs->assets.arena, "lights", GL_BYTE, lb->buf_size, lb->buffer); return lb; }