From 0f42915e7ff31785a18b61ad1e4ede5ff99c0ee9 Mon Sep 17 00:00:00 2001 From: Bobby Smiles Date: Sat, 12 May 2018 00:57:44 +0200 Subject: [PATCH] Add support for GameBoy Camera All the reverse-engineering work comes from AntonioND [1]. A new video backend API has been added to grab video images. By default, a dummy backend is provided. However, an OpenCV based backend is also provided (if enabled at compile-time with OPENCV=1 in Makefile. Disabled for MSVC). Other implementation should be possible (GStreamer for instance ?) in the future. With the OpenCV backend, the video device selection can be done using the Core parameter: [Core] GbCameraVideoDevice= Where can be either an integer which represent the device number (0 for default) or a string which specify the video device path. Tested with 64DD Mario Talent Studio (Japan), a transfer pak plugged in the first controller with a Japanese GameBoy camera. Also since the core currently requires a cart ROM (even if should strictly be required) I used Perfect Dark (Japan) to allow using the Transfer Pak. This is a core/ui limitation not related to this PR. [1] https://github.com/AntonioND/gbcam-rev-engineer --- .travis.yml | 3 + .../VisualStudio2013/mupen64plus-core.vcxproj | 55 ++++-- .../mupen64plus-core.vcxproj.filters | 15 ++ projects/unix/Makefile | 21 +++ src/backends/api/video_backend.h | 44 +++++ src/backends/dummy_video_backend.c | 46 +++++ src/backends/dummy_video_backend.h | 29 +++ src/backends/opencv_video_backend.cpp | 168 +++++++++++++++++ src/backends/opencv_video_backend.h | 51 +++++ src/device/gb/gb_cart.c | 178 +++++++++++++++++- src/device/gb/gb_cart.h | 22 ++- src/device/gb/m64282fp.c | 177 ++++++++++++++--- src/device/gb/m64282fp.h | 26 ++- src/main/main.c | 43 ++++- src/main/savestates.c | 10 +- 15 files changed, 822 insertions(+), 66 deletions(-) create mode 100644 src/backends/api/video_backend.h create mode 100644 src/backends/dummy_video_backend.c create mode 100644 src/backends/dummy_video_backend.h create mode 100644 src/backends/opencv_video_backend.cpp create mode 100644 src/backends/opencv_video_backend.h diff --git a/.travis.yml b/.travis.yml index 8ae2e3435..ac0729a15 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,6 +18,7 @@ addons: - liblircclient-dev - binutils-dev - nasm + - libopencv-dev env: - OSD=0 - OSD=1 @@ -51,6 +52,7 @@ matrix: - mxe-i686-w64-mingw32.shared-freetype - mxe-i686-w64-mingw32.shared-libpng - mxe-i686-w64-mingw32.shared-zlib + - mxe-i686-w64-mingw32.shared-opencv - mxe-i686-w64-mingw32.shared-pkgconf - nasm script: @@ -72,6 +74,7 @@ matrix: - mxe-x86-64-w64-mingw32.shared-freetype - mxe-x86-64-w64-mingw32.shared-libpng - mxe-x86-64-w64-mingw32.shared-zlib + - mxe-x86-64-w64-mingw32.shared-opencv - mxe-i686-w64-mingw32.shared-pkgconf - nasm script: diff --git a/projects/VisualStudio2013/mupen64plus-core.vcxproj b/projects/VisualStudio2013/mupen64plus-core.vcxproj index 7458557e1..36894dc0a 100644 --- a/projects/VisualStudio2013/mupen64plus-core.vcxproj +++ b/projects/VisualStudio2013/mupen64plus-core.vcxproj @@ -45,6 +45,17 @@ + + true + true + true + true + true + true + true + true + + @@ -254,8 +265,20 @@ + + + true + true + true + true + true + true + true + true + + @@ -656,7 +679,7 @@ Disabled - ..\..\src;..\..\..\mupen64plus-win32-deps\SDL-1.2.15\include;..\..\..\mupen64plus-win32-deps\zlib-1.2.8\include;..\..\..\mupen64plus-win32-deps\libpng-1.6.18\include;..\..\..\mupen64plus-win32-deps\freetype-2.6\include;%(AdditionalIncludeDirectories) + ..\..\src;..\..\..\mupen64plus-win32-deps\SDL-1.2.15\include;..\..\..\mupen64plus-win32-deps\zlib-1.2.8\include;..\..\..\mupen64plus-win32-deps\libpng-1.6.18\include;..\..\..\mupen64plus-win32-deps\freetype-2.6\include;..\..\..\mupen64plus-win32-deps\opencv-3.0.0\include;%(AdditionalIncludeDirectories) WIN32;_DEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;DYNAREC;M64P_OSD;M64P_PARALLEL;%(PreprocessorDefinitions) true EnableFastChecks @@ -668,7 +691,7 @@ Default - shell32.lib;opengl32.lib;glu32.lib;..\..\..\mupen64plus-win32-deps\SDL-1.2.15\lib\x86\SDL.lib;..\..\..\mupen64plus-win32-deps\zlib-1.2.8\lib\x86\zlib.lib;..\..\..\mupen64plus-win32-deps\libpng-1.6.18\lib\x86\libpng16.lib;..\..\..\mupen64plus-win32-deps\freetype-2.6\lib\x86\freetype26.lib;%(AdditionalDependencies) + shell32.lib;opengl32.lib;glu32.lib;..\..\..\mupen64plus-win32-deps\SDL-1.2.15\lib\x86\SDL.lib;..\..\..\mupen64plus-win32-deps\zlib-1.2.8\lib\x86\zlib.lib;..\..\..\mupen64plus-win32-deps\libpng-1.6.18\lib\x86\libpng16.lib;..\..\..\mupen64plus-win32-deps\freetype-2.6\lib\x86\freetype26.lib;..\..\..\mupen64plus-win32-deps\opencv-3.0.0\x86\vc12\lib\opencv_world300d.lib;%(AdditionalDependencies) $(OutDir)mupen64plus.dll true Windows @@ -682,7 +705,7 @@ Disabled - ..\..\src;..\..\..\mupen64plus-win32-deps\SDL-1.2.15\include;..\..\..\mupen64plus-win32-deps\zlib-1.2.8\include;..\..\..\mupen64plus-win32-deps\libpng-1.6.18\include;..\..\..\mupen64plus-win32-deps\freetype-2.6\include;%(AdditionalIncludeDirectories) + ..\..\src;..\..\..\mupen64plus-win32-deps\SDL-1.2.15\include;..\..\..\mupen64plus-win32-deps\zlib-1.2.8\include;..\..\..\mupen64plus-win32-deps\libpng-1.6.18\include;..\..\..\mupen64plus-win32-deps\freetype-2.6\include;..\..\..\mupen64plus-win32-deps\opencv-3.0.0\include;%(AdditionalIncludeDirectories) WIN32;_DEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;DYNAREC;M64P_PARALLEL;M64P_OSD;__x86_64__;%(PreprocessorDefinitions) EnableFastChecks MultiThreadedDebugDLL @@ -693,7 +716,7 @@ Default - shell32.lib;opengl32.lib;glu32.lib;..\..\..\mupen64plus-win32-deps\SDL-1.2.15\lib\x64\SDL.lib;..\..\..\mupen64plus-win32-deps\zlib-1.2.8\lib\x64\zlib.lib;..\..\..\mupen64plus-win32-deps\libpng-1.6.18\lib\x64\libpng16.lib;..\..\..\mupen64plus-win32-deps\freetype-2.6\lib\x64\freetype26.lib;%(AdditionalDependencies) + shell32.lib;opengl32.lib;glu32.lib;..\..\..\mupen64plus-win32-deps\SDL-1.2.15\lib\x64\SDL.lib;..\..\..\mupen64plus-win32-deps\zlib-1.2.8\lib\x64\zlib.lib;..\..\..\mupen64plus-win32-deps\libpng-1.6.18\lib\x64\libpng16.lib;..\..\..\mupen64plus-win32-deps\freetype-2.6\lib\x64\freetype26.lib;..\..\..\mupen64plus-win32-deps\opencv-3.0.0\x64\vc12\lib\opencv_world300d.lib;%(AdditionalDependencies) $(OutDir)mupen64plus.dll true Windows @@ -707,7 +730,7 @@ Disabled - ..\..\src;..\..\..\mupen64plus-win32-deps\SDL-1.2.15\include;..\..\..\mupen64plus-win32-deps\zlib-1.2.8\include;..\..\..\mupen64plus-win32-deps\libpng-1.6.18\include;..\..\..\mupen64plus-win32-deps\freetype-2.6\include;%(AdditionalIncludeDirectories) + ..\..\src;..\..\..\mupen64plus-win32-deps\SDL-1.2.15\include;..\..\..\mupen64plus-win32-deps\zlib-1.2.8\include;..\..\..\mupen64plus-win32-deps\libpng-1.6.18\include;..\..\..\mupen64plus-win32-deps\freetype-2.6\include;..\..\..\mupen64plus-win32-deps\opencv-3.0.0\include;%(AdditionalIncludeDirectories) WIN32;_DEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;DYNAREC;M64P_OSD;M64P_PARALLEL;NEW_DYNAREC=1;%(PreprocessorDefinitions) true EnableFastChecks @@ -721,7 +744,7 @@ - shell32.lib;opengl32.lib;glu32.lib;..\..\..\mupen64plus-win32-deps\SDL-1.2.15\lib\x86\SDL.lib;..\..\..\mupen64plus-win32-deps\zlib-1.2.8\lib\x86\zlib.lib;..\..\..\mupen64plus-win32-deps\libpng-1.6.18\lib\x86\libpng16.lib;..\..\..\mupen64plus-win32-deps\freetype-2.6\lib\x86\freetype26.lib;%(AdditionalDependencies) + shell32.lib;opengl32.lib;glu32.lib;..\..\..\mupen64plus-win32-deps\SDL-1.2.15\lib\x86\SDL.lib;..\..\..\mupen64plus-win32-deps\zlib-1.2.8\lib\x86\zlib.lib;..\..\..\mupen64plus-win32-deps\libpng-1.6.18\lib\x86\libpng16.lib;..\..\..\mupen64plus-win32-deps\freetype-2.6\lib\x86\freetype26.lib;..\..\..\mupen64plus-win32-deps\opencv-3.0.0\x86\vc12\lib\opencv_world300d.lib;%(AdditionalDependencies) $(OutDir)mupen64plus.dll true Windows @@ -738,7 +761,7 @@ Disabled - ..\..\src;..\..\..\mupen64plus-win32-deps\SDL-1.2.15\include;..\..\..\mupen64plus-win32-deps\zlib-1.2.8\include;..\..\..\mupen64plus-win32-deps\libpng-1.6.18\include;..\..\..\mupen64plus-win32-deps\freetype-2.6\include;%(AdditionalIncludeDirectories) + ..\..\src;..\..\..\mupen64plus-win32-deps\SDL-1.2.15\include;..\..\..\mupen64plus-win32-deps\zlib-1.2.8\include;..\..\..\mupen64plus-win32-deps\libpng-1.6.18\include;..\..\..\mupen64plus-win32-deps\freetype-2.6\include;..\..\..\mupen64plus-win32-deps\opencv-3.0.0\include;%(AdditionalIncludeDirectories) WIN32;_DEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;DYNAREC;M64P_PARALLEL;M64P_OSD;NEW_DYNAREC=1;__x86_64__;%(PreprocessorDefinitions) EnableFastChecks MultiThreadedDebugDLL @@ -751,7 +774,7 @@ - shell32.lib;opengl32.lib;glu32.lib;..\..\..\mupen64plus-win32-deps\SDL-1.2.15\lib\x64\SDL.lib;..\..\..\mupen64plus-win32-deps\zlib-1.2.8\lib\x64\zlib.lib;..\..\..\mupen64plus-win32-deps\libpng-1.6.18\lib\x64\libpng16.lib;..\..\..\mupen64plus-win32-deps\freetype-2.6\lib\x64\freetype26.lib;%(AdditionalDependencies) + shell32.lib;opengl32.lib;glu32.lib;..\..\..\mupen64plus-win32-deps\SDL-1.2.15\lib\x64\SDL.lib;..\..\..\mupen64plus-win32-deps\zlib-1.2.8\lib\x64\zlib.lib;..\..\..\mupen64plus-win32-deps\libpng-1.6.18\lib\x64\libpng16.lib;..\..\..\mupen64plus-win32-deps\freetype-2.6\lib\x64\freetype26.lib;..\..\..\mupen64plus-win32-deps\opencv-3.0.0\x64\vc12\lib\opencv_world300d.lib;%(AdditionalDependencies) $(OutDir)mupen64plus.dll true Windows @@ -764,7 +787,7 @@ - ..\..\src;..\..\..\mupen64plus-win32-deps\SDL-1.2.15\include;..\..\..\mupen64plus-win32-deps\zlib-1.2.8\include;..\..\..\mupen64plus-win32-deps\libpng-1.6.18\include;..\..\..\mupen64plus-win32-deps\freetype-2.6\include;%(AdditionalIncludeDirectories) + ..\..\src;..\..\..\mupen64plus-win32-deps\SDL-1.2.15\include;..\..\..\mupen64plus-win32-deps\zlib-1.2.8\include;..\..\..\mupen64plus-win32-deps\libpng-1.6.18\include;..\..\..\mupen64plus-win32-deps\freetype-2.6\include;..\..\..\mupen64plus-win32-deps\opencv-3.0.0\include;%(AdditionalIncludeDirectories) WIN32;NDEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;DYNAREC;M64P_OSD;M64P_PARALLEL;%(PreprocessorDefinitions) MultiThreadedDLL @@ -774,7 +797,7 @@ Default - shell32.lib;opengl32.lib;glu32.lib;..\..\..\mupen64plus-win32-deps\SDL-1.2.15\lib\x86\SDL.lib;..\..\..\mupen64plus-win32-deps\zlib-1.2.8\lib\x86\zlib.lib;..\..\..\mupen64plus-win32-deps\libpng-1.6.18\lib\x86\libpng16.lib;..\..\..\mupen64plus-win32-deps\freetype-2.6\lib\x86\freetype26.lib;%(AdditionalDependencies) + shell32.lib;opengl32.lib;glu32.lib;..\..\..\mupen64plus-win32-deps\SDL-1.2.15\lib\x86\SDL.lib;..\..\..\mupen64plus-win32-deps\zlib-1.2.8\lib\x86\zlib.lib;..\..\..\mupen64plus-win32-deps\libpng-1.6.18\lib\x86\libpng16.lib;..\..\..\mupen64plus-win32-deps\freetype-2.6\lib\x86\freetype26.lib;..\..\..\mupen64plus-win32-deps\opencv-3.0.0\x86\vc12\lib\opencv_world300.lib;%(AdditionalDependencies) $(OutDir)mupen64plus.dll true Windows @@ -788,7 +811,7 @@ - ..\..\src;..\..\..\mupen64plus-win32-deps\SDL-1.2.15\include;..\..\..\mupen64plus-win32-deps\zlib-1.2.8\include;..\..\..\mupen64plus-win32-deps\libpng-1.6.18\include;..\..\..\mupen64plus-win32-deps\freetype-2.6\include;%(AdditionalIncludeDirectories) + ..\..\src;..\..\..\mupen64plus-win32-deps\SDL-1.2.15\include;..\..\..\mupen64plus-win32-deps\zlib-1.2.8\include;..\..\..\mupen64plus-win32-deps\libpng-1.6.18\include;..\..\..\mupen64plus-win32-deps\freetype-2.6\include;..\..\..\mupen64plus-win32-deps\opencv-3.0.0\include;%(AdditionalIncludeDirectories) WIN32;NDEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;DYNAREC;M64P_PARALLEL;M64P_OSD;__x86_64__;%(PreprocessorDefinitions) MultiThreadedDLL @@ -798,7 +821,7 @@ Default - shell32.lib;opengl32.lib;glu32.lib;..\..\..\mupen64plus-win32-deps\SDL-1.2.15\lib\x64\SDL.lib;..\..\..\mupen64plus-win32-deps\zlib-1.2.8\lib\x64\zlib.lib;..\..\..\mupen64plus-win32-deps\libpng-1.6.18\lib\x64\libpng16.lib;..\..\..\mupen64plus-win32-deps\freetype-2.6\lib\x64\freetype26.lib;%(AdditionalDependencies) + shell32.lib;opengl32.lib;glu32.lib;..\..\..\mupen64plus-win32-deps\SDL-1.2.15\lib\x64\SDL.lib;..\..\..\mupen64plus-win32-deps\zlib-1.2.8\lib\x64\zlib.lib;..\..\..\mupen64plus-win32-deps\libpng-1.6.18\lib\x64\libpng16.lib;..\..\..\mupen64plus-win32-deps\freetype-2.6\lib\x64\freetype26.lib;..\..\..\mupen64plus-win32-deps\opencv-3.0.0\x64\vc12\lib\opencv_world300.lib;%(AdditionalDependencies) $(OutDir)mupen64plus.dll true Windows @@ -811,7 +834,7 @@ - ..\..\src;..\..\..\mupen64plus-win32-deps\SDL-1.2.15\include;..\..\..\mupen64plus-win32-deps\zlib-1.2.8\include;..\..\..\mupen64plus-win32-deps\libpng-1.6.18\include;..\..\..\mupen64plus-win32-deps\freetype-2.6\include;%(AdditionalIncludeDirectories) + ..\..\src;..\..\..\mupen64plus-win32-deps\SDL-1.2.15\include;..\..\..\mupen64plus-win32-deps\zlib-1.2.8\include;..\..\..\mupen64plus-win32-deps\libpng-1.6.18\include;..\..\..\mupen64plus-win32-deps\freetype-2.6\include;..\..\..\mupen64plus-win32-deps\opencv-3.0.0\include;%(AdditionalIncludeDirectories) WIN32;NDEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;DYNAREC;M64P_OSD;M64P_PARALLEL;NEW_DYNAREC=1;%(PreprocessorDefinitions) MultiThreadedDLL @@ -821,7 +844,7 @@ Default - shell32.lib;opengl32.lib;glu32.lib;..\..\..\mupen64plus-win32-deps\SDL-1.2.15\lib\x86\SDL.lib;..\..\..\mupen64plus-win32-deps\zlib-1.2.8\lib\x86\zlib.lib;..\..\..\mupen64plus-win32-deps\libpng-1.6.18\lib\x86\libpng16.lib;..\..\..\mupen64plus-win32-deps\freetype-2.6\lib\x86\freetype26.lib;%(AdditionalDependencies) + shell32.lib;opengl32.lib;glu32.lib;..\..\..\mupen64plus-win32-deps\SDL-1.2.15\lib\x86\SDL.lib;..\..\..\mupen64plus-win32-deps\zlib-1.2.8\lib\x86\zlib.lib;..\..\..\mupen64plus-win32-deps\libpng-1.6.18\lib\x86\libpng16.lib;..\..\..\mupen64plus-win32-deps\freetype-2.6\lib\x86\freetype26.lib;..\..\..\mupen64plus-win32-deps\opencv-3.0.0\x86\vc12\lib\opencv_world300.lib;%(AdditionalDependencies) $(OutDir)mupen64plus.dll true Windows @@ -835,7 +858,7 @@ - ..\..\src;..\..\..\mupen64plus-win32-deps-deps\SDL-1.2.15\include;..\..\..\mupen64plus-win32-deps\zlib-1.2.8\include;..\..\..\mupen64plus-win32-deps\libpng-1.6.18\include;..\..\..\mupen64plus-win32-deps\freetype-2.6\include;%(AdditionalIncludeDirectories) + ..\..\src;..\..\..\mupen64plus-win32-deps-deps\SDL-1.2.15\include;..\..\..\mupen64plus-win32-deps\zlib-1.2.8\include;..\..\..\mupen64plus-win32-deps\libpng-1.6.18\include;..\..\..\mupen64plus-win32-deps\freetype-2.6\include;..\..\..\mupen64plus-win32-deps\opencv-3.0.0\include;%(AdditionalIncludeDirectories) WIN32;NDEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;DYNAREC;M64P_PARALLEL;M64P_OSD;NEW_DYNAREC=1;__x86_64__;%(PreprocessorDefinitions) MultiThreadedDLL @@ -845,7 +868,7 @@ Default - shell32.lib;opengl32.lib;glu32.lib;..\..\..\mupen64plus-win32-deps\SDL-1.2.15\lib\x64\SDL.lib;..\..\..\mupen64plus-win32-deps\zlib-1.2.8\lib\x64\zlib.lib;..\..\..\mupen64plus-win32-deps\libpng-1.6.18\lib\x64\libpng16.lib;..\..\..\mupen64plus-win32-deps\freetype-2.6\lib\x64\freetype26.lib;%(AdditionalDependencies) + shell32.lib;opengl32.lib;glu32.lib;..\..\..\mupen64plus-win32-deps\SDL-1.2.15\lib\x64\SDL.lib;..\..\..\mupen64plus-win32-deps\zlib-1.2.8\lib\x64\zlib.lib;..\..\..\mupen64plus-win32-deps\libpng-1.6.18\lib\x64\libpng16.lib;..\..\..\mupen64plus-win32-deps\freetype-2.6\lib\x64\freetype26.lib;..\..\..\mupen64plus-win32-deps\opencv-3.0.0\x64\vc12\lib\opencv_world300.lib;%(AdditionalDependencies) $(OutDir)mupen64plus.dll true Windows diff --git a/projects/VisualStudio2013/mupen64plus-core.vcxproj.filters b/projects/VisualStudio2013/mupen64plus-core.vcxproj.filters index 3ecc36c32..abb4d73df 100644 --- a/projects/VisualStudio2013/mupen64plus-core.vcxproj.filters +++ b/projects/VisualStudio2013/mupen64plus-core.vcxproj.filters @@ -192,6 +192,12 @@ backends + + backends + + + backends + device\dd @@ -485,6 +491,12 @@ backends + + backends + + + backends + device\dd @@ -620,6 +632,9 @@ backends\api + + backends\api + backends\plugins_compat diff --git a/projects/unix/Makefile b/projects/unix/Makefile index 6a5515dc3..b04e210b4 100755 --- a/projects/unix/Makefile +++ b/projects/unix/Makefile @@ -271,6 +271,19 @@ endif CFLAGS += $(LIBPNG_CFLAGS) LDLIBS += $(LIBPNG_LDLIBS) +ifeq ($(OPENCV), 1) + # OpenCV lib + ifeq ($(origin OPENCV_CFLAGS) $(origin OPENCV_LDLIBS), undefined undefined) + ifeq ($(shell $(PKG_CONFIG) --modversion opencv 2>/dev/null),) + $(error No OpenCV development libraries found!) + endif + OPENCV_CFLAGS += $(shell $(PKG_CONFIG) --cflags opencv) + OPENCV_LDLIBS += $(shell $(PKG_CONFIG) --libs opencv) + endif + CFLAGS += $(OPENCV_CFLAGS) + LDLIBS += $(OPENCV_LDLIBS) +endif + # test for presence of SDL ifeq ($(origin SDL_CFLAGS) $(origin SDL_LDLIBS), undefined undefined) SDL_CONFIG = $(CROSS_COMPILE)sdl2-config @@ -446,6 +459,7 @@ SOURCE = \ $(SRCDIR)/backends/plugins_compat/input_plugin_compat.c \ $(SRCDIR)/backends/clock_ctime_plus_delta.c \ $(SRCDIR)/backends/file_storage.c \ + $(SRCDIR)/backends/dummy_video_backend.c \ $(SRCDIR)/device/cart/cart.c \ $(SRCDIR)/device/cart/af_rtc.c \ $(SRCDIR)/device/cart/cart_rom.c \ @@ -613,6 +627,12 @@ ifeq ($(DEBUGGER), 1) endif endif +ifeq ($(OPENCV), 1) + SOURCE += $(SRCDIR)/backends/opencv_video_backend.cpp + CFLAGS += -DM64P_OPENCV +endif + + # generate a list of object files to build, make a temporary directory for them OBJECTS := $(patsubst $(SRCDIR)/%.c, $(OBJDIR)/%.o, $(filter %.c, $(SOURCE))) OBJECTS += $(patsubst $(SRCDIR)/%.c, $(OBJDIR)/%.o, $(SRCDIR)/asm_defines/asm_defines.c) @@ -647,6 +667,7 @@ targets: @echo " PIC=(1|0) == Force enable/disable of position independent code" @echo " OSD=(1|0) == Enable/disable build of OpenGL On-screen display" @echo " NEW_DYNAREC=1 == Replace dynamic recompiler with Ari64's experimental dynarec" + @echo " OPENCV=1 == Enable OpenCV support" @echo " POSTFIX=name == String added to the name of the the build (default: '')" @echo " Install Options:" @echo " PREFIX=path == install/uninstall prefix (default: /usr/local/)" diff --git a/src/backends/api/video_backend.h b/src/backends/api/video_backend.h new file mode 100644 index 000000000..2ed448743 --- /dev/null +++ b/src/backends/api/video_backend.h @@ -0,0 +1,44 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Mupen64plus - video_backend.h * + * Mupen64Plus homepage: https://mupen64plus.org/ * + * Copyright (C) 2017 Bobby Smiles * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef M64P_BACKENDS_API_VIDEO_BACKEND_H +#define M64P_BACKENDS_API_VIDEO_BACKEND_H + +#include "api/m64p_types.h" + +struct video_input_backend_interface +{ + /* Open a video stream with following properties (width, height) + * Returns M64ERR_SUCCESS on success. + */ + m64p_error (*open)(void* vin, unsigned int width, unsigned int height); + + /* Close a previsouly opened video stream. + */ + void (*close)(void* vin); + + /* Grab a BGR image on an open stream + * Returns M64ERR_SUCCESS on success. + */ + m64p_error (*grab_image)(void* vin, void* data); +}; + +#endif diff --git a/src/backends/dummy_video_backend.c b/src/backends/dummy_video_backend.c new file mode 100644 index 000000000..e31fccb10 --- /dev/null +++ b/src/backends/dummy_video_backend.c @@ -0,0 +1,46 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Mupen64plus - dummy_video_backend.c * + * Mupen64Plus homepage: https://mupen64plus.org/ * + * Copyright (C) 2018 Bobby Smiles * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "backends/dummy_video_backend.h" +#include "backends/api/video_backend.h" + +#include "api/m64p_types.h" + +static m64p_error dummy_video_open(void* vin, unsigned int width, unsigned int height) +{ + return M64ERR_SUCCESS; +} + +static void dummy_video_close(void* vin) +{ +} + +static m64p_error dummy_grab_image(void* vin, void* data) +{ + return M64ERR_UNSUPPORTED; +} + +const struct video_input_backend_interface g_idummy_video_input_backend = +{ + dummy_video_open, + dummy_video_close, + dummy_grab_image +}; diff --git a/src/backends/dummy_video_backend.h b/src/backends/dummy_video_backend.h new file mode 100644 index 000000000..2c9444ebc --- /dev/null +++ b/src/backends/dummy_video_backend.h @@ -0,0 +1,29 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Mupen64plus - dummy_video_backend.h * + * Mupen64Plus homepage: https://mupen64plus.org/ * + * Copyright (C) 2018 Bobby Smiles * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef M64P_BACKENDS_DUMMY_VIDEO_BACKEND_H +#define M64P_BACKENDS_DUMMY_VIDEO_BACKEND_H + +#include "api/video_backend.h" + +extern const struct video_input_backend_interface g_idummy_video_input_backend; + +#endif diff --git a/src/backends/opencv_video_backend.cpp b/src/backends/opencv_video_backend.cpp new file mode 100644 index 000000000..41b8b539f --- /dev/null +++ b/src/backends/opencv_video_backend.cpp @@ -0,0 +1,168 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Mupen64plus - opencv_video_backend.h * + * Mupen64Plus homepage: https://mupen64plus.org/ * + * Copyright (C) 2017 Bobby Smiles * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "opencv2/core/version.hpp" + +#if CV_MAJOR_VERSION >= 3 +/* this is for opencv >= 3.0 (new style headers + videoio/highgui split) */ +#include +#include +#include +#include +#elif CV_MAJOR_VERSION >= 2 +/* otherwise go the safe way and let opencv include all its headers */ +#include +#else +#error "Unsupported version of OpenCV" +#endif + +#include + +extern "C" +{ +#include "backends/opencv_video_backend.h" +#include "backends/api/video_backend.h" + +#define M64P_CORE_PROTOTYPES 1 +#include "api/callbacks.h" +#include "api/m64p_types.h" +#include "main/util.h" + +/* Implements video_input_backend. + * Needs device field to be set before calling. + */ +static m64p_error opencv_video_open(void* vin, unsigned int width, unsigned int height) +{ + try { + int dev_num; + struct opencv_video_backend* b = static_cast(vin); + + /* allocate memory for cv::VideoCapture */ + cv::VideoCapture* cap = static_cast(std::malloc(sizeof(cv::VideoCapture))); + if (cap == NULL) { + DebugMessage(M64MSG_ERROR, "Failed to allocated memory for video device %s", b->device); + return M64ERR_NO_MEMORY; + } + + /* placement new to call cv::VideoCapture constructor */ + new(cap)cv::VideoCapture(); + + + /* open device (we support both device number or path */ + if (string_to_int(b->device, &dev_num)) { + cap->open(dev_num); + } + else { + cap->open(b->device); + } + + if (!cap->isOpened()) { + DebugMessage(M64MSG_ERROR, "Failed to open video device %s", b->device); + std::free(cap); + return M64ERR_SYSTEM_FAIL; + } + + /* TODO: adapt capture resolution to the desired resolution */ + + DebugMessage(M64MSG_INFO, "Video successfully opened: %s", b->device); + + b->cap = cap; + b->width = width; + b->height = height; + + return M64ERR_SUCCESS; + } + /* C++ exception must not cross C-API boundaries */ + catch(...) { return M64ERR_INTERNAL; } +} + +static void opencv_video_close(void* vin) +{ + try { + struct opencv_video_backend* b = static_cast(vin); + + if (b->cap != NULL) { + /* explicit call to cv::VideoCapture destructor */ + static_cast(b->cap)->~VideoCapture(); + + /* free allocated memory */ + std::free(b->cap); + b->cap = NULL; + } + + if (b->device != NULL) { + std::free(b->device); + b->device = NULL; + } + + DebugMessage(M64MSG_INFO, "Video closed"); + } + /* C++ exception must not cross C-API boundaries */ + catch(...) { return; } +} + +static m64p_error opencv_grab_image(void* vin, void* data) +{ + try { + struct opencv_video_backend* b = static_cast(vin); + cv::VideoCapture* cap = static_cast(b->cap); + + /* read next frame */ + cv::Mat frame; + if (cap == NULL || !cap->read(frame)) { + DebugMessage(M64MSG_ERROR, "Failed to grab frame !"); + return M64ERR_SYSTEM_FAIL; + } + + /* resize image to desired resolution */ + cv::Mat output = cv::Mat(b->height, b->width, CV_8UC3, data); + cv::resize(frame, output, output.size(), 0, 0, cv::INTER_AREA); + + return M64ERR_SUCCESS; + } + /* C++ exception must not cross C-API boundaries */ + catch(...) { return M64ERR_INTERNAL; } +} + + +#if 0 +void cv_imshow(const char* name, unsigned int width, unsigned int height, int channels, void* data) +{ + try { + int type = (channels == 1) ? CV_8UC1 : CV_8UC3; + + cv::Mat frame = cv::Mat(height, width, type, data); + cv::imshow(name, frame); + cv::waitKey(1); + } + /* C++ exception must not cross C-API boundaries */ + catch(...) { return; } +} +#endif + +const struct video_input_backend_interface g_iopencv_video_input_backend = +{ + opencv_video_open, + opencv_video_close, + opencv_grab_image +}; + +} diff --git a/src/backends/opencv_video_backend.h b/src/backends/opencv_video_backend.h new file mode 100644 index 000000000..999391b3d --- /dev/null +++ b/src/backends/opencv_video_backend.h @@ -0,0 +1,51 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Mupen64plus - opencv_video_backend.h * + * Mupen64Plus homepage: https://mupen64plus.org/ * + * Copyright (C) 2017 Bobby Smiles * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef M64P_BACKENDS_OPENCV_VIDEO_BACKEND_H +#define M64P_BACKENDS_OPENCV_VIDEO_BACKEND_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "backends/api/video_backend.h" + +struct opencv_video_backend +{ + char* device; + unsigned int width; + unsigned int height; + + /* using void* to avoid leaking C++ stuff in this header */ + void* cap; +}; + +#if 0 +void cv_imshow(const char* name, unsigned int width, unsigned int height, int channels, void* data); +#endif + +extern const struct video_input_backend_interface g_iopencv_video_input_backend; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/device/gb/gb_cart.c b/src/device/gb/gb_cart.c index 4baec1c66..fe6210a29 100644 --- a/src/device/gb/gb_cart.c +++ b/src/device/gb/gb_cart.c @@ -29,6 +29,12 @@ #include "api/callbacks.h" #include "backends/api/rumble_backend.h" #include "backends/api/storage_backend.h" +#include "backends/api/video_backend.h" +#if 0 +#include "backends/opencv_video_backend.h" +#else +#define cv_imshow(...) +#endif #include #include @@ -593,6 +599,159 @@ static int write_gb_cart_mmm01(struct gb_cart* gb_cart, uint16_t address, const return 0; } + +/* TODO: extract each mbc into its own module, store in gb_cart a union of them */ +static uint8_t apply_dithering_matrix(const uint8_t* d, uint8_t value, unsigned int x, unsigned int y) +{ + d += ((y & 3) * 4 + (x & 3)) * 3; + + if (value < d[0]) return 0x00; /* black */ + else if (value < d[1]) return 0x40; /* dark gray */ + else if (value < d[2]) return 0x80; /* light gray */ + return 0xc0; /* white */ +} + +static void grab_pocket_cam_image(struct pocket_cam* cam) +{ + static const uint8_t pm[4][2] = { + { 0x00, 0x01 }, /* negative */ + { 0x01, 0x00 }, /* positive */ + { 0x01, 0x02 }, /* edge detect */ + { 0x01, 0x02 } /* edge detect */ + }; + + uint8_t regs[M64282FP_REGS_COUNT]; + uint8_t tiles[16][16][16]; + uint8_t bgr[M64282FP_SENSOR_H][M64282FP_SENSOR_W][3]; + uint8_t img[M64282FP_SENSOR_H][M64282FP_SENSOR_W]; + unsigned int x, y; + + /* setup sensor regs */ + unsigned int pm_mode = (cam->regs[0] >> 1) & 0x3; + regs[M64282FP_Z_O] = cam->regs[5]; + regs[M64282FP_N_VH_G] = cam->regs[1]; + regs[M64282FP_C_LO] = cam->regs[3]; + regs[M64282FP_C_HI] = cam->regs[2]; + regs[M64282FP_P] = pm[pm_mode][0]; + regs[M64282FP_M] = pm[pm_mode][1]; + regs[M64282FP_X] = 0x01; + regs[M64282FP_E_I_V] = cam->regs[4]; + +#if 0 + DebugMessage(M64MSG_INFO, "GB cam regs:\n" + "P=%02x M=%02x X=%02x N_VH_G=%02x C=%04x E_I_V=%02x Z_O=%02x\n" + "%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n" + "%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n" + "%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n" + "%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x", + regs[M64282FP_P], regs[M64282FP_M], regs[M64282FP_X], regs[M64282FP_N_VH_G], + regs[M64282FP_C_HI] << 8 | regs[M64282FP_C_LO], regs[M64282FP_E_I_V], regs[M64282FP_Z_O], + cam->regs[6], cam->regs[7], cam->regs[8], + cam->regs[9], cam->regs[10], cam->regs[11], + cam->regs[12], cam->regs[13], cam->regs[14], + cam->regs[15], cam->regs[16], cam->regs[17], + cam->regs[18], cam->regs[19], cam->regs[20], + cam->regs[21], cam->regs[22], cam->regs[23], + cam->regs[24], cam->regs[25], cam->regs[26], + cam->regs[27], cam->regs[28], cam->regs[29], + cam->regs[30], cam->regs[31], cam->regs[32], + cam->regs[33], cam->regs[34], cam->regs[35], + cam->regs[36], cam->regs[37], cam->regs[38], + cam->regs[39], cam->regs[40], cam->regs[41], + cam->regs[42], cam->regs[43], cam->regs[44], + cam->regs[45], cam->regs[46], cam->regs[47], + cam->regs[48], cam->regs[49], cam->regs[50], + cam->regs[51], cam->regs[52], cam->regs[53]); +#endif + + /* grab BGR image */ + if (cam->ivin->grab_image(cam->vin, bgr) != M64ERR_SUCCESS) { + memset(cam->ram, UINT8_C(0xff), sizeof(tiles)); + return; + } + + cv_imshow("bgr image", M64282FP_SENSOR_W, M64282FP_SENSOR_H, 3, bgr); + + /* convert to gray using (2R+5G+1B)/8 formula */ + const uint8_t* bgr_ptr = &bgr[0][0][0]; + uint8_t* gray_ptr = &img[0][0]; + for (y = 0; y < M64282FP_SENSOR_H; ++y) { + for (x = 0; x < M64282FP_SENSOR_W; ++x) { + gray_ptr[0] = (1*bgr_ptr[0] + 5*bgr_ptr[1] + 2*bgr_ptr[2]) / 8; + bgr_ptr += 3; + gray_ptr += 1; + } + } + + /* apply m64282fp processings */ + cv_imshow("gray image", M64282FP_SENSOR_W, M64282FP_SENSOR_H, 1, img); + process_m64282fp_image(img, regs); + cv_imshow("m64282fp", M64282FP_SENSOR_W, M64282FP_SENSOR_H, 1, img); + + /* convert to dithered GB tile format */ + memset(tiles, 0, sizeof(tiles)); + for (y = 0; y < M64282FP_SENSOR_H; ++y) { + for (x = 0; x < M64282FP_SENSOR_W; ++x) { + /* apply dithering matrix */ + uint8_t c = UINT8_C(0xc0) - apply_dithering_matrix(&cam->regs[6], img[y][x], x, y); + img[y][x] = c; + /* encode as tiles */ + uint8_t* tile_base = &tiles[y >> 3][x >> 3][(y & 7) << 1]; + if (c & 0x40) { tile_base[0] |= (1U << (7 - (7 & x))); } + if (c & 0x80) { tile_base[1] |= (1U << (7 - (7 & x))); } + } + } + + cv_imshow("dithered", M64282FP_SENSOR_W, M64282FP_SENSOR_H, 1, img); + + /* copy to ram */ + memcpy(cam->ram, tiles, sizeof(tiles)); +} + +static void init_pocket_cam(struct pocket_cam* cam, uint8_t* ram, void* vin, const struct video_input_backend_interface* ivin) +{ + cam->ram = ram; + + cam->vin = vin; + cam->ivin = ivin; +} + +static void poweron_pocket_cam(struct pocket_cam* cam) +{ + memset(cam->regs, 0, POCKET_CAM_REGS_COUNT*sizeof(cam->regs[0])); +} + +static uint8_t read_pocket_cam_regs(struct pocket_cam* cam, unsigned int reg) +{ + /* All registers except reg 0 are write-only and therefore return 0x00 */ + return (reg == 0) + ? cam->regs[0] + : 0x00; +} + +static void write_pocket_cam_regs(struct pocket_cam* cam, unsigned int reg, uint8_t value) +{ + if (reg >= POCKET_CAM_REGS_COUNT) { +#if 0 /* silence it because it happens always (2x32 bytes writes go past 0x36 registers) */ + DebugMessage(M64MSG_WARNING, "Out of range camera register write %02x = %02x", + reg, value); +#endif + return; + } + + cam->regs[reg] = value; + + if (reg == 0) { + cam->regs[reg] &= 0x7; + + if (cam->regs[reg] & 0x1) { + grab_pocket_cam_image(cam); + cam->regs[reg] &= ~0x01; + } + } +} + + static int read_gb_cart_pocket_cam(struct gb_cart* gb_cart, uint16_t address, uint8_t* data, size_t size) { switch(address >> 13) @@ -612,7 +771,10 @@ static int read_gb_cart_pocket_cam(struct gb_cart* gb_cart, uint16_t address, ui /* 0xa000-0xbfff: RAM bank 00-0f, Camera registers & 0x10 */ case (0xa000 >> 13): if (gb_cart->ram_bank & 0x10) { - memset(data, read_m64282fp_regs(&gb_cart->cam, (address & 0x7f)), size); + size_t i; + for(i = 0; i < size; ++i) { + data[i] = read_pocket_cam_regs(&gb_cart->cam, ((address+i) & 0x7f)); + } } else { read_ram(gb_cart->ram_storage, gb_cart->iram_storage, 1, (address - 0xa000) + (gb_cart->ram_bank * 0x2000), data, size, UINT8_C(0xff)); @@ -658,7 +820,10 @@ static int write_gb_cart_pocket_cam(struct gb_cart* gb_cart, uint16_t address, c /* 0xa000-0xbfff: RAM bank 00-0f, Camera registers & 0x10 */ case (0xa000 >> 13): if (gb_cart->ram_bank & 0x10) { - write_m64282fp_regs(&gb_cart->cam, (address & 0x7f), value); + size_t i; + for(i = 0; i < size; ++i) { + write_pocket_cam_regs(&gb_cart->cam, ((address+i) & 0x7f), data[i]); + } } else { write_ram(gb_cart->ram_storage, gb_cart->iram_storage, gb_cart->ram_enable, (address - 0xa000) + (gb_cart->ram_bank * 0x2000), data, size, UINT8_C(0xff)); @@ -803,7 +968,8 @@ void init_gb_cart(struct gb_cart* gb_cart, void* rom_opaque, void (*init_rom)(void* user_data, void** rom_storage, const struct storage_backend_interface** irom_storage), void (*release_rom)(void* user_data), void* ram_opaque, void (*init_ram)(void* user_data, size_t ram_size, void** ram_storage, const struct storage_backend_interface** iram_storage), void (*release_ram)(void* user_data), void* clock, const struct clock_backend_interface* iclock, - void* rumble, const struct rumble_backend_interface* irumble) + void* rumble, const struct rumble_backend_interface* irumble, + void* vin, const struct video_input_backend_interface* ivin) { const struct parsed_cart_type* type; void* rom_storage = NULL; @@ -811,7 +977,7 @@ void init_gb_cart(struct gb_cart* gb_cart, void* ram_storage = NULL; const struct storage_backend_interface* iram_storage = NULL; struct mbc3_rtc rtc; - struct m64282fp cam; + struct pocket_cam cam; memset(&rtc, 0, sizeof(rtc)); memset(&cam, 0, sizeof(cam)); @@ -894,7 +1060,7 @@ void init_gb_cart(struct gb_cart* gb_cart, } if (type->extra_devices & GED_CAMERA) { - init_m64282fp(&cam); + init_pocket_cam(&cam, iram_storage->data(ram_storage), vin, ivin); } /* update gb_cart */ @@ -932,7 +1098,7 @@ void poweron_gb_cart(struct gb_cart* gb_cart) } if (gb_cart->extra_devices & GED_CAMERA) { - poweron_m64282fp(&gb_cart->cam); + poweron_pocket_cam(&gb_cart->cam); } if (gb_cart->extra_devices & GED_RUMBLE) { diff --git a/src/device/gb/gb_cart.h b/src/device/gb/gb_cart.h index c8b8564d3..df21606d6 100644 --- a/src/device/gb/gb_cart.h +++ b/src/device/gb/gb_cart.h @@ -30,6 +30,23 @@ struct storage_backend_interface; struct rumble_backend_interface; +struct video_input_backend_interface; + +enum pocket_cam_registers +{ + /* TODO: use pretty names for registers */ + POCKET_CAM_REGS_COUNT = 0x36 +}; + +struct pocket_cam +{ + uint8_t regs[POCKET_CAM_REGS_COUNT]; + + void* vin; + const struct video_input_backend_interface* ivin; + + uint8_t* ram; +}; struct gb_cart { @@ -48,7 +65,7 @@ struct gb_cart unsigned int extra_devices; struct mbc3_rtc rtc; - struct m64282fp cam; + struct pocket_cam cam; void* rumble; const struct rumble_backend_interface* irumble; @@ -61,7 +78,8 @@ void init_gb_cart(struct gb_cart* gb_cart, void* rom_opaque, void (*init_rom)(void* user_data, void** rom_storage, const struct storage_backend_interface** irom_storage), void (*release_rom)(void* user_data), void* ram_opaque, void (*init_ram)(void* user_data, size_t ram_size, void** ram_storage, const struct storage_backend_interface** iram_storage), void (*release_ram)(void* user_data), void* clock, const struct clock_backend_interface* iclock, - void* rumble, const struct rumble_backend_interface* irumble); + void* rumble, const struct rumble_backend_interface* irumble, + void* vin, const struct video_input_backend_interface* ivin); void poweron_gb_cart(struct gb_cart* gb_cart); diff --git a/src/device/gb/m64282fp.c b/src/device/gb/m64282fp.c index dbb4578e4..8605c986c 100644 --- a/src/device/gb/m64282fp.c +++ b/src/device/gb/m64282fp.c @@ -25,47 +25,178 @@ #include "m64282fp.h" -#include "api/m64p_types.h" -#include "api/callbacks.h" #include +int min(int a, int b) { return (a < b) ? a : b; } +int max(int a, int b) { return (a < b) ? b : a; } -void init_m64282fp(struct m64282fp* cam) +int clamp(int v, int low, int high) { + return min(high, max(low, v)); } -void poweron_m64282fp(struct m64282fp* cam) +static void do_kernel_filtering(int img[M64282FP_SENSOR_H][M64282FP_SENSOR_W], int a, const int k[6]) { - memset(cam->regs, 0, M64282FP_REGS_COUNT*sizeof(cam->regs[0])); + unsigned int x, y; + /* north line and last west pixel previous values */ + int tmp[1+M64282FP_SENSOR_W]; + + memcpy(tmp, &img[0][0], M64282FP_SENSOR_W*sizeof(img[0][0])); + + for (y = 0; y < M64282FP_SENSOR_H; ++y) { + + tmp[M64282FP_SENSOR_W] = img[y][0]; + + for (x = 0; x < M64282FP_SENSOR_W; ++x) { + + int px = img[y][x]; + int ms = img[min(y+1, M64282FP_SENSOR_H-1)][x]; + int me = img[y][min(x+1, M64282FP_SENSOR_W-1)]; + int mn = tmp[x]; + int mw = tmp[M64282FP_SENSOR_W]; + + img[y][x] = k[0]*px+((a*(k[1]*px+k[2]*mn+k[3]*mw+k[4]*me+k[5]*ms))/4); + + tmp[x] = px; + tmp[M64282FP_SENSOR_W] = px; + } + } } -uint8_t read_m64282fp_regs(struct m64282fp* cam, unsigned int reg) +static void do_1d_filtering(int img[M64282FP_SENSOR_H][M64282FP_SENSOR_W], uint8_t P, uint8_t M) { - /* All registers except reg 0 are write only and therefore return 0x00 */ - return (reg == 0) - ? cam->regs[0] - : 0x00; + unsigned int x, y; + + for (y = 0; y < M64282FP_SENSOR_H; ++y) { + for (x = 0; x < M64282FP_SENSOR_W; ++x) { + + int px = img[y][x]; + int s1 = img[min(y+1, M64282FP_SENSOR_H-1)][x]; + int s2 = img[min(y+2, M64282FP_SENSOR_H-1)][x]; + int s3 = img[min(y+3, M64282FP_SENSOR_H-1)][x]; + + int value = 0; + if (P & 0x01) { value += px; } + if (P & 0x02) { value += s1; } + if (P & 0x04) { value += s2; } + if (P & 0x08) { value += s3; } + + if (M & 0x01) { value -= px; } + if (M & 0x02) { value -= s1; } + if (M & 0x04) { value -= s2; } + if (M & 0x08) { value -= s3; } + + img[y][x] = value; + } + } } -void write_m64282fp_regs(struct m64282fp* cam, unsigned int reg, uint8_t value) + +void process_m64282fp_image( + uint8_t img[M64282FP_SENSOR_H][M64282FP_SENSOR_W], + const uint8_t regs[M64282FP_REGS_COUNT]) { - if (reg >= M64282FP_REGS_COUNT) { - DebugMessage(M64MSG_WARNING, "Out of range camera register write %02x = %02x", - reg, value); - return; + unsigned int x, y; + int tmp[M64282FP_SENSOR_H][M64282FP_SENSOR_W]; + + static const int kernels[6][6] = { + /* px +a*(px +mn +mw +me +ms) */ + { 1, 2, 0, -1, -1, 0 }, /* horiz enhancement */ + { 0, 2, 0, -1, -1, 0 }, /* horiz extraction */ + { 1, 2, -1, 0, 0, -1 }, /* vert enhancement */ + { 0, 2, -1, 0, 0, -1 }, /* vert extraction */ + { 1, 4, -1, -1, -1, -1 }, /* 2d enhancement */ + { 0, 4, -1, -1, -1, -1 }, /* 2d extraction */ + }; + + /* edge ratio (alpha) Q.2 format */ +#define Q2(x) (4*x) + static const int alpha_lut[8] = { + Q2(2/4), Q2(3/4), Q2(1), Q2(5/4), + Q2(2), Q2(3), Q2(4), Q2(5) + }; +#undef Q2 + + /* apply exposure effect */ + uint16_t ext_exposure = 0x0300; /* 0x0300: could be other value */ + uint16_t exposure = (regs[M64282FP_C_HI] << 8) | regs[M64282FP_C_LO]; + + /* TODO: handle the zero-exposure case */ + + for (y = 0; y < M64282FP_SENSOR_H; ++y) { + for (x = 0; x < M64282FP_SENSOR_W; ++x) { + int v = img[y][x]; + v = ((v * exposure) / ext_exposure); + v = ((v - 128) / 8) + 128; /* adapt to 3.1V / 5V */ + img[y][x] = clamp(v, 0, 255); + } } - cam->regs[reg] = value; + /* invert image when I bit is set */ + if (regs[M64282FP_E_I_V] & 0x8) { + for (y = 0; y < M64282FP_SENSOR_H; ++y) { + for (x = 0; x < M64282FP_SENSOR_W; ++x) { + img[y][x] = 255 - img[y][x]; + } + } + } - if (reg == 0) { - cam->regs[reg] &= 0x7; + /* make signed */ + for (y = 0; y < M64282FP_SENSOR_H; ++y) { + for (x = 0; x < M64282FP_SENSOR_W; ++x) { + tmp[y][x] = img[y][x] - 128; + } + } - if (cam->regs[reg] & 0x1) { - /* TODO */ - DebugMessage(M64MSG_INFO, "Starting m64282fp frame capture"); + unsigned int mode + = (((regs[M64282FP_N_VH_G] & 0x80) >> 7) << 3) /* N */ + | (((regs[M64282FP_N_VH_G] & 0x60) >> 5) << 1) /* VH */ + | (((regs[M64282FP_E_I_V] & 0x80) >> 7) << 0); /* E3 */ - /* End of capture */ - cam->regs[reg] &= ~0x01; + int alpha = alpha_lut[(regs[M64282FP_E_I_V] & 0x70) >> 4]; + + switch(mode) + { + case 0x0: /* 0000: positive image */ + do_1d_filtering(tmp, regs[M64282FP_P], regs[M64282FP_M]); + break; + case 0x1: /* 0001: undocumented - bug ??? */ + memset(tmp, 0, sizeof(tmp)); + break; + + case 0x2: /* 0010: horiz enhancement */ + do_kernel_filtering(tmp, alpha, kernels[0]); + do_1d_filtering(tmp, regs[M64282FP_P], regs[M64282FP_M]); + break; + case 0x3: /* 0011: horiz extraction */ + do_kernel_filtering(tmp, alpha, kernels[1]); + do_1d_filtering(tmp, regs[M64282FP_P], regs[M64282FP_M]); + break; + + case 0xc: /* 1100: vert enhancement */ + do_kernel_filtering(tmp, alpha, kernels[2]); + break; + case 0xd: /* 1101: vert extraction */ + do_kernel_filtering(tmp, alpha, kernels[3]); + break; + + case 0xe: /* 1110: 2D enhancement */ + do_kernel_filtering(tmp, alpha, kernels[4]); + break; + case 0xf: /* 1111: 2D extraction */ + do_kernel_filtering(tmp, alpha, kernels[5]); + break; + + default: + break; + } + + /* back to 0..255 range */ + for (y = 0; y < M64282FP_SENSOR_H; ++y) { + for (x = 0; x < M64282FP_SENSOR_W; ++x) { + img[y][x] = clamp(128 + tmp[y][x], 0, 255); } } + + /* gain and level control are not emulated */ } diff --git a/src/device/gb/m64282fp.h b/src/device/gb/m64282fp.h index c2f45d4c6..2ae2c0889 100644 --- a/src/device/gb/m64282fp.h +++ b/src/device/gb/m64282fp.h @@ -24,21 +24,27 @@ #include -enum m64282fp_registers +enum { - /* TODO: use pretty names for registers */ - M64282FP_REGS_COUNT = 0x36 + M64282FP_SENSOR_W = 128, + M64282FP_SENSOR_H = 128, }; -struct m64282fp +enum m64282fp_registers { - uint8_t regs[M64282FP_REGS_COUNT]; + M64282FP_Z_O, + M64282FP_N_VH_G, + M64282FP_C_LO, + M64282FP_C_HI, + M64282FP_P, + M64282FP_M, + M64282FP_X, + M64282FP_E_I_V, + M64282FP_REGS_COUNT }; -void init_m64282fp(struct m64282fp* cam); -void poweron_m64282fp(struct m64282fp* cam); - -uint8_t read_m64282fp_regs(struct m64282fp* cam, unsigned int reg); -void write_m64282fp_regs(struct m64282fp* cam, unsigned int reg, uint8_t value); +void process_m64282fp_image( + uint8_t img[M64282FP_SENSOR_H][M64282FP_SENSOR_W], + const uint8_t regs[M64282FP_REGS_COUNT]); #endif diff --git a/src/main/main.c b/src/main/main.c index 77b50463b..ac3af15b7 100644 --- a/src/main/main.c +++ b/src/main/main.c @@ -49,9 +49,12 @@ #include "backends/api/joybus.h" #include "backends/api/rumble_backend.h" #include "backends/api/storage_backend.h" +#include "backends/api/video_backend.h" #include "backends/plugins_compat/plugins_compat.h" #include "backends/clock_ctime_plus_delta.h" #include "backends/file_storage.h" +#include "backends/opencv_video_backend.h" +#include "backends/dummy_video_backend.h" #include "cheat.h" #include "device/device.h" #include "device/controllers/paks/mempak.h" @@ -269,6 +272,7 @@ int main_set_core_defaults(void) ConfigSetDefaultBool(g_CoreConfig, "DisableSpecRecomp", 1, "Disable speculative precompilation in new dynarec"); ConfigSetDefaultBool(g_CoreConfig, "RandomizeInterrupt", 1, "Randomize PI/SI Interrupt Timing"); ConfigSetDefaultInt(g_CoreConfig, "SiDmaDuration", -1, "Duration of SI DMA (-1: use per game settings)"); + ConfigSetDefaultString(g_CoreConfig, "GbCameraVideoDevice", "0", "Gameboy Camera Video device - backend specific"); /* handle upgrades */ if (bUpgrade) @@ -1098,6 +1102,8 @@ struct gb_cart_data int control_id; struct file_storage rom_fstorage; struct file_storage ram_fstorage; + void* gbcam_backend; + const struct video_input_backend_interface* igbcam_backend; }; static struct gb_cart_data l_gb_carts_data[GAME_CONTROLLERS_COUNT]; @@ -1192,8 +1198,6 @@ static void release_gb_ram(void* opaque) memset(&data->ram_fstorage, 0, sizeof(data->ram_fstorage)); } - - void main_change_gb_cart(int control_id) { struct transferpak* tpk = &g_dev.transferpaks[control_id]; @@ -1208,7 +1212,8 @@ void main_change_gb_cart(int control_id) data, init_gb_rom, release_gb_rom, data, init_gb_ram, release_gb_ram, NULL, &g_iclock_ctime_plus_delta, - &data->control_id, &g_irumble_backend_plugin_compat); + &data->control_id, &g_irumble_backend_plugin_compat, + data->gbcam_backend, data->igbcam_backend); if (gb_cart->read_gb_cart == NULL) { gb_cart = NULL; @@ -1253,6 +1258,9 @@ m64p_error main_run(void) struct file_storage mpk_storages[GAME_CONTROLLERS_COUNT]; struct file_storage mpk; + void* gbcam_backend = NULL; + const struct video_input_backend_interface* igbcam_backend = NULL; + /* XXX: select type of flashram from db */ uint32_t flashram_type = MX29L1100_ID; @@ -1327,6 +1335,23 @@ m64p_error main_run(void) l_pak_type_idx[PLUGIN_TRANSFER_PAK] = k; } + /* open GB cam video device */ +#if defined(M64P_OPENCV) + { + struct opencv_video_backend* cv_backend = malloc(sizeof(*cv_backend)); + memset(cv_backend, 0, sizeof(*cv_backend)); + cv_backend->device = strdup(ConfigGetParamString(g_CoreConfig, "GbCameraVideoDevice")); + + gbcam_backend = cv_backend; + igbcam_backend = &g_iopencv_video_input_backend; + } +#else + gbcam_backend = NULL; + igbcam_backend = &g_idummy_video_input_backend; +#endif + + igbcam_backend->open(gbcam_backend, M64282FP_SENSOR_W, M64282FP_SENSOR_H); + /* open storage files, provide default content if not present */ open_mpk_file(&mpk); open_eep_file(&eep); @@ -1380,6 +1405,9 @@ m64p_error main_run(void) l_gb_carts_data[i].control_id = (int)i; + l_gb_carts_data[i].gbcam_backend = gbcam_backend; + l_gb_carts_data[i].igbcam_backend = igbcam_backend; + l_paks_idx[i] = 0; //Don't use the selected pak if it's not available for the game, instead use NONE @@ -1417,7 +1445,8 @@ m64p_error main_run(void) &l_gb_carts_data[i], init_gb_rom, release_gb_rom, &l_gb_carts_data[i], init_gb_ram, release_gb_ram, NULL, &g_iclock_ctime_plus_delta, - &l_gb_carts_data[i].control_id, &g_irumble_backend_plugin_compat); + &l_gb_carts_data[i].control_id, &g_irumble_backend_plugin_compat, + l_gb_carts_data[i].gbcam_backend, l_gb_carts_data[i].igbcam_backend); init_transferpak(&g_dev.transferpaks[i], (g_dev.gb_carts[i].read_gb_cart == NULL) ? NULL : &g_dev.gb_carts[i]); l_paks[i][k] = &g_dev.transferpaks[i]; @@ -1550,6 +1579,9 @@ m64p_error main_run(void) } } + igbcam_backend->close(gbcam_backend); + free(gbcam_backend); + close_file_storage(&sra); close_file_storage(&fla); close_file_storage(&eep); @@ -1585,6 +1617,9 @@ m64p_error main_run(void) } } + igbcam_backend->close(gbcam_backend); + free(gbcam_backend); + /* release storage files */ close_file_storage(&sra); close_file_storage(&fla); diff --git a/src/main/savestates.c b/src/main/savestates.c index b40d464e0..9ec040e4f 100644 --- a/src/main/savestates.c +++ b/src/main/savestates.c @@ -571,7 +571,7 @@ static int savestates_load_m64p(struct device* dev, char *filepath) (gb_fingerprint[0] == 0x00) ? "(none)" : gb_fingerprint); if (gb_fingerprint[0] != 0x00) { - curr += 5*sizeof(unsigned int)+MBC3_RTC_REGS_COUNT*2+sizeof(uint64_t)+M64282FP_REGS_COUNT; + curr += 5*sizeof(unsigned int)+MBC3_RTC_REGS_COUNT*2+sizeof(uint64_t)+POCKET_CAM_REGS_COUNT; } } else { @@ -585,7 +585,7 @@ static int savestates_load_m64p(struct device* dev, char *filepath) COPYARRAY(dev->transferpaks[i].gb_cart->rtc.latched_regs, curr, uint8_t, MBC3_RTC_REGS_COUNT); dev->transferpaks[i].gb_cart->rtc.last_time = (time_t)ALIGNED_GETDATA(curr, int64_t); - COPYARRAY(dev->transferpaks[i].gb_cart->cam.regs, curr, uint8_t, M64282FP_REGS_COUNT); + COPYARRAY(dev->transferpaks[i].gb_cart->cam.regs, curr, uint8_t, POCKET_CAM_REGS_COUNT); } } } @@ -690,7 +690,7 @@ static int savestates_load_m64p(struct device* dev, char *filepath) (gb_fingerprint[0] == 0x00) ? "(none)" : gb_fingerprint); if (gb_fingerprint[0] != 0x00) { - curr += 5*sizeof(unsigned int)+MBC3_RTC_REGS_COUNT*2+sizeof(uint64_t)+M64282FP_REGS_COUNT; + curr += 5*sizeof(unsigned int)+MBC3_RTC_REGS_COUNT*2+sizeof(uint64_t)+POCKET_CAM_REGS_COUNT; } } else { @@ -703,7 +703,7 @@ static int savestates_load_m64p(struct device* dev, char *filepath) dev->transferpaks[i].gb_cart->rtc.last_time = (time_t)GETDATA(curr, int64_t); COPYARRAY(dev->transferpaks[i].gb_cart->rtc.regs, curr, uint8_t, MBC3_RTC_REGS_COUNT); COPYARRAY(dev->transferpaks[i].gb_cart->rtc.latched_regs, curr, uint8_t, MBC3_RTC_REGS_COUNT); - COPYARRAY(dev->transferpaks[i].gb_cart->cam.regs, curr, uint8_t, M64282FP_REGS_COUNT); + COPYARRAY(dev->transferpaks[i].gb_cart->cam.regs, curr, uint8_t, POCKET_CAM_REGS_COUNT); } } } @@ -1729,7 +1729,7 @@ static int savestates_save_m64p(const struct device* dev, char *filepath) PUTARRAY(dev->transferpaks[i].gb_cart->rtc.regs, curr, uint8_t, MBC3_RTC_REGS_COUNT); PUTARRAY(dev->transferpaks[i].gb_cart->rtc.latched_regs, curr, uint8_t, MBC3_RTC_REGS_COUNT); - PUTARRAY(dev->transferpaks[i].gb_cart->cam.regs, curr, uint8_t, M64282FP_REGS_COUNT); + PUTARRAY(dev->transferpaks[i].gb_cart->cam.regs, curr, uint8_t, POCKET_CAM_REGS_COUNT); } }