#include #include #include "prge.h" #include "crrn.h" #include "crrn.c" typedef struct { SDL_AudioSpec spec; SDL_AudioStream *stream; } SDLSound; typedef struct { SDLSound sdlsounds[MAX_SOUNDS_PLAYING]; SDL_AudioDeviceID audio_device; SDL_Window *window; SDL_GLContext glctx; } SDLContext; static SDLContext sdlctx; i32 init_sdl(prge_window_t *window) { if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO)) { printf("error: sdl init: %s\n", SDL_GetError()); return 0; } SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); if (window->flags & WINDOW_MULTISAMPLE) SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4); u32 flags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE; sdlctx.window = SDL_CreateWindow(window->name, window->width, window->height, flags); if (!sdlctx.window) { printf("error: sdl create window: %s\n", SDL_GetError()); return 0; } SDL_GetWindowSize(sdlctx.window, &window->width, &window->height); if (!(sdlctx.glctx = SDL_GL_CreateContext(sdlctx.window))) { printf("error: sdl create gl context: %s\n", SDL_GetError()); return 0; } if (!SDL_GL_MakeCurrent(sdlctx.window, sdlctx.glctx)) { printf("error: sdl make context current: %s\n", SDL_GetError()); return 0; } if (SDL_GL_SetSwapInterval(0)) printf("info: vsync disabled\n"); else printf("warning: failed to disable vsync\n"); u32 error = glewInit(); if (error != GLEW_OK) { printf("error: glew: %s\n", glewGetErrorString(error)); return 0; } if (window->flags & WINDOW_DEPTH_TEST) glEnable(GL_DEPTH_TEST); if (window->flags & WINDOW_MULTISAMPLE) glEnable(GL_MULTISAMPLE); sdlctx.audio_device = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, 0); if (!sdlctx.audio_device) { printf("error: sdl: couldn't open audio device\n"); return 0; } return 1; } void process_mouse_pos(input_t *input, SDL_MouseMotionEvent *event, prge_window_t window) { input->mouse.pos = (v2){(f32)event->x, window.height - (f32)event->y}; if (input->mouse.first) input->mouse.first = 0; v2 offset = {(f32)event->xrel, (f32)event->yrel}; input->mouse.offset = v2_add(input->mouse.offset, offset); } void process_key(i32 down, prge_key_t *key) { key->state = (down ? key_state_press : key_state_release); } void process_keyboard(input_t *input, SDL_KeyboardEvent *keyev) { switch (keyev->scancode) { case SDL_SCANCODE_D: process_key(keyev->down, &input->move_right); break; case SDL_SCANCODE_W: process_key(keyev->down, &input->move_forward); break; case SDL_SCANCODE_A: process_key(keyev->down, &input->move_left); break; case SDL_SCANCODE_S: process_key(keyev->down, &input->move_backward); break; case SDL_SCANCODE_E: process_key(keyev->down, &input->move_up); break; case SDL_SCANCODE_Q: process_key(keyev->down, &input->move_down); break; case SDL_SCANCODE_SPACE: process_key(keyev->down, &input->jump); break; case SDL_SCANCODE_RIGHT: process_key(keyev->down, &input->action_right); break; case SDL_SCANCODE_UP: process_key(keyev->down, &input->action_up); break; case SDL_SCANCODE_LEFT: process_key(keyev->down, &input->action_left); break; case SDL_SCANCODE_DOWN: process_key(keyev->down, &input->action_down); break; case SDL_SCANCODE_ESCAPE: process_key(keyev->down, &input->exit); break; default: break; } } void process_mouse_buttons(input_t *input, SDL_MouseButtonEvent *buttonev) { if (buttonev->button == SDL_BUTTON_LEFT) process_key(buttonev->down, &input->mouse.left); else if (buttonev->button == SDL_BUTTON_RIGHT) process_key(buttonev->down, &input->mouse.right); } void handle_events(prge_context_t *ctx) { SDL_Event event; while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_EVENT_KEY_DOWN: case SDL_EVENT_KEY_UP: process_keyboard(&ctx->input, &event.key); break; case SDL_EVENT_MOUSE_MOTION: process_mouse_pos(&ctx->input, &event.motion, ctx->window); break; case SDL_EVENT_MOUSE_BUTTON_DOWN: case SDL_EVENT_MOUSE_BUTTON_UP: process_mouse_buttons(&ctx->input, &event.button); break; case SDL_EVENT_WINDOW_RESIZED: ctx->window.width = event.window.data1; ctx->window.height = event.window.data2; break; case SDL_EVENT_QUIT: ctx->should_close = 1; break; default: break; } } } f32 get_elapsed_seconds(u64 current, u64 last) { f32 elapsed = ((f32)current-(f32)last)/1000.0f; return elapsed; } f32 lock_framerate(u64 last, f32 target) { f32 elapsed = get_elapsed_seconds(SDL_GetTicks(), last)*1000.0f; if (elapsed < target) { f32 sleep = target-elapsed; if (sleep > 0.0f) SDL_Delay(sleep); do { elapsed = get_elapsed_seconds(SDL_GetTicks(), last)*1000.0f; } while (elapsed < target); } f32 dt = get_elapsed_seconds(SDL_GetTicks(), last); return dt; } #include "stb_vorbis.c" i32 load_vorbis(arena_t *arena, sound_t *sounds, const char *filename) { sound_t sound; sound.name = filename; i32 i = find_sound(sounds, sound.name); if (i != -1) { printf("info: \"%s\" already loaded\n", sound.name); return i; } /* TODO(pryazha): Custom loader with arenas (or provide allocator to stb) */ i16 *output; i32 samples = stb_vorbis_decode_filename(filename, &sound.channels, &sound.freq, &output); if (samples == -1) { printf("error: \"%s\" failed to open\n", filename); return -1; } sound.bps = sizeof(i16); sound.size = sound.channels*samples*sound.bps; sound.data = push_arena(arena, sound.size); prb_memmove(sound.data, output, sound.size); free(output); i = load_sound(sounds, sound); if (i == -1) { pop_arena(arena, sound.size); return i; } printf("info: \"%s\" loaded successfully\n", filename); return i; } void play_sound(sound_t *sounds, sound_queue_t *queue, sound_queue_node_t *nodes, i32 id) { if ((id == -1) && (id >= MAX_SOUNDS_LOADED)) { printf("error: invalid sound id = %d\n", id); return; } if (!sounds[id].data) { printf("error: sound with id = %d doesn't exist\n", id); return; } sound_t *sound = sounds+id; if (!enqueue_sound(queue, nodes, sound)) { printf("error: failed to enqueue the sound \"%s\"\n", sound->name); return; } printf("info: queue content:\n"); for (sound_queue_node_t *node = queue->first; node; node = node->next) { printf("%s%s", node->sound->name, (node->next ? " -> " : "\n")); } } SDLSound *get_empty_sdl_sound() { for (i32 i = 0; i < MAX_SOUNDS_PLAYING; i++) { SDLSound *sdlsound = sdlctx.sdlsounds+i; if (!sdlsound->stream) return sdlsound; if (SDL_GetAudioStreamAvailable(sdlsound->stream) == 0) return sdlsound; } return 0; } void update_sdl_sounds(sound_queue_t *queue) { SDLSound *sdlsound; sound_t *sound; SDL_AudioSpec spec; while ((sdlsound = get_empty_sdl_sound()) && (sound = dequeue_sound(queue))) { switch (sound->bps) { case 2: spec.format = SDL_AUDIO_S16; break; default: printf("error: sound: \"%s\". Unsupported audio format\n", sound->name); return; } spec.channels = sound->channels; spec.freq = sound->freq; if (sdlsound->stream) { if (!prb_memeq(&sdlsound->spec, &spec, sizeof(SDL_AudioSpec))) { sdlsound->spec = spec; SDL_SetAudioStreamFormat(sdlsound->stream, &sdlsound->spec, 0); } } else { sdlsound->spec = spec; sdlsound->stream = SDL_CreateAudioStream(&sdlsound->spec, 0); if (!sdlsound->stream) { printf("error: sailed to create audio stream\n"); return; } if (!SDL_BindAudioStream(sdlctx.audio_device, sdlsound->stream)) { SDL_DestroyAudioStream(sdlsound->stream); printf("error: failed to bind audio stream to device\n"); return; } } printf("info: playing: \"%s\"\n", sound->name); SDL_PutAudioStreamData(sdlsound->stream, sound->data, sound->size); } } i32 main(void) { // engine init prge_context_t prgectx = init_prge(); u32 flags = WINDOW_DEPTH_TEST | WINDOW_MULTISAMPLE; prgectx.window = (prge_window_t){800, 600, "prge example", flags}; f32 fps = 60.0f; f32 mspf = 1000.0f/fps; // sdl and opengl init if (!init_sdl(&prgectx.window)) return 1; // game init game_context_t gctx = {0}; init(&prgectx, &gctx); u64 last = SDL_GetTicks(); while (!prgectx.should_close) { SDL_SetWindowRelativeMouseMode(sdlctx.window, prgectx.input.mouse.capture); handle_events(&prgectx); prgectx.input.dt = lock_framerate(last, mspf); last = SDL_GetTicks(); pop_arena(&prgectx.frame_arena, prgectx.frame_arena.used); glViewport(0, 0, prgectx.window.width, prgectx.window.height); update_and_render(&prgectx, &gctx); update_sdl_sounds(&prgectx.sound_queue); update_input(&prgectx.input); SDL_GL_SwapWindow(sdlctx.window); } return 0; }