int32_t CALLBACK wWinMain(
    HINSTANCE window_instance,
    HINSTANCE,  // hPrevInst is useless
    LPWSTR,     // not using lpCmdLine
    int)        // not using nCmdShow
{
    LARGE_INTEGER perf_count_freq_result;
    QueryPerformanceFrequency(&perf_count_freq_result);
    int64_t perf_count_freq = perf_count_freq_result.QuadPart;
    
    win32_load_xinput();
    
    wchar_t *wnd_class_name = L"Handmade Hero Window Class";
    WNDCLASSEXW wnd_class = {};  // c++11 aggregate init to zero the struct
    wnd_class.cbSize = sizeof(wnd_class);
    wnd_class.style = CS_HREDRAW | CS_VREDRAW;
    wnd_class.lpfnWndProc = win32_wnd_proc;
    wnd_class.hInstance = window_instance;
    wnd_class.lpszClassName = wnd_class_name;

    ATOM wnd_class_atom = RegisterClassExW(&wnd_class);
    if (0 == wnd_class_atom)
    {
        win32_debug_print_last_error();
        HANDMADE_ASSERT(0 != wnd_class_atom);
        return 0;
    }

    HWND hwnd = CreateWindowExW(0, wnd_class_name, L"Handmade Hero",
                                WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                                CW_USEDEFAULT, CW_USEDEFAULT,
                                CW_USEDEFAULT, CW_USEDEFAULT,
                                nullptr, nullptr, window_instance, nullptr);
    if (hwnd == nullptr)
    {
        win32_debug_print_last_error();
        HANDMADE_ASSERT(nullptr != hwnd);
        return 0;
    }

    win32_resize_backbuffer(&g_backbuffer, 1280, 720);
    
    // test sound
    win32_sound_output sound_output {};
    sound_output.running_sample_index = 0;
    sound_output.num_sound_ch = 2;
    sound_output.samples_per_sec = 48000;
    sound_output.sec_to_buffer = 2;
    sound_output.latency_sample_count = sound_output.samples_per_sec / 15;
    sound_output.bytes_per_sample = sizeof(int16_t) * sound_output.num_sound_ch;
    sound_output.sound_buffer_size = sound_output.samples_per_sec *
            sound_output.bytes_per_sample * sound_output.sec_to_buffer;

    // create buffer for 2 sec
    win32_init_direct_sound(hwnd, sound_output.num_sound_ch,
                            sound_output.samples_per_sec,
                            sound_output.sound_buffer_size);

    int16_t *samples = nullptr;
    if (g_sound_buffer)
    {
        // fill the whole buffer and started playing sound.
        win32_clear_sound_buffer(g_sound_buffer);
        g_sound_buffer->Play(0, 0, DSBPLAY_LOOPING);

        // allocate sound buffer sample
        // Guarantee to be allocation granularity (64KB) aligned
        // commited to page boundary (4KB), but the rest are wasted space
        // memory auto clears to 0
        // freed automatically when app terminates
        samples = static_cast<int16_t*>(win32_alloc_zeroed(
            nullptr, sound_output.sound_buffer_size));
    }

    // input
    const real32 left_thumb_norm_deadzone =
            win32_get_xinput_stick_normalized_deadzone(
                XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE);
    const real32 right_thumb_norm_deadzone =
            win32_get_xinput_stick_normalized_deadzone(
                XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE);
    game_input input[2] = {};
    game_input *new_input = &input[0];
    game_input *old_input = &input[1];

    // game memory
#if HANDMADE_INTERNAL_BUILD
    void *base_memory_ptr = reinterpret_cast<void*>(terabyte(2ULL));
#else
    void *base_memory_ptr = nullptr;
#endif
    game_memory memory {};
    memory.permanent_storage_size = megabyte(64ULL);
    memory.transient_storage_size = gigabyte(1ULL);
    uint64_t total_size = memory.permanent_storage_size +
            memory.transient_storage_size;
    // Guarantee to be allocation granularity (64KB) aligned
    // commited to page boundary (4KB), but the rest are wasted space
    // memory auto clears to 0
    // freed automatically when app terminates
    memory.permanent_storage = win32_alloc_zeroed(base_memory_ptr, total_size);
    memory.transient_storage = static_cast<int8_t*>(memory.permanent_storage) +
            memory.permanent_storage_size;
    if (g_backbuffer.memory && samples && memory.permanent_storage &&
        memory.transient_storage)
    {
        g_running = true;
    
        uint64_t last_cycle_count = __rdtsc();
        LARGE_INTEGER last_perf_counter;
        QueryPerformanceCounter(&last_perf_counter);
    
        while (g_running)
        {
            // We don't really need old input for keyboard as all keyboard
            // events are processed by wm msgs. So, just copy the old state.
            game_controller_input *kbd_controller =
                    get_controller(new_input, game_input::kbd_controller_index);
            *kbd_controller =
                    *get_controller(old_input, game_input::kbd_controller_index);

            win32_process_wm_msg_synchonously(kbd_controller);

            if (!g_running)
            {
                break;
            }

            constexpr uint32_t max_controller_count = std::min(
                XUSER_MAX_COUNT, game_input::max_controller_count - 1);
            for (DWORD controller_index = 0;
                 controller_index < max_controller_count;
                 ++controller_index)
            {
                // controller index 0 is reserved for keyboard
                DWORD true_controller_index = controller_index + 1;
                XINPUT_STATE controller_state {};
                game_controller_input *new_controller =
                        get_controller(new_input, true_controller_index);
                // Simply get the state of the controller from XInput.
                if (ERROR_SUCCESS == XInputGetState(controller_index,
                                                    &controller_state))
                {
                    // Controller is connected
                    new_controller->is_connected = true;
                    const game_controller_input *old_controller =
                            get_controller(old_input, true_controller_index);
                    // dwPacketNumber indicates if there have been state changes
                    controller_state.dwPacketNumber;
                    const XINPUT_GAMEPAD *pad = &controller_state.Gamepad;

                    // these are what we care about
                    win32_process_xinput_digital_button(
                        &new_controller->move_up,
                        &old_controller->move_up,
                        pad, XINPUT_GAMEPAD_DPAD_UP);
                    win32_process_xinput_digital_button(
                        &new_controller->move_down,
                        &old_controller->move_down,
                        pad, XINPUT_GAMEPAD_DPAD_DOWN);
                    win32_process_xinput_digital_button(
                        &new_controller->move_left,
                        &old_controller->move_left,
                        pad, XINPUT_GAMEPAD_DPAD_LEFT);
                    win32_process_xinput_digital_button(
                        &new_controller->move_right,
                        &old_controller->move_right,
                        pad, XINPUT_GAMEPAD_DPAD_RIGHT);

                    win32_process_xinput_digital_button(
                        &new_controller->action_up,
                        &old_controller->action_up,
                        pad, XINPUT_GAMEPAD_Y);
                    win32_process_xinput_digital_button(
                        &new_controller->action_down,
                        &old_controller->action_down,
                        pad, XINPUT_GAMEPAD_A);
                    win32_process_xinput_digital_button(
                        &new_controller->action_left,
                        &old_controller->action_left,
                        pad, XINPUT_GAMEPAD_X);
                    win32_process_xinput_digital_button(
                        &new_controller->action_right,
                        &old_controller->action_right,
                        pad, XINPUT_GAMEPAD_B);

                    win32_process_xinput_digital_button(
                        &new_controller->left_shoulder,
                        &old_controller->left_shoulder,
                        pad, XINPUT_GAMEPAD_LEFT_SHOULDER);
                    win32_process_xinput_digital_button(
                        &new_controller->right_shoulder,
                        &old_controller->right_shoulder,
                        pad, XINPUT_GAMEPAD_RIGHT_SHOULDER);
                
                    // bool32 start = pad->wButtons & XINPUT_GAMEPAD_START;
                    // bool32 back = pad->wButtons & XINPUT_GAMEPAD_BACK;
                    win32_process_xinput_digital_button(
                        &new_controller->start,
                        &old_controller->start,
                        pad, XINPUT_GAMEPAD_START);
                    win32_process_xinput_digital_button(
                        &new_controller->back,
                        &old_controller->back,
                        pad, XINPUT_GAMEPAD_BACK);

                    // process stick
                    new_controller->is_analog = true;
                    new_controller->left_stick.avg_x =
                            win32_xinput_thumb_resolve_deadzone_normalize(
                                pad->sThumbLX,
                                left_thumb_norm_deadzone);
                    new_controller->left_stick.avg_y =
                            win32_xinput_thumb_resolve_deadzone_normalize(
                                pad->sThumbLY,
                                left_thumb_norm_deadzone);

                    new_controller->right_stick.avg_x =
                            win32_xinput_thumb_resolve_deadzone_normalize(
                                pad->sThumbRX,
                                right_thumb_norm_deadzone);
                    new_controller->right_stick.avg_y =
                            win32_xinput_thumb_resolve_deadzone_normalize(
                                pad->sThumbRY,
                                right_thumb_norm_deadzone);
                }
                else
                {
                    // Controller is not connected
                    new_controller->is_connected = false;
                }
            }

            // DirectSound output test
            uint32_t byte_to_lock = 0;
            uint32_t bytes_to_write = 0;
            if (g_sound_buffer)
            {
                DWORD play_cursor;
                DWORD write_cursor;
                if (SUCCEEDED(g_sound_buffer->GetCurrentPosition(&play_cursor,
                                                                 &write_cursor)))
                {
                    // fill the buffer till the play cursor + latency,
                    // start location is the last location we wrote to
                    byte_to_lock = (sound_output.running_sample_index *
                                    sound_output.bytes_per_sample)
                            % sound_output.sound_buffer_size;

                    // TODO: this may be dangerous! overwriting part of memory
                    // between play cursor and write cursor
                    DWORD target_to_cursor = (play_cursor +
                                              sound_output.latency_sample_count
                                              * sound_output.bytes_per_sample)
                            % sound_output.sound_buffer_size;
                    if (byte_to_lock > target_to_cursor)
                    {
                        bytes_to_write = sound_output.sound_buffer_size -
                                byte_to_lock + target_to_cursor;
                    }
                    else
                    {
                        bytes_to_write = target_to_cursor - byte_to_lock;
                    }
                }
            }

            game_sound_buffer game_sound_buffer {};
            if (bytes_to_write > 0)
            {
                game_sound_buffer.samples = samples; 
                game_sound_buffer.sample_count = bytes_to_write /
                        sound_output.bytes_per_sample;
                game_sound_buffer.samples_per_sec = sound_output.samples_per_sec;
            }
        
            game_offscreen_buffer buffer {};
            buffer.width = g_backbuffer.width;
            buffer.height = g_backbuffer.height;
            buffer.pitch = g_backbuffer.pitch;
            buffer.memory = g_backbuffer.memory;

            game_update_and_render(&memory, &buffer, &game_sound_buffer, new_input);

            if (bytes_to_write > 0)
            {
                win32_fill_sound_buffer(&sound_output, &game_sound_buffer,
                                        byte_to_lock, bytes_to_write);
            }
        
            HDC device_context = GetDC(hwnd);
            win32_window_dimension dimension = win32_get_window_dimension(hwnd);
            win32_display_offscreen_buffer(&g_backbuffer, device_context,
                                           dimension.width, dimension.height);
            ReleaseDC(hwnd, device_context);

            // swap game input
            game_input *tmp_input = new_input;
            new_input = old_input;
            old_input = tmp_input;

            uint64_t end_cycle_count = __rdtsc();
            LARGE_INTEGER end_perf_counter;
            QueryPerformanceCounter(&end_perf_counter);

            // use signed, as it may go backward
            int64_t cycles_elapsed = end_cycle_count - last_cycle_count;
            int64_t counter_elapsed = end_perf_counter.QuadPart -
                    last_perf_counter.QuadPart;
            real32 mega_cycles_per_frame = static_cast<real32>(cycles_elapsed) /
                    1000000.0f;
            real32 ms_per_frame = 1000.0f * static_cast<real32>(counter_elapsed)
                    / static_cast<real32>(perf_count_freq);
            real32 fps = static_cast<real32>(perf_count_freq) /
                    static_cast<real32>(counter_elapsed);

            // char buf[256];
            // sprintf_s(buf, sizeof(buf), "%.2f Mc/f, %.2f ms/f, %.2f fps\n",
            //           mega_cycles_per_frame, ms_per_frame, fps);
            // OutputDebugStringA(buf);

            last_perf_counter = end_perf_counter;
            last_cycle_count = end_cycle_count;
        }
    }
    else
    {
        // fail to allocate memory, no game.
        OutputDebugStringA("Fail to alloc memory to backbuffer, sound buffer, "
                           "or game memory.\n");
    }
    // MessageBoxW(nullptr, L"hello world", L"hello", MB_OK | MB_ICONINFORMATION);
    return 0;
}
Beispiel #2
0
void GONG_CORE_PREFIX(retro_run)(void)
{
   int i = 0;
   int port = 0;
   bool updated = false;
   retro_inputs inputs[MAX_PLAYERS] = {{0}};

   if (GONG_CORE_PREFIX(environ_cb)(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated)
      check_variables();

   GONG_CORE_PREFIX(input_poll_cb)();

   for (port = 0; port < MAX_PLAYERS; port++)
   {
      for (i = 0; i < 16; i++)
      {
         if (GONG_CORE_PREFIX(input_state_cb)(port, RETRO_DEVICE_JOYPAD, 0, i))
         {
            inputs[port].realinput |= 1 << i;
         }
      }

      inputs[port].analogYLeft = GONG_CORE_PREFIX(input_state_cb)(port, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_Y) / 5000.0f;
      inputs[port].analogYRight = GONG_CORE_PREFIX(input_state_cb)(port, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_Y) / 5000.0f;

      if (inputs[port].analogYLeft > 0)
         inputs[port].realinput |= (1 << RETRO_DEVICE_ID_JOYPAD_DOWN);
      else if (inputs[port].analogYRight > 0)
         inputs[port].realinput |= (1 << RETRO_DEVICE_ID_JOYPAD_DOWN);

      if (inputs[port].analogYLeft < 0)
         inputs[port].realinput |= (1 << RETRO_DEVICE_ID_JOYPAD_UP);
      else if (inputs[port].analogYRight < 0)
         inputs[port].realinput |= (1 << RETRO_DEVICE_ID_JOYPAD_UP);

      inputs[port].input = inputs[port].realinput & ~g_state->previnput[port];
      inputs[port].not_input = g_state->previnput[port] & ~inputs[port].realinput;

      if (is_key_up_or_down(inputs[port].input, inputs[port].not_input, RETRO_DEVICE_ID_JOYPAD_UP))
         process_joypad(&g_state->g_input[port].buttons[B_MOVE_UP], inputs[port].realinput & (1 << RETRO_DEVICE_ID_JOYPAD_UP));
      else if (is_key_up_or_down(inputs[port].input, inputs[port].not_input, RETRO_DEVICE_ID_JOYPAD_DOWN))
         process_joypad(&g_state->g_input[port].buttons[B_MOVE_DOWN], inputs[port].realinput & (1 << RETRO_DEVICE_ID_JOYPAD_DOWN));
      else if (is_key_up_or_down(inputs[port].input, inputs[port].not_input, RETRO_DEVICE_ID_JOYPAD_DOWN))
         process_joypad(&g_state->g_input[port].buttons[B_MOVE_DOWN], inputs[port].realinput & (1 << RETRO_DEVICE_ID_JOYPAD_DOWN));

      if (is_key_up_or_down(inputs[port].input, inputs[port].not_input, RETRO_DEVICE_ID_JOYPAD_A))
         process_joypad(&g_state->g_input[port].buttons[B_SPEED_UP], inputs[port].realinput & (1 << RETRO_DEVICE_ID_JOYPAD_A));
      else if (is_key_up_or_down(inputs[port].input, inputs[port].not_input, RETRO_DEVICE_ID_JOYPAD_B))
         process_joypad(&g_state->g_input[port].buttons[B_SPEED_UP], inputs[port].realinput & (1 << RETRO_DEVICE_ID_JOYPAD_B));
      else if (is_key_up_or_down(inputs[port].input, inputs[port].not_input, RETRO_DEVICE_ID_JOYPAD_X))
         process_joypad(&g_state->g_input[port].buttons[B_SPEED_UP], inputs[port].realinput & (1 << RETRO_DEVICE_ID_JOYPAD_X));
      else if (is_key_up_or_down(inputs[port].input, inputs[port].not_input, RETRO_DEVICE_ID_JOYPAD_Y))
         process_joypad(&g_state->g_input[port].buttons[B_SPEED_UP], inputs[port].realinput & (1 << RETRO_DEVICE_ID_JOYPAD_Y));

      g_state->previnput[port] = inputs[port].realinput;
   }

   game_update_and_render(g_state->g_input, &game_buffer);

   GONG_CORE_PREFIX(video_cb)(video_buf, WIDTH, HEIGHT, WIDTH * sizeof(uint32_t));
}
Beispiel #3
0
int main(int argc, char* argv[])
{
	SDL_Window* sdl_window;
	SDL_Renderer* sdl_renderer;
	SDL_Texture* sdl_screen_texture;
	SDL_AudioSpec sdl_audio_spec;
	SDL_GameController* sdl_controller;
	memory_block_t memory;
	memory_block_t sound_samples;
	input_state_t keyboard_input, controller_input, user_input;
	surface_t offscreen_buffer;
	sdl_audio_ring_buffer_t audio_ring_buffer;
	int32_t offscreen_buffer_byte_width;
	int32_t screen_width, screen_height;
	real32_t target_seconds_per_frame;
	uint64_t perf_counter_freq;
	uint64_t last_counter;
	real32_t elapsed_frame_seconds;

	screen_width = 640;
	screen_height = 480;

	/* initialize sdl and create window */
	if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC | SDL_INIT_AUDIO) < 0) {
		/* TODO(lars): logging */
		return (1);
	}
	sdl_window = SDL_CreateWindow("sdl2-start", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, screen_width, screen_height, SDL_WINDOW_SHOWN);
	if (!sdl_window) {
		/* TODO(lars): logging */
		return (1);
	}
	global_refresh_rate = sdl_get_refresh_rate(sdl_window);

	/* create sdl renderer */
	sdl_renderer = SDL_CreateRenderer(sdl_window, -1, SDL_RENDERER_PRESENTVSYNC);
	if (!sdl_renderer) {
		sdl_renderer = SDL_CreateRenderer(sdl_window, -1, 0);
		if (!sdl_renderer) {
			/* TODO(lars): logging */
			return (1);
		}
	}
	SDL_SetRenderDrawColor(sdl_renderer, 0, 0, 0, 255);

	/* create sdl texture */
	sdl_screen_texture = SDL_CreateTexture(sdl_renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, screen_width, screen_height);
	if (!sdl_screen_texture) {
		/* TODO(lars): logging */
		return (1);
	}

	/* allocated needed memory */
	memory.size = game_query_needed_memory_size();
	memory.base = calloc(memory.size, sizeof(uint8_t));
	if (!memory.base) {
		/* TODO(lars): logging */
		return (1);
	}

	/* setup input */
	memset(&keyboard_input, 0, sizeof(input_state_t));
	memset(&controller_input, 0, sizeof(input_state_t));
	sdl_controller = NULL;

	/* create offscreen_buffer */
	offscreen_buffer.width = screen_width;
	offscreen_buffer.height = screen_height;
	offscreen_buffer.stride = sizeof(uint32_t);
	offscreen_buffer.buf = (uint32_t*)calloc(offscreen_buffer.width * offscreen_buffer.height, offscreen_buffer.stride);
	if (!offscreen_buffer.buf) {
		/* TODO(lars): logging */
		return (1);
	}
	offscreen_buffer_byte_width = offscreen_buffer.width * offscreen_buffer.stride;

	/* set target framerate */
	target_seconds_per_frame = 1.0f / TARGET_FRAMES_PER_SECOND;
	perf_counter_freq = SDL_GetPerformanceFrequency();

	/* setup audio */
	sdl_audio_spec.freq = 48000; /* 48 kHz */
	sdl_audio_spec.format = AUDIO_S16LSB;
	sdl_audio_spec.channels = 2;
	sdl_audio_spec.samples = 512;
	sdl_audio_spec.callback = &sdl_audio_callback;
	sdl_audio_spec.userdata = &audio_ring_buffer;

	if (SDL_OpenAudio(&sdl_audio_spec, NULL) == 0 && sdl_audio_spec.format == AUDIO_S16LSB) {
		int32_t audio_ring_buffer_size;

		audio_ring_buffer_size = sdl_audio_spec.freq * sizeof(int16_t) * sdl_audio_spec.channels;
		audio_ring_buffer.play_index = 0;
		audio_ring_buffer.write_index = 0;
		audio_ring_buffer.size = audio_ring_buffer_size;
		audio_ring_buffer.data = calloc(audio_ring_buffer_size, sizeof(uint8_t));
		if (audio_ring_buffer.data) {
			sound_samples.size = 15 * (int32_t)(target_seconds_per_frame * (real32_t)sdl_audio_spec.freq) * sdl_audio_spec.channels * sizeof(int16_t);
			sound_samples.base = calloc(sound_samples.size, sizeof(uint8_t));
			if (sound_samples.base) {
				/* unpause the audio */
				SDL_PauseAudio(0);
			}
			else {
				/* TODO(lars): logging */
				free(audio_ring_buffer.data);
				SDL_CloseAudio();
			}
		}
		else {
			/* TODO(lars): logging */
			SDL_CloseAudio();
		}
	}
	else {
		/* TODO(lars): logging */
		SDL_CloseAudio();
	}

	elapsed_frame_seconds = target_seconds_per_frame;
	last_counter = SDL_GetPerformanceCounter();
	global_is_running = 1;
	while (global_is_running) {
		int32_t window_width;
		int32_t window_height;
		int32_t current_audio_buffer_size;
		int32_t audio_bytes_to_write;

		SDL_GetWindowSize(sdl_window, &window_width, &window_height);

		/* handle user input */
		sdl_process_keyboard_input(sdl_window, &keyboard_input);
		sdl_process_controller_input(sdl_controller, &controller_input);
		user_input = merge_input_state(keyboard_input, controller_input);

		/* game update and render */
		game_update_and_render(&memory, &user_input, &offscreen_buffer, elapsed_frame_seconds);

		/* get game sound samples and fill the audio ring buffer */
		audio_bytes_to_write = 0;
		SDL_LockAudioDevice(1);
		if (audio_ring_buffer.write_index >= audio_ring_buffer.play_index) {
			current_audio_buffer_size = (audio_ring_buffer.write_index - audio_ring_buffer.play_index);
		}
		else {
			current_audio_buffer_size = (audio_ring_buffer.size - audio_ring_buffer.play_index) + audio_ring_buffer.write_index;
		}
		SDL_UnlockAudioDevice(1);
		if (current_audio_buffer_size < sound_samples.size) {
			audio_bytes_to_write = sound_samples.size - current_audio_buffer_size;
		}
		game_get_sound_samples(&sound_samples,  audio_bytes_to_write / (sizeof(int16_t) * sdl_audio_spec.channels), sdl_audio_spec.freq, sdl_audio_spec.channels);
		sdl_fill_audio_ring_buffer(&audio_ring_buffer, &sound_samples, audio_bytes_to_write);

		/* upload the offscreen buffer to the SDL_Renderer */
		SDL_RenderClear(sdl_renderer);
		if (SDL_UpdateTexture(sdl_screen_texture, 0, offscreen_buffer.buf, offscreen_buffer_byte_width) != 0) {
			/* TODO(lars): logging */
			return (1);
		}
		SDL_RenderCopyEx(sdl_renderer, sdl_screen_texture, NULL, NULL, 0, NULL, SDL_FLIP_VERTICAL);

		/* enforce target game framerate by sleeping */
		elapsed_frame_seconds = sdl_get_seconds_elapsed(last_counter, perf_counter_freq);
		if (elapsed_frame_seconds < target_seconds_per_frame) {
			int32_t ms_to_sleep;

			ms_to_sleep = (int32_t)((target_seconds_per_frame - elapsed_frame_seconds) * 1000.0f) - 1;
			if (ms_to_sleep > 0) {
				SDL_Delay(ms_to_sleep);
			}
			while (sdl_get_seconds_elapsed(last_counter, perf_counter_freq) < target_seconds_per_frame) {};
		}
		elapsed_frame_seconds = sdl_get_seconds_elapsed(last_counter, perf_counter_freq);

		/* display the frame */
		SDL_RenderPresent(sdl_renderer);

		last_counter = SDL_GetPerformanceCounter();
	}

	SDL_CloseAudio();
	SDL_GameControllerClose(sdl_controller);
	SDL_DestroyWindow(sdl_window);
	SDL_Quit();

	free(sound_samples.base);
	free(audio_ring_buffer.data);
	free(offscreen_buffer.buf);
	free(memory.base);

	return (0);
}