#define SDL_MAIN_USE_CALLBACKS 1 #include "SDL3/SDL_blendmode.h" #include "SDL3/SDL_camera.h" #include "SDL3/SDL_error.h" #include "SDL3/SDL_events.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 #include #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_Texture* textures[NUM_CHANNELS] = { NULL, NULL, NULL }; static bool texture_updated = false; static SDL_CameraID front_camera = 0; static SDL_CameraID 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; struct AppState { SDL_Camera* camera; SDL_Window* window; SDL_Renderer* renderer; int cam_idx, spec_idx; }; 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; /* 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); *stride = source->pitch / (BYTES_PER_PIXEL / SDL_BYTESPERPIXEL(source->format)); *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); *stride = source->pitch / (BYTES_PER_PIXEL / SDL_BYTESPERPIXEL(source->format)); *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); *stride = source->pitch / (BYTES_PER_PIXEL / SDL_BYTESPERPIXEL(source->format)); *buf = source->pixels; } } struct mImageSource gb_img_src[3]; bool SelectCamera(struct AppState* st) { int devcount = 0; SDL_CameraID *devices = SDL_GetCameras(&devcount); SDL_Renderer* renderer = st->renderer; if (st->cam_idx >= devcount) { st->cam_idx = 0; } else if (st->cam_idx < 0) { st->cam_idx = devcount - 1; } if (!devices) { SDL_Log("SDL_GetCameras failed: %s", SDL_GetError()); return false; } if (devcount == 0) { SDL_free(devices); SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0); SDL_RenderClear(renderer); SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); SDL_RenderDebugText(renderer, 4, 4, "connect a camera"); SDL_RenderPresent(renderer); } const SDL_CameraID device = devices[st->cam_idx]; const char *name = SDL_GetCameraName(device); const SDL_CameraPosition position = SDL_GetCameraPosition(device); const char *posstr = ""; char snprintfbuf[20]; int formats_len = 0; SDL_CameraSpec** formats = SDL_GetCameraSupportedFormats(device, &formats_len); if (st->spec_idx >= formats_len) { st->spec_idx = 0; } else if (st->spec_idx < 0) { st->spec_idx = formats_len - 1; } SDL_CameraSpec* format = formats[st->spec_idx]; const char* pixfmt_name = SDL_GetPixelFormatName(format->format); if (strncmp("SDL_PIXELFORMAT_", pixfmt_name, 16) == 0) { pixfmt_name += 16; } if (position == SDL_CAMERA_POSITION_FRONT_FACING) { front_camera = device; posstr = "[frontfacing]"; } else if (position == SDL_CAMERA_POSITION_BACK_FACING) { back_camera = device; posstr = "[backfacing]"; } SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0); SDL_RenderClear(renderer); SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); SDL_RenderDebugText(renderer, 4, 4, "camera: (up/down)"); snprintf(snprintfbuf, sizeof(snprintfbuf), "%d. %s", st->cam_idx, posstr); SDL_RenderDebugText(renderer, 8, 16, snprintfbuf); SDL_RenderDebugText(renderer, 4, 28, name); SDL_free((void*)name); SDL_RenderDebugText(renderer, 4, 52, "mode: (left/right)"); snprintf( snprintfbuf, sizeof(snprintfbuf), "%d.", st->spec_idx ); SDL_RenderDebugText(renderer, 8, 64, snprintfbuf); snprintf( snprintfbuf, sizeof(snprintfbuf), "%dx%d", format->width, format->height ); SDL_RenderDebugText(renderer, 4, 76, snprintfbuf); snprintf( snprintfbuf, sizeof(snprintfbuf), "%.1f Hz", ((float)format->framerate_numerator / (float)format->framerate_denominator) ); SDL_RenderDebugText(renderer, 4, 88, snprintfbuf); SDL_RenderDebugText(renderer, 4, 100, pixfmt_name); SDL_RenderDebugText(renderer, 4, 124, "confirm: (enter)"); SDL_free(formats); SDL_free(devices); SDL_RenderPresent(renderer); return true; } SDL_AppResult SDL_AppInit(void** appstate, int argc, char *argv[]) { int i; unsigned w, h; struct mArguments args; bool parsed = mArgumentsParse(&args, argc, argv, NULL, 0); struct AppState* st = malloc(sizeof(struct AppState)); memset(st, 0, sizeof(struct AppState)); *appstate = st; /* Enable standard application logging */ SDL_SetLogPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO); if (!parsed) { SDL_Log("Couldn't parse arguments"); return SDL_APP_FAILURE; } 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 SDL_APP_FAILURE; } if (!mCoreLoadFile(core, args.fname)) { SDL_Log("Failed to load ROM"); return SDL_APP_FAILURE; } 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]->desiredVideoDimensions(cores[0], &w, &h); if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_CAMERA)) { SDL_Log("Couldn't initialize SDL3: %s", SDL_GetError()); return SDL_APP_FAILURE; } st->window = SDL_CreateWindow("cgbwebcam", w, h, SDL_WINDOW_RESIZABLE); if (!st->window) { SDL_Log("Couldn't create window: %s", SDL_GetError()); return SDL_APP_FAILURE; } st->renderer = SDL_CreateRenderer(st->window, NULL); if (!st->renderer) { SDL_Log("Couldn't create renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } SDL_SetLogPriorities(SDL_LOG_PRIORITY_VERBOSE); 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(st->renderer, screens[0]); if (!texture) { SDL_Log("Couldn't create texture: %s", SDL_GetError()); return SDL_APP_FAILURE; } 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 SDL_APP_CONTINUE; /* start the main app loop. */ } static SDL_AppResult FlipCamera(struct AppState* st) { static Uint64 last_flip = 0; if ((SDL_GetTicks() - last_flip) < 3000) { /* must wait at least 3 seconds between flips. */ return SDL_APP_CONTINUE; } if (st->camera) { const SDL_CameraID current = SDL_GetCameraID(st->camera); SDL_CameraID 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(st->camera); st->camera = SDL_OpenCamera(nextcam, NULL); if (!st->camera) { SDL_Log("Failed to open camera device: %s", SDL_GetError()); return SDL_APP_FAILURE; } last_flip = SDL_GetTicks(); } } return SDL_APP_CONTINUE; } SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) { struct AppState* st = (struct AppState*)appstate; if (st->camera == NULL) { switch (event->type) { case SDL_EVENT_QUIT: SDL_Log("Quit!"); return false; case SDL_EVENT_KEY_DOWN: switch (event->key.key) { case SDLK_UP: st->cam_idx -= 1; break; case SDLK_DOWN: st->cam_idx += 1; break; case SDLK_LEFT: st->spec_idx -= 1; break; case SDLK_RIGHT: st->spec_idx += 1; break; case SDLK_RETURN: { SDL_CameraID *devices = SDL_GetCameras(NULL); SDL_CameraID device = devices[st->cam_idx]; SDL_CameraSpec** formats = SDL_GetCameraSupportedFormats(device, NULL); st->camera = SDL_OpenCamera(device, formats[st->spec_idx]); SDL_free(devices); SDL_free(formats); break; } default: break; } break; default: break; } } switch (event->type) { case SDL_EVENT_KEY_DOWN: { const SDL_Keycode sym = event->key.key; switch (sym) { case SDLK_ESCAPE: case SDLK_AC_BACK: return SDL_APP_SUCCESS; case SDLK_SPACE: FlipCamera(st); 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.key; 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(st); case SDL_EVENT_QUIT: SDL_Log("Quit!"); return SDL_APP_SUCCESS; case SDL_EVENT_CAMERA_DEVICE_APPROVED: { SDL_CameraSpec spec; SDL_Log("Camera approved!"); if (!SDL_GetCameraFormat(st->camera, &spec)) { SDL_Log("Couldn't get camera spec: %s", SDL_GetError()); return SDL_APP_FAILURE; } break; } case SDL_EVENT_CAMERA_DEVICE_DENIED: SDL_Log("Camera denied!"); return SDL_APP_FAILURE; /* case SDL_EVENT_WINDOW_RESIZED: { Sint32 winw = event->window.data1, winh = event->window.data2; SDL_UpdateWindowSurface(window); if (TODO: need to correct aspect ratio?) { if (!SDL_SetWindowSize(window, winw/2, winh)) { SDL_Log("Failed to resize window to %dx%d: %s", winw, winh, SDL_GetError()); return SDL_APP_FAILURE; } else { SDL_Log("Resized window to %dx%d", winw, winh); } } break; } */ default: break; } return SDL_APP_CONTINUE; } SDL_AppResult SDL_AppIterate(void* appstate) { struct AppState* st = (struct AppState*)appstate; if (st->camera == NULL) { if (!SelectCamera(st)) { SDL_Log("Failed to show camera device menu: %s", SDL_GetError()); return SDL_APP_FAILURE; } return SDL_APP_CONTINUE; } SDL_Renderer* renderer = st->renderer; int i; static int since_last_present = 0; if (scaled != NULL) { Uint64 timestampNS = 0; SDL_Surface *frame = st->camera ? SDL_AcquireCameraFrame(st->camera, ×tampNS) : NULL; if (frame) { struct SDL_Rect srcrect, dstrect; SDL_GetSurfaceClipRect(frame, &srcrect); SDL_GetSurfaceClipRect(scaled, &dstrect); int w = srcrect.h * dstrect.w / dstrect.h; /*SDL_Log("new frame %d x %d %s", frame->w, frame->h, SDL_GetPixelFormatName(frame->format->format));*/ texture_updated = false; if (w <= srcrect.w) { srcrect.x = (srcrect.w - w) / 2; srcrect.w = w; } else { int h = srcrect.w * dstrect.h / dstrect.w; srcrect.y = (srcrect.h - h) / 2; srcrect.h = h; } if (!SDL_BlitSurfaceScaled(frame, &srcrect, scaled, &dstrect, SDL_SCALEMODE_NEAREST)) { SDL_Log("failed to scale: %s", SDL_GetError()); } SDL_ReleaseCameraFrame(st->camera, frame); 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 = 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 SDL_APP_CONTINUE; /* keep iterating. */ } void SDL_AppQuit(void* appstate, SDL_AppResult result) { struct AppState* st = (struct AppState*)appstate; int i; for (i = 0; i < NUM_CHANNELS; i++) { mCoreConfigDeinit(&cores[i]->config); cores[i]->deinit(cores[i]); SDL_DestroyTexture(textures[i]); } SDL_CloseCamera(st->camera); SDL_DestroyRenderer(st->renderer); SDL_DestroyWindow(st->window); free(st); }