A small OpenGL 3+ renderer and game engine
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

408 lines
9.9 KiB

#include <cassert>
#define GLM_FORCE_XYZW_ONLY
#include <glm/gtc/matrix_transform.hpp>
#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;
}