view src/system_sdl.c @ 38:3b7910575a00

Implemented basic keyboard support
author Michael Pavone <pavone@retrodev.com>
date Mon, 04 Apr 2016 09:13:14 -0700
parents c677507682e3
children 8e39a877c651
line wrap: on
line source

#include <SDL.h>
#include <stdint.h>
#include <stdlib.h>
#include "controller.h"


static SDL_Window   *window;
static SDL_Renderer *renderer;
static SDL_Texture  *texture;
static int sample_rate;
static int buffer_size;
static uint8_t quitting;

static SDL_mutex * audio_mutex;
static SDL_cond * source_ready;
static SDL_cond * output_ready;
static int16_t *source_buffer;

static void audio_callback(void * userdata, uint8_t *stream, int len)
{
	uint8_t local_quit;
	int16_t *local_source;
	SDL_LockMutex(audio_mutex);
		local_source = NULL;
		do {
			if (!local_source) {
				local_source = source_buffer;
				source_buffer = NULL;
				SDL_CondSignal(output_ready);
			}
			if (!quitting && !local_source) {
				SDL_CondWait(source_ready, audio_mutex);
			}
		} while (!quitting && !local_source);
		local_quit = quitting;
	SDL_UnlockMutex(audio_mutex);
	if (!local_quit) {
		fflush(stdout);
		memcpy(stream, local_source, len);
	}
}

static void close_audio()
{
	SDL_LockMutex(audio_mutex);
		quitting = 1;
		SDL_CondSignal(source_ready);
	SDL_UnlockMutex(audio_mutex);
	SDL_CloseAudio();
}

void system_present_audio(int16_t *buffer)
{
	SDL_LockMutex(audio_mutex);
		while (source_buffer) {
			SDL_CondWait(output_ready, audio_mutex);
		}
		source_buffer = buffer;
		SDL_CondSignal(source_ready);
	SDL_UnlockMutex(audio_mutex);
}

int system_sample_rate()
{
	return sample_rate;
}

int system_buffer_size()
{
	return buffer_size;
}

static int num_controllers;
static SDL_GameController **game_controllers;

int system_init(int width, int height, int desired_sample_rate)
{
	if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_GAMECONTROLLER) < 0) {
		fprintf(stderr, "Failed to init SDL: %s\n", SDL_GetError());
		return 0;
	}
	atexit(SDL_Quit);
	window = SDL_CreateWindow("simple16", 0, 0, width, height, 0);
	if (!window) {
		fprintf(stderr, "Failed to create window: %s\n", SDL_GetError());
		return 0;
	}
	
	renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
	if (!renderer) {
		fprintf(stderr, "Failed to create renderer: %s\n", SDL_GetError());
		goto renderer_error;
	}
	
	texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGB444, SDL_TEXTUREACCESS_STREAMING, 320, 240);
	if (!texture) {
		fprintf(stderr, "Failed to create texture: %s\n", SDL_GetError());
		goto error;
	}
	
	audio_mutex = SDL_CreateMutex();
	source_ready = SDL_CreateCond();
	output_ready = SDL_CreateCond();
	
	SDL_AudioSpec desired, actual;
	desired.freq = desired_sample_rate;
	desired.format = AUDIO_S16SYS;
	desired.channels = 1;
	desired.callback = audio_callback;
	desired.userdata = NULL;
	desired.samples = 512;
	
	if (SDL_OpenAudio(&desired, &actual) < 0) {
		fprintf(stderr, "Failed to open audio: %s\n", SDL_GetError());
		goto error;
	}
	printf("Initialized audio at frequency %d with a %d sample buffer and %d channels\n", actual.freq, actual.samples, actual.channels);
	sample_rate = actual.freq;
	buffer_size = actual.samples;
	atexit(close_audio);
	SDL_PauseAudio(0);
	
	num_controllers = SDL_NumJoysticks();
	game_controllers = calloc(num_controllers, sizeof(*game_controllers));
	
	for (int i = 0; i < num_controllers; i++)
	{
		if (!SDL_IsGameController(i)) {
			printf("Joystick %d: %s is not recognized as a controller by SDL2\n", i, SDL_JoystickNameForIndex(i));
			continue;
		}
		game_controllers[i] = SDL_GameControllerOpen(i);
	}
	
	return 1;
	
error:
	SDL_DestroyRenderer(renderer);
renderer_error:
	SDL_DestroyWindow(window);
	return 0;
}

uint16_t *system_get_framebuffer(int *pitch)
{
	void *pixels;
	if (SDL_LockTexture(texture, NULL, &pixels, pitch) < 0) {
		fprintf(stderr, "Failed to lock texture: %s\n", SDL_GetError());
		return NULL;
	}
	return pixels;
}

void system_framebuffer_updated()
{
	SDL_UnlockTexture(texture);
	SDL_RenderCopy(renderer, texture, NULL, NULL);
	SDL_RenderPresent(renderer);
}

//SDL2 provides this, but only in 2.0.4+
SDL_GameController *controller_by_id(SDL_JoystickID which)
{
	for (int i = 0; i < num_controllers; i++)
	{
		if (!game_controllers[i]) {
			continue;
		}
		if (SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(game_controllers[i])) == which) {
			return game_controllers[i];
		}
	}
	return NULL;
}

static const uint16_t mapping[SDL_CONTROLLER_BUTTON_MAX] = {
	[SDL_CONTROLLER_BUTTON_LEFTSHOULDER]  = 0x001,
	[SDL_CONTROLLER_BUTTON_Y]             = 0x002,
	[SDL_CONTROLLER_BUTTON_X]             = 0x004,
	[SDL_CONTROLLER_BUTTON_RIGHTSHOULDER] = 0x008,
	[SDL_CONTROLLER_BUTTON_B]             = 0x010,
	[SDL_CONTROLLER_BUTTON_A]             = 0x020,
	[SDL_CONTROLLER_BUTTON_BACK]          = 0x040,
	[SDL_CONTROLLER_BUTTON_START]         = 0x080,
	[SDL_CONTROLLER_BUTTON_DPAD_RIGHT]    = 0x100,
	[SDL_CONTROLLER_BUTTON_DPAD_LEFT]     = 0x200,
	[SDL_CONTROLLER_BUTTON_DPAD_DOWN]     = 0x400,
	[SDL_CONTROLLER_BUTTON_DPAD_UP]       = 0x800,
};

static uint16_t keycode_to_bit(SDL_Keycode keycode)
{
	switch(keycode)
	{
	case SDLK_a:
		return 0x020;
	case SDLK_s:
		return 0x010;
	case SDLK_d:
		return 0x008;
	case SDLK_q:
		return 0x004;
	case SDLK_w:
		return 0x002;
	case SDLK_e:
		return 0x001;
	case SDLK_f:
		return 0x040;
	case SDLK_RETURN:
		return 0x080;
	case SDLK_UP:
		return 0x800;
	case SDLK_DOWN:
		return 0x400;
	case SDLK_LEFT:
		return 0x200;
	case SDLK_RIGHT:
		return 0x100;
	}
	return 0;
}

void system_poll_events()
{
	SDL_Event event;
	while(SDL_PollEvent(&event))
	{
		switch (event.type)
		{
		case SDL_QUIT:
			exit(0);
			break;
		case SDL_CONTROLLERBUTTONDOWN:
			controller_pressed(event.cbutton.which, mapping[event.cbutton.button]);
			break;
		case SDL_CONTROLLERBUTTONUP:
			controller_released(event.cbutton.which, mapping[event.cbutton.button]);
			break;
		case SDL_KEYDOWN:
			controller_pressed(0, keycode_to_bit(event.key.keysym.sym));
			break;
		case SDL_KEYUP:
			controller_released(0, keycode_to_bit(event.key.keysym.sym));
			break;
		//TODO: Controller hotplug
		}
	}
}