GB MBC: Pocket Cam support
Vicki Pfau vi@endrift.com
Wed, 26 Jul 2017 10:57:57 -0700
13 files changed,
111 insertions(+),
2 deletions(-)
jump to
M
include/mgba/core/interface.h
→
include/mgba/core/interface.h
@@ -61,6 +61,7 @@
enum mPeripheral { mPERIPH_ROTATION = 1, mPERIPH_RUMBLE, + mPERIPH_IMAGE_SOURCE, mPERIPH_CUSTOM = 0x1000 };@@ -80,6 +81,12 @@ time_t (*unixTime)(struct mRTCSource*);
void (*serialize)(struct mRTCSource*, struct mStateExtdataItem*); 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 {
M
include/mgba/internal/gb/mbc.h
→
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;
M
include/mgba/internal/gb/memory.h
→
include/mgba/internal/gb/memory.h
@@ -117,6 +117,7 @@ };
struct GBPocketCamState { bool registersActive; + uint8_t registers[0x36]; }; struct GBTAMA5State {@@ -179,6 +180,7 @@ time_t rtcLastLatch;
struct mRTCSource* rtc; struct mRotationSource* rotation; struct mRumble* rumble; + struct mImageSource* cam; }; struct LR35902Core;
M
src/gb/core.c
→
src/gb/core.c
@@ -479,6 +479,9 @@ break;
case mPERIPH_RUMBLE: gb->memory.rumble = periph; break; + case mPERIPH_IMAGE_SOURCE: + gb->memory.cam = periph; + break; default: return; }
M
src/gb/gb.c
→
src/gb/gb.c
@@ -294,6 +294,9 @@ gb->sramRealVf->close(gb->sramRealVf);
} 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) {
M
src/gb/mbc.c
→
src/gb/mbc.c
@@ -37,6 +37,7 @@
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 @@ break;
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 @@ } else {
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,9 +779,51 @@ }
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) {
M
src/gb/memory.c
→
src/gb/memory.c
@@ -105,6 +105,7 @@
gb->memory.rtc = NULL; gb->memory.rotation = NULL; gb->memory.rumble = NULL; + gb->memory.cam = NULL; GBIOInit(gb); }
M
src/platform/qt/CoreController.cpp
→
src/platform/qt/CoreController.cpp
@@ -47,8 +47,6 @@ m_threadContext.core->setVideoBuffer(m_threadContext.core, reinterpret_cast<color_t*>(m_activeBuffer->data()), size.width());
m_threadContext.startCallback = [](mCoreThread* context) { CoreController* controller = static_cast<CoreController*>(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::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) {
M
src/platform/qt/InputController.cpp
→
src/platform/qt/InputController.cpp
@@ -78,6 +78,27 @@ return lux->value;
}; setLuminanceLevel(0); #endif + + m_image.startRequestImage = [](mImageSource* context) { + InputControllerImage* image = static_cast<InputControllerImage*>(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<InputControllerImage*>(context); + image->resizedImage = image->image.scaled(w, h, Qt::KeepAspectRatioByExpanding); + image->resizedImage = image->resizedImage.convertToFormat(QImage::Format_RGB32); + const uint32_t* bits = reinterpret_cast<const uint32_t*>(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() {
M
src/platform/qt/InputController.h
→
src/platform/qt/InputController.h
@@ -9,6 +9,7 @@
#include "GamepadAxisEvent.h" #include "GamepadHatEvent.h" +#include <QImage> #include <QObject> #include <QSet> #include <QTimer>@@ -82,6 +83,7 @@ void releaseFocus(QWidget* focus);
mRumble* rumble(); mRotationSource* rotationSource(); + mImageSource* imageSource() { return &m_image; } GBALuminanceSource* luminance() { return &m_lux; } signals:@@ -114,6 +116,12 @@ uint8_t value;
} m_lux; 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;
M
src/platform/qt/resources.qrc
→
src/platform/qt/resources.qrc
@@ -3,5 +3,6 @@ <qresource>
<file>../../../res/mgba-1024.png</file> <file>../../../res/keymap.qpic</file> <file>../../../res/patrons.txt</file> + <file>../../../res/no-cam.png</file> </qresource> </RCC>