diff --git a/CMakeLists.txt b/CMakeLists.txt index 71f6e19..e7aea20 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,8 @@ cmake_minimum_required(VERSION 3.5) project(cgbwebcam) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + set(SDL_STATIC ON) set(SDL_SHARED OFF) diff --git a/main.c b/main.c index 8fd1d67..d42fd19 100644 --- a/main.c +++ b/main.c @@ -1,3 +1,4 @@ +#include "SDL_blendmode.h" #include "SDL_error.h" #include "SDL_stdinc.h" #include "SDL_surface.h" @@ -17,19 +18,21 @@ #include "mgba/feature/commandline.h" #include "mgba-util/image.h" +#define NUM_CHANNELS 3 + 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_Texture* textures[NUM_CHANNELS] = { NULL, NULL, 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 SDL_Surface* filtered[NUM_CHANNELS] = { NULL, NULL, NULL }; +static SDL_Surface* screens[NUM_CHANNELS] = { NULL, NULL, NULL }; -static struct mCore* core = NULL; +static struct mCore* cores[NUM_CHANNELS] = { NULL, NULL, NULL }; enum mColorFormat pixfmt_sdl_to_mgba(uint32_t sdl_fmt) { switch (sdl_fmt) { @@ -54,47 +57,90 @@ enum mColorFormat pixfmt_sdl_to_mgba(uint32_t sdl_fmt) { } } -void myStartRequestImage(struct mImageSource* self, unsigned w, unsigned h, int colorFormats) { +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("scaled: %d x %d", w, h); + SDL_Log("red: %d x %d", w, h); // SDL_DestroyTexture(texture); // texture = SDL_CreateTextureFromSurface(renderer, scaled); } -void myStopRequestImage(struct mImageSource* self) { - SDL_DestroySurface(scaled); - scaled = NULL; +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 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; +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; } } -const struct mImageSource gb_img_src = { - .requestImage = myRequestImage, - .startRequestImage = myStartRequestImage, - .stopRequestImage = myStopRequestImage, +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; + } +} + +const struct mImageSource gb_img_src[3] = { + { + .requestImage = myRequestImageRed, + .startRequestImage = myStartRequestImageRed, + .stopRequestImage = myStopRequestImageRed, + }, + { + .requestImage = myRequestImageGreen, + .startRequestImage = myStartRequestImageGreen, + .stopRequestImage = myStopRequestImageGreen, + }, + { + .requestImage = myRequestImageBlue, + .startRequestImage = myStartRequestImageBlue, + .stopRequestImage = myStopRequestImageBlue, + }, }; int SDL_AppInit(int argc, char *argv[]) { @@ -112,28 +158,33 @@ int SDL_AppInit(int argc, char *argv[]) { return -1; } - core = mCoreFind(args.fname); - if (!core || !core->init(core)) { - SDL_Log("Couldn't initialize mgba core!"); - return -1; + 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, "sgb.borders", "0"); + + mCoreLoadConfig(core); + + cores[i] = core; } - 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); + // 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()); @@ -185,61 +236,76 @@ int SDL_AppInit(int argc, char *argv[]) { return -1; } - SDL_CameraSpec spec = { + SDL_CameraSpec init_spec = { .format = SDL_PIXELFORMAT_XBGR1555, .width = 320, .height = 240, .interval_numerator = 1, .interval_denominator = 30, }; - camera = SDL_OpenCameraDevice(devid, &spec); + camera = SDL_OpenCameraDevice(devid, &init_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); + uint32_t fmt = SDL_PIXELFORMAT_XBGR8888; #elif BYTES_PER_PIXEL == 2 - screen = SDL_CreateSurface(w, h, SDL_PIXELFORMAT_XBGR1555); + uint32_t fmt = SDL_PIXELFORMAT_XBGR1555; #else #error "unknown pixel format" #endif - core->setVideoBuffer(core, screen->pixels, screen->pitch / BYTES_PER_PIXEL); + for (i = 0; i < NUM_CHANNELS; i++) { + int j; + struct mCore* core = cores[i]; - /* Create texture with appropriate format */ - texture = SDL_CreateTextureFromSurface(renderer, screen); - if (!texture) { - SDL_Log("Couldn't create texture: %s", SDL_GetError()); - return -1; + SDL_Surface* screen = SDL_CreateSurface(w, h, fmt); + core->setVideoBuffer(core, screen->pixels, screen->pitch / BYTES_PER_PIXEL); + screens[i] = screen; + + /* Create texture with appropriate format */ + SDL_Texture* 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, (void*)&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); } - 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); - } + 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. */ } @@ -262,11 +328,6 @@ static int FlipCamera(void) { 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); @@ -321,49 +382,77 @@ int SDL_AppEvent(const SDL_Event *event) { } int SDL_AppIterate(void) { - SDL_SetRenderDrawColor(renderer, 0x99, 0x99, 0x99, 255); - SDL_RenderClear(renderer); + int i; - Uint64 timestampNS = 0; - SDL_Surface *frame_next = camera ? SDL_AcquireCameraFrame(camera, ×tampNS) : NULL; + if (scaled != NULL) { + Uint64 timestampNS = 0; + SDL_Surface *frame = camera ? SDL_AcquireCameraFrame(camera, ×tampNS) : NULL; - if (frame_next) { - if (frame_current) { - if (SDL_ReleaseCameraFrame(camera, frame_current) < 0) { + if (frame) { + //SDL_Log("new frame %d x %d %s", frame->w, frame->h, SDL_GetPixelFormatName(frame->format->format)); + texture_updated = SDL_FALSE; + + struct SDL_Rect srcrect = frame->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, &srcrect, scaled, &scaled->clip_rect, SDL_SCALEMODE_NEAREST); + if (res != 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); + } + } + + for (i = 0; i < NUM_CHANNELS; i++) { + struct mCore* core = cores[i]; + core->runFrame(core); + } + + 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_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; + SDL_RenderPresent(renderer); } - 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); + int i; + for (i = 0; i < NUM_CHANNELS; i++) { + mCoreConfigDeinit(&cores[i]->config); + cores[i]->deinit(cores[i]); + SDL_DestroyTexture(textures[i]); + } - SDL_ReleaseCameraFrame(camera, frame_current); SDL_CloseCamera(camera); - SDL_DestroyTexture(texture); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); }