mirror of https://github.com/mgba-emu/mgba.git
GB MBC: Pocket Cam support
This commit is contained in:
parent
55330698cb
commit
31b9100f38
1
CHANGES
1
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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
56
src/gb/mbc.c
56
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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue