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 000000000..2070f21cc Binary files /dev/null and b/res/no-cam.png differ diff --git a/src/gb/core.c b/src/gb/core.c index 1b60361ee..db12ec457 100644 --- a/src/gb/core.c +++ b/src/gb/core.c @@ -479,6 +479,9 @@ static void _GBCoreSetPeripheral(struct mCore* core, int type, void* periph) { case mPERIPH_RUMBLE: gb->memory.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