cgbwebcam/main.c

495 lines
16 KiB
C

#define SDL_MAIN_USE_CALLBACKS 1
#include "SDL3/SDL_blendmode.h"
#include "SDL3/SDL_error.h"
#include "SDL3/SDL_init.h"
#include "SDL3/SDL_keycode.h"
#include "SDL3/SDL_log.h"
#include "SDL3/SDL_main.h"
#include "SDL3/SDL_pixels.h"
#include "SDL3/SDL_render.h"
#include "SDL3/SDL_stdinc.h"
#include "SDL3/SDL_surface.h"
#include "SDL3/SDL_timer.h"
#include "SDL3/SDL_video.h"
#include "mgba/core/config.h"
#include "mgba/core/core.h"
#include "mgba/core/interface.h"
#include "mgba/feature/commandline.h"
#include "mgba-util/image.h"
#define NUM_CHANNELS 3
#define FRAMESKIP_LIMIT 20
#if BYTES_PER_PIXEL == 4
#define SCREEN_FMT SDL_PIXELFORMAT_XBGR8888
#elif BYTES_PER_PIXEL == 2
#define SCREEN_FMT SDL_PIXELFORMAT_XBGR1555
#else
#error "unknown pixel format"
#endif
static SDL_Window* window = NULL;
static SDL_Renderer* renderer = NULL;
static SDL_Camera* camera = NULL;
static SDL_CameraSpec spec;
static SDL_Texture* textures[NUM_CHANNELS] = { NULL, NULL, NULL };
static SDL_bool texture_updated = SDL_FALSE;
static SDL_CameraDeviceID front_camera = 0;
static SDL_CameraDeviceID back_camera = 0;
static SDL_Surface* scaled = NULL;
static SDL_Surface* filtered[NUM_CHANNELS] = { NULL, NULL, NULL };
static SDL_Surface* screens[NUM_CHANNELS] = { NULL, NULL, NULL };
static struct mCore* cores[NUM_CHANNELS] = { NULL, NULL, NULL };
static unsigned keys = 0;
enum mColorFormat pixfmt_sdl_to_mgba(uint32_t sdl_fmt) {
switch (sdl_fmt) {
case SDL_PIXELFORMAT_ABGR1555: return mCOLOR_BGR5;
case SDL_PIXELFORMAT_ABGR8888: return mCOLOR_ABGR8;
case SDL_PIXELFORMAT_ARGB1555: return mCOLOR_RGB5;
case SDL_PIXELFORMAT_ARGB8888: return mCOLOR_ARGB8;
case SDL_PIXELFORMAT_BGR565: return mCOLOR_BGR565;
case SDL_PIXELFORMAT_BGRA8888: return mCOLOR_BGRA8;
case SDL_PIXELFORMAT_BGRX8888: return mCOLOR_BGRX8;
case SDL_PIXELFORMAT_RGB565: return mCOLOR_RGB565;
case SDL_PIXELFORMAT_RGBA8888: return mCOLOR_RGBA8;
case SDL_PIXELFORMAT_RGBX8888: return mCOLOR_RGBX8;
case SDL_PIXELFORMAT_XBGR1555: return mCOLOR_BGR5;
case SDL_PIXELFORMAT_XBGR8888: return mCOLOR_XBGR8;
case SDL_PIXELFORMAT_XRGB1555: return mCOLOR_RGB5;
case SDL_PIXELFORMAT_XRGB8888: return mCOLOR_XRGB8;
/* does this even make sense? */
case SDL_PIXELFORMAT_INDEX8: return mCOLOR_PAL8;
/* others don't quite match between mgba and SDL */
default: return mCOLOR_ANY;
}
}
void myStartRequestImageRed(struct mImageSource* self, unsigned w, unsigned h, int colorFormats) {
SDL_DestroySurface(scaled);
SDL_DestroySurface(filtered[0]);
scaled = SDL_CreateSurface(w, h, SDL_PIXELFORMAT_XBGR1555);
filtered[0] = SDL_CreateSurface(w, h, SDL_PIXELFORMAT_XBGR1555);
SDL_Log("red: %d x %d", w, h);
}
void myStartRequestImageGreen(struct mImageSource* self, unsigned w, unsigned h, int colorFormats) {
SDL_DestroySurface(filtered[1]);
filtered[1] = SDL_CreateSurface(w, h, SDL_PIXELFORMAT_XBGR1555);
SDL_Log("green: %d x %d", w, h);
}
void myStartRequestImageBlue(struct mImageSource* self, unsigned w, unsigned h, int colorFormats) {
SDL_DestroySurface(filtered[2]);
filtered[2] = SDL_CreateSurface(w, h, SDL_PIXELFORMAT_XBGR1555);
SDL_Log("blue: %d x %d", w, h);
}
void myStopRequestImageRed(struct mImageSource* self) {
SDL_DestroySurface(scaled);
SDL_DestroySurface(filtered[0]);
scaled = NULL;
filtered[0] = NULL;
}
void myStopRequestImageGreen(struct mImageSource* self) {
SDL_DestroySurface(filtered[1]);
filtered[1] = NULL;
}
void myStopRequestImageBlue(struct mImageSource* self) {
SDL_DestroySurface(filtered[2]);
filtered[2] = NULL;
}
void myRequestImageRed(struct mImageSource* self, const void** buf, size_t* stride, enum mColorFormat* fmt) {
SDL_Surface* source = filtered[0];
if (source != NULL) {
*fmt = pixfmt_sdl_to_mgba(source->format->format);
*stride = source->pitch / (BYTES_PER_PIXEL / source->format->bytes_per_pixel);
*buf = source->pixels;
}
}
void myRequestImageGreen(struct mImageSource* self, const void** buf, size_t* stride, enum mColorFormat* fmt) {
SDL_Surface* source = filtered[1];
if (source != NULL) {
*fmt = pixfmt_sdl_to_mgba(source->format->format);
*stride = source->pitch / (BYTES_PER_PIXEL / source->format->bytes_per_pixel);
*buf = source->pixels;
}
}
void myRequestImageBlue(struct mImageSource* self, const void** buf, size_t* stride, enum mColorFormat* fmt) {
SDL_Surface* source = filtered[2];
if (source != NULL) {
*fmt = pixfmt_sdl_to_mgba(source->format->format);
*stride = source->pitch / (BYTES_PER_PIXEL / source->format->bytes_per_pixel);
*buf = source->pixels;
}
}
struct mImageSource gb_img_src[3];
int SDL_AppInit(int argc, char *argv[]) {
int devcount = 0;
int i;
unsigned w, h;
SDL_CameraDeviceID *devices;
SDL_CameraDeviceID devid;
struct mArguments args;
bool parsed = mArgumentsParse(&args, argc, argv, NULL, 0);
/* Enable standard application logging */
SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO);
if (!parsed) {
SDL_Log("Couldn't parse arguments");
return -1;
}
gb_img_src[0].requestImage = myRequestImageRed;
gb_img_src[0].startRequestImage = myStartRequestImageRed;
gb_img_src[0].stopRequestImage = myStopRequestImageRed;
gb_img_src[1].requestImage = myRequestImageGreen;
gb_img_src[1].startRequestImage = myStartRequestImageGreen;
gb_img_src[1].stopRequestImage = myStopRequestImageGreen;
gb_img_src[2].requestImage = myRequestImageBlue;
gb_img_src[2].startRequestImage = myStartRequestImageBlue;
gb_img_src[2].stopRequestImage = myStopRequestImageBlue;
for (i = 0; i < NUM_CHANNELS; i++) {
struct mCore* core = mCoreFind(args.fname);
if (!core || !core->init(core)) {
SDL_Log("Couldn't initialize mgba core!");
return -1;
}
if (!mCoreLoadFile(core, args.fname)) {
SDL_Log("Failed to load ROM");
return -1;
}
mCoreConfigInit(&core->config, NULL);
/*mCoreConfigLoad(&core->config);*/
mArgumentsApply(&args, NULL, 0, &core->config);
mCoreConfigSetDefaultValue(&core->config, "idleOptimization", "detect");
mCoreConfigSetDefaultValue(&core->config, "frameskip", "4");
mCoreConfigSetDefaultValue(&core->config, "sgb.borders", "0");
mCoreLoadConfig(core);
cores[i] = core;
}
/* without loss of generality */
cores[0]->currentVideoSize(cores[0], &w, &h);
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_CAMERA) != 0) {
SDL_Log("Couldn't initialize SDL3: %s", SDL_GetError());
return -1;
}
window = SDL_CreateWindow("cgbwebcam", w, h, 0);
if (!window) {
SDL_Log("Couldn't create window: %s", SDL_GetError());
return -1;
}
renderer = SDL_CreateRenderer(window, NULL, SDL_RENDERER_PRESENTVSYNC);
if (!renderer) {
SDL_Log("Couldn't create renderer: %s", SDL_GetError());
return -1;
}
SDL_LogSetAllPriority(SDL_LOG_PRIORITY_VERBOSE);
devices = SDL_GetCameraDevices(&devcount);
if (!devices) {
SDL_Log("SDL_GetCameraDevices failed: %s", SDL_GetError());
return -1;
}
SDL_Log("Saw %d camera devices.", devcount);
for (i = 0; i < devcount; i++) {
const SDL_CameraDeviceID device = devices[i];
char *name = SDL_GetCameraDeviceName(device);
const SDL_CameraPosition position = SDL_GetCameraDevicePosition(device);
const char *posstr = "";
if (position == SDL_CAMERA_POSITION_FRONT_FACING) {
front_camera = device;
posstr = "[front-facing] ";
} else if (position == SDL_CAMERA_POSITION_BACK_FACING) {
back_camera = device;
posstr = "[back-facing] ";
}
SDL_Log(" - Camera #%d: %s %s", i, posstr, name);
SDL_free(name);
}
devid = front_camera ? front_camera : devices[0]; /* no front-facing? just take the first one. */
SDL_free(devices);
if (!devid) {
SDL_Log("No cameras available?");
return -1;
}
spec.format = SDL_PIXELFORMAT_XBGR1555;
spec.width = 320;
spec.height = 240;
spec.interval_numerator = 1;
spec.interval_denominator = 30;
camera = SDL_OpenCameraDevice(devid, &spec);
if (!camera) {
SDL_Log("Failed to open camera device: %s", SDL_GetError());
return -1;
}
for (i = 0; i < NUM_CHANNELS; i++) {
int j;
struct mCore* core = cores[i];
SDL_Texture* texture;
SDL_Surface* screen = SDL_CreateSurface(w, h, SCREEN_FMT);
core->setVideoBuffer(core, screen->pixels, screen->pitch / BYTES_PER_PIXEL);
screens[i] = screen;
/* Create texture with appropriate format */
texture = SDL_CreateTextureFromSurface(renderer, screens[0]);
if (!texture) {
SDL_Log("Couldn't create texture: %s", SDL_GetError());
return -1;
}
SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_ADD);
textures[i] = texture;
core->setPeripheral(core, mPERIPH_IMAGE_SOURCE, &gb_img_src[i]);
core->reset(core);
core->setKeys(core, 0);
for (j = 0; j < 600; j++) {
core->runFrame(core);
}
core->setKeys(core, 1); /* title screen */
core->runFrame(core);
core->setKeys(core, 0);
for (j = 0; j < 25; j++) {
core->runFrame(core);
}
core->setKeys(core, 1); /* select 'shoot' */
core->runFrame(core);
core->setKeys(core, 0);
for (j = 0; j < 75; j++) {
core->runFrame(core);
}
core->setKeys(core, 1); /* select 'shoot' again */
core->runFrame(core);
core->setKeys(core, 0);
for (j = 0; j < 100; j++) {
core->runFrame(core);
}
SDL_Log("prepared core %d", i);
}
SDL_SetTextureColorMod(textures[0], 255, 0, 0);
SDL_SetTextureColorMod(textures[1], 0, 255, 0);
SDL_SetTextureColorMod(textures[2], 0, 0, 255);
return 0; /* start the main app loop. */
}
static int FlipCamera(void) {
static Uint64 last_flip = 0;
if ((SDL_GetTicks() - last_flip) < 3000) { /* must wait at least 3 seconds between flips. */
return 0;
}
if (camera) {
const SDL_CameraDeviceID current = SDL_GetCameraInstanceID(camera);
SDL_CameraDeviceID nextcam = 0;
if (current == front_camera) {
nextcam = back_camera;
} else if (current == back_camera) {
nextcam = front_camera;
}
if (nextcam) {
SDL_Log("Flip camera!");
SDL_CloseCamera(camera);
camera = SDL_OpenCameraDevice(nextcam, NULL);
if (!camera) {
SDL_Log("Failed to open camera device: %s", SDL_GetError());
return -1;
}
last_flip = SDL_GetTicks();
}
}
return 0;
}
int SDL_AppEvent(const SDL_Event *event) {
switch (event->type) {
case SDL_EVENT_KEY_DOWN: {
const SDL_Keycode sym = event->key.keysym.sym;
switch (sym) {
case SDLK_ESCAPE: case SDLK_AC_BACK: return 1;
case SDLK_SPACE: FlipCamera(); break;
case SDLK_UP: keys |= 0x40; break;
case SDLK_DOWN: keys |= 0x80; break;
case SDLK_LEFT: keys |= 0x20; break;
case SDLK_RIGHT: keys |= 0x10; break;
case SDLK_a: case SDLK_z: keys |= 1; break;
case SDLK_b: case SDLK_x: keys |= 2; break;
case SDLK_RETURN: keys |= 8; break;
case SDLK_BACKSPACE: keys |= 4; break;
}
break;
}
case SDL_EVENT_KEY_UP: {
const SDL_Keycode sym = event->key.keysym.sym;
switch (sym) {
case SDLK_UP: keys &= ~0x40; break;
case SDLK_DOWN: keys &= ~0x80; break;
case SDLK_LEFT: keys &= ~0x20; break;
case SDLK_RIGHT: keys &= ~0x10; break;
case SDLK_a: case SDLK_z: keys &= ~1; break;
case SDLK_b: case SDLK_x: keys &= ~2; break;
case SDLK_RETURN: keys &= ~8; break;
case SDLK_BACKSPACE: keys &= ~4; break;
}
break;
}
case SDL_EVENT_MOUSE_BUTTON_DOWN:
/* !!! FIXME: only flip if clicked in the area of a "flip" icon. */
return FlipCamera();
case SDL_EVENT_QUIT:
SDL_Log("Quit!");
return 1;
case SDL_EVENT_CAMERA_DEVICE_APPROVED:
SDL_Log("Camera approved!");
if (SDL_GetCameraFormat(camera, &spec) < 0) {
SDL_Log("Couldn't get camera spec: %s", SDL_GetError());
return -1;
}
break;
case SDL_EVENT_CAMERA_DEVICE_DENIED:
SDL_Log("Camera denied!");
return -1;
}
return 0;
}
int SDL_AppIterate(void) {
int i;
static int since_last_present = 0;
if (scaled != NULL) {
Uint64 timestampNS = 0;
SDL_Surface *frame = camera ? SDL_AcquireCameraFrame(camera, &timestampNS) : NULL;
if (frame) {
struct SDL_Rect srcrect = frame->clip_rect;
int w = srcrect.h * scaled->clip_rect.w / scaled->clip_rect.h;
/*SDL_Log("new frame %d x %d %s", frame->w, frame->h, SDL_GetPixelFormatName(frame->format->format));*/
texture_updated = SDL_FALSE;
if (w >= srcrect.w) {
srcrect.x = (srcrect.w - w) / 2;
srcrect.w = w;
} else {
int h = srcrect.w * scaled->clip_rect.h / scaled->clip_rect.w;
srcrect.y = (srcrect.h - h) / 2;
srcrect.h = h;
}
if (SDL_BlitSurfaceScaled(frame, &srcrect, scaled, &scaled->clip_rect, SDL_SCALEMODE_NEAREST) < 0) {
SDL_Log("failed to scale: %s", SDL_GetError());
}
if (SDL_ReleaseCameraFrame(camera, frame) < 0) {
SDL_Log("err SDL_ReleaseCameraFrame: %s", SDL_GetError());
}
SDL_SetSurfaceColorMod(scaled, 255, 0, 0);
SDL_BlitSurface(scaled, NULL, filtered[0], NULL);
SDL_SetSurfaceColorMod(scaled, 0, 255, 0);
SDL_BlitSurface(scaled, NULL, filtered[1], NULL);
SDL_SetSurfaceColorMod(scaled, 0, 0, 255);
SDL_BlitSurface(scaled, NULL, filtered[2], NULL);
}
}
if (since_last_present < FRAMESKIP_LIMIT) {
for (i = 0; i < NUM_CHANNELS; i++) {
struct mCore* core = cores[i];
core->setKeys(core, keys);
core->runFrame(core);
}
/*hack*/
if (keys && since_last_present < FRAMESKIP_LIMIT - 8) {
since_last_present = FRAMESKIP_LIMIT - 8;
}
}
if (!texture_updated) {
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer);
/* Update SDL_Texture with last video frame (only once per new frame) */
for (i = 0; i < NUM_CHANNELS; i++) {
SDL_Surface* screen = screens[i];
SDL_Texture* texture = textures[i];
SDL_UpdateTexture(texture, NULL, screen->pixels, screen->pitch);
texture_updated = SDL_TRUE;
SDL_RenderTexture(renderer, texture, NULL, NULL);
}
SDL_RenderPresent(renderer);
since_last_present = 0;
} else {
since_last_present++;
if (since_last_present >= FRAMESKIP_LIMIT) {
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer);
for (i = 0; i < NUM_CHANNELS; i++) {
SDL_Texture *texture = textures[i];
SDL_RenderTexture(renderer, texture, NULL, NULL);
}
SDL_RenderPresent(renderer);
}
}
return 0; /* keep iterating. */
}
void SDL_AppQuit(void) {
int i;
for (i = 0; i < NUM_CHANNELS; i++) {
mCoreConfigDeinit(&cores[i]->config);
cores[i]->deinit(cores[i]);
SDL_DestroyTexture(textures[i]);
}
SDL_CloseCamera(camera);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
}