#include "SDL_error.h" #include "SDL_stdinc.h" #include "SDL_surface.h" #include "mgba/core/config.h" #include #define SDL_MAIN_USE_CALLBACKS 1 #include "SDL3/SDL_init.h" #include "SDL3/SDL_log.h" #include "SDL3/SDL_main.h" #include "SDL3/SDL_pixels.h" #include "SDL3/SDL_render.h" #include "SDL3/SDL_timer.h" #include "SDL3/SDL_video.h" #include "mgba/core/core.h" #include "mgba/core/interface.h" #include "mgba/feature/commandline.h" #include "mgba-util/image.h" static SDL_Window* window = NULL; static SDL_Renderer* renderer = NULL; static SDL_Camera* camera = NULL; static SDL_CameraSpec spec; static SDL_Texture* texture = NULL; static SDL_bool texture_updated = SDL_FALSE; static SDL_Surface* frame_current = NULL; static SDL_CameraDeviceID front_camera = 0; static SDL_CameraDeviceID back_camera = 0; static SDL_Surface* scaled = NULL; static SDL_Surface* screen = NULL; static struct mCore* core = NULL; 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 myStartRequestImage(struct mImageSource* self, unsigned w, unsigned h, int colorFormats) { SDL_DestroySurface(scaled); scaled = SDL_CreateSurface(w, h, SDL_PIXELFORMAT_XBGR1555); SDL_Log("scaled: %d x %d", w, h); // SDL_DestroyTexture(texture); // texture = SDL_CreateTextureFromSurface(renderer, scaled); } void myStopRequestImage(struct mImageSource* self) { SDL_DestroySurface(scaled); scaled = NULL; } void myRequestImage(struct mImageSource* self, const void** buf, size_t* stride, enum mColorFormat* fmt) { if (scaled != NULL && frame_current != NULL) { struct SDL_Rect srcrect = frame_current->clip_rect; int w = srcrect.h * scaled->clip_rect.w / scaled->clip_rect.h; 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; } int res = SDL_BlitSurfaceScaled(frame_current, &srcrect, scaled, &scaled->clip_rect, SDL_SCALEMODE_NEAREST); if (res != 0) { SDL_Log("failed to scale: %s", SDL_GetError()); } *fmt = pixfmt_sdl_to_mgba(scaled->format->format); *stride = scaled->pitch / (BYTES_PER_PIXEL / scaled->format->bytes_per_pixel); *buf = scaled->pixels; } } const struct mImageSource gb_img_src = { .requestImage = myRequestImage, .startRequestImage = myStartRequestImage, .stopRequestImage = myStopRequestImage, }; int SDL_AppInit(int argc, char *argv[]) { int devcount = 0; int i; 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; } 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, "sgb.borders", "0"); mCoreLoadConfig(core); unsigned w, h; core->currentVideoSize(core, &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, 0); if (!renderer) { SDL_Log("Couldn't create renderer: %s", SDL_GetError()); return -1; } SDL_LogSetAllPriority(SDL_LOG_PRIORITY_VERBOSE); SDL_CameraDeviceID *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); } const SDL_CameraDeviceID 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; } SDL_CameraSpec spec = { .format = SDL_PIXELFORMAT_XBGR1555, .width = 320, .height = 240, .interval_numerator = 1, .interval_denominator = 30, }; camera = SDL_OpenCameraDevice(devid, &spec); if (!camera) { SDL_Log("Failed to open camera device: %s", SDL_GetError()); return -1; } #if BYTES_PER_PIXEL == 4 screen = SDL_CreateSurface(w, h, SDL_PIXELFORMAT_XBGR8888); #elif BYTES_PER_PIXEL == 2 screen = SDL_CreateSurface(w, h, SDL_PIXELFORMAT_XBGR1555); #else #error "unknown pixel format" #endif core->setVideoBuffer(core, screen->pixels, screen->pitch / BYTES_PER_PIXEL); /* Create texture with appropriate format */ texture = SDL_CreateTextureFromSurface(renderer, screen); if (!texture) { SDL_Log("Couldn't create texture: %s", SDL_GetError()); return -1; } core->setPeripheral(core, mPERIPH_IMAGE_SOURCE, (void*)&gb_img_src); core->reset(core); core->setKeys(core, 0); for (i = 0; i < 600; i++) { core->runFrame(core); } core->setKeys(core, 1); // title screen core->runFrame(core); core->setKeys(core, 0); for (i = 0; i < 25; i++) { core->runFrame(core); } core->setKeys(core, 1); // select 'shoot' core->runFrame(core); core->setKeys(core, 0); for (i = 0; i < 75; i++) { core->runFrame(core); } core->setKeys(core, 1); // select 'shoot' again core->runFrame(core); core->setKeys(core, 0); for (i = 0; i < 100; i++) { core->runFrame(core); } 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!"); if (frame_current) { SDL_ReleaseCameraFrame(camera, frame_current); frame_current = NULL; } 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; if (sym == SDLK_ESCAPE || sym == SDLK_AC_BACK) { SDL_Log("Key : Escape!"); return 1; } else if (sym == SDLK_SPACE) { FlipCamera(); return 0; } 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) { SDL_SetRenderDrawColor(renderer, 0x99, 0x99, 0x99, 255); SDL_RenderClear(renderer); Uint64 timestampNS = 0; SDL_Surface *frame_next = camera ? SDL_AcquireCameraFrame(camera, ×tampNS) : NULL; if (frame_next) { if (frame_current) { if (SDL_ReleaseCameraFrame(camera, frame_current) < 0) { SDL_Log("err SDL_ReleaseCameraFrame: %s", SDL_GetError()); } } SDL_Log("new frame %d x %d %s", frame_next->w, frame_next->h, SDL_GetPixelFormatName(frame_next->format->format)); /* It's not needed to keep the frame once updated the texture is updated. * But in case of 0-copy, it's needed to have the frame while using the texture. */ frame_current = frame_next; texture_updated = SDL_FALSE; } core->runFrame(core); /* Update SDL_Texture with last video frame (only once per new frame) */ if (frame_current && !texture_updated) { // if (scaled) SDL_UpdateTexture(texture, NULL, scaled->pixels, scaled->pitch); SDL_UpdateTexture(texture, NULL, screen->pixels, screen->pitch); texture_updated = SDL_TRUE; } SDL_RenderTexture(renderer, texture, NULL, NULL); SDL_RenderPresent(renderer); return 0; /* keep iterating. */ } void SDL_AppQuit(void) { mCoreConfigDeinit(&core->config); core->deinit(core); SDL_ReleaseCameraFrame(camera, frame_current); SDL_CloseCamera(camera); SDL_DestroyTexture(texture); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); }