GB MBC: Pocket Cam support

This commit is contained in:
Vicki Pfau 2017-07-26 10:57:57 -07:00
parent 55330698cb
commit 31b9100f38
13 changed files with 111 additions and 2 deletions

View File

@ -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

View File

@ -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,

View File

@ -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;

View File

@ -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;

BIN
res/no-cam.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -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;
}

View File

@ -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) {

View File

@ -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;

View File

@ -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);
}

View File

@ -47,8 +47,6 @@ CoreController::CoreController(mCore* core, QObject* parent)
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::setOverride(std::unique_ptr<Override> 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) {

View File

@ -78,6 +78,27 @@ InputController::InputController(int playerId, QWidget* topLevel, QObject* paren
};
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() {

View File

@ -9,6 +9,7 @@
#include "GamepadAxisEvent.h"
#include "GamepadHatEvent.h"
#include <QImage>
#include <QObject>
#include <QSet>
#include <QTimer>
@ -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;

View File

@ -3,5 +3,6 @@
<file>../../../res/mgba-1024.png</file>
<file>../../../res/keymap.qpic</file>
<file>../../../res/patrons.txt</file>
<file>../../../res/no-cam.png</file>
</qresource>
</RCC>