commit fa030c4bc46326bf9a696678819356e6da62a523 Author: lif <> Date: Sat Mar 2 22:43:07 2024 -0800 initial grey version diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..954d547 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +_build +.cache +compile_commands.json +gbcamera.gb diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..b9d7ea7 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "SDL"] + path = SDL + url = https://github.com/libsdl-org/SDL/ +[submodule "mgba"] + path = mgba + url = https://github.com/mgba-emu/mgba/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..71f6e19 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,35 @@ +cmake_minimum_required(VERSION 3.5) +project(cgbwebcam) + +set(SDL_STATIC ON) +set(SDL_SHARED OFF) + +set(SDL_AUDIO OFF) +set(SDL_CAMERA ON) +set(SDL_VIDEO ON) +add_subdirectory(SDL) + +set(BUILD_GL OFF) +set(BUILD_GLES2 OFF) +set(BUILD_GLES3 OFF) +set(BUILD_LIBRETRO OFF) +set(BUILD_PYTHON OFF) +set(BUILD_QT OFF) +set(BUILD_SDL OFF) +set(BUILD_SHARED OFF) +set(BUILD_STATIC ON) +set(DISABLE_DEPS ON) +set(ENABLE_SCRIPTING OFF) +set(M_CORE_GB ON) +set(M_CORE_GBA OFF) +set(USE_DEBUGGERS OFF) +set(USE_DISCORD_RPC OFF) +set(USE_FFMPEG OFF) +set(USE_LUA OFF) +add_subdirectory(mgba) + +include_directories(SDL/include) +include_directories(mgba/include) + +add_executable(cgbwebcam main.c) +target_link_libraries(cgbwebcam SDL3-static mgba) diff --git a/SDL b/SDL new file mode 160000 index 0000000..7ff9be7 --- /dev/null +++ b/SDL @@ -0,0 +1 @@ +Subproject commit 7ff9be739827a53763b393897a371674d45b053d diff --git a/main.c b/main.c new file mode 100644 index 0000000..8fd1d67 --- /dev/null +++ b/main.c @@ -0,0 +1,369 @@ +#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); +} diff --git a/mgba b/mgba new file mode 160000 index 0000000..3571b11 --- /dev/null +++ b/mgba @@ -0,0 +1 @@ +Subproject commit 3571b112dc821d3612e5073afb8b6be41a35dc55