From 6c16f339224a4736f4ed57d15bb3e5f968a635ab Mon Sep 17 00:00:00 2001 From: 0scar Date: Tue, 29 Sep 2020 08:51:02 +0200 Subject: Initial independent commit --- src/engine.c | 686 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 686 insertions(+) create mode 100644 src/engine.c (limited to 'src/engine.c') diff --git a/src/engine.c b/src/engine.c new file mode 100644 index 0000000..fa528c0 --- /dev/null +++ b/src/engine.c @@ -0,0 +1,686 @@ +#include + +#include +#include +#include + +#if defined (_WIN32) || defined (__WIN32__) || defined (WIN32) + /* include winapi */ +#elif defined (__APPLE__) + /* mac includes */ +#elif defined (__linux) || defined (__linux__) || defined (linux) + +#include + +#endif + + +#define ENGINE_INTERNALS +#include +#include +#include +#include + +#include +//#include +//#include + +#define DEFAULT_NUM_PROCS 8 + +#ifdef BENCHMARK +#define BENCHEXPR(timevar, expr) { \ + u32 t = SDL_GetTicks(); \ + expr \ + timevar += SDL_GetTicks() - t; \ +} + +extern i32 drawcall_len; + +#else +#define BENCHEXPR(timevar, expr) expr +#endif + +i32 bindings_cmp(const Keybinding *a, const Keybinding *b) { + return a->modifiers ^ b->modifiers; +} + +i32 binding_to_int(const Keybinding *b) { + return b->keycode; +} + +/* Defines (struct) List_Keybinding */ +/* Defines (struct) hashmap_Keybinding, + * and hashmap_Keybinding_lookup, and hashmap_Keybinding_insert */ +DEFINE_HASHMAP(Keybinding, 64, bindings_cmp, binding_to_int) + +static u64 FPS_CAP = 50; +Platform *GLOBAL_PLATFORM = NULL; + +i32 nproc(void) { + return SDL_GetCPUCount(); +} + +i32 cmp_int(const void *a, const void *b) { + const i32 *x = a; + const i32 *y = b; + + return *x - *y; +} + +v2_i32 get_canvas_size(SDL_Renderer *renderer) { + v2_i32 realsize; + SDL_GetRendererOutputSize(renderer, &(realsize.x), &(realsize.y)); + + /* Set logical render size */ + return realsize; +} + +Texture *load_texture(SDL_Renderer *render, const TextureSpec *ts) { + SDL_Texture* new_texture = NULL; + SDL_Surface* loaded_surface = NULL; + Texture *t = NULL; + + if (ts == NULL) { + ERROR("Invalid TextureSpec\n"); + return NULL; + } + + loaded_surface = IMG_Load(ts->path); + if (loaded_surface == NULL) { + ERROR("Unable to load image \"%s\"!\n", ts->path); + ERROR("SDL_image Error: %s\n", IMG_GetError()); + return NULL; + } + + const i32 tw = loaded_surface->w / ts->width; + + SDL_SetColorKey(loaded_surface, SDL_TRUE, + SDL_MapRGB(loaded_surface->format, 0xFF, 0x00, 0xFF)); + + /*Create texture from surface pixels */ + new_texture = SDL_CreateTextureFromSurface(render, loaded_surface); + if (new_texture == NULL) { + ERROR("Unable to create texture from \"%s\"!\n", ts->path); + ERROR("SDL Error: %s\n", SDL_GetError()); + } + + /*Get rid of old loaded surface */ + SDL_FreeSurface(loaded_surface); + + t = (Texture*)malloc(sizeof(Texture)); + t->texture = new_texture; + /* Assigning const value */ + *(i32*)&t->tilesize = tw; + *(i32*)&t->width = ts->width; + *(i32*)&t->height = ts->height; + + return t; +} + +void engine_update_window(Window *w, SDL_WindowEvent *e) { + switch (e->event) { + case SDL_WINDOWEVENT_NONE: + case SDL_WINDOWEVENT_SHOWN: + case SDL_WINDOWEVENT_HIDDEN: + case SDL_WINDOWEVENT_EXPOSED: + case SDL_WINDOWEVENT_MOVED: + break; + case SDL_WINDOWEVENT_RESIZED: + w->windowsize = get_canvas_size(w->renderer); + LOG("Resized window to %dx%d", w->windowsize.x, w->windowsize.y); + ui_resolve_constraints(); + if (w->game_w != NULL && w->game_h != NULL) { + *w->game_h = w->windowsize.x; + *w->game_w = w->windowsize.y; + } + break; + case SDL_WINDOWEVENT_SIZE_CHANGED: + case SDL_WINDOWEVENT_MINIMIZED: + case SDL_WINDOWEVENT_MAXIMIZED: + case SDL_WINDOWEVENT_RESTORED: + case SDL_WINDOWEVENT_ENTER: + case SDL_WINDOWEVENT_LEAVE: + case SDL_WINDOWEVENT_FOCUS_GAINED: + case SDL_WINDOWEVENT_FOCUS_LOST: + case SDL_WINDOWEVENT_CLOSE: + case SDL_WINDOWEVENT_TAKE_FOCUS: + case SDL_WINDOWEVENT_HIT_TEST: + break; + default: + WARN("Unhandled window event 0x%04x", (i32)e->event); + break; + } + return; +} + +Platform *engine_init( + const char *windowtitle, + v2_i32 windowsize, + const f32 render_scale, + const u32 flags, + const usize initial_memory, + const FontSpec *fonts[], + const TextureSpec *textures[]) { + +#ifdef BENCHMARK + u32 init_start = SDL_GetTicks(); +#endif + +#if defined (__linux) || defined (__linux__) || defined (linux) + { + pid_t pid = getpid(); + INFO("Starting with pid %lu", pid); + } +#endif + + Platform *p = (Platform*)malloc(sizeof(Platform)); + Window *w = (Window*)malloc(sizeof(Window)); + SDL_Window *window = NULL; + SDL_Renderer *renderer = NULL; + + /* initialize resources */ + struct Resources *resources = (struct Resources*)malloc(sizeof(struct Resources)); + resources->textures_len = 0; + resources->fonts_len = 0; + resources->texturepaths_len = 0; + resources->fontpaths_len = 0; + resources->texture_paths = NULL; + resources->font_paths = NULL; + resources->textures = NULL; + resources->fonts = NULL; + + + { /* Init subsystems */ + INFO_("initializing sdl..."); + if (SDL_Init(SDL_INIT_VIDEO) < 0) { + ERROR("failed to initialize sdl: %s\n", SDL_GetError()); + exit(EXIT_FAILURE); + } else printf("ok\n"); + + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); + + INFO_("creating window..."); + window = SDL_CreateWindow(windowtitle, + SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, + windowsize.x, windowsize.y, + SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_INPUT_FOCUS | SDL_WINDOW_MOUSE_FOCUS | flags); + + if (window == NULL) { + ERROR("failed to create window: %s\n", SDL_GetError()); + exit(EXIT_FAILURE); + } else printf("ok\n"); + + INFO_("creating renderer..."); + renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); + if (renderer == NULL) { + ERROR("failed to create renderer: %s\n", SDL_GetError()); + exit(EXIT_FAILURE); + } else printf("ok\n"); + + INFO_("initializing sdl_image..."); + if (IMG_Init(IMG_INIT_PNG) != IMG_INIT_PNG) { + ERROR("failed to initialize sdl_image png support\n"); + exit(EXIT_FAILURE); + } else printf("ok\n"); + + INFO_("initializing sdl_ttf..."); + if (TTF_Init() == -1) { + ERROR("failed to initialize sdl_ttf: %s\n", TTF_GetError()); + exit(EXIT_FAILURE); + } else printf("ok\n"); + } + + + { /* Resource loading */ + + /* Count resources */ + usize n_textures = 0; + usize n_fonts = 0; + + if (textures != NULL) while (textures[n_textures] != NULL) n_textures++; + if (fonts != NULL) while (fonts[n_fonts] != NULL) n_fonts++; + + INFO("Number of textures: "TERM_COLOR_YELLOW"%d"TERM_COLOR_RESET, n_textures); + INFO("Number of fonts: "TERM_COLOR_YELLOW"%d"TERM_COLOR_RESET, n_fonts); + + + /* Save the textures and fonts, if we should need to reload them later */ + resources->texture_paths = (TextureSpec**)textures; + resources->font_paths = (FontSpec**)fonts; + + /* Allocate memory for textures and fonts */ + resources->textures = (Texture**)malloc(sizeof(Texture*) * n_textures); + resources->fonts = (TTF_Font**)malloc(sizeof(TTF_Font*) * n_fonts); + resources->textures_size = n_textures; + + for (usize i = 0; i < n_textures; i++) resources->textures[i] = NULL; + for (usize i = 0; i < n_fonts; i++) resources->fonts[i] = NULL; + + /* Load textures */ + for (usize i = 0; i < n_textures; i++) { + Texture *t = NULL; + INFO_("loading texture \"" + TERM_COLOR_YELLOW"%s"TERM_COLOR_RESET + "\"...", textures[i]->path); + + t = load_texture(renderer, textures[i]); + if (t == NULL) { + puts(""); + ERROR("failed to load texture\n"); + exit(EXIT_FAILURE); + } + + if (t->tilesize < 8) { + puts(""); + ERROR("texture too small!\n"); + exit(EXIT_FAILURE); + } + + if (t->texture == NULL) { + puts(""); + ERROR("failed to load texture\n"); + } else { + printf("ok\n"); + resources->textures[i] = t; + resources->textures_len++; + } + } + + /* Load fonts */ + for (usize i = 0; i < n_fonts; i++) { + INFO_("loading font \"" + TERM_COLOR_YELLOW"%s"TERM_COLOR_RESET + "\"...", fonts[i]->font_path); + + TTF_Font *font = TTF_OpenFont(fonts[i]->font_path + , fonts[i]->ptsize); + if (!font) { + ERROR("failed to load font: %s\n", TTF_GetError()); + } else { + printf("ok\n"); + resources->fonts[i] = font; + resources->fonts_len++; + } + } + + if (resources->textures_len != n_textures) { + WARN("Done. %d/%d textures loaded.", resources->textures_len, n_textures); + } else { + INFO("Done. All %d textures loaded.", n_textures); + } + + if (resources->fonts_len != n_fonts) { + WARN("Done. %d/%d fonts loaded.", resources->fonts_len, n_fonts); + } else { + INFO("Done. All %d fonts loaded.", n_fonts); + } + + resources->texturepaths_len = resources->textures_len; + resources->fontpaths_len = resources->fonts_len; + + } + + + { /* Adjust window and such */ + /* Set actual windowsize, which might be forced by OS */ + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0"); + INFO("Adjusting window size..."); + windowsize = get_canvas_size(renderer); + + SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); + + INFO("Windowsize: <%d,%d>", windowsize.x, windowsize.y); + } + + w->renderer = renderer; + w->window = window; + w->render_scale = render_scale; + w->windowsize = windowsize; + w->game_w = NULL; + w->game_h = NULL; + + p->data = (void*)resources; + p->data_len = sizeof(struct Resources); + p->window = w; + p->quit = false; + + p->frame = 0; + p->fps_target = 60; + + p->mem = memory_new(initial_memory); + + /* Getting the mouse coords now resolves the issue where a click "isn't + * registered" when the mouse isn't moved before the user clicks */ + SDL_GetMouseState(&p->mouse_pos.x, &p->mouse_pos.y); + + p->mousedown = (v2_i32){-1, -1}; + p->mouseup = (v2_i32){-1, -1}; + + p->mouse_lclick = false; + p->mouse_rclick = false; + + p->camera_x = 0; + p->camera_y = 0; + + p->edit_text = NULL; + p->edit_pos = 0; + + p->bindings = NULL; + +#ifdef BENCHMARK + u32 init_stop = SDL_GetTicks(); + INFO("Initialization took %dms", init_stop - init_start); +#endif + + INFO("Available cores: %d", nproc()); + + GLOBAL_PLATFORM = p; + + return p; +} + +i32 engine_run(Platform *p, StateType initial_state) { + if (p == NULL) { + ERROR("Platform is uninitialized.\n"); + INFO("initialize with `engine_init`"); + return -1; + } + + memory* mem = p->mem; + + StateType state = initial_state; + + { + u32 state_init_time = SDL_GetTicks(); + State_init(state, mem); + INFO("Initializing state \"%s\" took %ldms", StateTypeStr[state], SDL_GetTicks() - state_init_time); + } + + u32 time = SDL_GetTicks(); + + // Update ticks + u64 ticks = 0; + + /* Profiling values */ +#ifdef BENCHMARK + u64 profile_tick_counter = 0; + //u64 profile_slack = 0; + u64 profile_rendering = 0; + u64 profile_gameloop = 0; + u64 profile_input = 0; + u64 profile_input_handling = 0; + u64 profile_num_drawcalls = 0; + u32 profile_interval_timer = time; + const u32 profile_interval_ms = 5000; + const f32 profile_interval_scale = (f32)(profile_interval_ms) / 100.0f; +#endif + + const f64 frame_interval = 1000.0 / FPS_CAP; + + StateType (*update_func)(void*) = State_updateFunc(state); + + /* Main loop */ + do { + const u32 now = SDL_GetTicks(); + const u64 dt = now - time; + time = now; + /* Wait frame_interval */ + if (dt < frame_interval) { +#ifndef BENCHMARK + SDL_Delay(frame_interval - dt); + +#else + /* We want to know how much time is spend sleeping */ + //profile_slack += frame_interval - dt; +#endif + } + +#ifdef BENCHMARK + if (time - profile_interval_timer > profile_interval_ms) { + /* Ticks/frames since last measurement */ + u32 fps = (ticks - profile_tick_counter) / profile_interval_scale; + u64 drawcalls = profile_num_drawcalls / profile_interval_scale / fps; + + u32 sum = + + profile_rendering + //+ profile_slack + + profile_input + + profile_input_handling + + profile_gameloop + ; + + + /* Log fps and slack percentage */ + LOG("fps:%d\t" + "rendering:%.2f%%\t" + //"slack:%.2f%%\t" + "input:%.2f%% (%.2f%%)\t" + "gameloop:%.2f%%\t" + "unaccounted:%llu / %llu ms\t" + "avg drawcalls:%llu", + fps, + 100.0f * (f32)profile_rendering / (f32)sum, + //100.0f * (f32)profile_slack / (f32)sum, + 100.0f * (f32)profile_input / (f32)sum, + 100.0f * (f32)profile_input_handling / (f32)sum, + 100.0f * (f32)profile_gameloop / (f32)sum, + time - profile_interval_timer - sum, sum, + drawcalls); + /* Reset values */ + profile_tick_counter = ticks; + profile_interval_timer = time; + //profile_slack = 0; + profile_rendering = 0; + profile_gameloop = 0; + profile_input = 0; + profile_input_handling = 0; + profile_num_drawcalls = 0; + } +#endif + + /* Events */ + BENCHEXPR(profile_input, { + + if (GLOBAL_PLATFORM->mouse_lclick) { + GLOBAL_PLATFORM->mouseup.x = -1; + GLOBAL_PLATFORM->mouseup.y = -1; + GLOBAL_PLATFORM->mousedown.x = -1; + GLOBAL_PLATFORM->mousedown.y = -1; + GLOBAL_PLATFORM->mouse_lclick = false; + } + if (GLOBAL_PLATFORM->mouse_rclick) { + GLOBAL_PLATFORM->mouse_rclick = false; + } + + /* Window events */ + SDL_Event e[8]; + i32 num_events; + SDL_PumpEvents(); + while ((num_events = SDL_PeepEvents(e, 8, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_SYSWMEVENT)) > 0) { + for (i32 i = 0; i < num_events; i++) { + switch (e[i].type) { + case SDL_QUIT: + state = STATE_quit; + break; + case SDL_WINDOWEVENT: + engine_update_window(p->window, &e[i].window); + break; + default: + WARN("Unhandled event 0x%04x", (i32)e[i].type); + } + } + } + + /* Mouse events */ + while ((num_events = SDL_PeepEvents(e, 8, SDL_GETEVENT, SDL_MOUSEMOTION, SDL_MOUSEWHEEL)) > 0) { + for (i32 i = 0; i < num_events; i++) { + switch (e[i].type) { + case SDL_MOUSEWHEEL: break; + case SDL_MOUSEMOTION: + { + SDL_MouseMotionEvent m = e[i].motion; + /* In case of a first-person game, use xrel and yrel */ + GLOBAL_PLATFORM->mouse_pos.x = m.x; + GLOBAL_PLATFORM->mouse_pos.y = m.y; + } + break; + case SDL_MOUSEBUTTONUP: + { + switch (e[i].button.button) { + case SDL_BUTTON_LEFT: + GLOBAL_PLATFORM->mouseup = GLOBAL_PLATFORM->mouse_pos; + + GLOBAL_PLATFORM->mouse_lclick = true; + case SDL_BUTTON_RIGHT: + break; + default: + break; + } + } + break; + case SDL_MOUSEBUTTONDOWN: + switch (e[i].button.button) { + case SDL_BUTTON_LEFT: + GLOBAL_PLATFORM->mousedown = GLOBAL_PLATFORM->mouse_pos; + break; + case SDL_BUTTON_RIGHT: + break; + default: + break; + } + break; + default: + WARN("Unhandled mouse event 0x%04x", (i32)e[i].type); + break; + } + } + } + + BENCHEXPR(profile_input_handling, { + while ((num_events = SDL_PeepEvents(e, 8, SDL_GETEVENT, SDL_KEYDOWN, SDL_KEYUP)) > 0) { + for (i32 i = 0; i < num_events; i++) { + switch (e[i].type) { + case SDL_KEYDOWN: { + Keybinding lookupkey = ((Keybinding){ + .keycode = e[i].key.keysym.sym, + .modifiers = e[i].key.keysym.mod + }); + Keybinding *kb = hashmap_Keybinding_lookup(GLOBAL_PLATFORM->bindings, &lookupkey); + if (kb != NULL && kb->action != NULL) kb->action(mem->data); + } + break; + case SDL_KEYUP: + break; + default: + WARN("Unhandled mouse event 0x%04x", (i32)e[i].type); + break; + } + } + } + }); + }); + + /* update */ + StateType next_state; + BENCHEXPR(profile_gameloop, {next_state = update_func((void*)(mem->data));} );//State_update(state, mem);}); + + if (next_state != STATE_null) { + if (next_state == STATE_quit) break; + + drawcall_reset(); + + engine_window_resize_pointers_reset(); + State_free(state, mem); + memory_clear(mem); + + GLOBAL_PLATFORM->bindings = NULL; + + state = next_state; + update_func = State_updateFunc(state); +#ifdef BENCHMARK + { + u32 t = SDL_GetTicks(); + State_init(state, mem); + LOG("Initializing %s took %dms", StateTypeStr[state], SDL_GetTicks() - t); + } +#else + State_init(state, mem); +#endif + } else { +#ifdef BENCHMARK + profile_num_drawcalls += drawcall_len; +#endif + render_begin(p->window); + BENCHEXPR(profile_rendering, {render_present(p->window);}) + } + + + ticks++; + } while (state != STATE_quit); + + return 0; +} + + +void stop(Platform *p) { + if (p == NULL) return; + + { /* Deallocate resources */ + struct Resources *r = (struct Resources*)p->data; + if (r != NULL) { + /* Destroy textures */ + for (usize i = 0; i < r->textures_len; i++) { + if (r->textures[i] != NULL) { + SDL_DestroyTexture(r->textures[i]->texture); + r->textures[i] = NULL; + } + } + free(r->textures); + + /* Destroy Fonts */ + for (usize i = 0; i < r->fonts_len; i++) { + if (r->fonts[i] != NULL) { + TTF_CloseFont(r->fonts[i]); + r->fonts[i] = NULL; + } + } + free(r->fonts); + } + } + + { /* Deallocate window */ + Window *w = p->window; + if (w != NULL) { + if (w->window != NULL) { SDL_DestroyWindow(w->window); w->window = NULL; } + if (w->renderer != NULL) { SDL_DestroyRenderer(w->renderer); w->renderer = NULL; } + } + } + + TTF_Quit(); + IMG_Quit(); + SDL_Quit(); +} + +void engine_fps_max(u64 cap) { FPS_CAP = cap; } + +void engine_bindkey(i32 key, void (*action)(void*)) { + /* TODO: Make a hashmap ++ linked list */ + //if (key >= 512) { + // ERROR("Key value too high! (got %d)\n", key); + //} + if (GLOBAL_PLATFORM->bindings == NULL) { + //GLOBAL_PLATFORM->bindings = memory_allocate(GLOBAL_PLATFORM->mem, 512 * sizeof(Keybinding)); + GLOBAL_PLATFORM->bindings = memory_allocate(GLOBAL_PLATFORM->mem, sizeof(hashmap_Keybinding)); + } + + Keybinding kb = (Keybinding){.keycode = key, .action = action}; + hashmap_Keybinding_insert(GLOBAL_PLATFORM->mem, GLOBAL_PLATFORM->bindings, &kb); +} + + +u32 get_time(void) {return SDL_GetTicks();} +v2_i32 get_windowsize(void) {return GLOBAL_PLATFORM->window->windowsize;} +v2_i32 *get_mousepos(void) { return &GLOBAL_PLATFORM->mouse_pos; } -- cgit v1.3