From 31b9100f381fb7ee75ba8ee006a86ab65e6e0b35 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 26 Jul 2017 10:57:57 -0700 Subject: [PATCH] GB MBC: Pocket Cam support --- CHANGES | 1 + include/mgba/core/interface.h | 7 ++++ include/mgba/internal/gb/mbc.h | 5 +++ include/mgba/internal/gb/memory.h | 2 + res/no-cam.png | Bin 0 -> 1824 bytes src/gb/core.c | 3 ++ src/gb/gb.c | 3 ++ src/gb/mbc.c | 56 ++++++++++++++++++++++++++++ src/gb/memory.c | 1 + src/platform/qt/CoreController.cpp | 5 ++- src/platform/qt/InputController.cpp | 21 +++++++++++ src/platform/qt/InputController.h | 8 ++++ src/platform/qt/resources.qrc | 1 + 13 files changed, 111 insertions(+), 2 deletions(-) create mode 100644 res/no-cam.png diff --git a/CHANGES b/CHANGES index 410c5946a..ef13fdd4d 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,7 @@ 0.7.0: (Future) Features: - ELF support + - Game Boy Camera support Bugfixes: - GB Audio: Make audio unsigned with bias (fixes mgba.io/i/749) - Python: Fix importing .gb or .gba before .core diff --git a/include/mgba/core/interface.h b/include/mgba/core/interface.h index 44c7e13d5..44590e71d 100644 --- a/include/mgba/core/interface.h +++ b/include/mgba/core/interface.h @@ -61,6 +61,7 @@ struct mKeyCallback { enum mPeripheral { mPERIPH_ROTATION = 1, mPERIPH_RUMBLE, + mPERIPH_IMAGE_SOURCE, mPERIPH_CUSTOM = 0x1000 }; @@ -82,6 +83,12 @@ struct mRTCSource { bool (*deserialize)(struct mRTCSource*, const struct mStateExtdataItem*); }; +struct mImageSource { + void (*startRequestImage)(struct mImageSource*); + void (*stopRequestImage)(struct mImageSource*); + void (*requestImage)(struct mImageSource*, unsigned w, unsigned h, const uint32_t** buffer, size_t* stride); +}; + enum mRTCGenericType { RTC_NO_OVERRIDE, RTC_FIXED, diff --git a/include/mgba/internal/gb/mbc.h b/include/mgba/internal/gb/mbc.h index 7ec4cf052..86f58c8f0 100644 --- a/include/mgba/internal/gb/mbc.h +++ b/include/mgba/internal/gb/mbc.h @@ -21,6 +21,11 @@ void GBMBCSwitchBank(struct GB* gb, int bank); void GBMBCSwitchBank0(struct GB* gb, int bank); void GBMBCSwitchSramBank(struct GB* gb, int bank); +enum GBCam { + GBCAM_WIDTH = 128, + GBCAM_HEIGHT = 112 +}; + struct GBMBCRTCSaveBuffer { uint32_t sec; uint32_t min; diff --git a/include/mgba/internal/gb/memory.h b/include/mgba/internal/gb/memory.h index 44eb2a541..eb3f9c508 100644 --- a/include/mgba/internal/gb/memory.h +++ b/include/mgba/internal/gb/memory.h @@ -117,6 +117,7 @@ struct GBMBC7State { struct GBPocketCamState { bool registersActive; + uint8_t registers[0x36]; }; struct GBTAMA5State { @@ -179,6 +180,7 @@ struct GBMemory { struct mRTCSource* rtc; struct mRotationSource* rotation; struct mRumble* rumble; + struct mImageSource* cam; }; struct LR35902Core; diff --git a/res/no-cam.png b/res/no-cam.png new file mode 100644 index 0000000000000000000000000000000000000000..2070f21ccd812ab1bfcaad5cd250bbfff54b8eba GIT binary patch literal 1824 zcmV+*2jBRKP)&GAtoHU>9PLeUoKCM5tmL27)l`hy+B|Vc3S9 zm>6T?Pw-!S?L*)0u3oEZG?`$&`!HWu-BahDuCAr)_R!xLTzjR_6wnmV6foEV!oe0W z=snOB&=fG}0$!GB*n;O?yv}$>0mC$crylX$@$|?AHCzJ-9y`bPz*D~iXru-J1P>kK z`{3!m9Wz?4&M&n(jcoM#0$?RYs2z&UNl19Yv~$>meJ9uENYOSNwjx1SB1Ze(N~%sU!<8BVmnvP8x%FHCJ-lhgbAiHq>vqKZDq&7idz?8gS4OGcj@!BI zJYiIZk%DBZ$E~Lzqd<$a1qg~jm~2q8qOA(WZs44x8Z?jsD33AP0R z06@1_#i(O3ajkhg$yQ6POdCm^dJ!j^VzWQTcTobwjYIKv7K zP>7LBmyj+ijU6EE<9oxWfbVZ@a5Wa z{G;^|Y_H`<7Amti1j};@Okat~BbETxb3ykaQ-J_hP{rzJAnN9Il$#3x zqFpeZp`;-9Igr<`1F`K*`hZvX4RIexKyFCLGOGgQUKeh+!>NiD6PCV1z-orcqvtKhcm6KfOP8XZ@y2<>7d-1Kon%B`)$!SJ0`K!yf^+(;a|r4j}Iqp zv13wO%L?|CdY5v1FG~8>2aWZtK$rNqQu;B{MlU!vvVwgD&k~OBN$9W{Wh{RG8&a_N zZOxOA)_UpP%(vN9;^S>QSm(t^t9{U0IevGkmtS3iHC_T%dVo7Q{*eklzrF>ld<3kp z0Cw>23SfKuni>1%^KV+AAX9w4eW_Vi!kxwfn}5yjFnnBhtV6|>v^*k+i&b%I1?&!$IZh_|nUKCR83rIg6;J(y z)6vz;65r_}+CUBH5ftrgjjwPn$cXe8u)ploPv-#u(`tMbRw=;#&4_^XZbZ?SFq9pQ z603?+*#lk4sekc74Vh#HE&})@@hO-Yfs2N+Fn3WEr*bn&5Q`3KK!GAdCSjwJKM|SX zqs&i|Pb-*h5YoaZ&7)*7rOH`LsQ50;XAo7=!g$#p@GOYaTy;6+*h~RZQ3z7OzZSsp zOEcawS2i;Q*C!}~O7jQ*ZXJ-NIsd}IHhVza9G^*T40IZXqyX8Awz#7114u>ydfQ9| zRdMXIKtlG3Ht=B<`Ltz*%d?I&!cTCy`-s?cxAam*$ zLWtHX0j=2{5aNC=r|Pv1gk`7xek31e&Epy)wehhwQqn&v5l|7PQox0>vj+r!n2u~J z|Nb0T==6g%cg3R-0~KH_aEZ|qV3;AS*j6W*+WbiM7SMi4tNf||8PH;OOi9~(-n7Lk zpmH6Ud|9i^ss9Clw=xf=ZVib9qJMbyD}4pX69h1E@E>= zA5{tfuJVVg-?{t-Zwys!uBlRdFtH{EeOTkfb!PYj0Ps4;uWoWfmEeO3bs^}3IxlWA z$DaURtYX0fbbL%Ja&d!`T!b3)SF zZj8@efow0r7CQ(4=uI!)|K47j|EO#VXbSir3usQmrhuk^K^HLC)9@dScNwaEZJgWy O0000memory.rumble = periph; break; + case mPERIPH_IMAGE_SOURCE: + gb->memory.cam = periph; + break; default: return; } diff --git a/src/gb/gb.c b/src/gb/gb.c index 720a862c7..0ef8bb051 100644 --- a/src/gb/gb.c +++ b/src/gb/gb.c @@ -294,6 +294,9 @@ void GBUnloadROM(struct GB* gb) { } gb->sramRealVf = NULL; gb->sramVf = NULL; + if (gb->memory.cam && gb->memory.cam->stopRequestImage) { + gb->memory.cam->stopRequestImage(gb->memory.cam); + } } void GBSynthesizeROM(struct VFile* vf) { diff --git a/src/gb/mbc.c b/src/gb/mbc.c index 00560f648..9055d34c6 100644 --- a/src/gb/mbc.c +++ b/src/gb/mbc.c @@ -37,6 +37,7 @@ static void _GBMBC7Write(struct GBMemory* memory, uint16_t address, uint8_t valu static uint8_t _GBTAMA5Read(struct GBMemory*, uint16_t address); static uint8_t _GBPocketCamRead(struct GBMemory*, uint16_t address); +static void _GBPocketCamCapture(struct GBMemory*); void GBMBCSwitchBank(struct GB* gb, int bank) { size_t bankStart = bank * GB_SIZE_CART_BANK0; @@ -242,6 +243,9 @@ void GBMBCInit(struct GB* gb) { case GB_POCKETCAM: gb->memory.mbcWrite = _GBPocketCam; gb->memory.mbcRead = _GBPocketCamRead; + if (gb->memory.cam && gb->memory.cam->startRequestImage) { + gb->memory.cam->startRequestImage(gb->memory.cam); + } break; } @@ -757,6 +761,16 @@ void _GBPocketCam(struct GB* gb, uint16_t address, uint8_t value) { memory->mbcState.pocketCam.registersActive = true; } break; + case 0x5: + address &= 0x7F; + if (address == 0 && value & 1) { + value &= 6; // TODO: Timing + _GBPocketCamCapture(memory); + } + if (address < sizeof(memory->mbcState.pocketCam.registers)) { + memory->mbcState.pocketCam.registers[address] = value; + } + break; default: mLOG(GB_MBC, STUB, "Pocket Cam unknown address: %04X:%02X", address, value); break; @@ -765,11 +779,53 @@ void _GBPocketCam(struct GB* gb, uint16_t address, uint8_t value) { uint8_t _GBPocketCamRead(struct GBMemory* memory, uint16_t address) { if (memory->mbcState.pocketCam.registersActive) { + if ((address & 0x7F) == 0) { + return memory->mbcState.pocketCam.registers[0]; + } return 0; } return memory->sramBank[address & (GB_SIZE_EXTERNAL_RAM - 1)]; } +void _GBPocketCamCapture(struct GBMemory* memory) { + if (!memory->cam) { + return; + } + const uint32_t* image = NULL; + size_t stride; + memory->cam->requestImage(memory->cam, GBCAM_WIDTH, GBCAM_HEIGHT, &image, &stride); + if (!image) { + return; + } + memset(&memory->sram[0x100], 0, GBCAM_HEIGHT * GBCAM_WIDTH / 4); + struct GBPocketCamState* pocketCam = &memory->mbcState.pocketCam; + size_t x, y; + for (y = 0; y < GBCAM_HEIGHT; ++y) { + for (x = 0; x < GBCAM_WIDTH; ++x) { + uint32_t color = image[y * stride + x]; + uint32_t gray = ((color & 0xFF) + ((color >> 8) & 0xFF) + ((color >> 16) & 0xFF)); + uint16_t exposure = (pocketCam->registers[2] << 8) | (pocketCam->registers[3]); + gray = (gray + 1) * exposure / 0x300; + // TODO: Additional processing + int matrixEntry = 3 * ((x & 3) + 4 * (y & 3)); + if (gray < pocketCam->registers[matrixEntry + 6]) { + gray = 0x101; + } else if (gray < pocketCam->registers[matrixEntry + 7]) { + gray = 0x100; + } else if (gray < pocketCam->registers[matrixEntry + 8]) { + gray = 0x001; + } else { + gray = 0; + } + int coord = (((x >> 3) & 0xF) * 8 + (y & 0x7)) * 2 + (y & ~0x7) * 0x20; + uint16_t existing; + LOAD_16LE(existing, coord + 0x100, memory->sram); + existing |= gray << (7 - (x & 7)); + STORE_16LE(existing, coord + 0x100, memory->sram); + } + } +} + void _GBTAMA5(struct GB* gb, uint16_t address, uint8_t value) { struct GBMemory* memory = &gb->memory; struct GBTAMA5State* tama5 = &memory->mbcState.tama5; diff --git a/src/gb/memory.c b/src/gb/memory.c index 83556927c..0c3d7e57c 100644 --- a/src/gb/memory.c +++ b/src/gb/memory.c @@ -105,6 +105,7 @@ void GBMemoryInit(struct GB* gb) { gb->memory.rtc = NULL; gb->memory.rotation = NULL; gb->memory.rumble = NULL; + gb->memory.cam = NULL; GBIOInit(gb); } diff --git a/src/platform/qt/CoreController.cpp b/src/platform/qt/CoreController.cpp index 86c375edb..d4eea69cf 100644 --- a/src/platform/qt/CoreController.cpp +++ b/src/platform/qt/CoreController.cpp @@ -47,8 +47,6 @@ CoreController::CoreController(mCore* core, QObject* parent) m_threadContext.startCallback = [](mCoreThread* context) { CoreController* controller = static_cast(context->userData); - context->core->setPeripheral(context->core, mPERIPH_ROTATION, controller->m_inputController->rotationSource()); - context->core->setPeripheral(context->core, mPERIPH_RUMBLE, controller->m_inputController->rumble()); switch (context->core->platform(context->core)) { #ifdef M_CORE_GBA @@ -285,6 +283,9 @@ void CoreController::setOverride(std::unique_ptr override) { void CoreController::setInputController(InputController* inputController) { m_inputController = inputController; + m_threadContext.core->setPeripheral(m_threadContext.core, mPERIPH_ROTATION, m_inputController->rotationSource()); + m_threadContext.core->setPeripheral(m_threadContext.core, mPERIPH_RUMBLE, m_inputController->rumble()); + m_threadContext.core->setPeripheral(m_threadContext.core, mPERIPH_IMAGE_SOURCE, m_inputController->imageSource()); } void CoreController::setLogger(LogController* logger) { diff --git a/src/platform/qt/InputController.cpp b/src/platform/qt/InputController.cpp index 2da1d72bb..980051144 100644 --- a/src/platform/qt/InputController.cpp +++ b/src/platform/qt/InputController.cpp @@ -78,6 +78,27 @@ InputController::InputController(int playerId, QWidget* topLevel, QObject* paren }; setLuminanceLevel(0); #endif + + m_image.startRequestImage = [](mImageSource* context) { + InputControllerImage* image = static_cast(context); + image->image.load(":/res/no-cam.png"); + }; + m_image.stopRequestImage = nullptr; + m_image.requestImage = [](mImageSource* context, unsigned w, unsigned h, const uint32_t** buffer, size_t* stride) { + InputControllerImage* image = static_cast(context); + image->resizedImage = image->image.scaled(w, h, Qt::KeepAspectRatioByExpanding); + image->resizedImage = image->resizedImage.convertToFormat(QImage::Format_RGB32); + const uint32_t* bits = reinterpret_cast(image->resizedImage.constBits()); + QSize size = image->resizedImage.size(); + if (size.width() > w) { + bits += size.width() / 2; + } + if (size.height() > h) { + bits += (size.height() / 2) * size.width(); + } + *buffer = bits; + *stride = size.width(); + }; } InputController::~InputController() { diff --git a/src/platform/qt/InputController.h b/src/platform/qt/InputController.h index c97d11818..fbad1810e 100644 --- a/src/platform/qt/InputController.h +++ b/src/platform/qt/InputController.h @@ -9,6 +9,7 @@ #include "GamepadAxisEvent.h" #include "GamepadHatEvent.h" +#include #include #include #include @@ -82,6 +83,7 @@ public: mRumble* rumble(); mRotationSource* rotationSource(); + mImageSource* imageSource() { return &m_image; } GBALuminanceSource* luminance() { return &m_lux; } signals: @@ -115,6 +117,12 @@ private: uint8_t m_luxValue; int m_luxLevel; + struct InputControllerImage : mImageSource { + InputController* p; + QImage image; + QImage resizedImage; + } m_image; + mInputMap m_inputMap; ConfigController* m_config = nullptr; int m_playerId; diff --git a/src/platform/qt/resources.qrc b/src/platform/qt/resources.qrc index 1629b7eaf..7d61b6dd1 100644 --- a/src/platform/qt/resources.qrc +++ b/src/platform/qt/resources.qrc @@ -3,5 +3,6 @@ ../../../res/mgba-1024.png ../../../res/keymap.qpic ../../../res/patrons.txt + ../../../res/no-cam.png