diff --git a/CHANGES b/CHANGES index af9ab92b3..b292bde0d 100644 --- a/CHANGES +++ b/CHANGES @@ -49,6 +49,8 @@ Features: - Savestates now contain any RTC override data - Command line ability to override configuration values - Add option to allow preloading the entire ROM before running + - GB: Video/audio channel enabling/disabling + - Add option to lock video to integer scaling Bugfixes: - LR35902: Fix core never exiting with certain event patterns - GB Timer: Improve DIV reset behavior @@ -73,6 +75,11 @@ Bugfixes: - GB: Fix flickering when screen is strobed quickly - FFmpeg: Fix overflow and general issues with audio encoding - Qt: Fix crash when changing audio settings after a game is closed + - GBA BIOS: Fix ArcTan sign in HLE BIOS + - GBA BIOS: Fix ArcTan2 sign in HLE BIOS (fixes mgba.io/i/689) + - GBA Video: Don't update background scanline params in mode 0 (fixes mgba.io/i/377) + - Qt: Ensure CLI backend is attached when submitting commands (fixes mgba.io/i/662) + - Core: Fix crash with rewind if savestates shrink Misc: - SDL: Remove scancode key input - GBA Video: Clean up unused timers @@ -126,6 +133,7 @@ Misc: - Qt: Add .gb/.gbc files to the extension list in Info.plist - Feature: Make -l option explicit - Core: Ability to enumerate and modify video and audio channels + - Debugger: Make attaching a backend idempotent medusa alpha 1: (2017-04-08) Features: diff --git a/include/mgba-util/common.h b/include/mgba-util/common.h index 6f3b0695b..6abc28b66 100644 --- a/include/mgba-util/common.h +++ b/include/mgba-util/common.h @@ -56,6 +56,11 @@ typedef intptr_t ssize_t; #include #endif +#ifdef PSP2 +// For PATH_MAX on modern toolchains +#include +#endif + #ifndef SSIZE_MAX #define SSIZE_MAX ((ssize_t) (SIZE_MAX >> 1)) #endif diff --git a/include/mgba/core/config.h b/include/mgba/core/config.h index 6dbc73dc4..0c6426e7a 100644 --- a/include/mgba/core/config.h +++ b/include/mgba/core/config.h @@ -42,6 +42,7 @@ struct mCoreOptions { int width; int height; bool lockAspectRatio; + bool lockIntegerScaling; bool resampleVideo; bool suspendScreensaver; char* shader; diff --git a/include/mgba/internal/gb/video.h b/include/mgba/internal/gb/video.h index 80899c723..fc751d43a 100644 --- a/include/mgba/internal/gb/video.h +++ b/include/mgba/internal/gb/video.h @@ -71,6 +71,10 @@ struct GBVideoRenderer { uint8_t* vram; union GBOAM* oam; struct mTileCache* cache; + + bool disableBG; + bool disableOBJ; + bool disableWIN; }; DECL_BITFIELD(GBRegisterLCDC, uint8_t); diff --git a/src/core/config.c b/src/core/config.c index b4d654c54..cbb6f32ee 100644 --- a/src/core/config.c +++ b/src/core/config.c @@ -349,6 +349,9 @@ void mCoreConfigMap(const struct mCoreConfig* config, struct mCoreOptions* opts) if (_lookupIntValue(config, "lockAspectRatio", &fakeBool)) { opts->lockAspectRatio = fakeBool; } + if (_lookupIntValue(config, "lockIntegerScaling", &fakeBool)) { + opts->lockIntegerScaling = fakeBool; + } if (_lookupIntValue(config, "resampleVideo", &fakeBool)) { opts->resampleVideo = fakeBool; } @@ -396,6 +399,7 @@ void mCoreConfigLoadDefaults(struct mCoreConfig* config, const struct mCoreOptio ConfigurationSetIntValue(&config->defaultsTable, 0, "volume", opts->volume); ConfigurationSetIntValue(&config->defaultsTable, 0, "mute", opts->mute); ConfigurationSetIntValue(&config->defaultsTable, 0, "lockAspectRatio", opts->lockAspectRatio); + ConfigurationSetIntValue(&config->defaultsTable, 0, "lockIntegerScaling", opts->lockIntegerScaling); ConfigurationSetIntValue(&config->defaultsTable, 0, "resampleVideo", opts->resampleVideo); ConfigurationSetIntValue(&config->defaultsTable, 0, "suspendScreensaver", opts->suspendScreensaver); } diff --git a/src/core/rewind.c b/src/core/rewind.c index 9919acf2e..53ee67188 100644 --- a/src/core/rewind.c +++ b/src/core/rewind.c @@ -50,6 +50,8 @@ void mCoreRewindAppend(struct mCoreRewindContext* context, struct mCore* core) { if (size2 > size) { context->currentState->truncate(context->currentState, size2); size = size2; + } else if (size > size2) { + nextState->truncate(nextState, size); } void* current = context->currentState->map(context->currentState, size, MAP_READ); void* next = nextState->map(nextState, size, MAP_READ); diff --git a/src/debugger/cli-debugger.c b/src/debugger/cli-debugger.c index d5b6dc535..96fc1e1a2 100644 --- a/src/debugger/cli-debugger.c +++ b/src/debugger/cli-debugger.c @@ -751,6 +751,9 @@ void CLIDebuggerAttachSystem(struct CLIDebugger* debugger, struct CLIDebuggerSys } void CLIDebuggerAttachBackend(struct CLIDebugger* debugger, struct CLIDebuggerBackend* backend) { + if (debugger->backend == backend) { + return; + } if (debugger->backend && debugger->backend->deinit) { debugger->backend->deinit(debugger->backend); } diff --git a/src/ds/ds.c b/src/ds/ds.c index 75dcf95dc..da4b15651 100644 --- a/src/ds/ds.c +++ b/src/ds/ds.c @@ -465,11 +465,13 @@ void DSAttachDebugger(struct DS* ds, struct mDebugger* debugger) { } void DSDetachDebugger(struct DS* ds) { - ds->debugger = NULL; - ARMHotplugDetach(ds->ds7.cpu, CPU_COMPONENT_DEBUGGER); - ARMHotplugDetach(ds->ds9.cpu, CPU_COMPONENT_DEBUGGER); + if (ds->debugger) { + ARMHotplugDetach(ds->ds7.cpu, CPU_COMPONENT_DEBUGGER); + ARMHotplugDetach(ds->ds9.cpu, CPU_COMPONENT_DEBUGGER); + } ds->ds7.cpu->components[CPU_COMPONENT_DEBUGGER] = NULL; ds->ds9.cpu->components[CPU_COMPONENT_DEBUGGER] = NULL; + ds->debugger = NULL; } bool DSLoadROM(struct DS* ds, struct VFile* vf) { diff --git a/src/gb/audio.c b/src/gb/audio.c index 7bed5d621..8fbca6d9b 100644 --- a/src/gb/audio.c +++ b/src/gb/audio.c @@ -640,8 +640,8 @@ static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate) { audio->lastRight = sampleRight; audio->clock += audio->sampleInterval; if (audio->clock >= CLOCKS_PER_BLIP_FRAME) { - blip_end_frame(audio->left, audio->clock); - blip_end_frame(audio->right, audio->clock); + blip_end_frame(audio->left, CLOCKS_PER_BLIP_FRAME); + blip_end_frame(audio->right, CLOCKS_PER_BLIP_FRAME); audio->clock -= CLOCKS_PER_BLIP_FRAME; } } diff --git a/src/gb/core.c b/src/gb/core.c index 3ed226cbd..8902220c7 100644 --- a/src/gb/core.c +++ b/src/gb/core.c @@ -629,7 +629,19 @@ static size_t _GBCoreListAudioChannels(const struct mCore* core, const struct mC static void _GBCoreEnableVideoLayer(struct mCore* core, size_t id, bool enable) { struct GB* gb = core->board; - // TODO + switch (id) { + case 0: + gb->video.renderer->disableBG = !enable; + break; + case 1: + gb->video.renderer->disableOBJ = !enable; + break; + case 2: + gb->video.renderer->disableWIN = !enable; + break; + default: + break; + } } static void _GBCoreEnableAudioChannel(struct mCore* core, size_t id, bool enable) { diff --git a/src/gb/renderers/software.c b/src/gb/renderers/software.c index 6c4740d76..c8922e0b8 100644 --- a/src/gb/renderers/software.c +++ b/src/gb/renderers/software.c @@ -49,6 +49,10 @@ void GBVideoSoftwareRendererCreate(struct GBVideoSoftwareRenderer* renderer) { renderer->d.getPixels = GBVideoSoftwareRendererGetPixels; renderer->d.putPixels = GBVideoSoftwareRendererPutPixels; + renderer->d.disableBG = false; + renderer->d.disableOBJ = false; + renderer->d.disableWIN = false; + renderer->temporaryBuffer = 0; } @@ -128,9 +132,12 @@ static void GBVideoSoftwareRendererDrawRange(struct GBVideoRenderer* renderer, i if (GBRegisterLCDCIsTileMap(softwareRenderer->lcdc)) { maps += GB_SIZE_MAP; } + if (softwareRenderer->d.disableBG) { + memset(&softwareRenderer->row[startX], 0, endX - startX); + } if (GBRegisterLCDCIsBgEnable(softwareRenderer->lcdc) || softwareRenderer->model >= GB_MODEL_CGB) { if (GBRegisterLCDCIsWindow(softwareRenderer->lcdc) && softwareRenderer->wy <= y && endX >= softwareRenderer->wx - 7) { - if (softwareRenderer->wx - 7 > 0) { + if (softwareRenderer->wx - 7 > 0 && !softwareRenderer->d.disableBG) { GBVideoSoftwareRendererDrawBackground(softwareRenderer, maps, startX, softwareRenderer->wx - 7, softwareRenderer->scx, softwareRenderer->scy + y); } @@ -138,15 +145,17 @@ static void GBVideoSoftwareRendererDrawRange(struct GBVideoRenderer* renderer, i if (GBRegisterLCDCIsWindowTileMap(softwareRenderer->lcdc)) { maps += GB_SIZE_MAP; } - GBVideoSoftwareRendererDrawBackground(softwareRenderer, maps, softwareRenderer->wx - 7, endX, 7 - softwareRenderer->wx, softwareRenderer->currentWy); - } else { + if (!softwareRenderer->d.disableWIN) { + GBVideoSoftwareRendererDrawBackground(softwareRenderer, maps, softwareRenderer->wx - 7, endX, 7 - softwareRenderer->wx, softwareRenderer->currentWy); + } + } else if (!softwareRenderer->d.disableBG) { GBVideoSoftwareRendererDrawBackground(softwareRenderer, maps, startX, endX, softwareRenderer->scx, softwareRenderer->scy + y); } - } else { + } else if (!softwareRenderer->d.disableBG) { memset(&softwareRenderer->row[startX], 0, endX - startX); } - if (GBRegisterLCDCIsObjEnable(softwareRenderer->lcdc)) { + if (GBRegisterLCDCIsObjEnable(softwareRenderer->lcdc) && !softwareRenderer->d.disableOBJ) { size_t i; for (i = 0; i < oamMax; ++i) { GBVideoSoftwareRendererDrawObj(softwareRenderer, &obj[i], startX, endX, y); diff --git a/src/gba/audio.c b/src/gba/audio.c index 2ceba8a5a..be736e78b 100644 --- a/src/gba/audio.c +++ b/src/gba/audio.c @@ -297,8 +297,8 @@ static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate) { audio->lastRight = sampleRight; audio->clock += audio->sampleInterval; if (audio->clock >= CLOCKS_PER_FRAME) { - blip_end_frame(audio->psg.left, audio->clock); - blip_end_frame(audio->psg.right, audio->clock); + blip_end_frame(audio->psg.left, CLOCKS_PER_FRAME); + blip_end_frame(audio->psg.right, CLOCKS_PER_FRAME); audio->clock -= CLOCKS_PER_FRAME; } } diff --git a/src/gba/bios.c b/src/gba/bios.c index d119caadd..dd420f364 100644 --- a/src/gba/bios.c +++ b/src/gba/bios.c @@ -271,7 +271,7 @@ static void _Div(struct GBA* gba, int32_t num, int32_t denom) { } } -static int16_t _ArcTan(int16_t i) { +static int16_t _ArcTan(int32_t i, int32_t* r1, int32_t* r3) { int32_t a = -((i * i) >> 14); int32_t b = ((0xA9 * a) >> 14) + 0x390; b = ((b * a) >> 14) + 0x91C; @@ -280,10 +280,16 @@ static int16_t _ArcTan(int16_t i) { b = ((b * a) >> 14) + 0x2081; b = ((b * a) >> 14) + 0x3651; b = ((b * a) >> 14) + 0xA2F9; + if (r1) { + *r1 = a; + } + if (r3) { + *r3 = b; + } return (i * b) >> 16; } -static int16_t _ArcTan2(int16_t x, int16_t y) { +static int16_t _ArcTan2(int32_t x, int32_t y, int32_t* r1) { if (!y) { if (x >= 0) { return 0; @@ -299,21 +305,21 @@ static int16_t _ArcTan2(int16_t x, int16_t y) { if (y >= 0) { if (x >= 0) { if (x >= y) { - return _ArcTan((y << 14)/ x); + return _ArcTan((y << 14) / x, r1, NULL); } } else if (-x >= y) { - return _ArcTan((y << 14) / x) + 0x8000; + return _ArcTan((y << 14) / x, r1, NULL) + 0x8000; } - return 0x4000 - _ArcTan((x << 14) / y); + return 0x4000 - _ArcTan((x << 14) / y, r1, NULL); } else { if (x <= 0) { if (-x > -y) { - return _ArcTan((y << 14) / x) + 0x8000; + return _ArcTan((y << 14) / x, r1, NULL) + 0x8000; } } else if (x >= -y) { - return _ArcTan((y << 14) / x) + 0x10000; + return _ArcTan((y << 14) / x, r1, NULL) + 0x10000; } - return 0xC000 - _ArcTan((x << 14) / y); + return 0xC000 - _ArcTan((x << 14) / y, r1, NULL); } } @@ -356,10 +362,11 @@ void GBASwi16(struct ARMCore* cpu, int immediate) { cpu->gprs[0] = sqrt((uint32_t) cpu->gprs[0]); break; case 0x9: - cpu->gprs[0] = (uint16_t) _ArcTan(cpu->gprs[0]); + cpu->gprs[0] = _ArcTan(cpu->gprs[0], &cpu->gprs[1], &cpu->gprs[3]); break; case 0xA: - cpu->gprs[0] = (uint16_t) _ArcTan2(cpu->gprs[0], cpu->gprs[1]); + cpu->gprs[0] = (uint16_t) _ArcTan2(cpu->gprs[0], cpu->gprs[1], &cpu->gprs[1]); + cpu->gprs[3] = 0x170; break; case 0xB: case 0xC: diff --git a/src/gba/gba.c b/src/gba/gba.c index 053abd61e..c0eeb6b31 100644 --- a/src/gba/gba.c +++ b/src/gba/gba.c @@ -268,6 +268,7 @@ static void GBAProcessEvents(struct ARMCore* cpu) { } } +#ifdef USE_DEBUGGERS void GBAAttachDebugger(struct GBA* gba, struct mDebugger* debugger) { gba->debugger = (struct ARMDebugger*) debugger->platform; gba->debugger->setSoftwareBreakpoint = _setSoftwareBreakpoint; @@ -277,10 +278,13 @@ void GBAAttachDebugger(struct GBA* gba, struct mDebugger* debugger) { } void GBADetachDebugger(struct GBA* gba) { - gba->debugger = 0; - ARMHotplugDetach(gba->cpu, CPU_COMPONENT_DEBUGGER); - gba->cpu->components[CPU_COMPONENT_DEBUGGER] = 0; + if (gba->debugger) { + ARMHotplugDetach(gba->cpu, CPU_COMPONENT_DEBUGGER); + } + gba->cpu->components[CPU_COMPONENT_DEBUGGER] = NULL; + gba->debugger = NULL; } +#endif bool GBALoadMB(struct GBA* gba, struct VFile* vf) { GBAUnloadROM(gba); diff --git a/src/gba/renderers/video-software.c b/src/gba/renderers/video-software.c index 51c8dc3d0..2d0876a71 100644 --- a/src/gba/renderers/video-software.c +++ b/src/gba/renderers/video-software.c @@ -581,10 +581,12 @@ static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* render } } } - softwareRenderer->bg[2].sx += softwareRenderer->bg[2].dmx; - softwareRenderer->bg[2].sy += softwareRenderer->bg[2].dmy; - softwareRenderer->bg[3].sx += softwareRenderer->bg[3].dmx; - softwareRenderer->bg[3].sy += softwareRenderer->bg[3].dmy; + if (GBARegisterDISPCNTGetMode(softwareRenderer->dispcnt) != 0) { + softwareRenderer->bg[2].sx += softwareRenderer->bg[2].dmx; + softwareRenderer->bg[2].sy += softwareRenderer->bg[2].dmy; + softwareRenderer->bg[3].sx += softwareRenderer->bg[3].dmx; + softwareRenderer->bg[3].sy += softwareRenderer->bg[3].dmy; + } GBAVideoSoftwareRendererPostprocessBuffer(softwareRenderer); diff --git a/src/platform/opengl/gl.c b/src/platform/opengl/gl.c index 46d468d1b..cb9bdd106 100644 --- a/src/platform/opengl/gl.c +++ b/src/platform/opengl/gl.c @@ -67,6 +67,10 @@ static void mGLContextResized(struct VideoBackend* v, unsigned w, unsigned h) { drawH = w * v->height / v->width; } } + if (v->lockIntegerScaling) { + drawW -= drawW % v->width; + drawH -= drawH % v->height; + } glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glClearColor(0, 0, 0, 0); diff --git a/src/platform/opengl/gles2.c b/src/platform/opengl/gles2.c index 2dff30f3d..47842273e 100644 --- a/src/platform/opengl/gles2.c +++ b/src/platform/opengl/gles2.c @@ -171,6 +171,10 @@ static void mGLES2ContextResized(struct VideoBackend* v, unsigned w, unsigned h) drawH = w * v->height / v->width; } } + if (v->lockIntegerScaling) { + drawW -= drawW % v->width; + drawH -= drawH % v->height; + } glViewport(0, 0, v->width, v->height); glClearColor(0.f, 0.f, 0.f, 1.f); glClear(GL_COLOR_BUFFER_BIT); diff --git a/src/platform/psp2/main.c b/src/platform/psp2/main.c index 95dabc13c..d41ae49c6 100644 --- a/src/platform/psp2/main.c +++ b/src/platform/psp2/main.c @@ -16,7 +16,6 @@ #include #include #include -#include #include #include #include @@ -164,6 +163,7 @@ int main() { mPSP2MapKey(&runner.params.keyMap, SCE_CTRL_RIGHT, GUI_INPUT_RIGHT); mPSP2MapKey(&runner.params.keyMap, SCE_CTRL_SQUARE, mGUI_INPUT_SCREEN_MODE); + scePowerSetArmClockFrequency(444); mGUIRunloop(&runner); vita2d_fini(); diff --git a/src/platform/psp2/psp2-context.c b/src/platform/psp2/psp2-context.c index b3aba3593..b2bef2bae 100644 --- a/src/platform/psp2/psp2-context.c +++ b/src/platform/psp2/psp2-context.c @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -31,7 +32,6 @@ #include #include #include -#include #include @@ -63,8 +63,8 @@ bool frameLimiter = true; extern const uint8_t _binary_backdrop_png_start[]; static vita2d_texture* backdrop = 0; -#define PSP2_SAMPLES 128 -#define PSP2_AUDIO_BUFFER_SIZE (PSP2_SAMPLES * 40) +#define PSP2_SAMPLES 256 +#define PSP2_AUDIO_BUFFER_SIZE (PSP2_SAMPLES * 20) static struct mPSP2AudioContext { struct RingFIFO buffer; @@ -176,7 +176,6 @@ void mPSP2Setup(struct mGUIRunner* runner) { mCoreConfigSetDefaultIntValue(&runner->config, "threadedVideo", 1); mCoreLoadForeignConfig(runner->core, &runner->config); - scePowerSetArmClockFrequency(333); mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_CROSS, GBA_KEY_A); mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_CIRCLE, GBA_KEY_B); mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_START, GBA_KEY_START); @@ -193,8 +192,10 @@ void mPSP2Setup(struct mGUIRunner* runner) { desc = (struct mInputAxis) { GBA_KEY_RIGHT, GBA_KEY_LEFT, 192, 64 }; mInputBindAxis(&runner->core->inputMap, PSP2_INPUT, 1, &desc); - tex = vita2d_create_empty_texture_format(256, 256, SCE_GXM_TEXTURE_FORMAT_X8U8U8U8_1BGR); - screenshot = vita2d_create_empty_texture_format(256, 256, SCE_GXM_TEXTURE_FORMAT_X8U8U8U8_1BGR); + unsigned width, height; + runner->core->desiredVideoDimensions(runner->core, &width, &height); + tex = vita2d_create_empty_texture_format(256, toPow2(height), SCE_GXM_TEXTURE_FORMAT_X8U8U8U8_1BGR); + screenshot = vita2d_create_empty_texture_format(256, toPow2(height), SCE_GXM_TEXTURE_FORMAT_X8U8U8U8_1BGR); outputBuffer = vita2d_texture_get_datap(tex); runner->core->setVideoBuffer(runner->core, outputBuffer, 256); @@ -219,7 +220,6 @@ void mPSP2Setup(struct mGUIRunner* runner) { } void mPSP2LoadROM(struct mGUIRunner* runner) { - scePowerSetArmClockFrequency(444); float rate = 60.0f / 1.001f; sceDisplayGetRefreshRate(&rate); double ratio = GBAAudioCalculateRatio(1, rate, 1); @@ -262,7 +262,6 @@ void mPSP2PrepareForFrame(struct mGUIRunner* runner) { blip_clear(runner->core->getAudioChannel(runner->core, 1)); break; } - sceKernelDelayThread(400); } blip_read_samples(runner->core->getAudioChannel(runner->core, 0), &samples[0].left, PSP2_SAMPLES, true); blip_read_samples(runner->core->getAudioChannel(runner->core, 1), &samples[0].right, PSP2_SAMPLES, true); @@ -297,7 +296,6 @@ void mPSP2UnloadROM(struct mGUIRunner* runner) { default: break; } - scePowerSetArmClockFrequency(333); } void mPSP2Paused(struct mGUIRunner* runner) { diff --git a/src/platform/python/CMakeLists.txt b/src/platform/python/CMakeLists.txt index adda679bd..7b7605e7d 100644 --- a/src/platform/python/CMakeLists.txt +++ b/src/platform/python/CMakeLists.txt @@ -2,8 +2,13 @@ find_program(PYTHON python) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in ${CMAKE_CURRENT_BINARY_DIR}/setup.py) +get_property(INCLUDE_DIRECTORIES DIRECTORY PROPERTY INCLUDE_DIRECTORIES) +set(INCLUDE_FLAGS) +foreach(DIR IN LISTS INCLUDE_DIRECTORIES) + list(APPEND INCLUDE_FLAGS "-I${DIR}") +endforeach() add_custom_command(OUTPUT build/lib/${BINARY_NAME}/__init__.py - COMMAND BINDIR=${CMAKE_CURRENT_BINARY_DIR}/.. ${PYTHON} ${CMAKE_CURRENT_BINARY_DIR}/setup.py build --build-base ${CMAKE_CURRENT_BINARY_DIR} + COMMAND BINDIR=${CMAKE_CURRENT_BINARY_DIR}/.. CPPFLAGS="${INCLUDE_FLAGS}" ${PYTHON} ${CMAKE_CURRENT_BINARY_DIR}/setup.py build --build-base ${CMAKE_CURRENT_BINARY_DIR} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} DEPENDS ${BINARY_NAME} DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/setup.py diff --git a/src/platform/qt/DebuggerConsoleController.cpp b/src/platform/qt/DebuggerConsoleController.cpp index 0752eb89e..ff8577414 100644 --- a/src/platform/qt/DebuggerConsoleController.cpp +++ b/src/platform/qt/DebuggerConsoleController.cpp @@ -38,8 +38,16 @@ void DebuggerConsoleController::enterLine(const QString& line) { m_cond.wakeOne(); } +void DebuggerConsoleController::detach() { + m_lines.append(QString()); + m_cond.wakeOne(); + DebuggerController::detach(); +} + void DebuggerConsoleController::attachInternal() { + m_history.clear(); mCore* core = m_gameController->thread()->core; + CLIDebuggerAttachBackend(&m_cliDebugger, &m_backend.d); CLIDebuggerAttachSystem(&m_cliDebugger, core->cliDebuggerSystem(core)); } @@ -60,6 +68,8 @@ void DebuggerConsoleController::init(struct CLIDebuggerBackend* be) { void DebuggerConsoleController::deinit(struct CLIDebuggerBackend* be) { Backend* consoleBe = reinterpret_cast(be); DebuggerConsoleController* self = consoleBe->self; + self->m_lines.append(QString()); + self->m_cond.wakeOne(); } const char* DebuggerConsoleController::readLine(struct CLIDebuggerBackend* be, size_t* len) { @@ -71,6 +81,9 @@ const char* DebuggerConsoleController::readLine(struct CLIDebuggerBackend* be, s self->m_cond.wait(&self->m_mutex); } self->m_last = self->m_lines.takeFirst().toUtf8(); + if (self->m_last.isEmpty()) { + self->m_last = "\n"; + } *len = self->m_last.size(); return self->m_last.constData(); @@ -87,6 +100,9 @@ const char* DebuggerConsoleController::historyLast(struct CLIDebuggerBackend* be DebuggerConsoleController* self = consoleBe->self; GameController::Interrupter interrupter(self->m_gameController, true); QMutexLocker lock(&self->m_mutex); + if (self->m_history.isEmpty()) { + return "\n"; + } self->m_last = self->m_history.last().toUtf8(); return self->m_last.constData(); } diff --git a/src/platform/qt/DebuggerConsoleController.h b/src/platform/qt/DebuggerConsoleController.h index 36e7903cb..543dabeae 100644 --- a/src/platform/qt/DebuggerConsoleController.h +++ b/src/platform/qt/DebuggerConsoleController.h @@ -30,6 +30,7 @@ signals: public slots: void enterLine(const QString&); + virtual void detach() override; protected: virtual void attachInternal() override; diff --git a/src/platform/qt/Display.cpp b/src/platform/qt/Display.cpp index 64ba9c96e..c0b062afc 100644 --- a/src/platform/qt/Display.cpp +++ b/src/platform/qt/Display.cpp @@ -54,6 +54,7 @@ Display* Display::create(QWidget* parent) { Display::Display(QWidget* parent) : QWidget(parent) , m_lockAspectRatio(false) + , m_lockIntegerScaling(false) , m_filter(false) { setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); @@ -78,6 +79,10 @@ QSize Display::viewportSize() { ds.setHeight(s.width() * m_coreHeight / m_coreWidth); } } + if (isIntegerScalingLocked()) { + ds.setWidth(ds.width() - ds.width() % m_coreWidth); + ds.setHeight(ds.height() - ds.height() % m_coreHeight); + } return ds; } @@ -90,6 +95,10 @@ void Display::lockAspectRatio(bool lock) { m_messagePainter.resize(size(), m_lockAspectRatio, devicePixelRatio()); } +void Display::lockIntegerScaling(bool lock) { + m_lockIntegerScaling = lock; +} + void Display::filter(bool filter) { m_filter = filter; } diff --git a/src/platform/qt/Display.h b/src/platform/qt/Display.h index 54d728b9d..b836f6047 100644 --- a/src/platform/qt/Display.h +++ b/src/platform/qt/Display.h @@ -38,6 +38,7 @@ public: static void setDriver(Driver driver) { s_driver = driver; } bool isAspectRatioLocked() const { return m_lockAspectRatio; } + bool isIntegerScalingLocked() const { return m_lockIntegerScaling; } bool isFiltered() const { return m_filter; } virtual bool isDrawing() const = 0; @@ -57,6 +58,7 @@ public slots: virtual void unpauseDrawing() = 0; virtual void forceDraw() = 0; virtual void lockAspectRatio(bool lock); + virtual void lockIntegerScaling(bool lock); virtual void filter(bool filter); virtual void framePosted(const uint32_t*) = 0; virtual void setShaders(struct VDir*) = 0; @@ -78,6 +80,7 @@ private: MessagePainter m_messagePainter; bool m_lockAspectRatio; + bool m_lockIntegerScaling; bool m_filter; QTimer m_mouseTimer; int m_coreWidth; diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index 6afdd927b..5adfa8d56 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -73,6 +73,8 @@ void DisplayGL::startDrawing(mCoreThread* thread) { mCoreSyncSetVideoSync(&m_context->sync, false); lockAspectRatio(isAspectRatioLocked()); + lockIntegerScaling(isIntegerScalingLocked()); + unsigned width, height; thread->core->desiredVideoDimensions(thread->core, &width, &height); setSystemDimensions(width, height); @@ -140,6 +142,13 @@ void DisplayGL::lockAspectRatio(bool lock) { } } +void DisplayGL::lockIntegerScaling(bool lock) { + Display::lockIntegerScaling(lock); + if (m_drawThread) { + QMetaObject::invokeMethod(m_painter, "lockIntegerScaling", Q_ARG(bool, lock)); + } +} + void DisplayGL::filter(bool filter) { Display::filter(filter); if (m_drawThread) { @@ -294,6 +303,13 @@ void PainterGL::lockAspectRatio(bool lock) { } } +void PainterGL::lockIntegerScaling(bool lock) { + m_backend->lockIntegerScaling = lock; + if (m_started && !m_active) { + forceDraw(); + } +} + void PainterGL::filter(bool filter) { m_backend->filter = filter; if (m_started && !m_active) { diff --git a/src/platform/qt/DisplayGL.h b/src/platform/qt/DisplayGL.h index b4b4bd056..0a7227c01 100644 --- a/src/platform/qt/DisplayGL.h +++ b/src/platform/qt/DisplayGL.h @@ -55,6 +55,7 @@ public slots: void unpauseDrawing() override; void forceDraw() override; void lockAspectRatio(bool lock) override; + void lockIntegerScaling(bool lock) override; void filter(bool filter) override; void framePosted(const uint32_t*) override; void setShaders(struct VDir*) override; @@ -96,6 +97,7 @@ public slots: void unpause(); void resize(const QSize& size); void lockAspectRatio(bool lock); + void lockIntegerScaling(bool lock); void filter(bool filter); void setShaders(struct VDir*); diff --git a/src/platform/qt/DisplayQt.cpp b/src/platform/qt/DisplayQt.cpp index 0ca443e8b..88b79c3fe 100644 --- a/src/platform/qt/DisplayQt.cpp +++ b/src/platform/qt/DisplayQt.cpp @@ -31,6 +31,11 @@ void DisplayQt::lockAspectRatio(bool lock) { update(); } +void DisplayQt::lockIntegerScaling(bool lock) { + Display::lockIntegerScaling(lock); + update(); +} + void DisplayQt::filter(bool filter) { Display::filter(filter); update(); diff --git a/src/platform/qt/DisplayQt.h b/src/platform/qt/DisplayQt.h index d40b1053c..eb7ec5723 100644 --- a/src/platform/qt/DisplayQt.h +++ b/src/platform/qt/DisplayQt.h @@ -30,6 +30,7 @@ public slots: void unpauseDrawing() override { m_isDrawing = true; } void forceDraw() override { update(); } void lockAspectRatio(bool lock) override; + void lockIntegerScaling(bool lock) override; void filter(bool filter) override; void framePosted(const uint32_t*) override; void setShaders(struct VDir*) override {} diff --git a/src/platform/qt/GameController.cpp b/src/platform/qt/GameController.cpp index 84aee5d66..7422496f8 100644 --- a/src/platform/qt/GameController.cpp +++ b/src/platform/qt/GameController.cpp @@ -623,6 +623,7 @@ void GameController::closeGame() { return; } + setDebugger(nullptr); if (mCoreThreadIsPaused(&m_threadContext)) { mCoreThreadUnpause(&m_threadContext); } diff --git a/src/platform/qt/SettingsView.cpp b/src/platform/qt/SettingsView.cpp index bca13d294..7f2198546 100644 --- a/src/platform/qt/SettingsView.cpp +++ b/src/platform/qt/SettingsView.cpp @@ -177,6 +177,7 @@ void SettingsView::updateConfig() { saveSetting("frameskip", m_ui.frameskip); saveSetting("fpsTarget", m_ui.fpsTarget); saveSetting("lockAspectRatio", m_ui.lockAspectRatio); + saveSetting("lockIntegerScaling", m_ui.lockIntegerScaling); saveSetting("volume", m_ui.volume); saveSetting("mute", m_ui.mute); saveSetting("rewindEnable", m_ui.rewind); @@ -260,6 +261,7 @@ void SettingsView::reloadConfig() { loadSetting("frameskip", m_ui.frameskip); loadSetting("fpsTarget", m_ui.fpsTarget); loadSetting("lockAspectRatio", m_ui.lockAspectRatio); + loadSetting("lockIntegerScaling", m_ui.lockIntegerScaling); loadSetting("volume", m_ui.volume); loadSetting("mute", m_ui.mute); loadSetting("rewindEnable", m_ui.rewind); diff --git a/src/platform/qt/SettingsView.ui b/src/platform/qt/SettingsView.ui index fa264cdbd..db0b49728 100644 --- a/src/platform/qt/SettingsView.ui +++ b/src/platform/qt/SettingsView.ui @@ -7,7 +7,7 @@ 0 0 650 - 450 + 454 @@ -115,7 +115,7 @@ true - + 1536 @@ -181,7 +181,7 @@ true - + 44100 @@ -380,13 +380,20 @@ - + Bilinear filtering + + + + Force integer scaling + + + diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 05fd28e45..fa6cf2e6a 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -493,6 +493,7 @@ void Window::selectPatch() { void Window::openView(QWidget* widget) { connect(this, SIGNAL(shutdown()), widget, SLOT(close())); + connect(m_controller, SIGNAL(gameStopped(mCoreThread*)), widget, SLOT(close())); widget->setAttribute(Qt::WA_DeleteOnClose); widget->show(); } @@ -811,6 +812,39 @@ void Window::gameStarted(mCoreThread* context, const QString& fname) { m_hitUnimplementedBiosCall = false; m_fpsTimer.start(); m_focusCheck.start(); + + m_controller->threadInterrupt(); + if (m_controller->isLoaded()) { + mCore* core = m_controller->thread()->core; + const mCoreChannelInfo* videoLayers; + const mCoreChannelInfo* audioChannels; + size_t nVideo = core->listVideoLayers(core, &videoLayers); + size_t nAudio = core->listAudioChannels(core, &audioChannels); + + if (nVideo) { + for (size_t i = 0; i < nVideo; ++i) { + QAction* action = new QAction(videoLayers[i].visibleName, m_videoLayers); + action->setCheckable(true); + action->setChecked(true); + connect(action, &QAction::triggered, [this, videoLayers, i](bool enable) { + m_controller->setVideoLayerEnabled(videoLayers[i].id, enable); + }); + m_videoLayers->addAction(action); + } + } + if (nAudio) { + for (size_t i = 0; i < nAudio; ++i) { + QAction* action = new QAction(audioChannels[i].visibleName, m_audioChannels); + action->setCheckable(true); + action->setChecked(true); + connect(action, &QAction::triggered, [this, audioChannels, i](bool enable) { + m_controller->setAudioChannelEnabled(audioChannels[i].id, enable); + }); + m_audioChannels->addAction(action); + } + } + } + m_controller->threadContinue(); } void Window::gameStopped() { @@ -834,6 +868,9 @@ void Window::gameStopped() { m_screenWidget->setMinimumSize(m_display->minimumSize()); setMouseTracking(false); + m_videoLayers->clear(); + m_audioChannels->clear(); + m_fpsTimer.stop(); m_focusCheck.stop(); } @@ -1301,6 +1338,14 @@ void Window::setupMenu(QMenuBar* menubar) { }, this); m_config->updateOption("lockAspectRatio"); + ConfigOption* lockIntegerScaling = m_config->addOption("lockIntegerScaling"); + lockIntegerScaling->addBoolean(tr("Force integer scaling"), avMenu); + lockIntegerScaling->connect([this](const QVariant& value) { + m_display->lockIntegerScaling(value.toBool()); + m_screenWidget->setLockIntegerScaling(value.toBool()); + }, this); + m_config->updateOption("lockIntegerScaling"); + ConfigOption* resampleVideo = m_config->addOption("resampleVideo"); resampleVideo->addBoolean(tr("Bilinear filtering"), avMenu); resampleVideo->connect([this](const QVariant& value) { @@ -1376,45 +1421,12 @@ void Window::setupMenu(QMenuBar* menubar) { #endif avMenu->addSeparator(); - QMenu* videoLayers = avMenu->addMenu(tr("Video layers")); - m_inputModel->addMenu(videoLayers, avMenu); - for (int i = 0; i < 4; ++i) { - QAction* enableBg = new QAction(tr("Background %0").arg(i), videoLayers); - enableBg->setCheckable(true); - enableBg->setChecked(true); - connect(enableBg, &QAction::triggered, [this, i](bool enable) { m_controller->setVideoLayerEnabled(i, enable); }); - addControlledAction(videoLayers, enableBg, QString("enableBG%0").arg(i)); - } + m_videoLayers = avMenu->addMenu(tr("Video layers")); + m_inputModel->addMenu(m_videoLayers, avMenu); - QAction* enableObj = new QAction(tr("OBJ (sprites)"), videoLayers); - enableObj->setCheckable(true); - enableObj->setChecked(true); - connect(enableObj, &QAction::triggered, [this](bool enable) { m_controller->setVideoLayerEnabled(4, enable); }); - addControlledAction(videoLayers, enableObj, "enableOBJ"); - - QMenu* audioChannels = avMenu->addMenu(tr("Audio channels")); - m_inputModel->addMenu(audioChannels, avMenu); - - for (int i = 0; i < 4; ++i) { - QAction* enableCh = new QAction(tr("Channel %0").arg(i + 1), audioChannels); - enableCh->setCheckable(true); - enableCh->setChecked(true); - connect(enableCh, &QAction::triggered, [this, i](bool enable) { m_controller->setAudioChannelEnabled(i, enable); }); - addControlledAction(audioChannels, enableCh, QString("enableCh%0").arg(i + 1)); - } - - QAction* enableChA = new QAction(tr("Channel A"), audioChannels); - enableChA->setCheckable(true); - enableChA->setChecked(true); - connect(enableChA, &QAction::triggered, [this, i](bool enable) { m_controller->setAudioChannelEnabled(4, enable); }); - addControlledAction(audioChannels, enableChA, QString("enableChA")); - - QAction* enableChB = new QAction(tr("Channel B"), audioChannels); - enableChB->setCheckable(true); - enableChB->setChecked(true); - connect(enableChB, &QAction::triggered, [this, i](bool enable) { m_controller->setAudioChannelEnabled(5, enable); }); - addControlledAction(audioChannels, enableChB, QString("enableChB")); + m_audioChannels = avMenu->addMenu(tr("Audio channels")); + m_inputModel->addMenu(m_audioChannels, avMenu); QMenu* toolsMenu = menubar->addMenu(tr("&Tools")); m_inputModel->addMenu(toolsMenu); @@ -1657,6 +1669,10 @@ void WindowBackground::setCenteredAspectRatio(int width, int height) { m_aspectHeight = height; } +void WindowBackground::setLockIntegerScaling(bool lock) { + m_lockIntegerScaling = lock; +} + void WindowBackground::paintEvent(QPaintEvent*) { const QPixmap* logo = pixmap(); if (!logo) { @@ -1680,6 +1696,10 @@ void WindowBackground::paintEvent(QPaintEvent*) { ds.setHeight(ds.width() * m_aspectHeight / m_aspectWidth); } } + if (m_lockIntegerScaling) { + ds.setWidth(ds.width() - ds.width() % m_aspectWidth); + ds.setHeight(ds.height() - ds.height() % m_aspectHeight); + } QPoint origin = QPoint((s.width() - ds.width()) / 2, (s.height() - ds.height()) / 2); QRect full(origin, ds); painter.drawPixmap(full, *logo); diff --git a/src/platform/qt/Window.h b/src/platform/qt/Window.h index 5a461a459..597533e7e 100644 --- a/src/platform/qt/Window.h +++ b/src/platform/qt/Window.h @@ -172,6 +172,8 @@ private: QTimer m_fpsTimer; QList m_mruFiles; QMenu* m_mruMenu; + QMenu* m_videoLayers; + QMenu* m_audioChannels; ShaderSelector* m_shaderView; bool m_fullscreenOnStart; QTimer m_focusCheck; @@ -207,6 +209,7 @@ public: virtual QSize sizeHint() const override; void setLockAspectRatio(int width, int height); void setCenteredAspectRatio(int width, int height); + void setLockIntegerScaling(bool lock); protected: virtual void paintEvent(QPaintEvent*) override; @@ -216,6 +219,7 @@ private: bool m_centered; int m_aspectWidth; int m_aspectHeight; + bool m_lockIntegerScaling; }; } diff --git a/src/platform/video-backend.h b/src/platform/video-backend.h index 79264c4e2..f45a56dc5 100644 --- a/src/platform/video-backend.h +++ b/src/platform/video-backend.h @@ -35,6 +35,7 @@ struct VideoBackend { bool filter; bool lockAspectRatio; + bool lockIntegerScaling; }; struct VideoShader {