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.
 
 
 

314 lines
7.4 KiB

#if defined (_WIN32)
#include <SDL.h>
#else
#include <SDL2/SDL.h>
#endif
#include <glm/glm.hpp>
#include <glm/geometric.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include "default_shaders.cpp"
#include "dumbLog.h"
#include "input.h"
#include "render_object.h"
#include "renderer.h"
#define CLEAR_COL_R 55.f / 255.f
#define CLEAR_COL_G 55.f / 255.f
#define CLEAR_COL_B 55.f / 255.f
#define CLEAR_COL_A 1.f
#define USE_SECOND_MONITOR 0
// forward declarations
bool createWindow(const char* title,
SDL_Handles* handles,
glm::vec2& viewport_dims);
bool initContext(SDL_Handles* handles);
bool initGlOptions();
bool initSDL(SDL_Handles* handles, Uint32 SDL_init_flags);
bool initShaders(render_state* rs);
void openglDebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity,
GLsizei length, const GLchar* message, const void* userParam);
void setDefaults(render_state* rs, glm::vec2 viewport_dims);
// interface
render_state*
renInit(const char* title, glm::vec2 viewport_dims, Uint32 SDL_init_flags)
{
render_state* rs = UTIL_ALLOC(1, render_state);
rs->handles = UTIL_ALLOC(1, SDL_Handles);
setDefaults(rs, viewport_dims);
if (initSDL(rs->handles, SDL_init_flags) &&
createWindow(title, rs->handles, rs->viewport_dims) &&
initContext(rs->handles) &&
initGlOptions() &&
initShaders(rs))
{
return rs;
}
LOG(Error) << "renderer initialization failed, aborting\n";
return nullptr;
}
void
renShutdown(render_state* rs)
{
for (uint i = 0; i < rs->render_group_count; i++) {
for (uint j = 0; j < rs->render_groups[i]->entity_count; j++)
entFree(rs->render_groups[i]->entities[j]);
utilSafeFree(rs->render_groups[i]->entities);
}
shaderFree(rs->default_shader->program_id);
utilSafeFree(rs->default_shader);
rs->default_shader = nullptr;
shaderFree(rs->simple_shader->program_id);
utilSafeFree(rs->simple_shader);
rs->simple_shader = nullptr;
lightsOut(rs->lights);
utilSafeFree(rs->render_groups);
rs->render_groups = nullptr;
arenaFree(rs->arena);
meShutdownAssimp();
SDL_GL_DeleteContext(rs->handles->glContext);
SDL_DestroyWindow(rs->handles->window);
SDL_Quit();
utilSafeFree(rs->handles);
}
render_group*
renAllocateGroup(uint entity_count, shader_wrapper shader)
{
assert(entity_count > 0);
render_group* rg = UTIL_ALLOC(1, render_group);
rg->entities = UTIL_ALLOC(entity_count, entity);
rg->entity_count = entity_count;
rg->shader = shader;
return rg;
}
void
renDoRenderLoop(render_state* rs,
uint framerate,
frame_callback_fn cb_func_pre,
frame_callback_fn cb_func_post)
{
uint delay = (framerate > 0) ? 1 / framerate : 0;
uint frameStart, frameTime;
static input_state is = {};
while (rs->running) {
frameStart = SDL_GetTicks();
if (cb_func_pre != nullptr) {
cb_func_pre(rs);
} else {
inputProcessEvents(&is);
if (is.window_closed || is.escape) {
rs->running = false;
return;
}
}
renRenderFrame(rs);
if (cb_func_post != nullptr) cb_func_post(rs);
SDL_GL_SwapWindow(rs->handles->window);
frameTime = SDL_GetTicks() - frameStart;
if (delay > frameTime)
SDL_Delay(delay - frameTime);
}
}
void
renRenderFrame(render_state* rs)
{
glClearColor(rs->clear_col.R,
rs->clear_col.G,
rs->clear_col.B,
rs->clear_col.A);
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
for (uint i = 0; i < rs->render_group_count; i++) {
render_group* rg = rs->render_groups[i];
for (uint j = 0; j < rg->entity_count; j++) {
entity& e = rg->entities[j];
roDraw(e.render_objs,
e.world_transform,
rs->cam,
rg->shader,
rs->lights);
}
}
}
bool
renAddLight(render_state* rs, glm::vec3 pos, glm::vec3 color, float intensity)
{
if (!lightsAdd(rs->lights, pos, color, intensity)) {
LOG(Error) << "Error adding light\n";
return false;
}
return true;
}
glm::vec2
renGetWindowDims(render_state* rs)
{
int x = 0, y = 0;
SDL_GetWindowSize(rs->handles->window, &x, &y);
glm::vec2 dims(x, y);
return dims;
}
// internal
bool
createWindow(const char* title, SDL_Handles* handles, glm::vec2& viewport_dims)
{
uint display_id = 0;
if (USE_SECOND_MONITOR && SDL_GetNumVideoDisplays() > 1)
display_id = 1;
handles->window = SDL_CreateWindow(
title,
SDL_WINDOWPOS_CENTERED_DISPLAY(display_id),
SDL_WINDOWPOS_CENTERED_DISPLAY(display_id),
viewport_dims.x,
viewport_dims.y,
SDL_WINDOW_OPENGL|SDL_WINDOW_RESIZABLE
);
if (!handles->window) {
LOG(Error) << "Error creating window: " << SDL_GetError() << "\n";
return false;
}
return true;
}
bool
initContext(SDL_Handles* handles)
{
handles->glContext = SDL_GL_CreateContext(handles->window);
if (!handles->glContext) {
LOG(Error) << "Error creating glContext: " << SDL_GetError() << "\n";
return false;
}
// TODO: this doesn't work inside VM with QXL graphics
if (SDL_GL_SetSwapInterval(1) != 0) // vsync
LOG(Error) << "SDL Errors: " << SDL_GetError() << "\n";
return true;
}
bool
initGlOptions()
{
if (glewInit()) {
LOG(Error) << "failed to initialize OpenGL\n";
return false;
}
LOG(Info) << "opengl vendor: " << glGetString(GL_VENDOR) << "\n";
LOG(Info)<< "opengl renderer: " << glGetString(GL_RENDERER) << "\n";
LOG(Info) << "opengl version: " << glGetString(GL_VERSION) << "\n";
glEnable(GL_DEPTH_TEST);
glEnable(GL_LINE_SMOOTH);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
#if 0
// TODO: blending messes up rendering with mesa on intel graphics 4000
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_ONE, GL_SRC_ALPHA);
#endif
// TODO: glDebugMessageCallback is only availabe from >v4.3
// check and warn if context doesn't support this function here
glEnable (GL_DEBUG_OUTPUT);
glDebugMessageCallback((GLDEBUGPROC) openglDebugCallback, 0);
// hide VRAM debug messages
glDebugMessageControl(GL_DONT_CARE, 33361, GL_DONT_CARE, 0, 0, GL_FALSE);
return true;
}
bool
initSDL(SDL_Handles* handles, Uint32 SDL_init_flags)
{
Uint32 flags = SDL_INIT_VIDEO | SDL_init_flags;
if (SDL_Init(flags) != 0) {
LOG(Error) << "Error, SDL_Init: " << SDL_GetError() << "\n";
return false;
}
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->currentDisplayMode);
return true;
}
bool
initShaders(render_state* rs)
{
rs->default_shader =
shaderInitDefault(DEFAULT_VERTEX_SHADER, DEFAULT_FRAGMENT_SHADER);
rs->simple_shader =
shaderInitSimple(SIMPLE_VERTEX_SHADER, SIMPLE_FRAGMENT_SHADER);
if (rs->default_shader == nullptr ||
rs->simple_shader == nullptr)
{
LOG(Error) << "shader loading failed\n";
return false;
} else {
return true;
}
}
void
openglDebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity,
GLsizei length, const GLchar* message, const void* userParam)
{
LOG((type == GL_DEBUG_TYPE_ERROR) ? Error : Debug)
<< (type == GL_DEBUG_TYPE_ERROR ? "** GL Error **" : "")
<< ", type: " << type
<< ", severity: " << severity
<< ", message: " << message << "\n";
}
void
setDefaults(render_state* rs, glm::vec2 viewport_dims)
{
rs->running = true;
rs->viewport_dims = viewport_dims;
rs->lights = lightsInit();
rs->clear_col.R = CLEAR_COL_R;
rs->clear_col.B = CLEAR_COL_B;
rs->clear_col.G = CLEAR_COL_G;
rs->clear_col.A = CLEAR_COL_A;
}