From dd531637c20b2848d123440386895cc5db5a737f Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 12 Feb 2023 02:10:51 -0800 Subject: [PATCH 01/17] Core: Revise screen size/info API --- include/mgba/core/core.h | 6 ++- include/mgba/core/interface.h | 9 +++++ include/mgba/internal/gb/video.h | 3 ++ src/core/core.c | 2 +- src/core/serialize.c | 4 +- src/gb/core.c | 44 ++++++++++++++++++--- src/gba/core.c | 43 +++++++++++++++++--- src/platform/3ds/main.c | 2 +- src/platform/example/client-server/server.c | 2 +- src/platform/libretro/libretro.c | 18 +++------ src/platform/openemu/mGBAGameCore.m | 6 +-- src/platform/psp2/psp2-context.c | 4 +- src/platform/python/mgba/core.py | 2 +- src/platform/qt/CoreController.cpp | 4 +- src/platform/qt/FrameView.cpp | 2 +- src/platform/sdl/gl-sdl.c | 2 +- src/platform/sdl/main.c | 4 +- src/platform/sdl/sw-sdl1.c | 2 +- src/platform/sdl/sw-sdl2.c | 2 +- src/platform/switch/main.c | 4 +- src/platform/test/cinema-main.c | 6 +-- src/platform/wii/main.c | 2 +- 22 files changed, 125 insertions(+), 48 deletions(-) diff --git a/include/mgba/core/core.h b/include/mgba/core/core.h index d3f2b4e04..7a6e89f56 100644 --- a/include/mgba/core/core.h +++ b/include/mgba/core/core.h @@ -66,7 +66,11 @@ struct mCore { void (*loadConfig)(struct mCore*, const struct mCoreConfig*); void (*reloadConfigOption)(struct mCore*, const char* option, const struct mCoreConfig*); - void (*desiredVideoDimensions)(const struct mCore*, unsigned* width, unsigned* height); + void (*baseVideoSize)(const struct mCore*, unsigned* width, unsigned* height); + void (*currentVideoSize)(const struct mCore*, unsigned* width, unsigned* height); + unsigned (*videoScale)(const struct mCore*); + size_t (*screenRegions)(const struct mCore*, const struct mCoreScreenRegion**); + void (*setVideoBuffer)(struct mCore*, color_t* buffer, size_t stride); void (*setVideoGLTex)(struct mCore*, unsigned texid); diff --git a/include/mgba/core/interface.h b/include/mgba/core/interface.h index 9046d1a4f..21c776744 100644 --- a/include/mgba/core/interface.h +++ b/include/mgba/core/interface.h @@ -293,6 +293,15 @@ struct mCoreMemoryBlock { uint32_t segmentStart; }; +struct mCoreScreenRegion { + size_t id; + const char* description; + int16_t x; + int16_t y; + int16_t w; + int16_t h; +}; + enum mCoreRegisterType { mCORE_REGISTER_GPR = 0, mCORE_REGISTER_FPR, diff --git a/include/mgba/internal/gb/video.h b/include/mgba/internal/gb/video.h index 31c54b089..c9b179739 100644 --- a/include/mgba/internal/gb/video.h +++ b/include/mgba/internal/gb/video.h @@ -22,6 +22,9 @@ enum { GB_VIDEO_VBLANK_PIXELS = 10, GB_VIDEO_VERTICAL_TOTAL_PIXELS = 154, + SGB_VIDEO_HORIZONTAL_PIXELS = 256, + SGB_VIDEO_VERTICAL_PIXELS = 224, + // TODO: Figure out exact lengths GB_VIDEO_MODE_2_LENGTH = 80, GB_VIDEO_MODE_3_LENGTH_BASE = 172, diff --git a/src/core/core.c b/src/core/core.c index c4c3a3ce8..f28b8b245 100644 --- a/src/core/core.c +++ b/src/core/core.c @@ -361,7 +361,7 @@ bool mCoreTakeScreenshotVF(struct mCore* core, struct VFile* vf) { size_t stride; const void* pixels = 0; unsigned width, height; - core->desiredVideoDimensions(core, &width, &height); + core->currentVideoSize(core, &width, &height); core->getPixels(core, &pixels, &stride); png_structp png = PNGWriteOpen(vf); png_infop info = PNGWriteHeader(png, width, height); diff --git a/src/core/serialize.c b/src/core/serialize.c index 53e0aa168..c4b0b70a3 100644 --- a/src/core/serialize.c +++ b/src/core/serialize.c @@ -176,7 +176,7 @@ static bool _savePNGState(struct mCore* core, struct VFile* vf, struct mStateExt mappedMemoryFree(state, stateSize); unsigned width, height; - core->desiredVideoDimensions(core, &width, &height); + core->currentVideoSize(core, &width, &height); png_structp png = PNGWriteOpen(vf); png_infop info = PNGWriteHeader(png, width, height); if (!png || !info) { @@ -529,7 +529,7 @@ bool mCoreLoadStateNamed(struct mCore* core, struct VFile* vf, int flags) { mappedMemoryFree(state, core->stateSize(core)); unsigned width, height; - core->desiredVideoDimensions(core, &width, &height); + core->currentVideoSize(core, &width, &height); struct mStateExtdataItem item; if (flags & SAVESTATE_SCREENSHOT && mStateExtdataGet(&extdata, EXTDATA_SCREENSHOT, &item)) { diff --git a/src/gb/core.c b/src/gb/core.c index 59fb7c016..b35616f2b 100644 --- a/src/gb/core.c +++ b/src/gb/core.c @@ -60,6 +60,15 @@ static const struct mCoreMemoryBlock _GBCMemoryBlocks[] = { { GB_BASE_HRAM, "hram", "HRAM", "High RAM", GB_BASE_HRAM, GB_BASE_HRAM + GB_SIZE_HRAM, GB_SIZE_HRAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, }; +static const struct mCoreScreenRegion _GBScreenRegions[] = { + { 0, "Screen", 0, 0, GB_VIDEO_HORIZONTAL_PIXELS, GB_VIDEO_VERTICAL_PIXELS } +}; + +static const struct mCoreScreenRegion _SGBScreenRegions[] = { + { 0, "Screen", (SGB_VIDEO_HORIZONTAL_PIXELS - GB_VIDEO_HORIZONTAL_PIXELS) / 2, (SGB_VIDEO_VERTICAL_PIXELS - GB_VIDEO_VERTICAL_PIXELS) / 2, GB_VIDEO_HORIZONTAL_PIXELS, GB_VIDEO_VERTICAL_PIXELS }, + { 1, "Border", 0, 0, SGB_VIDEO_HORIZONTAL_PIXELS, SGB_VIDEO_VERTICAL_PIXELS }, +}; + static const struct mCoreRegisterInfo _GBRegisters[] = { { "b", NULL, 1, 0xFF, mCORE_REGISTER_GPR }, { "c", NULL, 1, 0xFF, mCORE_REGISTER_GPR }, @@ -355,14 +364,36 @@ static void _GBCoreReloadConfigOption(struct mCore* core, const char* option, co } } -static void _GBCoreDesiredVideoDimensions(const struct mCore* core, unsigned* width, unsigned* height) { +static void _GBCoreBaseVideoSize(const struct mCore* core, unsigned* width, unsigned* height) { + UNUSED(core); + *width = SGB_VIDEO_HORIZONTAL_PIXELS; + *height = SGB_VIDEO_VERTICAL_PIXELS; +} + +static void _GBCoreCurrentVideoSize(const struct mCore* core, unsigned* width, unsigned* height) { const struct GB* gb = core->board; if (gb && (!(gb->model & GB_MODEL_SGB) || !gb->video.sgbBorders)) { *width = GB_VIDEO_HORIZONTAL_PIXELS; *height = GB_VIDEO_VERTICAL_PIXELS; } else { - *width = 256; - *height = 224; + *width = SGB_VIDEO_HORIZONTAL_PIXELS; + *height = SGB_VIDEO_VERTICAL_PIXELS; + } +} + +static unsigned _GBCoreVideoScale(const struct mCore* core) { + UNUSED(core); + return 1; +} + +static size_t _GBCoreScreenRegions(const struct mCore* core, const struct mCoreScreenRegion** regions) { + const struct GB* gb = core->board; + if (gb && (!(gb->model & GB_MODEL_SGB) || !gb->video.sgbBorders)) { + *regions = _GBScreenRegions; + return 1; + } else { + *regions = _SGBScreenRegions; + return 2; } } @@ -424,7 +455,7 @@ static void _GBCoreSetAVStream(struct mCore* core, struct mAVStream* stream) { gb->stream = stream; if (stream && stream->videoDimensionsChanged) { unsigned width, height; - core->desiredVideoDimensions(core, &width, &height); + core->currentVideoSize(core, &width, &height); stream->videoDimensionsChanged(stream, width, height); } if (stream && stream->audioRateChanged) { @@ -1232,7 +1263,10 @@ struct mCore* GBCoreCreate(void) { core->setSync = _GBCoreSetSync; core->loadConfig = _GBCoreLoadConfig; core->reloadConfigOption = _GBCoreReloadConfigOption; - core->desiredVideoDimensions = _GBCoreDesiredVideoDimensions; + core->baseVideoSize = _GBCoreBaseVideoSize; + core->currentVideoSize = _GBCoreCurrentVideoSize; + core->videoScale = _GBCoreVideoScale; + core->screenRegions = _GBCoreScreenRegions; core->setVideoBuffer = _GBCoreSetVideoBuffer; core->setVideoGLTex = _GBCoreSetVideoGLTex; core->getPixels = _GBCoreGetPixels; diff --git a/src/gba/core.c b/src/gba/core.c index 50f0adf93..851a9dee6 100644 --- a/src/gba/core.c +++ b/src/gba/core.c @@ -129,6 +129,10 @@ static const struct mCoreMemoryBlock _GBAMemoryBlocksEEPROM[] = { { GBA_REGION_SRAM_MIRROR, "eeprom", "EEPROM", "EEPROM (8kiB)", 0, GBA_SIZE_EEPROM, GBA_SIZE_EEPROM, mCORE_MEMORY_RW }, }; +static const struct mCoreScreenRegion _GBAScreenRegions[] = { + { 0, "Screen", 0, 0, GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS } +}; + static const struct mCoreRegisterInfo _GBARegisters[] = { { "r0", NULL, 4, 0xFFFFFFFF, mCORE_REGISTER_GPR }, { "r1", NULL, 4, 0xFFFFFFFF, mCORE_REGISTER_GPR }, @@ -422,19 +426,45 @@ static void _GBACoreReloadConfigOption(struct mCore* core, const char* option, c } } -static void _GBACoreDesiredVideoDimensions(const struct mCore* core, unsigned* width, unsigned* height) { +static void _GBACoreBaseVideoSize(const struct mCore* core, unsigned* width, unsigned* height) { + UNUSED(core); + *width = GBA_VIDEO_HORIZONTAL_PIXELS; + *height = GBA_VIDEO_VERTICAL_PIXELS; +} + +static void _GBACoreCurrentVideoSize(const struct mCore* core, unsigned* width, unsigned* height) { + int scale = 1; #ifdef BUILD_GLES3 const struct GBACore* gbacore = (const struct GBACore*) core; - int scale = gbacore->glRenderer.scale; + if (gbacore->glRenderer.outputTex != (unsigned) -1) { + scale = gbacore->glRenderer.scale; + } #else UNUSED(core); - int scale = 1; #endif *width = GBA_VIDEO_HORIZONTAL_PIXELS * scale; *height = GBA_VIDEO_VERTICAL_PIXELS * scale; } +static unsigned _GBACoreVideoScale(const struct mCore* core) { +#ifdef BUILD_GLES3 + const struct GBACore* gbacore = (const struct GBACore*) core; + if (gbacore->glRenderer.outputTex != (unsigned) -1) { + return gbacore->glRenderer.scale; + } +#else + UNUSED(core); +#endif + return 1; +} + +static size_t _GBACoreScreenRegions(const struct mCore* core, const struct mCoreScreenRegion** regions) { + UNUSED(core); + *regions = _GBAScreenRegions; + return 1; +} + static void _GBACoreSetVideoBuffer(struct mCore* core, color_t* buffer, size_t stride) { struct GBACore* gbacore = (struct GBACore*) core; gbacore->renderer.outputBuffer = buffer; @@ -500,7 +530,7 @@ static void _GBACoreSetAVStream(struct mCore* core, struct mAVStream* stream) { gba->stream = stream; if (stream && stream->videoDimensionsChanged) { unsigned width, height; - core->desiredVideoDimensions(core, &width, &height); + core->currentVideoSize(core, &width, &height); stream->videoDimensionsChanged(stream, width, height); } if (stream && stream->audioRateChanged) { @@ -1358,7 +1388,10 @@ struct mCore* GBACoreCreate(void) { core->setSync = _GBACoreSetSync; core->loadConfig = _GBACoreLoadConfig; core->reloadConfigOption = _GBACoreReloadConfigOption; - core->desiredVideoDimensions = _GBACoreDesiredVideoDimensions; + core->baseVideoSize = _GBACoreBaseVideoSize; + core->currentVideoSize = _GBACoreCurrentVideoSize; + core->videoScale = _GBACoreVideoScale; + core->screenRegions = _GBACoreScreenRegions; core->setVideoBuffer = _GBACoreSetVideoBuffer; core->setVideoGLTex = _GBACoreSetVideoGLTex; core->getPixels = _GBACoreGetPixels; diff --git a/src/platform/3ds/main.c b/src/platform/3ds/main.c index 5356130ce..e0dd2a7dc 100644 --- a/src/platform/3ds/main.c +++ b/src/platform/3ds/main.c @@ -496,7 +496,7 @@ static void _drawTex(struct mCore* core, bool faded, bool both) { int wide = isWide ? 2 : 1; unsigned corew, coreh; - core->desiredVideoDimensions(core, &corew, &coreh); + core->currentVideoSize(core, &corew, &coreh); int w = corew; int h = coreh; diff --git a/src/platform/example/client-server/server.c b/src/platform/example/client-server/server.c index ba0347b69..0dcec9b15 100644 --- a/src/platform/example/client-server/server.c +++ b/src/platform/example/client-server/server.c @@ -83,7 +83,7 @@ bool _mExampleRun(const struct mArguments* args, Socket client) { // Get the dimensions required for this core and send them to the client. unsigned width, height; - core->desiredVideoDimensions(core, &width, &height); + core->baseVideoSize(core, &width, &height); ssize_t bufferSize = width * height * BYTES_PER_PIXEL; uint32_t sendNO; sendNO = htonl(width); diff --git a/src/platform/libretro/libretro.c b/src/platform/libretro/libretro.c index 2e4b67c80..8c08e7bb6 100644 --- a/src/platform/libretro/libretro.c +++ b/src/platform/libretro/libretro.c @@ -414,19 +414,13 @@ void retro_get_system_info(struct retro_system_info* info) { void retro_get_system_av_info(struct retro_system_av_info* info) { unsigned width, height; - core->desiredVideoDimensions(core, &width, &height); + core->currentVideoSize(core, &width, &height); info->geometry.base_width = width; info->geometry.base_height = height; -#ifdef M_CORE_GB - if (core->platform(core) == mPLATFORM_GB) { - info->geometry.max_width = 256; - info->geometry.max_height = 224; - } else -#endif - { - info->geometry.max_width = width; - info->geometry.max_height = height; - } + + core->baseVideoSize(core, &width, &height); + info->geometry.max_width = width; + info->geometry.max_height = height; info->geometry.aspect_ratio = width / (double) height; info->timing.fps = core->frequency(core) / (float) core->frameCycles(core); @@ -614,7 +608,7 @@ void retro_run(void) { core->runFrame(core); unsigned width, height; - core->desiredVideoDimensions(core, &width, &height); + core->currentVideoSize(core, &width, &height); videoCallback(outputBuffer, width, height, BYTES_PER_PIXEL * 256); #ifdef M_CORE_GBA diff --git a/src/platform/openemu/mGBAGameCore.m b/src/platform/openemu/mGBAGameCore.m index 36bb3464a..7656a8945 100644 --- a/src/platform/openemu/mGBAGameCore.m +++ b/src/platform/openemu/mGBAGameCore.m @@ -68,7 +68,7 @@ outputBuffer = nil; unsigned width, height; - core->desiredVideoDimensions(core, &width, &height); + core->baseVideoSize(core, &width, &height); outputBuffer = malloc(width * height * BYTES_PER_PIXEL); core->setVideoBuffer(core, outputBuffer, width); core->setAudioBufferSize(core, SAMPLES); @@ -143,14 +143,14 @@ - (OEIntRect)screenRect { unsigned width, height; - core->desiredVideoDimensions(core, &width, &height); + core->currentVideoSize(core, &width, &height); return OEIntRectMake(0, 0, width, height); } - (OEIntSize)bufferSize { unsigned width, height; - core->desiredVideoDimensions(core, &width, &height); + core->baseVideoSize(core, &width, &height); return OEIntSizeMake(width, height); } diff --git a/src/platform/psp2/psp2-context.c b/src/platform/psp2/psp2-context.c index f860bc4a1..749ff4589 100644 --- a/src/platform/psp2/psp2-context.c +++ b/src/platform/psp2/psp2-context.c @@ -323,7 +323,7 @@ void mPSP2Setup(struct mGUIRunner* runner) { mInputBindAxis(&runner->core->inputMap, PSP2_INPUT, 1, &desc); unsigned width, height; - runner->core->desiredVideoDimensions(runner->core, &width, &height); + runner->core->baseVideoSize(runner->core, &width, &height); tex[0] = vita2d_create_empty_texture_format(256, toPow2(height), SCE_GXM_TEXTURE_FORMAT_X8U8U8U8_1BGR); tex[1] = vita2d_create_empty_texture_format(256, toPow2(height), SCE_GXM_TEXTURE_FORMAT_X8U8U8U8_1BGR); currentTex = 0; @@ -614,7 +614,7 @@ void mPSP2Swap(struct mGUIRunner* runner) { void mPSP2Draw(struct mGUIRunner* runner, bool faded) { unsigned width, height; - runner->core->desiredVideoDimensions(runner->core, &width, &height); + runner->core->currentVideoSize(runner->core, &width, &height); if (interframeBlending) { _drawTex(tex[!currentTex], width, height, faded, false); } diff --git a/src/platform/python/mgba/core.py b/src/platform/python/mgba/core.py index 3a9b90c3a..51f543d3a 100644 --- a/src/platform/python/mgba/core.py +++ b/src/platform/python/mgba/core.py @@ -235,7 +235,7 @@ class Core(object): def desired_video_dimensions(self): width = ffi.new("unsigned*") height = ffi.new("unsigned*") - self._core.desiredVideoDimensions(self._core, width, height) + self._core.currentVideoSize(self._core, width, height) return width[0], height[0] @protected diff --git a/src/platform/qt/CoreController.cpp b/src/platform/qt/CoreController.cpp index 07902facc..c3f12a19f 100644 --- a/src/platform/qt/CoreController.cpp +++ b/src/platform/qt/CoreController.cpp @@ -266,7 +266,7 @@ mPlatform CoreController::platform() const { QSize CoreController::screenDimensions() const { unsigned width, height; - m_threadContext.core->desiredVideoDimensions(m_threadContext.core, &width, &height); + m_threadContext.core->currentVideoSize(m_threadContext.core, &width, &height); return QSize(width, height); } @@ -1186,7 +1186,7 @@ int CoreController::updateAutofire() { void CoreController::finishFrame() { if (!m_hwaccel) { unsigned width, height; - m_threadContext.core->desiredVideoDimensions(m_threadContext.core, &width, &height); + m_threadContext.core->currentVideoSize(m_threadContext.core, &width, &height); QMutexLocker locker(&m_bufferMutex); memcpy(m_completeBuffer.data(), m_activeBuffer.constData(), width * height * BYTES_PER_PIXEL); diff --git a/src/platform/qt/FrameView.cpp b/src/platform/qt/FrameView.cpp index 1f9d4705d..630d0ecd8 100644 --- a/src/platform/qt/FrameView.cpp +++ b/src/platform/qt/FrameView.cpp @@ -559,7 +559,7 @@ void FrameView::newVl() { } #endif unsigned width, height; - m_vl->desiredVideoDimensions(m_vl, &width, &height); + m_vl->baseVideoSize(m_vl, &width, &height); m_framebuffer = QImage(width, height, QImage::Format_RGBX8888); m_vl->setVideoBuffer(m_vl, reinterpret_cast(m_framebuffer.bits()), width); m_vl->reset(m_vl); diff --git a/src/platform/sdl/gl-sdl.c b/src/platform/sdl/gl-sdl.c index 6c1508d33..437bff75b 100644 --- a/src/platform/sdl/gl-sdl.c +++ b/src/platform/sdl/gl-sdl.c @@ -64,7 +64,7 @@ void mSDLGLRunloop(struct mSDLRenderer* renderer, void* user) { renderer->player.windowUpdated = 0; } } - renderer->core->desiredVideoDimensions(renderer->core, &renderer->width, &renderer->height); + renderer->core->currentVideoSize(renderer->core, &renderer->width, &renderer->height); if (renderer->width != v->width || renderer->height != v->height) { renderer->core->setVideoBuffer(renderer->core, renderer->outputBuffer, renderer->width); v->setDimensions(v, renderer->width, renderer->height); diff --git a/src/platform/sdl/main.c b/src/platform/sdl/main.c index 96a797986..e82a00483 100644 --- a/src/platform/sdl/main.c +++ b/src/platform/sdl/main.c @@ -107,7 +107,7 @@ int main(int argc, char** argv) { return 1; } - renderer.core->desiredVideoDimensions(renderer.core, &renderer.width, &renderer.height); + renderer.core->baseVideoSize(renderer.core, &renderer.width, &renderer.height); renderer.ratio = graphicsOpts.multiplier; if (renderer.ratio == 0) { renderer.ratio = 1; @@ -275,7 +275,7 @@ int mSDLRun(struct mSDLRenderer* renderer, struct mArguments* args) { if (!didFail) { #if SDL_VERSION_ATLEAST(2, 0, 0) - renderer->core->desiredVideoDimensions(renderer->core, &renderer->width, &renderer->height); + renderer->core->currentVideoSize(renderer->core, &renderer->width, &renderer->height); unsigned width = renderer->width * renderer->ratio; unsigned height = renderer->height * renderer->ratio; if (width != (unsigned) renderer->viewportWidth && height != (unsigned) renderer->viewportHeight) { diff --git a/src/platform/sdl/sw-sdl1.c b/src/platform/sdl/sw-sdl1.c index bfe5d2037..cf917fd0a 100644 --- a/src/platform/sdl/sw-sdl1.c +++ b/src/platform/sdl/sw-sdl1.c @@ -28,7 +28,7 @@ bool mSDLSWInit(struct mSDLRenderer* renderer) { SDL_WM_SetCaption(projectName, ""); unsigned width, height; - renderer->core->desiredVideoDimensions(renderer->core, &width, &height); + renderer->core->baseVideoSize(renderer->core, &width, &height); SDL_Surface* surface = SDL_GetVideoSurface(); SDL_LockSurface(surface); diff --git a/src/platform/sdl/sw-sdl2.c b/src/platform/sdl/sw-sdl2.c index 48afc33a6..75ad1c2ea 100644 --- a/src/platform/sdl/sw-sdl2.c +++ b/src/platform/sdl/sw-sdl2.c @@ -21,7 +21,7 @@ void mSDLSWCreate(struct mSDLRenderer* renderer) { bool mSDLSWInit(struct mSDLRenderer* renderer) { unsigned width, height; - renderer->core->desiredVideoDimensions(renderer->core, &width, &height); + renderer->core->baseVideoSize(renderer->core, &width, &height); renderer->window = SDL_CreateWindow(projectName, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, renderer->viewportWidth, renderer->viewportHeight, SDL_WINDOW_OPENGL | (SDL_WINDOW_FULLSCREEN_DESKTOP * renderer->player.fullscreen)); SDL_GetWindowSize(renderer->window, &renderer->viewportWidth, &renderer->viewportHeight); renderer->player.window = renderer->window; diff --git a/src/platform/switch/main.c b/src/platform/switch/main.c index 14996d5b0..3e891ab3d 100644 --- a/src/platform/switch/main.c +++ b/src/platform/switch/main.c @@ -477,7 +477,7 @@ static void _drawFrame(struct mGUIRunner* runner, bool faded) { } unsigned width, height; - runner->core->desiredVideoDimensions(runner->core, &width, &height); + runner->core->currentVideoSize(runner->core, &width, &height); glActiveTexture(GL_TEXTURE0); if (usePbo) { @@ -530,7 +530,7 @@ static void _drawScreenshot(struct mGUIRunner* runner, const color_t* pixels, un glBindTexture(GL_TEXTURE_2D, screenshotTex); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); - runner->core->desiredVideoDimensions(runner->core, &width, &height); + runner->core->currentVideoSize(runner->core, &width, &height); glDisable(GL_BLEND); bool wasPbo = usePbo; usePbo = false; diff --git a/src/platform/test/cinema-main.c b/src/platform/test/cinema-main.c index 28bb07693..c9d85003c 100644 --- a/src/platform/test/cinema-main.c +++ b/src/platform/test/cinema-main.c @@ -1032,7 +1032,7 @@ void CInemaTestRun(struct CInemaTest* test) { return; } struct CInemaImage image; - core->desiredVideoDimensions(core, &image.width, &image.height); + core->baseVideoSize(core, &image.width, &image.height); ssize_t bufferSize = image.width * image.height * BYTES_PER_PIXEL; image.data = malloc(bufferSize); image.stride = image.width; @@ -1075,7 +1075,7 @@ void CInemaTestRun(struct CInemaTest* test) { for (frame = 0; frame < skip; ++frame) { core->runFrame(core); } - core->desiredVideoDimensions(core, &image.width, &image.height); + core->currentVideoSize(core, &image.width, &image.height); #ifdef USE_FFMPEG struct FFmpegDecoder decoder; @@ -1139,7 +1139,7 @@ void CInemaTestRun(struct CInemaTest* test) { break; } CIlog(3, "Test frame: %u\n", frameCounter); - core->desiredVideoDimensions(core, &image.width, &image.height); + core->currentVideoSize(core, &image.width, &image.height); uint8_t* diff = NULL; struct CInemaImage expected = { .data = NULL, diff --git a/src/platform/wii/main.c b/src/platform/wii/main.c index cdfe00b2f..db3ed7242 100644 --- a/src/platform/wii/main.c +++ b/src/platform/wii/main.c @@ -1511,7 +1511,7 @@ void _prepareForFrame(struct mGUIRunner* runner) { } void _drawFrame(struct mGUIRunner* runner, bool faded) { - runner->core->desiredVideoDimensions(runner->core, &corew, &coreh); + runner->core->currentVideoSize(runner->core, &corew, &coreh); uint32_t color = 0xFFFFFF3F; if (!faded) { color |= 0xC0; From d6c3b012d1ddb8843bc16ac6a0fd8e70a619169e Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 12 Feb 2023 17:33:44 -0800 Subject: [PATCH 02/17] Video: Start revising VideoBackend API --- include/mgba-util/geometry.h | 22 ++++++ src/platform/opengl/gl.c | 139 ++++++++++++++++++++-------------- src/platform/opengl/gl.h | 4 +- src/platform/opengl/gles2.c | 108 +++++++++++++++----------- src/platform/opengl/gles2.h | 4 +- src/platform/qt/DisplayGL.cpp | 12 +-- src/platform/sdl/gl-common.c | 2 +- src/platform/sdl/gl-sdl.c | 18 ++++- src/platform/sdl/gles2-sdl.c | 11 ++- src/platform/video-backend.h | 21 +++-- 10 files changed, 219 insertions(+), 122 deletions(-) create mode 100644 include/mgba-util/geometry.h diff --git a/include/mgba-util/geometry.h b/include/mgba-util/geometry.h new file mode 100644 index 000000000..a98693006 --- /dev/null +++ b/include/mgba-util/geometry.h @@ -0,0 +1,22 @@ +/* Copyright (c) 2013-2023 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef GEOMETRY_H +#define GEOMETRY_H + +#include + +CXX_GUARD_START + +struct Rectangle { + int x; + int y; + unsigned width; + unsigned height; +}; + +CXX_GUARD_END + +#endif diff --git a/src/platform/opengl/gl.c b/src/platform/opengl/gl.c index 76089c4c0..8ad34197c 100644 --- a/src/platform/opengl/gl.c +++ b/src/platform/opengl/gl.c @@ -21,76 +21,95 @@ static const GLint _glTexCoords[] = { 0, 1 }; +static inline void _initTex(void) { + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); +#ifndef _WIN32 + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); +#endif +} + static void mGLContextInit(struct VideoBackend* v, WHandle handle) { UNUSED(handle); struct mGLContext* context = (struct mGLContext*) v; - v->width = 1; - v->height = 1; + memset(context->layerDims, 0, sizeof(context->layerDims)); glGenTextures(2, context->tex); glBindTexture(GL_TEXTURE_2D, context->tex[0]); - glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); -#ifndef _WIN32 - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); -#endif + _initTex(); glBindTexture(GL_TEXTURE_2D, context->tex[1]); - glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); -#ifndef _WIN32 - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); -#endif + _initTex(); context->activeTex = 0; + + glGenTextures(VIDEO_LAYER_MAX, context->tex); + int i; + for (i = 0; i < VIDEO_LAYER_MAX; ++i) { + glBindTexture(GL_TEXTURE_2D, context->layers[i]); + _initTex(); + } } -static void mGLContextSetDimensions(struct VideoBackend* v, unsigned width, unsigned height) { +static inline void _setTexDims(const struct Rectangle* dims) { +#ifdef COLOR_16_BIT +#ifdef COLOR_5_6_5 + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(dims->width), toPow2(dims->height), 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0); +#else + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(dims->width), toPow2(dims->height), 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, 0); +#endif +#elif defined(__BIG_ENDIAN__) + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(dims->width), toPow2(dims->height), 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, 0); +#else + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(dims->width), toPow2(dims->height), 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); +#endif +} + +static void mGLContextSetLayerDimensions(struct VideoBackend* v, enum VideoLayer layer, const struct Rectangle* dims) { struct mGLContext* context = (struct mGLContext*) v; - if (width == v->width && height == v->height) { + if (layer >= VIDEO_LAYER_MAX) { return; } - v->width = width; - v->height = height; + context->layerDims[layer].x = dims->x; + context->layerDims[layer].y = dims->y; + if (dims->width == context->layerDims[layer].width && dims->height == context->layerDims[layer].height) { + return; + } + context->layerDims[layer].width = dims->width; + context->layerDims[layer].height = dims->height; - glBindTexture(GL_TEXTURE_2D, context->tex[0]); -#ifdef COLOR_16_BIT -#ifdef COLOR_5_6_5 - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0); -#else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, 0); -#endif -#elif defined(__BIG_ENDIAN__) - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, 0); -#else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); -#endif + if (layer == VIDEO_LAYER_IMAGE) { + glBindTexture(GL_TEXTURE_2D, context->tex[0]); + _setTexDims(dims); + glBindTexture(GL_TEXTURE_2D, context->tex[1]); + _setTexDims(dims); + } else { + glBindTexture(GL_TEXTURE_2D, context->layers[layer]); + _setTexDims(dims); + } +} - glBindTexture(GL_TEXTURE_2D, context->tex[1]); -#ifdef COLOR_16_BIT -#ifdef COLOR_5_6_5 - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0); -#else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, 0); -#endif -#elif defined(__BIG_ENDIAN__) - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, 0); -#else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); -#endif +static void mGLContextLayerDimensions(const struct VideoBackend* v, enum VideoLayer layer, struct Rectangle* dims) { + struct mGLContext* context = (struct mGLContext*) v; + if (layer >= VIDEO_LAYER_MAX) { + return; + } + memcpy(dims, &context->layerDims[layer], sizeof(*dims)); } static void mGLContextDeinit(struct VideoBackend* v) { struct mGLContext* context = (struct mGLContext*) v; glDeleteTextures(2, context->tex); + glDeleteTextures(VIDEO_LAYER_MAX, context->layers); } static void mGLContextResized(struct VideoBackend* v, unsigned w, unsigned h) { + struct mGLContext* context = (struct mGLContext*) v; unsigned drawW = w; unsigned drawH = h; if (v->lockAspectRatio) { - lockAspectRatioUInt(v->width, v->height, &drawW, &drawH); + lockAspectRatioUInt(context->layerDims[VIDEO_LAYER_IMAGE].width, context->layerDims[VIDEO_LAYER_IMAGE].height, &drawW, &drawH); } if (v->lockIntegerScaling) { - lockIntegerRatioUInt(v->width, &drawW); - lockIntegerRatioUInt(v->height, &drawH); + lockIntegerRatioUInt(context->layerDims[VIDEO_LAYER_IMAGE].width, &drawW); + lockIntegerRatioUInt(context->layerDims[VIDEO_LAYER_IMAGE].height, &drawH); } glMatrixMode(GL_MODELVIEW); glLoadIdentity(); @@ -114,7 +133,7 @@ void mGLContextDrawFrame(struct VideoBackend* v) { glTexCoordPointer(2, GL_INT, 0, _glTexCoords); glMatrixMode(GL_PROJECTION); glLoadIdentity(); - glOrtho(0, v->width, v->height, 0, 0, 1); + glOrtho(0, context->layerDims[VIDEO_LAYER_IMAGE].width, context->layerDims[VIDEO_LAYER_IMAGE].height, 0, 0, 1); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); if (v->interframeBlending) { @@ -143,32 +162,38 @@ void mGLContextDrawFrame(struct VideoBackend* v) { glDisable(GL_BLEND); } -void mGLContextPostFrame(struct VideoBackend* v, const void* frame) { +void mGLContextPostFrame(struct VideoBackend* v, enum VideoLayer layer, const void* frame) { struct mGLContext* context = (struct mGLContext*) v; - context->activeTex ^= 1; - glBindTexture(GL_TEXTURE_2D, context->tex[context->activeTex]); + if (layer >= VIDEO_LAYER_MAX) { + return; + } + if (layer == VIDEO_LAYER_IMAGE) { + context->activeTex ^= 1; + glBindTexture(GL_TEXTURE_2D, context->tex[context->activeTex]); + } else { + glBindTexture(GL_TEXTURE_2D, context->layers[layer]); + } #ifdef COLOR_16_BIT #ifdef COLOR_5_6_5 - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, v->width, v->height, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, frame); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, context->layerDims[layer].width, context->layerDims[layer].height, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, frame); #else - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, v->width, v->height, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, frame); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, context->layerDims[layer].width, context->layerDims[layer].height, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, frame); #endif #elif defined(__BIG_ENDIAN__) - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, v->width, v->height, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, frame); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, context->layerDims[layer].width, context->layerDims[layer].height, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, frame); #else - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, v->width, v->height, GL_RGBA, GL_UNSIGNED_BYTE, frame); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, context->layerDims[layer].width, context->layerDims[layer].height, GL_RGBA, GL_UNSIGNED_BYTE, frame); #endif } void mGLContextCreate(struct mGLContext* context) { context->d.init = mGLContextInit; context->d.deinit = mGLContextDeinit; - context->d.setDimensions = mGLContextSetDimensions; - context->d.resized = mGLContextResized; - context->d.swap = 0; + context->d.setLayerDimensions = mGLContextSetLayerDimensions; + context->d.layerDimensions = mGLContextLayerDimensions; + context->d.contextResized = mGLContextResized; + context->d.swap = NULL; context->d.clear = mGLContextClear; - context->d.postFrame = mGLContextPostFrame; + context->d.setImage = mGLContextPostFrame; context->d.drawFrame = mGLContextDrawFrame; - context->d.setMessage = 0; - context->d.clearMessage = 0; } diff --git a/src/platform/opengl/gl.h b/src/platform/opengl/gl.h index 3ff528864..c819e2f37 100644 --- a/src/platform/opengl/gl.h +++ b/src/platform/opengl/gl.h @@ -26,8 +26,10 @@ CXX_GUARD_START struct mGLContext { struct VideoBackend d; - GLuint tex[2]; int activeTex; + GLuint tex[2]; + GLuint layers[VIDEO_LAYER_MAX]; + struct Rectangle layerDims[VIDEO_LAYER_MAX]; }; void mGLContextCreate(struct mGLContext*); diff --git a/src/platform/opengl/gles2.c b/src/platform/opengl/gles2.c index ffbac5a04..fb2a07bba 100644 --- a/src/platform/opengl/gles2.c +++ b/src/platform/opengl/gles2.c @@ -101,12 +101,15 @@ static const GLfloat _vertices[] = { static void mGLES2ContextInit(struct VideoBackend* v, WHandle handle) { UNUSED(handle); struct mGLES2Context* context = (struct mGLES2Context*) v; - v->width = 1; - v->height = 1; - glGenTextures(1, &context->tex); - glBindTexture(GL_TEXTURE_2D, context->tex); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + memset(context->layerDims, 0, sizeof(context->layerDims)); + + glGenTextures(VIDEO_LAYER_MAX, context->tex); + int i; + for (i = 0; i < VIDEO_LAYER_MAX; ++i) { + glBindTexture(GL_TEXTURE_2D, context->tex[i]); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } glGenBuffers(1, &context->vbo); glBindBuffer(GL_ARRAY_BUFFER, context->vbo); @@ -177,40 +180,55 @@ static void mGLES2ContextInit(struct VideoBackend* v, WHandle handle) { context->finalShader.tex = 0; } -static void mGLES2ContextSetDimensions(struct VideoBackend* v, unsigned width, unsigned height) { +static void mGLES2ContextSetLayerDimensions(struct VideoBackend* v, enum VideoLayer layer, const struct Rectangle* dims) { struct mGLES2Context* context = (struct mGLES2Context*) v; - if (width == v->width && height == v->height) { + if (layer >= VIDEO_LAYER_MAX) { return; } - v->width = width; - v->height = height; + context->layerDims[layer].x = dims->x; + context->layerDims[layer].y = dims->y; + if (dims->width == context->layerDims[layer].width && dims->height == context->layerDims[layer].height) { + return; + } + context->layerDims[layer].width = dims->width; + context->layerDims[layer].height = dims->height; - glBindTexture(GL_TEXTURE_2D, context->tex); + glBindTexture(GL_TEXTURE_2D, context->tex[layer]); #ifdef COLOR_16_BIT #ifdef COLOR_5_6_5 - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dims->width, dims->height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0); #else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dims->width, dims->height, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, 0); #endif #elif defined(__BIG_ENDIAN__) - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dims->width, dims->height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, 0); #else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dims->width, dims->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); #endif - size_t n; - for (n = 0; n < context->nShaders; ++n) { - if (context->shaders[n].width < 0 || context->shaders[n].height < 0) { - context->shaders[n].dirty = true; + if (layer == VIDEO_LAYER_BACKGROUND) { + size_t n; + for (n = 0; n < context->nShaders; ++n) { + if (context->shaders[n].width < 0 || context->shaders[n].height < 0) { + context->shaders[n].dirty = true; + } } + context->initialShader.dirty = true; + context->interframeShader.dirty = true; } - context->initialShader.dirty = true; - context->interframeShader.dirty = true; +} + +static void mGLES2ContextLayerDimensions(const struct VideoBackend* v, enum VideoLayer layer, struct Rectangle* dims) { + struct mGLES2Context* context = (struct mGLES2Context*) v; + if (layer >= VIDEO_LAYER_MAX) { + return; + } + memcpy(dims, &context->layerDims[layer], sizeof(*dims)); } static void mGLES2ContextDeinit(struct VideoBackend* v) { struct mGLES2Context* context = (struct mGLES2Context*) v; - glDeleteTextures(1, &context->tex); + glDeleteTextures(VIDEO_LAYER_MAX, context->tex); glDeleteBuffers(1, &context->vbo); mGLES2ShaderDeinit(&context->initialShader); mGLES2ShaderDeinit(&context->finalShader); @@ -223,11 +241,11 @@ static void mGLES2ContextResized(struct VideoBackend* v, unsigned w, unsigned h) unsigned drawW = w; unsigned drawH = h; if (v->lockAspectRatio) { - lockAspectRatioUInt(v->width, v->height, &drawW, &drawH); + lockAspectRatioUInt(context->layerDims[VIDEO_LAYER_IMAGE].width, context->layerDims[VIDEO_LAYER_IMAGE].height, &drawW, &drawH); } if (v->lockIntegerScaling) { - lockIntegerRatioUInt(v->width, &drawW); - lockIntegerRatioUInt(v->height, &drawH); + lockIntegerRatioUInt(context->layerDims[VIDEO_LAYER_IMAGE].width, &drawW); + lockIntegerRatioUInt(context->layerDims[VIDEO_LAYER_IMAGE].height, &drawH); } size_t n; for (n = 0; n < context->nShaders; ++n) { @@ -260,19 +278,19 @@ void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) { drawW = viewport[2]; padW = viewport[0]; } else if (shader->width < 0) { - drawW = context->d.width * -shader->width; + drawW = context->layerDims[VIDEO_LAYER_IMAGE].width * -shader->width; } if (!drawH) { drawH = viewport[3]; padH = viewport[1]; } else if (shader->height < 0) { - drawH = context->d.height * -shader->height; + drawH = context->layerDims[VIDEO_LAYER_IMAGE].height * -shader->height; } if (shader->integerScaling) { padW = 0; padH = 0; - drawW -= drawW % context->d.width; - drawH -= drawH % context->d.height; + drawW -= drawW % context->layerDims[VIDEO_LAYER_IMAGE].width; + drawH -= drawH % context->layerDims[VIDEO_LAYER_IMAGE].height; } if (shader->dirty) { @@ -301,7 +319,7 @@ void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, shader->filter ? GL_LINEAR : GL_NEAREST); glUseProgram(shader->program); glUniform1i(shader->texLocation, 0); - glUniform2f(shader->texSizeLocation, context->d.width, context->d.height); + glUniform2f(shader->texSizeLocation, context->layerDims[VIDEO_LAYER_IMAGE].width, context->layerDims[VIDEO_LAYER_IMAGE].height); glUniform2f(shader->outputSizeLocation, drawW, drawH); #ifdef BUILD_GLES3 if (shader->vao != (GLuint) -1) { @@ -371,7 +389,7 @@ void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) { void mGLES2ContextDrawFrame(struct VideoBackend* v) { struct mGLES2Context* context = (struct mGLES2Context*) v; glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, context->tex); + glBindTexture(GL_TEXTURE_2D, context->tex[VIDEO_LAYER_IMAGE]); GLint viewport[4]; glGetIntegerv(GL_VIEWPORT, viewport); @@ -392,7 +410,7 @@ void mGLES2ContextDrawFrame(struct VideoBackend* v) { _drawShader(context, &context->finalShader); if (v->interframeBlending) { context->interframeShader.blend = false; - glBindTexture(GL_TEXTURE_2D, context->tex); + glBindTexture(GL_TEXTURE_2D, context->tex[VIDEO_LAYER_IMAGE]); _drawShader(context, &context->initialShader); glViewport(0, 0, viewport[2], viewport[3]); _drawShader(context, &context->interframeShader); @@ -406,33 +424,35 @@ void mGLES2ContextDrawFrame(struct VideoBackend* v) { #endif } -void mGLES2ContextPostFrame(struct VideoBackend* v, const void* frame) { +void mGLES2ContextPostFrame(struct VideoBackend* v, enum VideoLayer layer, const void* frame) { struct mGLES2Context* context = (struct mGLES2Context*) v; - glBindTexture(GL_TEXTURE_2D, context->tex); + if (layer >= VIDEO_LAYER_MAX) { + return; + } + glBindTexture(GL_TEXTURE_2D, context->tex[layer]); #ifdef COLOR_16_BIT #ifdef COLOR_5_6_5 - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, v->width, v->height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, frame); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, context->layerDims[layer].width, context->layerDims[layer].height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, frame); #else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, v->width, v->height, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, frame); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, context->layerDims[layer].width, context->layerDims[layer].height, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, frame); #endif #elif defined(__BIG_ENDIAN__) - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, v->width, v->height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, frame); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, context->layerDims[layer].width, context->layerDims[layer].height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, frame); #else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, v->width, v->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, frame); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, context->layerDims[layer].width, context->layerDims[layer].height, 0, GL_RGBA, GL_UNSIGNED_BYTE, frame); #endif } void mGLES2ContextCreate(struct mGLES2Context* context) { context->d.init = mGLES2ContextInit; context->d.deinit = mGLES2ContextDeinit; - context->d.setDimensions = mGLES2ContextSetDimensions; - context->d.resized = mGLES2ContextResized; - context->d.swap = 0; + context->d.setLayerDimensions = mGLES2ContextSetLayerDimensions; + context->d.layerDimensions = mGLES2ContextLayerDimensions; + context->d.contextResized = mGLES2ContextResized; + context->d.swap = NULL; context->d.clear = mGLES2ContextClear; - context->d.postFrame = mGLES2ContextPostFrame; + context->d.setImage = mGLES2ContextPostFrame; context->d.drawFrame = mGLES2ContextDrawFrame; - context->d.setMessage = 0; - context->d.clearMessage = 0; context->shaders = 0; context->nShaders = 0; } diff --git a/src/platform/opengl/gles2.h b/src/platform/opengl/gles2.h index b497b4bfa..a789305db 100644 --- a/src/platform/opengl/gles2.h +++ b/src/platform/opengl/gles2.h @@ -79,9 +79,11 @@ struct mGLES2Shader { struct mGLES2Context { struct VideoBackend d; - GLuint tex; + GLuint tex[VIDEO_LAYER_MAX]; GLuint vbo; + struct Rectangle layerDims[VIDEO_LAYER_MAX]; + struct mGLES2Shader initialShader; struct mGLES2Shader finalShader; struct mGLES2Shader interframeShader; diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index 95549b0c8..41ff32bbc 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -624,7 +624,9 @@ void PainterGL::resizeContext() { return; } dequeueAll(false); - m_backend->setDimensions(m_backend, size.width(), size.height()); + + Rectangle dims = {0, 0, size.width(), size.height()}; + m_backend->setLayerDimensions(m_backend, VIDEO_LAYER_IMAGE, &dims); } void PainterGL::setMessagePainter(MessagePainter* messagePainter) { @@ -797,9 +799,9 @@ void PainterGL::unpause() { void PainterGL::performDraw() { float r = m_window->devicePixelRatio(); - m_backend->resized(m_backend, m_size.width() * r, m_size.height() * r); + m_backend->contextResized(m_backend, m_size.width() * r, m_size.height() * r); if (m_buffer) { - m_backend->postFrame(m_backend, m_buffer); + m_backend->setImage(m_backend, VIDEO_LAYER_IMAGE, m_buffer); } m_backend->drawFrame(m_backend); if (m_showOSD && m_messagePainter && !glContextHasBug(OpenGLBug::IG4ICD_CRASH)) { @@ -855,7 +857,7 @@ void PainterGL::dequeue() { #if defined(BUILD_GLES2) || defined(BUILD_GLES3) if (supportsShaders()) { mGLES2Context* gl2Backend = reinterpret_cast(m_backend); - gl2Backend->tex = m_bridgeTexOut; + gl2Backend->tex[VIDEO_LAYER_IMAGE] = m_bridgeTexOut; } #endif } @@ -953,7 +955,7 @@ int PainterGL::glTex() { #if defined(BUILD_GLES2) || defined(BUILD_GLES3) if (supportsShaders()) { mGLES2Context* gl2Backend = reinterpret_cast(m_backend); - return gl2Backend->tex; + return gl2Backend->tex[VIDEO_LAYER_IMAGE]; } #endif #ifdef BUILD_GL diff --git a/src/platform/sdl/gl-common.c b/src/platform/sdl/gl-common.c index f0073a904..264e3cafe 100644 --- a/src/platform/sdl/gl-common.c +++ b/src/platform/sdl/gl-common.c @@ -8,7 +8,7 @@ #include void mSDLGLDoViewport(int w, int h, struct VideoBackend* v) { - v->resized(v, w, h); + v->contextResized(v, w, h); v->clear(v); v->swap(v); v->clear(v); diff --git a/src/platform/sdl/gl-sdl.c b/src/platform/sdl/gl-sdl.c index 437bff75b..a6d0a4e0e 100644 --- a/src/platform/sdl/gl-sdl.c +++ b/src/platform/sdl/gl-sdl.c @@ -37,7 +37,13 @@ bool mSDLGLInit(struct mSDLRenderer* renderer) { renderer->gl.d.filter = renderer->filter; renderer->gl.d.swap = mSDLGLCommonSwap; renderer->gl.d.init(&renderer->gl.d, 0); - renderer->gl.d.setDimensions(&renderer->gl.d, renderer->width, renderer->height); + struct Rectangle dims = { + .x = 0, + .y = 0, + .width = renderer->width, + .height = renderer->height + }; + renderer->gl.d.setLayerDimensions(&renderer->gl.d, VIDEO_LAYER_IMAGE, &dims); mSDLGLDoViewport(renderer->viewportWidth, renderer->viewportHeight, &renderer->gl.d); return true; @@ -65,13 +71,17 @@ void mSDLGLRunloop(struct mSDLRenderer* renderer, void* user) { } } renderer->core->currentVideoSize(renderer->core, &renderer->width, &renderer->height); - if (renderer->width != v->width || renderer->height != v->height) { + struct Rectangle dims; + v->layerDimensions(v, VIDEO_LAYER_IMAGE, &dims); + if (renderer->width != dims.width || renderer->height != dims.height) { renderer->core->setVideoBuffer(renderer->core, renderer->outputBuffer, renderer->width); - v->setDimensions(v, renderer->width, renderer->height); + dims.width = renderer->width; + dims.height = renderer->height; + v->setLayerDimensions(v, VIDEO_LAYER_IMAGE, &dims); } if (mCoreSyncWaitFrameStart(&context->impl->sync)) { - v->postFrame(v, renderer->outputBuffer); + v->setImage(v, VIDEO_LAYER_IMAGE, renderer->outputBuffer); } mCoreSyncWaitFrameEnd(&context->impl->sync); v->drawFrame(v); diff --git a/src/platform/sdl/gles2-sdl.c b/src/platform/sdl/gles2-sdl.c index 7237b6273..e65d430ee 100644 --- a/src/platform/sdl/gles2-sdl.c +++ b/src/platform/sdl/gles2-sdl.c @@ -50,7 +50,14 @@ bool mSDLGLES2Init(struct mSDLRenderer* renderer) { renderer->gl2.d.swap = mSDLGLCommonSwap; #endif renderer->gl2.d.init(&renderer->gl2.d, 0); - renderer->gl2.d.setDimensions(&renderer->gl2.d, renderer->width, renderer->height); + + struct Rectangle dims = { + .x = 0, + .y = 0, + .width = renderer->width, + .height = renderer->height + }; + renderer->gl2.d.setLayerDimensions(&renderer->gl2.d, VIDEO_LAYER_IMAGE, &dims); mSDLGLDoViewport(renderer->viewportWidth, renderer->viewportHeight, &renderer->gl2.d); return true; @@ -79,7 +86,7 @@ void mSDLGLES2Runloop(struct mSDLRenderer* renderer, void* user) { } if (mCoreSyncWaitFrameStart(&context->impl->sync)) { - v->postFrame(v, renderer->outputBuffer); + v->setImage(v, VIDEO_LAYER_IMAGE, renderer->outputBuffer); } mCoreSyncWaitFrameEnd(&context->impl->sync); v->drawFrame(v); diff --git a/src/platform/video-backend.h b/src/platform/video-backend.h index 6d714629a..3b7a4b245 100644 --- a/src/platform/video-backend.h +++ b/src/platform/video-backend.h @@ -10,6 +10,8 @@ CXX_GUARD_START +#include + #ifdef _WIN32 #include typedef HWND WHandle; @@ -17,26 +19,31 @@ typedef HWND WHandle; typedef void* WHandle; #endif +enum VideoLayer { + VIDEO_LAYER_BACKGROUND = 0, + VIDEO_LAYER_IMAGE, + VIDEO_LAYER_OVERLAY, + VIDEO_LAYER_MAX +}; + struct VideoBackend { void (*init)(struct VideoBackend*, WHandle handle); void (*deinit)(struct VideoBackend*); - void (*setDimensions)(struct VideoBackend*, unsigned width, unsigned height); + void (*setLayerDimensions)(struct VideoBackend*, enum VideoLayer, const struct Rectangle*); + void (*layerDimensions)(const struct VideoBackend*, enum VideoLayer, struct Rectangle*); void (*swap)(struct VideoBackend*); void (*clear)(struct VideoBackend*); - void (*resized)(struct VideoBackend*, unsigned w, unsigned h); - void (*postFrame)(struct VideoBackend*, const void* frame); + void (*contextResized)(struct VideoBackend*, unsigned w, unsigned h); + void (*setImage)(struct VideoBackend*, enum VideoLayer, const void* frame); void (*drawFrame)(struct VideoBackend*); - void (*setMessage)(struct VideoBackend*, const char* message); - void (*clearMessage)(struct VideoBackend*); void* user; - unsigned width; - unsigned height; bool filter; bool lockAspectRatio; bool lockIntegerScaling; bool interframeBlending; + enum VideoLayer cropToLayer; }; struct VideoShader { From bd6edce5cffc4e4376850dd9579b6cc394b6c05e Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 13 Feb 2023 15:13:33 -0800 Subject: [PATCH 03/17] Qt: Start adding background/bezel image support --- src/platform/qt/Display.h | 1 + src/platform/qt/DisplayGL.cpp | 4 ++ src/platform/qt/DisplayGL.h | 2 + src/platform/qt/DisplayQt.cpp | 80 ++++++++++++++++++++++++++++++-- src/platform/qt/DisplayQt.h | 2 + src/platform/qt/SettingsView.cpp | 12 +++++ src/platform/qt/SettingsView.h | 1 + src/platform/qt/SettingsView.ui | 35 ++++++++++++++ src/platform/qt/Window.cpp | 9 ++++ 9 files changed, 143 insertions(+), 3 deletions(-) diff --git a/src/platform/qt/Display.h b/src/platform/qt/Display.h index 574a0e8e9..dda48d875 100644 --- a/src/platform/qt/Display.h +++ b/src/platform/qt/Display.h @@ -54,6 +54,7 @@ public: virtual VideoShader* shaders() = 0; virtual int framebufferHandle() { return -1; } virtual void setVideoScale(int) {} + virtual void setBackgroundImage(const QImage&) = 0; virtual void setVideoProxy(std::shared_ptr proxy) { m_videoProxy = proxy; } std::shared_ptr videoProxy() { return m_videoProxy; } diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index 41ff32bbc..fce25624d 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -396,6 +396,10 @@ void DisplayGL::setVideoScale(int scale) { QMetaObject::invokeMethod(m_painter.get(), "resizeContext"); } +void DisplayGL::setBackgroundImage(const QImage& image) { + // TODO +} + void DisplayGL::resizeEvent(QResizeEvent* event) { Display::resizeEvent(event); resizePainter(); diff --git a/src/platform/qt/DisplayGL.h b/src/platform/qt/DisplayGL.h index 3ed31a9d8..87dd983dd 100644 --- a/src/platform/qt/DisplayGL.h +++ b/src/platform/qt/DisplayGL.h @@ -107,6 +107,8 @@ public slots: void clearShaders() override; void resizeContext() override; void setVideoScale(int scale) override; + void setBackgroundImage(const QImage&) override; + protected: virtual void paintEvent(QPaintEvent*) override { forceDraw(); } diff --git a/src/platform/qt/DisplayQt.cpp b/src/platform/qt/DisplayQt.cpp index 20ec44f0d..7669b5cf1 100644 --- a/src/platform/qt/DisplayQt.cpp +++ b/src/platform/qt/DisplayQt.cpp @@ -92,19 +92,93 @@ void DisplayQt::resizeContext() { } } +void DisplayQt::setBackgroundImage(const QImage& image) { + m_background = image; + update(); +} + void DisplayQt::paintEvent(QPaintEvent*) { QPainter painter(this); painter.fillRect(QRect(QPoint(), size()), Qt::black); if (isFiltered()) { painter.setRenderHint(QPainter::SmoothPixmapTransform); } - QRect full(clampSize(QSize(m_width, m_height), size(), isAspectRatioLocked(), isIntegerScalingLocked())); + + QRect bgRect(0, 0, m_background.width(), m_background.height()); + QRect imRect(0, 0, m_width, m_height); + QSize outerFrame; + + if (bgRect.width() > imRect.width()) { + imRect.moveLeft(bgRect.width() - imRect.width()); + outerFrame.setWidth(bgRect.width()); + } else { + bgRect.moveLeft(imRect.width() - bgRect.width()); + outerFrame.setWidth(imRect.width()); + } + + if (bgRect.height() > imRect.height()) { + imRect.moveTop(bgRect.height() - imRect.height()); + outerFrame.setHeight(bgRect.height()); + } else { + bgRect.moveTop(imRect.height() - bgRect.height()); + outerFrame.setHeight(imRect.height()); + } + + QRect full(clampSize(outerFrame, size(), isAspectRatioLocked(), isIntegerScalingLocked())); + + if (m_background.isNull()) { + imRect = full; + } else { + if (imRect.x()) { + imRect.moveLeft(imRect.x() * full.width() / bgRect.width() / 2); + imRect.setWidth(imRect.width() * full.width() / bgRect.width()); + bgRect.setWidth(full.width()); + } else { + bgRect.moveLeft(bgRect.x() * full.width() / imRect.width() / 2); + bgRect.setWidth(bgRect.width() * full.width() / imRect.width()); + imRect.setWidth(full.width()); + } + if (imRect.y()) { + imRect.moveTop(imRect.y() * full.height() / bgRect.height() / 2); + imRect.setHeight(imRect.height() * full.height() / bgRect.height()); + bgRect.setHeight(full.height()); + } else { + bgRect.moveTop(bgRect.y() * full.height() / imRect.height() / 2); + bgRect.setHeight(bgRect.height() * full.height() / imRect.height()); + imRect.setHeight(full.height()); + } + + if (bgRect.right() > imRect.right()) { + if (bgRect.right() < full.right()) { + imRect.translate((full.right() - bgRect.right()), 0); + bgRect.translate((full.right() - bgRect.right()), 0); + } + } else { + if (imRect.right() < full.right()) { + bgRect.translate((full.right() - imRect.right()), 0); + imRect.translate((full.right() - imRect.right()), 0); + } + } + + if (bgRect.bottom() > imRect.bottom()) { + if (bgRect.bottom() < full.bottom()) { + imRect.translate(0, (full.bottom() - bgRect.bottom())); + bgRect.translate(0, (full.bottom() - bgRect.bottom())); + } + } else { + if (imRect.bottom() < full.bottom()) { + bgRect.translate(0, (full.bottom() - imRect.bottom())); + imRect.translate(0, (full.bottom() - imRect.bottom())); + } + } + painter.drawImage(bgRect, m_background); + } if (hasInterframeBlending()) { - painter.drawImage(full, m_oldBacking, QRect(0, 0, m_width, m_height)); + painter.drawImage(imRect, m_oldBacking, QRect(0, 0, m_width, m_height)); painter.setOpacity(0.5); } - painter.drawImage(full, m_backing, QRect(0, 0, m_width, m_height)); + painter.drawImage(imRect, m_backing, QRect(0, 0, m_width, m_height)); painter.setOpacity(1); if (isShowOSD() || isShowFrameCounter()) { messagePainter()->paint(&painter); diff --git a/src/platform/qt/DisplayQt.h b/src/platform/qt/DisplayQt.h index e8623468f..acdd95f24 100644 --- a/src/platform/qt/DisplayQt.h +++ b/src/platform/qt/DisplayQt.h @@ -36,6 +36,7 @@ public slots: void setShaders(struct VDir*) override {} void clearShaders() override {} void resizeContext() override; + void setBackgroundImage(const QImage&) override; protected: virtual void paintEvent(QPaintEvent*) override; @@ -46,6 +47,7 @@ private: int m_height; QImage m_backing{nullptr}; QImage m_oldBacking{nullptr}; + QImage m_background; std::shared_ptr m_context = nullptr; }; diff --git a/src/platform/qt/SettingsView.cpp b/src/platform/qt/SettingsView.cpp index acf272cf6..75a3908dc 100644 --- a/src/platform/qt/SettingsView.cpp +++ b/src/platform/qt/SettingsView.cpp @@ -143,6 +143,9 @@ SettingsView::SettingsView(ConfigController* controller, InputController* inputC connect(m_ui.cheatsBrowse, &QAbstractButton::pressed, [this] () { selectPath(m_ui.cheatsPath, m_ui.cheatsSameDir); }); + connect(m_ui.bgImageBrowse, &QAbstractButton::pressed, [this] () { + selectImage(m_ui.bgImage); + }); connect(m_ui.clearCache, &QAbstractButton::pressed, this, &SettingsView::libraryCleared); // TODO: Move to reloadConfig() @@ -445,6 +448,13 @@ void SettingsView::selectPath(QLineEdit* field, QCheckBox* sameDir) { } } +void SettingsView::selectImage(QLineEdit* field) { + QString path = GBAApp::app()->getOpenFileName(this, tr("Select image"), tr("Image file (*.png *.jpg *.jpeg)")); + if (!path.isNull()) { + field->setText(makePortablePath(path)); + } +} + void SettingsView::updateConfig() { saveSetting("gba.bios", m_ui.gbaBios); saveSetting("gb.bios", m_ui.gbBios); @@ -501,6 +511,7 @@ void SettingsView::updateConfig() { saveSetting("vbaBugCompat", m_ui.vbaBugCompat); saveSetting("updateAutoCheck", m_ui.updateAutoCheck); saveSetting("showFilenameInLibrary", m_ui.showFilenameInLibrary); + saveSetting("backgroundImage", m_ui.bgImage); if (m_ui.audioBufferSize->currentText().toInt() > 8192) { m_ui.audioBufferSize->setCurrentText("8192"); @@ -725,6 +736,7 @@ void SettingsView::reloadConfig() { loadSetting("vbaBugCompat", m_ui.vbaBugCompat, true); loadSetting("updateAutoCheck", m_ui.updateAutoCheck); loadSetting("showFilenameInLibrary", m_ui.showFilenameInLibrary); + loadSetting("backgroundImage", m_ui.bgImage); m_ui.libraryStyle->setCurrentIndex(loadSetting("libraryStyle").toInt()); diff --git a/src/platform/qt/SettingsView.h b/src/platform/qt/SettingsView.h index d13a6b453..3543419fc 100644 --- a/src/platform/qt/SettingsView.h +++ b/src/platform/qt/SettingsView.h @@ -71,6 +71,7 @@ public slots: private slots: void selectBios(QLineEdit*); void selectPath(QLineEdit*, QCheckBox*); + void selectImage(QLineEdit*); void updateConfig(); void reloadConfig(); void updateChecked(); diff --git a/src/platform/qt/SettingsView.ui b/src/platform/qt/SettingsView.ui index 95f71565b..03050e607 100644 --- a/src/platform/qt/SettingsView.ui +++ b/src/platform/qt/SettingsView.ui @@ -907,6 +907,41 @@ + + + + + + + 0 + 0 + + + + + + + + Browse + + + + + + + + + Qt::Horizontal + + + + + + + Custom border: + + + diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 02f8459cf..ce0c64a06 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -1053,6 +1053,8 @@ void Window::reloadDisplayDriver() { #elif defined(M_CORE_GBA) m_display->setMinimumSize(GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS); #endif + + m_display->setBackgroundImage(QImage{m_config->getOption("backgroundImage")}); } void Window::reloadAudioDriver() { @@ -1875,6 +1877,13 @@ void Window::setupOptions() { updateTitle(); }, this); + ConfigOption* backgroundImage = m_config->addOption("backgroundImage"); + backgroundImage->connect([this](const QVariant& value) { + if (m_display) { + m_display->setBackgroundImage(QImage{value.toString()}); + } + }, this); + m_config->updateOption("backgroundImage"); } void Window::attachWidget(QWidget* widget) { From efbc4a49ce88eb313e6220ad7d16c035f8875801 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 20 Feb 2023 22:59:23 -0800 Subject: [PATCH 04/17] Util: Add some basic geometry math --- include/mgba-util/geometry.h | 7 +- src/util/CMakeLists.txt | 2 + src/util/geometry.c | 36 ++++++ src/util/test/geometry.c | 213 +++++++++++++++++++++++++++++++++++ 4 files changed, 256 insertions(+), 2 deletions(-) create mode 100644 src/util/geometry.c create mode 100644 src/util/test/geometry.c diff --git a/include/mgba-util/geometry.h b/include/mgba-util/geometry.h index a98693006..4ed46f0b0 100644 --- a/include/mgba-util/geometry.h +++ b/include/mgba-util/geometry.h @@ -13,10 +13,13 @@ CXX_GUARD_START struct Rectangle { int x; int y; - unsigned width; - unsigned height; + int width; + int height; }; +void RectangleUnion(struct Rectangle* dst, const struct Rectangle* add); +void RectangleCenter(const struct Rectangle* ref, struct Rectangle* rect); + CXX_GUARD_END #endif diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index 9246b20c4..e0aa3f8a4 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -16,6 +16,7 @@ set(SOURCE_FILES convolve.c elf-read.c export.c + geometry.c patch.c patch-fast.c patch-ips.c @@ -33,6 +34,7 @@ set(GUI_FILES gui/menu.c) set(TEST_FILES + test/geometry.c test/sfo.c test/string-parser.c test/string-utf8.c diff --git a/src/util/geometry.c b/src/util/geometry.c new file mode 100644 index 000000000..92eb92b9a --- /dev/null +++ b/src/util/geometry.c @@ -0,0 +1,36 @@ +/* Copyright (c) 2013-2023 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include + +void RectangleUnion(struct Rectangle* dst, const struct Rectangle* add) { + int x0 = dst->x; + int y0 = dst->y; + int x1 = dst->x + dst->width; + int y1 = dst->y + dst->height; + + if (add->x < x0) { + x0 = add->x; + } + if (add->y < y0) { + y0 = add->y; + } + if (add->x + add->width > x1) { + x1 = add->x + add->width; + } + if (add->y + add->height > y1) { + y1 = add->y + add->height; + } + + dst->x = x0; + dst->y = y0; + dst->width = x1 - x0; + dst->height = y1 - y0; +} + +void RectangleCenter(const struct Rectangle* ref, struct Rectangle* rect) { + rect->x = ref->x + (ref->width - rect->width) / 2; + rect->y = ref->y + (ref->height - rect->height) / 2; +} diff --git a/src/util/test/geometry.c b/src/util/test/geometry.c new file mode 100644 index 000000000..74171e0f7 --- /dev/null +++ b/src/util/test/geometry.c @@ -0,0 +1,213 @@ +/* Copyright (c) 2013-2023 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "util/test/suite.h" + +#include + +M_TEST_DEFINE(unionRectOrigin) { + struct Rectangle a = { + .x = 0, + .y = 0, + .width = 1, + .height = 1 + }; + struct Rectangle b = { + .x = 1, + .y = 1, + .width = 1, + .height = 1 + }; + RectangleUnion(&a, &b); + assert_int_equal(a.x, 0); + assert_int_equal(a.y, 0); + assert_int_equal(a.width, 2); + assert_int_equal(a.height, 2); +} + +M_TEST_DEFINE(unionRectOriginSwapped) { + struct Rectangle a = { + .x = 1, + .y = 1, + .width = 1, + .height = 1 + }; + struct Rectangle b = { + .x = 0, + .y = 0, + .width = 1, + .height = 1 + }; + RectangleUnion(&a, &b); + assert_int_equal(a.x, 0); + assert_int_equal(a.y, 0); + assert_int_equal(a.width, 2); + assert_int_equal(a.height, 2); +} + +M_TEST_DEFINE(unionRectNonOrigin) { + struct Rectangle a = { + .x = 1, + .y = 1, + .width = 1, + .height = 1 + }; + struct Rectangle b = { + .x = 2, + .y = 2, + .width = 1, + .height = 1 + }; + RectangleUnion(&a, &b); + assert_int_equal(a.x, 1); + assert_int_equal(a.y, 1); + assert_int_equal(a.width, 2); + assert_int_equal(a.height, 2); +} + +M_TEST_DEFINE(unionRectOverlapping) { + struct Rectangle a = { + .x = 0, + .y = 0, + .width = 2, + .height = 2 + }; + struct Rectangle b = { + .x = 1, + .y = 1, + .width = 2, + .height = 2 + }; + RectangleUnion(&a, &b); + assert_int_equal(a.x, 0); + assert_int_equal(a.y, 0); + assert_int_equal(a.width, 3); + assert_int_equal(a.height, 3); +} + +M_TEST_DEFINE(unionRectSubRect) { + struct Rectangle a = { + .x = 0, + .y = 0, + .width = 3, + .height = 3 + }; + struct Rectangle b = { + .x = 1, + .y = 1, + .width = 1, + .height = 1 + }; + RectangleUnion(&a, &b); + assert_int_equal(a.x, 0); + assert_int_equal(a.y, 0); + assert_int_equal(a.width, 3); + assert_int_equal(a.height, 3); +} + +M_TEST_DEFINE(unionRectNegativeOrigin) { + struct Rectangle a = { + .x = -1, + .y = -1, + .width = 1, + .height = 1 + }; + struct Rectangle b = { + .x = 0, + .y = 0, + .width = 1, + .height = 1 + }; + RectangleUnion(&a, &b); + assert_int_equal(a.x, -1); + assert_int_equal(a.y, -1); + assert_int_equal(a.width, 2); + assert_int_equal(a.height, 2); +} + +M_TEST_DEFINE(centerRectBasic) { + struct Rectangle ref = { + .x = 0, + .y = 0, + .width = 4, + .height = 4 + }; + struct Rectangle a = { + .x = 0, + .y = 0, + .width = 2, + .height = 2 + }; + RectangleCenter(&ref, &a); + assert_int_equal(a.x, 1); + assert_int_equal(a.y, 1); +} + +M_TEST_DEFINE(centerRectRoundDown) { + struct Rectangle ref = { + .x = 0, + .y = 0, + .width = 4, + .height = 4 + }; + struct Rectangle a = { + .x = 0, + .y = 0, + .width = 1, + .height = 1 + }; + RectangleCenter(&ref, &a); + assert_int_equal(a.x, 1); + assert_int_equal(a.y, 1); +} + +M_TEST_DEFINE(centerRectRoundDown2) { + struct Rectangle ref = { + .x = 0, + .y = 0, + .width = 4, + .height = 4 + }; + struct Rectangle a = { + .x = 0, + .y = 0, + .width = 3, + .height = 2 + }; + RectangleCenter(&ref, &a); + assert_int_equal(a.x, 0); + assert_int_equal(a.y, 1); +} + +M_TEST_DEFINE(centerRectOffset) { + struct Rectangle ref = { + .x = 1, + .y = 1, + .width = 4, + .height = 4 + }; + struct Rectangle a = { + .x = 0, + .y = 0, + .width = 2, + .height = 2 + }; + RectangleCenter(&ref, &a); + assert_int_equal(a.x, 2); + assert_int_equal(a.y, 2); +} + +M_TEST_SUITE_DEFINE(Geometry, + cmocka_unit_test(unionRectOrigin), + cmocka_unit_test(unionRectOriginSwapped), + cmocka_unit_test(unionRectNonOrigin), + cmocka_unit_test(unionRectOverlapping), + cmocka_unit_test(unionRectSubRect), + cmocka_unit_test(unionRectNegativeOrigin), + cmocka_unit_test(centerRectBasic), + cmocka_unit_test(centerRectRoundDown), + cmocka_unit_test(centerRectRoundDown2), + cmocka_unit_test(centerRectOffset), +) From 09a53abe998e81d44c00eb10248e4f824881b7f6 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 20 Feb 2023 23:12:30 -0800 Subject: [PATCH 05/17] OpenGL: Add basic border rendering to GL 1.x driver --- CMakeLists.txt | 5 ++ src/platform/opengl/gl.c | 101 ++++++++++++++++++++++------------ src/platform/opengl/gles2.c | 11 +++- src/platform/qt/DisplayGL.cpp | 45 ++++++++++++++- src/platform/qt/DisplayGL.h | 4 +- src/platform/video-backend.c | 22 ++++++++ src/platform/video-backend.h | 3 + 7 files changed, 152 insertions(+), 39 deletions(-) create mode 100644 src/platform/video-backend.c diff --git a/CMakeLists.txt b/CMakeLists.txt index ac498d0d2..e86a761bc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -738,6 +738,11 @@ elseif(BUILD_GLES2) set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libgles2") endif() +if(USE_EPOXY OR BUILD_GL OR BUILD_GLES2) + # This file should probably go somewhere else + list(APPEND FEATURE_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/video-backend.c) +endif() + if(WIN32 AND NOT (LIBMGBA_ONLY OR SKIP_LIBRARY OR USE_EPOXY)) message(FATAL_ERROR "Windows requires epoxy module!") endif() diff --git a/src/platform/opengl/gl.c b/src/platform/opengl/gl.c index 8ad34197c..03bd831d2 100644 --- a/src/platform/opengl/gl.c +++ b/src/platform/opengl/gl.c @@ -9,9 +9,9 @@ static const GLint _glVertices[] = { 0, 0, - 256, 0, - 256, 256, - 0, 256 + 1, 0, + 1, 1, + 0, 1 }; static const GLint _glTexCoords[] = { @@ -101,15 +101,19 @@ static void mGLContextDeinit(struct VideoBackend* v) { } static void mGLContextResized(struct VideoBackend* v, unsigned w, unsigned h) { - struct mGLContext* context = (struct mGLContext*) v; unsigned drawW = w; unsigned drawH = h; + + unsigned maxW; + unsigned maxH; + VideoBackendGetFrameSize(v, &maxW, &maxH); + if (v->lockAspectRatio) { - lockAspectRatioUInt(context->layerDims[VIDEO_LAYER_IMAGE].width, context->layerDims[VIDEO_LAYER_IMAGE].height, &drawW, &drawH); + lockAspectRatioUInt(maxW, maxH, &drawW, &drawH); } if (v->lockIntegerScaling) { - lockIntegerRatioUInt(context->layerDims[VIDEO_LAYER_IMAGE].width, &drawW); - lockIntegerRatioUInt(context->layerDims[VIDEO_LAYER_IMAGE].height, &drawH); + lockIntegerRatioUInt(maxW, &drawW); + lockIntegerRatioUInt(maxH, &drawH); } glMatrixMode(GL_MODELVIEW); glLoadIdentity(); @@ -124,33 +128,7 @@ static void mGLContextClear(struct VideoBackend* v) { glClear(GL_COLOR_BUFFER_BIT); } -void mGLContextDrawFrame(struct VideoBackend* v) { - struct mGLContext* context = (struct mGLContext*) v; - glEnable(GL_TEXTURE_2D); - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - glEnableClientState(GL_VERTEX_ARRAY); - glVertexPointer(2, GL_INT, 0, _glVertices); - glTexCoordPointer(2, GL_INT, 0, _glTexCoords); - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glOrtho(0, context->layerDims[VIDEO_LAYER_IMAGE].width, context->layerDims[VIDEO_LAYER_IMAGE].height, 0, 0, 1); - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - if (v->interframeBlending) { - glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA); - glBlendColor(1, 1, 1, 0.5); - glBindTexture(GL_TEXTURE_2D, context->tex[context->activeTex ^ 1]); - if (v->filter) { - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - } else { - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - } - glDrawArrays(GL_TRIANGLE_FAN, 0, 4); - glEnable(GL_BLEND); - } - glBindTexture(GL_TEXTURE_2D, context->tex[context->activeTex]); +static void _setFilter(struct VideoBackend* v) { if (v->filter) { glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); @@ -158,6 +136,61 @@ void mGLContextDrawFrame(struct VideoBackend* v) { glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); } +} + +static void _setFrame(struct Rectangle* dims, int frameW, int frameH) { + GLint viewport[4]; + glGetIntegerv(GL_VIEWPORT, viewport); + glScissor(viewport[0] + dims->x * viewport[2] / frameW, + viewport[1] + dims->y * viewport[3] / frameH, + dims->width * viewport[2] / frameW, + dims->height * viewport[3] / frameH); + glTranslatef(dims->x, dims->y, 0); + glScalef(toPow2(dims->width), toPow2(dims->height), 1); +} + +void mGLContextDrawFrame(struct VideoBackend* v) { + struct mGLContext* context = (struct mGLContext*) v; + glEnable(GL_TEXTURE_2D); + glEnable(GL_SCISSOR_TEST); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glEnableClientState(GL_VERTEX_ARRAY); + glVertexPointer(2, GL_INT, 0, _glVertices); + glTexCoordPointer(2, GL_INT, 0, _glTexCoords); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + unsigned frameW, frameH; + VideoBackendGetFrameSize(v, &frameW, &frameH); + glOrtho(0, frameW, frameH, 0, 0, 1); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + int layer; + for (layer = 0; layer < VIDEO_LAYER_IMAGE; ++layer) { + if (context->layerDims[layer].width < 1 || context->layerDims[layer].height < 1) { + continue; + } + + glDisable(GL_BLEND); + glBindTexture(GL_TEXTURE_2D, context->layers[layer]); + _setFilter(v); + glPushMatrix(); + _setFrame(&context->layerDims[layer], frameW, frameH); + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + glPopMatrix(); + } + + _setFrame(&context->layerDims[VIDEO_LAYER_IMAGE], frameW, frameH); + if (v->interframeBlending) { + glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA); + glBlendColor(1, 1, 1, 0.5); + glBindTexture(GL_TEXTURE_2D, context->tex[context->activeTex ^ 1]); + _setFilter(v); + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + glEnable(GL_BLEND); + } + glBindTexture(GL_TEXTURE_2D, context->tex[context->activeTex]); + _setFilter(v); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); glDisable(GL_BLEND); } diff --git a/src/platform/opengl/gles2.c b/src/platform/opengl/gles2.c index fb2a07bba..674955a77 100644 --- a/src/platform/opengl/gles2.c +++ b/src/platform/opengl/gles2.c @@ -240,12 +240,17 @@ static void mGLES2ContextResized(struct VideoBackend* v, unsigned w, unsigned h) struct mGLES2Context* context = (struct mGLES2Context*) v; unsigned drawW = w; unsigned drawH = h; + + unsigned maxW; + unsigned maxH; + VideoBackendGetFrameSize(v, &maxW, &maxH); + if (v->lockAspectRatio) { - lockAspectRatioUInt(context->layerDims[VIDEO_LAYER_IMAGE].width, context->layerDims[VIDEO_LAYER_IMAGE].height, &drawW, &drawH); + lockAspectRatioUInt(maxW, maxH, &drawW, &drawH); } if (v->lockIntegerScaling) { - lockIntegerRatioUInt(context->layerDims[VIDEO_LAYER_IMAGE].width, &drawW); - lockIntegerRatioUInt(context->layerDims[VIDEO_LAYER_IMAGE].height, &drawH); + lockIntegerRatioUInt(maxW, &drawW); + lockIntegerRatioUInt(maxH, &drawH); } size_t n; for (n = 0; n < context->nShaders; ++n) { diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index fce25624d..e3155dc62 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -397,7 +397,7 @@ void DisplayGL::setVideoScale(int scale) { } void DisplayGL::setBackgroundImage(const QImage& image) { - // TODO + QMetaObject::invokeMethod(m_painter.get(), "setBackgroundImage", Q_ARG(const QImage&, image)); } void DisplayGL::resizeEvent(QResizeEvent* event) { @@ -631,12 +631,29 @@ void PainterGL::resizeContext() { Rectangle dims = {0, 0, size.width(), size.height()}; m_backend->setLayerDimensions(m_backend, VIDEO_LAYER_IMAGE, &dims); + recenterLayers(); } void PainterGL::setMessagePainter(MessagePainter* messagePainter) { m_messagePainter = messagePainter; } +void PainterGL::recenterLayers() { + const static std::initializer_list centeredLayers{VIDEO_LAYER_BACKGROUND, VIDEO_LAYER_IMAGE}; + Rectangle frame = {0}; + for (VideoLayer l : centeredLayers) { + Rectangle dims; + m_backend->layerDimensions(m_backend, l, &dims); + RectangleUnion(&frame, &dims); + } + for (VideoLayer l : centeredLayers) { + Rectangle dims; + m_backend->layerDimensions(m_backend, l, &dims); + RectangleCenter(&frame, &dims); + m_backend->setLayerDimensions(m_backend, l, &dims); + } +} + void PainterGL::resize(const QSize& size) { qreal r = m_window->devicePixelRatio(); m_size = size; @@ -995,6 +1012,32 @@ void PainterGL::updateFramebufferHandle() { m_context->setFramebufferHandle(m_bridgeTexIn); } +void PainterGL::setBackgroundImage(const QImage& image) { + if (!m_started) { + makeCurrent(); + } + + Rectangle dims = {0, 0, 0, 0}; + if (!image.isNull()) { + dims.width = static_cast(image.width()); + dims.height = static_cast(image.height()); + } + m_backend->setLayerDimensions(m_backend, VIDEO_LAYER_BACKGROUND, &dims); + recenterLayers(); + + if (!image.isNull()) { + m_background = image.convertToFormat(QImage::Format_RGB32); + m_background = m_background.rgbSwapped(); + m_backend->setImage(m_backend, VIDEO_LAYER_BACKGROUND, m_background.constBits()); + } else { + m_background = QImage(); + } + + if (!m_started) { + m_gl->doneCurrent(); + } +} + void PainterGL::swapTex() { if (!m_started) { return; diff --git a/src/platform/qt/DisplayGL.h b/src/platform/qt/DisplayGL.h index 87dd983dd..eed3a37c0 100644 --- a/src/platform/qt/DisplayGL.h +++ b/src/platform/qt/DisplayGL.h @@ -109,7 +109,6 @@ public slots: void setVideoScale(int scale) override; void setBackgroundImage(const QImage&) override; - protected: virtual void paintEvent(QPaintEvent*) override { forceDraw(); } virtual void resizeEvent(QResizeEvent*) override; @@ -178,6 +177,7 @@ public slots: void filter(bool filter); void resizeContext(); void updateFramebufferHandle(); + void setBackgroundImage(const QImage&); void setShaders(struct VDir*); void clearShaders(); @@ -196,6 +196,7 @@ private: void performDraw(); void dequeue(); void dequeueAll(bool keep = false); + void recenterLayers(); std::array, 3> m_buffers; QList m_free; @@ -214,6 +215,7 @@ private: QWindow* m_window; QSurface* m_surface; QSurfaceFormat m_format; + QImage m_background; std::unique_ptr m_paintDev; std::unique_ptr m_gl; int m_finalTexIdx = 0; diff --git a/src/platform/video-backend.c b/src/platform/video-backend.c new file mode 100644 index 000000000..3447fcdd8 --- /dev/null +++ b/src/platform/video-backend.c @@ -0,0 +1,22 @@ +/* Copyright (c) 2013-2023 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "video-backend.h" + +void VideoBackendGetFrameSize(const struct VideoBackend* v, unsigned* width, unsigned* height) { + *width = 0; + *height = 0; + int i; + for (i = 0; i < VIDEO_LAYER_MAX; ++i) { + struct Rectangle dims; + v->layerDimensions(v, i, &dims); + if (dims.x + dims.width > *width) { + *width = dims.x + dims.width; + } + if (dims.y + dims.height > *height) { + *height = dims.y + dims.height; + } + } +} diff --git a/src/platform/video-backend.h b/src/platform/video-backend.h index 3b7a4b245..3bb1c0a69 100644 --- a/src/platform/video-backend.h +++ b/src/platform/video-backend.h @@ -21,6 +21,7 @@ typedef void* WHandle; enum VideoLayer { VIDEO_LAYER_BACKGROUND = 0, + VIDEO_LAYER_BEZEL, VIDEO_LAYER_IMAGE, VIDEO_LAYER_OVERLAY, VIDEO_LAYER_MAX @@ -55,6 +56,8 @@ struct VideoShader { size_t nPasses; }; +void VideoBackendGetFrameSize(const struct VideoBackend*, unsigned* width, unsigned* height); + CXX_GUARD_END #endif From c7e4db58e30b78af8d7bc47f10ea6737df61c71d Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 22 Feb 2023 23:36:57 -0800 Subject: [PATCH 06/17] OpenGL: Add basic border rendering to modern GL driver --- src/platform/opengl/gles2.c | 66 ++++++++++++++++++++++++++----------- src/platform/opengl/gles2.h | 2 ++ 2 files changed, 48 insertions(+), 20 deletions(-) diff --git a/src/platform/opengl/gles2.c b/src/platform/opengl/gles2.c index 674955a77..653b863a9 100644 --- a/src/platform/opengl/gles2.c +++ b/src/platform/opengl/gles2.c @@ -206,7 +206,10 @@ static void mGLES2ContextSetLayerDimensions(struct VideoBackend* v, enum VideoLa glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dims->width, dims->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); #endif - if (layer == VIDEO_LAYER_BACKGROUND) { + unsigned newW; + unsigned newH; + VideoBackendGetFrameSize(v, &newW, &newH); + if (newW != context->width || newH != context->height) { size_t n; for (n = 0; n < context->nShaders; ++n) { if (context->shaders[n].width < 0 || context->shaders[n].height < 0) { @@ -215,6 +218,8 @@ static void mGLES2ContextSetLayerDimensions(struct VideoBackend* v, enum VideoLa } context->initialShader.dirty = true; context->interframeShader.dirty = true; + context->width = newW; + context->height = newH; } } @@ -241,9 +246,8 @@ static void mGLES2ContextResized(struct VideoBackend* v, unsigned w, unsigned h) unsigned drawW = w; unsigned drawH = h; - unsigned maxW; - unsigned maxH; - VideoBackendGetFrameSize(v, &maxW, &maxH); + unsigned maxW = context->width; + unsigned maxH = context->height; if (v->lockAspectRatio) { lockAspectRatioUInt(maxW, maxH, &drawW, &drawH); @@ -272,7 +276,7 @@ static void mGLES2ContextClear(struct VideoBackend* v) { glClear(GL_COLOR_BUFFER_BIT); } -void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) { +static void _drawShaderEx(struct mGLES2Context* context, struct mGLES2Shader* shader, int layer) { GLint viewport[4]; glGetIntegerv(GL_VIEWPORT, viewport); int drawW = shader->width; @@ -283,19 +287,19 @@ void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) { drawW = viewport[2]; padW = viewport[0]; } else if (shader->width < 0) { - drawW = context->layerDims[VIDEO_LAYER_IMAGE].width * -shader->width; + drawW = context->width * -shader->width; } if (!drawH) { drawH = viewport[3]; padH = viewport[1]; } else if (shader->height < 0) { - drawH = context->layerDims[VIDEO_LAYER_IMAGE].height * -shader->height; + drawH = context->height * -shader->height; } if (shader->integerScaling) { padW = 0; padH = 0; - drawW -= drawW % context->layerDims[VIDEO_LAYER_IMAGE].width; - drawH -= drawH % context->layerDims[VIDEO_LAYER_IMAGE].height; + drawW -= drawW % context->width; + drawH -= drawH % context->height; } if (shader->dirty) { @@ -309,22 +313,29 @@ void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) { shader->dirty = false; } + if (layer >= 0 && layer < VIDEO_LAYER_MAX) { + glViewport(context->layerDims[layer].x, context->layerDims[layer].y, context->layerDims[layer].width, context->layerDims[layer].height); + } else { + glViewport(padW, padH, drawW, drawH); + } + glBindFramebuffer(GL_FRAMEBUFFER, shader->fbo); - glViewport(padW, padH, drawW, drawH); if (shader->blend) { glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } else { glDisable(GL_BLEND); - glClearColor(0.f, 0.f, 0.f, 1.f); - glClear(GL_COLOR_BUFFER_BIT); + if (layer <= VIDEO_LAYER_BACKGROUND) { + glClearColor(0.f, 0.f, 0.f, 1.f); + glClear(GL_COLOR_BUFFER_BIT); + } } glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, shader->filter ? GL_LINEAR : GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, shader->filter ? GL_LINEAR : GL_NEAREST); glUseProgram(shader->program); glUniform1i(shader->texLocation, 0); - glUniform2f(shader->texSizeLocation, context->layerDims[VIDEO_LAYER_IMAGE].width, context->layerDims[VIDEO_LAYER_IMAGE].height); + glUniform2f(shader->texSizeLocation, context->width, context->height); glUniform2f(shader->outputSizeLocation, drawW, drawH); #ifdef BUILD_GLES3 if (shader->vao != (GLuint) -1) { @@ -391,21 +402,36 @@ void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) { glBindTexture(GL_TEXTURE_2D, shader->tex); } +static void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) { + _drawShaderEx(context, shader, -1); +} + void mGLES2ContextDrawFrame(struct VideoBackend* v) { struct mGLES2Context* context = (struct mGLES2Context*) v; glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, context->tex[VIDEO_LAYER_IMAGE]); GLint viewport[4]; glGetIntegerv(GL_VIEWPORT, viewport); context->finalShader.filter = v->filter; - _drawShader(context, &context->initialShader); - if (v->interframeBlending) { - context->interframeShader.blend = true; - glViewport(0, 0, viewport[2], viewport[3]); - _drawShader(context, &context->interframeShader); + + int layer; + for (layer = 0; layer <= VIDEO_LAYER_IMAGE; ++layer) { + if (context->layerDims[layer].width < 1 || context->layerDims[layer].height < 1) { + continue; + } + glBindTexture(GL_TEXTURE_2D, context->tex[layer]); + _drawShaderEx(context, &context->initialShader, layer); + if (layer != VIDEO_LAYER_IMAGE) { + continue; + } + if (v->interframeBlending) { + context->interframeShader.blend = true; + glViewport(0, 0, viewport[2], viewport[3]); + _drawShader(context, &context->interframeShader); + } } + size_t n; for (n = 0; n < context->nShaders; ++n) { glViewport(0, 0, viewport[2], viewport[3]); @@ -416,7 +442,7 @@ void mGLES2ContextDrawFrame(struct VideoBackend* v) { if (v->interframeBlending) { context->interframeShader.blend = false; glBindTexture(GL_TEXTURE_2D, context->tex[VIDEO_LAYER_IMAGE]); - _drawShader(context, &context->initialShader); + _drawShaderEx(context, &context->initialShader, VIDEO_LAYER_IMAGE); glViewport(0, 0, viewport[2], viewport[3]); _drawShader(context, &context->interframeShader); } diff --git a/src/platform/opengl/gles2.h b/src/platform/opengl/gles2.h index a789305db..ade50e455 100644 --- a/src/platform/opengl/gles2.h +++ b/src/platform/opengl/gles2.h @@ -83,6 +83,8 @@ struct mGLES2Context { GLuint vbo; struct Rectangle layerDims[VIDEO_LAYER_MAX]; + unsigned width; + unsigned height; struct mGLES2Shader initialShader; struct mGLES2Shader finalShader; From 08f360af90613f1a2b4d0ae2b8a0d334f6eb9129 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 28 Feb 2023 00:13:32 -0800 Subject: [PATCH 07/17] Qt: Ask the display, not the core, what size it should be --- src/platform/qt/Display.h | 1 + src/platform/qt/DisplayGL.cpp | 16 ++++++++++++++++ src/platform/qt/DisplayGL.h | 4 ++++ src/platform/qt/DisplayQt.cpp | 18 +++++++++++++----- src/platform/qt/DisplayQt.h | 1 + src/platform/qt/Window.cpp | 4 ++-- 6 files changed, 37 insertions(+), 7 deletions(-) diff --git a/src/platform/qt/Display.h b/src/platform/qt/Display.h index dda48d875..4fc100799 100644 --- a/src/platform/qt/Display.h +++ b/src/platform/qt/Display.h @@ -55,6 +55,7 @@ public: virtual int framebufferHandle() { return -1; } virtual void setVideoScale(int) {} virtual void setBackgroundImage(const QImage&) = 0; + virtual QSize contentSize() const = 0; virtual void setVideoProxy(std::shared_ptr proxy) { m_videoProxy = proxy; } std::shared_ptr videoProxy() { return m_videoProxy; } diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index e3155dc62..dc7ad9a86 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -245,6 +245,8 @@ void DisplayGL::startDrawing(std::shared_ptr controller) { show(); m_gl->reset(); } + + QTimer::singleShot(8, this, &DisplayGL::updateContentSize); } bool DisplayGL::supportsFormat(const QSurfaceFormat& format) { @@ -331,12 +333,14 @@ void DisplayGL::unpauseDrawing() { if (!m_gl && shouldDisableUpdates()) { setUpdatesEnabled(false); } + QMetaObject::invokeMethod(this, "updateContentSize", Qt::QueuedConnection); } } void DisplayGL::forceDraw() { if (m_hasStarted) { QMetaObject::invokeMethod(m_painter.get(), "forceDraw"); + QMetaObject::invokeMethod(this, "updateContentSize", Qt::QueuedConnection); } } @@ -398,6 +402,7 @@ void DisplayGL::setVideoScale(int scale) { void DisplayGL::setBackgroundImage(const QImage& image) { QMetaObject::invokeMethod(m_painter.get(), "setBackgroundImage", Q_ARG(const QImage&, image)); + QMetaObject::invokeMethod(this, "updateContentSize", Qt::QueuedConnection); } void DisplayGL::resizeEvent(QResizeEvent* event) { @@ -452,6 +457,10 @@ void DisplayGL::setupProxyThread() { m_proxyThread.start(); } +void DisplayGL::updateContentSize() { + QMetaObject::invokeMethod(m_painter.get(), "contentSize", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QSize, m_cachedContentSize)); +} + int DisplayGL::framebufferHandle() { return m_painter->glTex(); } @@ -972,6 +981,13 @@ VideoShader* PainterGL::shaders() { return &m_shader; } +QSize PainterGL::contentSize() const { + unsigned width, height; + VideoBackendGetFrameSize(m_backend, &width, &height); + return {static_cast(width > static_cast(INT_MAX) ? INT_MAX : width), + static_cast(height > static_cast(INT_MAX) ? INT_MAX : height)}; +} + int PainterGL::glTex() { #if defined(BUILD_GLES2) || defined(BUILD_GLES3) if (supportsShaders()) { diff --git a/src/platform/qt/DisplayGL.h b/src/platform/qt/DisplayGL.h index eed3a37c0..a02b2289c 100644 --- a/src/platform/qt/DisplayGL.h +++ b/src/platform/qt/DisplayGL.h @@ -88,6 +88,7 @@ public: VideoShader* shaders() override; void setVideoProxy(std::shared_ptr) override; int framebufferHandle() override; + QSize contentSize() const override { return m_cachedContentSize; } static bool supportsFormat(const QSurfaceFormat&); @@ -115,6 +116,7 @@ protected: private slots: void setupProxyThread(); + void updateContentSize(); private: void resizePainter(); @@ -131,6 +133,7 @@ private: mGLWidget* m_gl; QOffscreenSurface m_proxySurface; std::unique_ptr m_proxyContext; + QSize m_cachedContentSize; }; class PainterGL : public QObject { @@ -182,6 +185,7 @@ public slots: void setShaders(struct VDir*); void clearShaders(); VideoShader* shaders(); + QSize contentSize() const; signals: void created(); diff --git a/src/platform/qt/DisplayQt.cpp b/src/platform/qt/DisplayQt.cpp index 7669b5cf1..dee0d1244 100644 --- a/src/platform/qt/DisplayQt.cpp +++ b/src/platform/qt/DisplayQt.cpp @@ -106,22 +106,18 @@ void DisplayQt::paintEvent(QPaintEvent*) { QRect bgRect(0, 0, m_background.width(), m_background.height()); QRect imRect(0, 0, m_width, m_height); - QSize outerFrame; + QSize outerFrame = contentSize(); if (bgRect.width() > imRect.width()) { imRect.moveLeft(bgRect.width() - imRect.width()); - outerFrame.setWidth(bgRect.width()); } else { bgRect.moveLeft(imRect.width() - bgRect.width()); - outerFrame.setWidth(imRect.width()); } if (bgRect.height() > imRect.height()) { imRect.moveTop(bgRect.height() - imRect.height()); - outerFrame.setHeight(bgRect.height()); } else { bgRect.moveTop(imRect.height() - bgRect.height()); - outerFrame.setHeight(imRect.height()); } QRect full(clampSize(outerFrame, size(), isAspectRatioLocked(), isIntegerScalingLocked())); @@ -184,3 +180,15 @@ void DisplayQt::paintEvent(QPaintEvent*) { messagePainter()->paint(&painter); } } + +QSize DisplayQt::contentSize() const { + QSize outerFrame(m_width, m_height); + + if (m_background.width() > outerFrame.width()) { + outerFrame.setWidth(m_background.width()); + } + if (m_background.height() > outerFrame.height()) { + outerFrame.setHeight(m_background.height()); + } + return outerFrame; +} diff --git a/src/platform/qt/DisplayQt.h b/src/platform/qt/DisplayQt.h index acdd95f24..889a6a367 100644 --- a/src/platform/qt/DisplayQt.h +++ b/src/platform/qt/DisplayQt.h @@ -22,6 +22,7 @@ public: bool isDrawing() const override { return m_isDrawing; } bool supportsShaders() const override { return false; } VideoShader* shaders() override { return nullptr; } + QSize contentSize() const override; public slots: void stopDrawing() override; diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index ce0c64a06..083732cd8 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -1488,8 +1488,8 @@ void Window::setupMenu(QMenuBar* menubar) { Action* setSize = m_frameSizes[i]; showNormal(); QSize size(GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS); - if (m_controller) { - size = m_controller->screenDimensions(); + if (m_display) { + size = m_display->contentSize(); } size *= i; m_savedScale = i; From 48c9261b057a137caaf906c2482705b4a45c2f68 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 28 Feb 2023 22:27:11 -0800 Subject: [PATCH 08/17] SDL: Refactor use of VideoBackend to slim down GL backends --- src/platform/sdl/gl-common.c | 42 ++++++++++++++++++++++++++++++++ src/platform/sdl/gl-common.h | 1 + src/platform/sdl/gl-sdl.c | 46 ++---------------------------------- src/platform/sdl/gles2-sdl.c | 36 ++-------------------------- src/platform/sdl/main.h | 2 ++ 5 files changed, 49 insertions(+), 78 deletions(-) diff --git a/src/platform/sdl/gl-common.c b/src/platform/sdl/gl-common.c index 264e3cafe..47988497d 100644 --- a/src/platform/sdl/gl-common.c +++ b/src/platform/sdl/gl-common.c @@ -5,6 +5,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "main.h" +#include +#include #include void mSDLGLDoViewport(int w, int h, struct VideoBackend* v) { @@ -66,3 +68,43 @@ bool mSDLGLCommonInit(struct mSDLRenderer* renderer) { #endif return true; } + +void mSDLGLCommonRunloop(struct mSDLRenderer* renderer, void* user) { + struct mCoreThread* context = user; + SDL_Event event; + struct VideoBackend* v = renderer->backend; + + while (mCoreThreadIsActive(context)) { + while (SDL_PollEvent(&event)) { + mSDLHandleEvent(context, &renderer->player, &event); + // Event handling can change the size of the screen + if (renderer->player.windowUpdated) { +#if SDL_VERSION_ATLEAST(2, 0, 0) + SDL_GetWindowSize(renderer->window, &renderer->viewportWidth, &renderer->viewportHeight); +#else + renderer->viewportWidth = renderer->player.newWidth; + renderer->viewportHeight = renderer->player.newHeight; + mSDLGLCommonInit(renderer); +#endif + mSDLGLDoViewport(renderer->viewportWidth, renderer->viewportHeight, v); + renderer->player.windowUpdated = 0; + } + } + renderer->core->currentVideoSize(renderer->core, &renderer->width, &renderer->height); + struct Rectangle dims; + v->layerDimensions(v, VIDEO_LAYER_IMAGE, &dims); + if (renderer->width != dims.width || renderer->height != dims.height) { + renderer->core->setVideoBuffer(renderer->core, renderer->outputBuffer, renderer->width); + dims.width = renderer->width; + dims.height = renderer->height; + v->setLayerDimensions(v, VIDEO_LAYER_IMAGE, &dims); + } + + if (mCoreSyncWaitFrameStart(&context->impl->sync)) { + v->setImage(v, VIDEO_LAYER_IMAGE, renderer->outputBuffer); + } + mCoreSyncWaitFrameEnd(&context->impl->sync); + v->drawFrame(v); + v->swap(v); + } +} diff --git a/src/platform/sdl/gl-common.h b/src/platform/sdl/gl-common.h index be98d7964..5658abee4 100644 --- a/src/platform/sdl/gl-common.h +++ b/src/platform/sdl/gl-common.h @@ -15,6 +15,7 @@ struct mSDLRenderer; void mSDLGLDoViewport(int w, int h, struct VideoBackend* v); void mSDLGLCommonSwap(struct VideoBackend* context); bool mSDLGLCommonInit(struct mSDLRenderer* renderer); +void mSDLGLCommonRunloop(struct mSDLRenderer* renderer, void* user); CXX_GUARD_END diff --git a/src/platform/sdl/gl-sdl.c b/src/platform/sdl/gl-sdl.c index a6d0a4e0e..226353f90 100644 --- a/src/platform/sdl/gl-sdl.c +++ b/src/platform/sdl/gl-sdl.c @@ -8,19 +8,17 @@ #include "gl-common.h" #include -#include -#include #include "platform/opengl/gl.h" static bool mSDLGLInit(struct mSDLRenderer* renderer); -static void mSDLGLRunloop(struct mSDLRenderer* renderer, void* user); static void mSDLGLDeinit(struct mSDLRenderer* renderer); void mSDLGLCreate(struct mSDLRenderer* renderer) { renderer->init = mSDLGLInit; renderer->deinit = mSDLGLDeinit; - renderer->runloop = mSDLGLRunloop; + renderer->runloop = mSDLGLCommonRunloop; + renderer->backend = &renderer->gl.d; } bool mSDLGLInit(struct mSDLRenderer* renderer) { @@ -49,46 +47,6 @@ bool mSDLGLInit(struct mSDLRenderer* renderer) { return true; } -void mSDLGLRunloop(struct mSDLRenderer* renderer, void* user) { - struct mCoreThread* context = user; - SDL_Event event; - struct VideoBackend* v = &renderer->gl.d; - - while (mCoreThreadIsActive(context)) { - while (SDL_PollEvent(&event)) { - mSDLHandleEvent(context, &renderer->player, &event); - // Event handling can change the size of the screen - if (renderer->player.windowUpdated) { -#if SDL_VERSION_ATLEAST(2, 0, 0) - SDL_GetWindowSize(renderer->window, &renderer->viewportWidth, &renderer->viewportHeight); -#else - renderer->viewportWidth = renderer->player.newWidth; - renderer->viewportHeight = renderer->player.newHeight; - mSDLGLCommonInit(renderer); -#endif - mSDLGLDoViewport(renderer->viewportWidth, renderer->viewportHeight, v); - renderer->player.windowUpdated = 0; - } - } - renderer->core->currentVideoSize(renderer->core, &renderer->width, &renderer->height); - struct Rectangle dims; - v->layerDimensions(v, VIDEO_LAYER_IMAGE, &dims); - if (renderer->width != dims.width || renderer->height != dims.height) { - renderer->core->setVideoBuffer(renderer->core, renderer->outputBuffer, renderer->width); - dims.width = renderer->width; - dims.height = renderer->height; - v->setLayerDimensions(v, VIDEO_LAYER_IMAGE, &dims); - } - - if (mCoreSyncWaitFrameStart(&context->impl->sync)) { - v->setImage(v, VIDEO_LAYER_IMAGE, renderer->outputBuffer); - } - mCoreSyncWaitFrameEnd(&context->impl->sync); - v->drawFrame(v); - v->swap(v); - } -} - void mSDLGLDeinit(struct mSDLRenderer* renderer) { if (renderer->gl.d.deinit) { renderer->gl.d.deinit(&renderer->gl.d); diff --git a/src/platform/sdl/gles2-sdl.c b/src/platform/sdl/gles2-sdl.c index e65d430ee..f5574435f 100644 --- a/src/platform/sdl/gles2-sdl.c +++ b/src/platform/sdl/gles2-sdl.c @@ -11,20 +11,19 @@ #endif #include -#include #ifdef __linux__ #include #endif static bool mSDLGLES2Init(struct mSDLRenderer* renderer); -static void mSDLGLES2Runloop(struct mSDLRenderer* renderer, void* user); static void mSDLGLES2Deinit(struct mSDLRenderer* renderer); void mSDLGLES2Create(struct mSDLRenderer* renderer) { renderer->init = mSDLGLES2Init; renderer->deinit = mSDLGLES2Deinit; - renderer->runloop = mSDLGLES2Runloop; + renderer->runloop = mSDLGLCommonRunloop; + renderer->backend = &renderer->gl2.d; } bool mSDLGLES2Init(struct mSDLRenderer* renderer) { @@ -63,37 +62,6 @@ bool mSDLGLES2Init(struct mSDLRenderer* renderer) { return true; } -void mSDLGLES2Runloop(struct mSDLRenderer* renderer, void* user) { - struct mCoreThread* context = user; - SDL_Event event; - struct VideoBackend* v = &renderer->gl2.d; - - while (mCoreThreadIsActive(context)) { - while (SDL_PollEvent(&event)) { - mSDLHandleEvent(context, &renderer->player, &event); - // Event handling can change the size of the screen - if (renderer->player.windowUpdated) { -#if SDL_VERSION_ATLEAST(2, 0, 0) - SDL_GetWindowSize(renderer->window, &renderer->viewportWidth, &renderer->viewportHeight); -#else - renderer->viewportWidth = renderer->player.newWidth; - renderer->viewportHeight = renderer->player.newHeight; - mSDLGLCommonInit(renderer); -#endif - mSDLGLDoViewport(renderer->viewportWidth, renderer->viewportHeight, v); - renderer->player.windowUpdated = 0; - } - } - - if (mCoreSyncWaitFrameStart(&context->impl->sync)) { - v->setImage(v, VIDEO_LAYER_IMAGE, renderer->outputBuffer); - } - mCoreSyncWaitFrameEnd(&context->impl->sync); - v->drawFrame(v); - v->swap(v); - } -} - void mSDLGLES2Deinit(struct mSDLRenderer* renderer) { if (renderer->gl2.d.deinit) { renderer->gl2.d.deinit(&renderer->gl2.d); diff --git a/src/platform/sdl/main.h b/src/platform/sdl/main.h index c97e50e33..10135da2f 100644 --- a/src/platform/sdl/main.h +++ b/src/platform/sdl/main.h @@ -76,6 +76,8 @@ struct mSDLRenderer { struct mGLES2Context gl2; #endif + struct VideoBackend* backend; + #ifdef USE_PIXMAN pixman_image_t* pix; pixman_image_t* screenpix; From 44fb887737a1b742b6349fb63716677993073b84 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 28 Feb 2023 23:34:55 -0800 Subject: [PATCH 09/17] SDL: Border rendering --- src/platform/sdl/gl-common.c | 77 ++++++++++++++++++++++++++++++++++++ src/platform/sdl/gl-common.h | 1 + src/platform/video-backend.c | 19 ++++----- src/platform/video-backend.h | 1 + 4 files changed, 89 insertions(+), 9 deletions(-) diff --git a/src/platform/sdl/gl-common.c b/src/platform/sdl/gl-common.c index 47988497d..53219a7bb 100644 --- a/src/platform/sdl/gl-common.c +++ b/src/platform/sdl/gl-common.c @@ -9,6 +9,11 @@ #include #include +#ifdef USE_PNG +#include +#include +#endif + void mSDLGLDoViewport(int w, int h, struct VideoBackend* v) { v->contextResized(v, w, h); v->clear(v); @@ -26,6 +31,60 @@ void mSDLGLCommonSwap(struct VideoBackend* context) { #endif } +bool mSDLGLCommonLoadBackground(struct VideoBackend* context) { +#ifdef USE_PNG + struct mSDLRenderer* renderer = context->user; + const char* bgImage = mCoreConfigGetValue(&renderer->core->config, "backgroundImage"); + if (!bgImage) { + return false; + } + struct VFile* vf = VFileOpen(bgImage, O_RDONLY); + if (!vf) { + return false; + } + + bool ok = false; + png_structp png = PNGReadOpen(vf, 0); + png_infop info = png_create_info_struct(png); + png_infop end = png_create_info_struct(png); + if (!png || !info || !end) { + goto done; + } + + if (!PNGReadHeader(png, info)) { + goto done; + } + unsigned width = png_get_image_width(png, info); + unsigned height = png_get_image_height(png, info); + uint32_t* pixels = malloc(width * height * 4); + if (!pixels) { + goto done; + } + + if (!PNGReadPixels(png, info, pixels, width, height, width) || !PNGReadFooter(png, end)) { + free(pixels); + goto done; + } + + struct Rectangle dims = { + .width = width, + .height = height + }; + context->setLayerDimensions(context, VIDEO_LAYER_BACKGROUND, &dims); + context->setImage(context, VIDEO_LAYER_BACKGROUND, pixels); + free(pixels); + ok = true; + +done: + PNGReadClose(png, info, end); + vf->close(vf); + return ok; +#else + UNUSED(context); + return false; +#endif +} + bool mSDLGLCommonInit(struct mSDLRenderer* renderer) { #ifndef COLOR_16_BIT SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); @@ -74,6 +133,24 @@ void mSDLGLCommonRunloop(struct mSDLRenderer* renderer, void* user) { SDL_Event event; struct VideoBackend* v = renderer->backend; + if (mSDLGLCommonLoadBackground(v)) { + renderer->player.windowUpdated = true; + + struct Rectangle frame; + VideoBackendGetFrame(v, &frame); + int i; + for (i = 0; i <= VIDEO_LAYER_IMAGE; ++i) { + struct Rectangle dims; + v->layerDimensions(v, i, &dims); + RectangleCenter(&frame, &dims); + v->setLayerDimensions(v, i, &dims); + } + +#if SDL_VERSION_ATLEAST(2, 0, 0) + SDL_SetWindowSize(renderer->window, frame.width * renderer->ratio, frame.height * renderer->ratio); +#endif + } + while (mCoreThreadIsActive(context)) { while (SDL_PollEvent(&event)) { mSDLHandleEvent(context, &renderer->player, &event); diff --git a/src/platform/sdl/gl-common.h b/src/platform/sdl/gl-common.h index 5658abee4..f52cfd633 100644 --- a/src/platform/sdl/gl-common.h +++ b/src/platform/sdl/gl-common.h @@ -16,6 +16,7 @@ void mSDLGLDoViewport(int w, int h, struct VideoBackend* v); void mSDLGLCommonSwap(struct VideoBackend* context); bool mSDLGLCommonInit(struct mSDLRenderer* renderer); void mSDLGLCommonRunloop(struct mSDLRenderer* renderer, void* user); +bool mSDLGLCommonLoadBackground(struct VideoBackend* context); CXX_GUARD_END diff --git a/src/platform/video-backend.c b/src/platform/video-backend.c index 3447fcdd8..998764d0d 100644 --- a/src/platform/video-backend.c +++ b/src/platform/video-backend.c @@ -5,18 +5,19 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "video-backend.h" -void VideoBackendGetFrameSize(const struct VideoBackend* v, unsigned* width, unsigned* height) { - *width = 0; - *height = 0; +void VideoBackendGetFrame(const struct VideoBackend* v, struct Rectangle* frame) { + memset(frame, 0, sizeof(*frame)); int i; for (i = 0; i < VIDEO_LAYER_MAX; ++i) { struct Rectangle dims; v->layerDimensions(v, i, &dims); - if (dims.x + dims.width > *width) { - *width = dims.x + dims.width; - } - if (dims.y + dims.height > *height) { - *height = dims.y + dims.height; - } + RectangleUnion(frame, &dims); } } + +void VideoBackendGetFrameSize(const struct VideoBackend* v, unsigned* width, unsigned* height) { + struct Rectangle frame; + VideoBackendGetFrame(v, &frame); + *width = frame.width; + *height = frame.height; +} diff --git a/src/platform/video-backend.h b/src/platform/video-backend.h index 3bb1c0a69..5a3319721 100644 --- a/src/platform/video-backend.h +++ b/src/platform/video-backend.h @@ -56,6 +56,7 @@ struct VideoShader { size_t nPasses; }; +void VideoBackendGetFrame(const struct VideoBackend*, struct Rectangle* frame); void VideoBackendGetFrameSize(const struct VideoBackend*, unsigned* width, unsigned* height); CXX_GUARD_END From fd0deaaecceec513849e2d141c3fbe420dd6aee9 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 14 Mar 2023 01:11:11 -0700 Subject: [PATCH 10/17] GBA Memory: Play slightly nicer with CSE --- src/gba/memory.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/gba/memory.c b/src/gba/memory.c index 4f3063cfb..6d0ff6156 100644 --- a/src/gba/memory.c +++ b/src/gba/memory.c @@ -405,7 +405,7 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) { #define LOAD_CART \ wait += waitstatesRegion[address >> BASE_OFFSET]; \ - if ((address & (GBA_SIZE_ROM0 - 1)) < memory->romSize) { \ + if ((address & (GBA_SIZE_ROM0 - 4)) < memory->romSize) { \ LOAD_32(value, address & (GBA_SIZE_ROM0 - 4), memory->rom); \ } else if (memory->vfame.cartType) { \ value = GBAVFameGetPatternValue(address, 32); \ @@ -570,11 +570,11 @@ uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { case GBA_REGION_ROM1_EX: case GBA_REGION_ROM2: wait = memory->waitstatesNonseq16[address >> BASE_OFFSET]; - if ((address & (GBA_SIZE_ROM0 - 1)) < memory->romSize) { + if ((address & (GBA_SIZE_ROM0 - 2)) < memory->romSize) { LOAD_16(value, address & (GBA_SIZE_ROM0 - 2), memory->rom); } else if (memory->vfame.cartType) { value = GBAVFameGetPatternValue(address, 16); - } else if ((address & (GBA_SIZE_ROM0 - 1)) >= AGB_PRINT_BASE) { + } else if ((address & (GBA_SIZE_ROM0 - 2)) >= AGB_PRINT_BASE) { uint32_t agbPrintAddr = address & 0x00FFFFFF; if (agbPrintAddr == AGB_PRINT_PROTECT) { value = memory->agbPrintProtect; @@ -595,7 +595,7 @@ uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { value = GBASavedataReadEEPROM(&memory->savedata); } else if ((address & 0x0DFC0000) >= 0x0DF80000 && memory->hw.devices & HW_EREADER) { value = GBACartEReaderRead(&memory->ereader, address); - } else if ((address & (GBA_SIZE_ROM0 - 1)) < memory->romSize) { + } else if ((address & (GBA_SIZE_ROM0 - 2)) < memory->romSize) { LOAD_16(value, address & (GBA_SIZE_ROM0 - 2), memory->rom); } else if (memory->vfame.cartType) { value = GBAVFameGetPatternValue(address, 16); @@ -1219,7 +1219,7 @@ void GBAPatch32(struct ARMCore* cpu, uint32_t address, int32_t value, int32_t* o mLOG(GBA_MEM, STUB, "Unimplemented memory Patch32: 0x%08X", address); break; case GBA_REGION_PALETTE_RAM: - LOAD_32(oldValue, address & (GBA_SIZE_PALETTE_RAM - 1), gba->video.palette); + LOAD_32(oldValue, address & (GBA_SIZE_PALETTE_RAM - 4), gba->video.palette); STORE_32(value, address & (GBA_SIZE_PALETTE_RAM - 4), gba->video.palette); gba->video.renderer->writePalette(gba->video.renderer, address & (GBA_SIZE_PALETTE_RAM - 4), value); gba->video.renderer->writePalette(gba->video.renderer, (address & (GBA_SIZE_PALETTE_RAM - 4)) + 2, value >> 16); @@ -1320,7 +1320,7 @@ void GBAPatch16(struct ARMCore* cpu, uint32_t address, int16_t value, int16_t* o case GBA_REGION_ROM2: case GBA_REGION_ROM2_EX: _pristineCow(gba); - if ((address & (GBA_SIZE_ROM0 - 1)) >= gba->memory.romSize) { + if ((address & (GBA_SIZE_ROM0 - 2)) >= gba->memory.romSize) { gba->memory.romSize = (address & (GBA_SIZE_ROM0 - 2)) + 2; gba->memory.romMask = toPow2(gba->memory.romSize) - 1; } From 434789c6d1ab825bc4b2954df852af3817f63133 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 14 Mar 2023 01:27:19 -0700 Subject: [PATCH 11/17] Qt: Placate Coverity a bit --- src/platform/qt/ApplicationUpdater.cpp | 1 + src/platform/qt/BattleChipModel.h | 4 ++-- src/platform/qt/DisplayQt.h | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/platform/qt/ApplicationUpdater.cpp b/src/platform/qt/ApplicationUpdater.cpp index 21def050c..7eebe2c57 100644 --- a/src/platform/qt/ApplicationUpdater.cpp +++ b/src/platform/qt/ApplicationUpdater.cpp @@ -100,6 +100,7 @@ ApplicationUpdater::UpdateInfo ApplicationUpdater::currentVersion() { info.version = QLatin1String(projectVersion); info.rev = gitRevision; info.commit = QLatin1String(gitCommit); + info.size = 0; return info; } diff --git a/src/platform/qt/BattleChipModel.h b/src/platform/qt/BattleChipModel.h index c0f115896..9c22002ee 100644 --- a/src/platform/qt/BattleChipModel.h +++ b/src/platform/qt/BattleChipModel.h @@ -49,10 +49,10 @@ private: BattleChip createChip(int id) const; QMap m_chipIdToName; - int m_flavor; + int m_flavor = 0; int m_scale = 1; QList m_deck; }; -} \ No newline at end of file +} diff --git a/src/platform/qt/DisplayQt.h b/src/platform/qt/DisplayQt.h index e8623468f..269848229 100644 --- a/src/platform/qt/DisplayQt.h +++ b/src/platform/qt/DisplayQt.h @@ -42,8 +42,8 @@ protected: private: bool m_isDrawing = false; - int m_width; - int m_height; + int m_width = -1; + int m_height = -1; QImage m_backing{nullptr}; QImage m_oldBacking{nullptr}; std::shared_ptr m_context = nullptr; From ea3e6d6b548bf0b81e39a57d51baa4c0e959d411 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 15 Mar 2023 20:52:52 -0700 Subject: [PATCH 12/17] Core: Fixx M_*8 macros --- include/mgba/core/interface.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/mgba/core/interface.h b/include/mgba/core/interface.h index 9046d1a4f..fc6f7ee6c 100644 --- a/include/mgba/core/interface.h +++ b/include/mgba/core/interface.h @@ -27,9 +27,9 @@ typedef uint32_t color_t; #define M_G5(X) (((X) >> 5) & 0x1F) #define M_B5(X) (((X) >> 10) & 0x1F) -#define M_R8(X) (((((X) << 3) & 0xF8) * 0x21) >> 5) -#define M_G8(X) (((((X) >> 2) & 0xF8) * 0x21) >> 5) -#define M_B8(X) (((((X) >> 7) & 0xF8) * 0x21) >> 5) +#define M_R8(X) (((((X) << 3) & 0xF8) * 0x21) >> 2) +#define M_G8(X) (((((X) >> 2) & 0xF8) * 0x21) >> 2) +#define M_B8(X) (((((X) >> 7) & 0xF8) * 0x21) >> 2) #define M_RGB5_TO_BGR8(X) ((M_R5(X) << 3) | (M_G5(X) << 11) | (M_B5(X) << 19)) #define M_RGB5_TO_RGB8(X) ((M_R5(X) << 19) | (M_G5(X) << 11) | (M_B5(X) << 3)) From cd0b5193cb1eccad4400c6de587cf3352fd3fca3 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 15 Mar 2023 22:44:52 -0700 Subject: [PATCH 13/17] Core: An empty config string is a null config value --- src/core/config.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/config.c b/src/core/config.c index 715628f0f..b9f0167ec 100644 --- a/src/core/config.c +++ b/src/core/config.c @@ -79,7 +79,7 @@ static const char* _lookupValue(const struct mCoreConfig* config, const char* ke static bool _lookupCharValue(const struct mCoreConfig* config, const char* key, char** out) { const char* value = _lookupValue(config, key); - if (!value) { + if (!value || !value[0]) { return false; } if (*out) { From fc35395ab83fbe8328515e1a1528f614a1899297 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 16 Mar 2023 23:37:54 -0700 Subject: [PATCH 14/17] Core: Handle relative paths for saves, screenshots, etc consistently (fixes #2826) --- CHANGES | 1 + CMakeLists.txt | 2 ++ include/mgba-util/vfs.h | 3 +++ src/core/directories.c | 39 ++++++++++++++++++++++++--------------- src/util/vfs.c | 39 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 69 insertions(+), 15 deletions(-) diff --git a/CHANGES b/CHANGES index 11e7601df..dfcbfceb8 100644 --- a/CHANGES +++ b/CHANGES @@ -28,6 +28,7 @@ Other fixes: - Scripting: Fix receiving packets for client sockets - Scripting: Fix empty receive calls returning unknown error on Windows Misc: + - Core: Handle relative paths for saves, screenshots, etc consistently (fixes mgba.io/i/2826) - GB Serialize: Add missing savestate support for MBC6 and NT (newer) - GBA: Improve detection of valid ELF ROMs - Qt: Include wayland QPA in AppImage (fixes mgba.io/i/2796) diff --git a/CMakeLists.txt b/CMakeLists.txt index ac498d0d2..aebbfecae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -338,6 +338,8 @@ find_function(popcount32) find_function(futimens) find_function(futimes) +find_function(realpath) + if(ANDROID AND ANDROID_NDK_MAJOR GREATER 13) find_function(localtime_r) set(HAVE_STRTOF_L ON) diff --git a/include/mgba-util/vfs.h b/include/mgba-util/vfs.h index 334f84419..eee68b99e 100644 --- a/include/mgba-util/vfs.h +++ b/include/mgba-util/vfs.h @@ -100,6 +100,9 @@ struct VFile* VFileFromFILE(FILE* file); void separatePath(const char* path, char* dirname, char* basename, char* extension); +bool isAbsolute(const char* path); +void makeAbsolute(const char* path, const char* base, char* out); + struct VFile* VDirFindFirst(struct VDir* dir, bool (*filter)(struct VFile*)); struct VFile* VDirFindNextAvailable(struct VDir*, const char* basename, const char* infix, const char* suffix, int mode); diff --git a/src/core/directories.c b/src/core/directories.c index 63c66e564..c35773f1e 100644 --- a/src/core/directories.c +++ b/src/core/directories.c @@ -116,10 +116,15 @@ struct VFile* mDirectorySetOpenSuffix(struct mDirectorySet* dirs, struct VDir* d } void mDirectorySetMapOptions(struct mDirectorySet* dirs, const struct mCoreOptions* opts) { + char abspath[PATH_MAX + 1]; + char configDir[PATH_MAX + 1]; + mCoreConfigDirectory(configDir, sizeof(configDir)); + if (opts->savegamePath) { - struct VDir* dir = VDirOpen(opts->savegamePath); - if (!dir && VDirCreate(opts->savegamePath)) { - dir = VDirOpen(opts->savegamePath); + makeAbsolute(opts->savegamePath, configDir, abspath); + struct VDir* dir = VDirOpen(abspath); + if (!dir && VDirCreate(abspath)) { + dir = VDirOpen(abspath); } if (dir) { if (dirs->save && dirs->save != dirs->base) { @@ -130,9 +135,10 @@ void mDirectorySetMapOptions(struct mDirectorySet* dirs, const struct mCoreOptio } if (opts->savestatePath) { - struct VDir* dir = VDirOpen(opts->savestatePath); - if (!dir && VDirCreate(opts->savestatePath)) { - dir = VDirOpen(opts->savestatePath); + makeAbsolute(opts->savestatePath, configDir, abspath); + struct VDir* dir = VDirOpen(abspath); + if (!dir && VDirCreate(abspath)) { + dir = VDirOpen(abspath); } if (dir) { if (dirs->state && dirs->state != dirs->base) { @@ -143,9 +149,10 @@ void mDirectorySetMapOptions(struct mDirectorySet* dirs, const struct mCoreOptio } if (opts->screenshotPath) { - struct VDir* dir = VDirOpen(opts->screenshotPath); - if (!dir && VDirCreate(opts->screenshotPath)) { - dir = VDirOpen(opts->screenshotPath); + makeAbsolute(opts->screenshotPath, configDir, abspath); + struct VDir* dir = VDirOpen(abspath); + if (!dir && VDirCreate(abspath)) { + dir = VDirOpen(abspath); } if (dir) { if (dirs->screenshot && dirs->screenshot != dirs->base) { @@ -156,9 +163,10 @@ void mDirectorySetMapOptions(struct mDirectorySet* dirs, const struct mCoreOptio } if (opts->patchPath) { - struct VDir* dir = VDirOpen(opts->patchPath); - if (!dir && VDirCreate(opts->patchPath)) { - dir = VDirOpen(opts->patchPath); + makeAbsolute(opts->patchPath, configDir, abspath); + struct VDir* dir = VDirOpen(abspath); + if (!dir && VDirCreate(abspath)) { + dir = VDirOpen(abspath); } if (dir) { if (dirs->patch && dirs->patch != dirs->base) { @@ -169,9 +177,10 @@ void mDirectorySetMapOptions(struct mDirectorySet* dirs, const struct mCoreOptio } if (opts->cheatsPath) { - struct VDir* dir = VDirOpen(opts->cheatsPath); - if (!dir && VDirCreate(opts->cheatsPath)) { - dir = VDirOpen(opts->cheatsPath); + makeAbsolute(opts->cheatsPath, configDir, abspath); + struct VDir* dir = VDirOpen(abspath); + if (!dir && VDirCreate(abspath)) { + dir = VDirOpen(abspath); } if (dir) { if (dirs->cheats && dirs->cheats != dirs->base) { diff --git a/src/util/vfs.c b/src/util/vfs.c index 28d366c53..d99f5020f 100644 --- a/src/util/vfs.c +++ b/src/util/vfs.c @@ -13,6 +13,10 @@ #ifdef __3DS__ #include #endif +#ifdef _WIN32 +#include +#include +#endif struct VFile* VFileOpen(const char* path, int flags) { #ifdef USE_VFS_FILE @@ -207,6 +211,41 @@ void separatePath(const char* path, char* dirname, char* basename, char* extensi } } +bool isAbsolute(const char* path) { + // XXX: Is this robust? +#ifdef _WIN32 + WCHAR wpath[PATH_MAX]; + MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, PATH_MAX); + return !PathIsRelativeW(wpath); +#else + return path[0] == '/'; +#endif +} + +void makeAbsolute(const char* path, const char* base, char* out) { + if (isAbsolute(path)) { + strncpy(out, path, PATH_MAX); + return; + } + + char buf[PATH_MAX]; + snprintf(buf, sizeof(buf), "%s" PATH_SEP "%s", base, path); +#ifdef _WIN32 + WCHAR wbuf[PATH_MAX]; + WCHAR wout[PATH_MAX]; + MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, PATH_MAX); + if (GetFullPathNameW(wbuf, PATH_MAX, wout, NULL)) { + WideCharToMultiByte(CP_UTF8, 0, wout, -1, out, PATH_MAX, 0, 0); + return; + } +#elif defined(HAVE_REALPATH) + if (realpath(buf, out)) { + return; + } +#endif + strncpy(out, buf, PATH_MAX); +} + struct VFile* VDirFindFirst(struct VDir* dir, bool (*filter)(struct VFile*)) { dir->rewind(dir); struct VDirEntry* dirent = dir->listNext(dir); From eb7b90e5d9d226fdba025e9594ccfc82f6e43ad4 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 17 Mar 2023 02:29:47 -0700 Subject: [PATCH 15/17] Qt: Fix OSD on modern macOS (fixes #2736) --- CHANGES | 1 + src/platform/qt/DisplayGL.cpp | 39 +++++++++++++++++++++++++++++++---- src/platform/qt/DisplayGL.h | 6 ++++++ 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index dfcbfceb8..8f3aac614 100644 --- a/CHANGES +++ b/CHANGES @@ -25,6 +25,7 @@ Other fixes: - Qt: Fix full-buffer rewind - Qt: Fix crash if loading a shader fails - Qt: Fix black screen when starting with a game (fixes mgba.io/i/2781) + - Qt: Fix OSD on modern macOS (fixes mgba.io/i/2736) - Scripting: Fix receiving packets for client sockets - Scripting: Fix empty receive calls returning unknown error on Windows Misc: diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index acc001f9a..9d93a6b57 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -70,6 +70,10 @@ mGLWidget::mGLWidget(QWidget* parent) connect(&m_refresh, &QTimer::timeout, this, static_cast(&QWidget::update)); } +mGLWidget::~mGLWidget() { + // This is needed for unique_ptr to work +} + void mGLWidget::initializeGL() { m_vao = std::make_unique(); m_vao->create(); @@ -99,6 +103,8 @@ void mGLWidget::initializeGL() { m_vaoDone = false; m_tex = 0; + + m_paintDev = std::make_unique(); } bool mGLWidget::finalizeVAO() { @@ -150,6 +156,23 @@ void mGLWidget::paintGL() { } else { m_refresh.start(17); } + + if (m_showOSD && m_messagePainter) { + qreal r = window()->devicePixelRatio(); + m_paintDev->setDevicePixelRatio(r); + m_paintDev->setSize(size() * r); + QPainter painter(m_paintDev.get()); + m_messagePainter->paint(&painter); + painter.end(); + } +} + +void mGLWidget::setMessagePainter(MessagePainter* messagePainter) { + m_messagePainter = messagePainter; +} + +void mGLWidget::setShowOSD(bool showOSD) { + m_showOSD = showOSD; } DisplayGL::DisplayGL(const QSurfaceFormat& format, QWidget* parent) @@ -170,6 +193,7 @@ DisplayGL::DisplayGL(const QSurfaceFormat& format, QWidget* parent) m_gl = new mGLWidget; m_gl->setAttribute(Qt::WA_NativeWindow); m_gl->setFormat(format); + m_gl->setMessagePainter(messagePainter()); QBoxLayout* layout = new QVBoxLayout; layout->addWidget(m_gl); layout->setContentsMargins(0, 0, 0, 0); @@ -372,6 +396,9 @@ void DisplayGL::interframeBlending(bool enable) { void DisplayGL::showOSDMessages(bool enable) { Display::showOSDMessages(enable); + if (m_gl) { + m_gl->setShowOSD(enable); + } QMetaObject::invokeMethod(m_painter.get(), "showOSD", Q_ARG(bool, enable)); } @@ -525,7 +552,9 @@ void PainterGL::create() { mGLES2Context* gl2Backend; #endif - m_paintDev = std::make_unique(); + if (!m_widget) { + m_paintDev = std::make_unique(); + } #if defined(BUILD_GLES2) || defined(BUILD_GLES3) if (m_supportsShaders) { @@ -650,8 +679,10 @@ void PainterGL::setMessagePainter(MessagePainter* messagePainter) { void PainterGL::resize(const QSize& size) { qreal r = m_window->devicePixelRatio(); m_size = size; - m_paintDev->setSize(m_size * r); - m_paintDev->setDevicePixelRatio(r); + if (m_paintDev) { + m_paintDev->setSize(m_size * r); + m_paintDev->setDevicePixelRatio(r); + } if (m_started && !m_active) { forceDraw(); } @@ -818,7 +849,7 @@ void PainterGL::performDraw() { m_backend->postFrame(m_backend, m_buffer); } m_backend->drawFrame(m_backend); - if (m_showOSD && m_messagePainter && !glContextHasBug(OpenGLBug::IG4ICD_CRASH)) { + if (m_showOSD && m_messagePainter && m_paintDev && !glContextHasBug(OpenGLBug::IG4ICD_CRASH)) { m_painter.begin(m_paintDev.get()); m_messagePainter->paint(&m_painter); m_painter.end(); diff --git a/src/platform/qt/DisplayGL.h b/src/platform/qt/DisplayGL.h index a0c0c17d4..129d8adc4 100644 --- a/src/platform/qt/DisplayGL.h +++ b/src/platform/qt/DisplayGL.h @@ -51,9 +51,12 @@ Q_OBJECT public: mGLWidget(QWidget* parent = nullptr); + ~mGLWidget(); void setTex(GLuint tex) { m_tex = tex; } void setVBO(GLuint vbo) { m_vbo = vbo; } + void setMessagePainter(MessagePainter*); + void setShowOSD(bool showOSD); bool finalizeVAO(); void reset(); @@ -72,6 +75,9 @@ private: QTimer m_refresh; int m_refreshResidue = 0; + std::unique_ptr m_paintDev; + MessagePainter* m_messagePainter = nullptr; + bool m_showOSD = false; }; class PainterGL; From ce0b1507c3f2aaaa0d1919b7b1fa69c30fe12692 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 19 Mar 2023 01:24:33 -0700 Subject: [PATCH 16/17] OpenGL: Fix layers not recentering properly when scale is reduced --- src/platform/opengl/gles2.c | 24 ++++++++++++------------ src/platform/qt/DisplayGL.cpp | 2 ++ 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/platform/opengl/gles2.c b/src/platform/opengl/gles2.c index 653b863a9..43d9a772b 100644 --- a/src/platform/opengl/gles2.c +++ b/src/platform/opengl/gles2.c @@ -185,26 +185,26 @@ static void mGLES2ContextSetLayerDimensions(struct VideoBackend* v, enum VideoLa if (layer >= VIDEO_LAYER_MAX) { return; } - context->layerDims[layer].x = dims->x; - context->layerDims[layer].y = dims->y; - if (dims->width == context->layerDims[layer].width && dims->height == context->layerDims[layer].height) { - return; - } - context->layerDims[layer].width = dims->width; - context->layerDims[layer].height = dims->height; + if (dims->width != context->layerDims[layer].width && dims->height != context->layerDims[layer].height) { + context->layerDims[layer].width = dims->width; + context->layerDims[layer].height = dims->height; - glBindTexture(GL_TEXTURE_2D, context->tex[layer]); + glBindTexture(GL_TEXTURE_2D, context->tex[layer]); #ifdef COLOR_16_BIT #ifdef COLOR_5_6_5 - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dims->width, dims->height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dims->width, dims->height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0); #else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dims->width, dims->height, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dims->width, dims->height, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, 0); #endif #elif defined(__BIG_ENDIAN__) - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dims->width, dims->height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dims->width, dims->height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, 0); #else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dims->width, dims->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dims->width, dims->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); #endif + } + + context->layerDims[layer].x = dims->x; + context->layerDims[layer].y = dims->y; unsigned newW; unsigned newH; diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index dc7ad9a86..162160121 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -653,6 +653,8 @@ void PainterGL::recenterLayers() { for (VideoLayer l : centeredLayers) { Rectangle dims; m_backend->layerDimensions(m_backend, l, &dims); + dims.x = 0; + dims.y = 0; RectangleUnion(&frame, &dims); } for (VideoLayer l : centeredLayers) { From e3e8296105acd6d6c031ed4c29a41e8e8c1645f6 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 19 Mar 2023 02:23:37 -0700 Subject: [PATCH 17/17] OpenGL: Separate sizes of image and drawn layer for image --- include/mgba-util/geometry.h | 5 ++ src/platform/opengl/gl.c | 87 ++++++++++++++++++++++++------ src/platform/opengl/gl.h | 1 + src/platform/opengl/gles2.c | 80 +++++++++++++++++++++------ src/platform/opengl/gles2.h | 1 + src/platform/qt/CoreController.cpp | 4 ++ src/platform/qt/CoreController.h | 1 + src/platform/qt/DisplayGL.cpp | 25 +++++---- src/platform/video-backend.h | 2 + 9 files changed, 164 insertions(+), 42 deletions(-) diff --git a/include/mgba-util/geometry.h b/include/mgba-util/geometry.h index 4ed46f0b0..344eb074d 100644 --- a/include/mgba-util/geometry.h +++ b/include/mgba-util/geometry.h @@ -10,6 +10,11 @@ CXX_GUARD_START +struct Size { + int width; + int height; +}; + struct Rectangle { int x; int y; diff --git a/src/platform/opengl/gl.c b/src/platform/opengl/gl.c index 03bd831d2..fea950c17 100644 --- a/src/platform/opengl/gl.c +++ b/src/platform/opengl/gl.c @@ -33,6 +33,7 @@ static void mGLContextInit(struct VideoBackend* v, WHandle handle) { UNUSED(handle); struct mGLContext* context = (struct mGLContext*) v; memset(context->layerDims, 0, sizeof(context->layerDims)); + memset(context->imageSizes, -1, sizeof(context->imageSizes)); glGenTextures(2, context->tex); glBindTexture(GL_TEXTURE_2D, context->tex[0]); _initTex(); @@ -48,17 +49,17 @@ static void mGLContextInit(struct VideoBackend* v, WHandle handle) { } } -static inline void _setTexDims(const struct Rectangle* dims) { +static inline void _setTexDims(int width, int height) { #ifdef COLOR_16_BIT #ifdef COLOR_5_6_5 - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(dims->width), toPow2(dims->height), 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0); #else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(dims->width), toPow2(dims->height), 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, 0); #endif #elif defined(__BIG_ENDIAN__) - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(dims->width), toPow2(dims->height), 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, 0); #else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(dims->width), toPow2(dims->height), 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); #endif } @@ -75,14 +76,16 @@ static void mGLContextSetLayerDimensions(struct VideoBackend* v, enum VideoLayer context->layerDims[layer].width = dims->width; context->layerDims[layer].height = dims->height; - if (layer == VIDEO_LAYER_IMAGE) { - glBindTexture(GL_TEXTURE_2D, context->tex[0]); - _setTexDims(dims); - glBindTexture(GL_TEXTURE_2D, context->tex[1]); - _setTexDims(dims); - } else { - glBindTexture(GL_TEXTURE_2D, context->layers[layer]); - _setTexDims(dims); + if (context->imageSizes[layer].width <= 0 || context->imageSizes[layer].height <= 0) { + if (layer == VIDEO_LAYER_IMAGE) { + glBindTexture(GL_TEXTURE_2D, context->tex[0]); + _setTexDims(dims->width, dims->height); + glBindTexture(GL_TEXTURE_2D, context->tex[1]); + _setTexDims(dims->width, dims->height); + } else { + glBindTexture(GL_TEXTURE_2D, context->layers[layer]); + _setTexDims(dims->width, dims->height); + } } } @@ -195,6 +198,46 @@ void mGLContextDrawFrame(struct VideoBackend* v) { glDisable(GL_BLEND); } +static void mGLContextSetImageSize(struct VideoBackend* v, enum VideoLayer layer, int width, int height) { + struct mGLContext* context = (struct mGLContext*) v; + if (layer >= VIDEO_LAYER_MAX) { + return; + } + if (width <= 0 || height <= 0) { + context->imageSizes[layer].width = -1; + context->imageSizes[layer].height = -1; + width = context->layerDims[layer].width; + height = context->layerDims[layer].height; + } else { + context->imageSizes[layer].width = width; + context->imageSizes[layer].height = height; + } + if (layer == VIDEO_LAYER_IMAGE) { + glBindTexture(GL_TEXTURE_2D, context->tex[0]); + _setTexDims(width, height); + glBindTexture(GL_TEXTURE_2D, context->tex[1]); + _setTexDims(width, height); + } else { + glBindTexture(GL_TEXTURE_2D, context->layers[layer]); + _setTexDims(width, height); + } +} + +static void mGLContextImageSize(struct VideoBackend* v, enum VideoLayer layer, int* width, int* height) { + struct mGLContext* context = (struct mGLContext*) v; + if (layer >= VIDEO_LAYER_MAX) { + return; + } + + if (context->imageSizes[layer].width <= 0 || context->imageSizes[layer].height <= 0) { + *width = context->layerDims[layer].width; + *height = context->layerDims[layer].height; + } else { + *width = context->imageSizes[layer].width; + *height = context->imageSizes[layer].height; + } +} + void mGLContextPostFrame(struct VideoBackend* v, enum VideoLayer layer, const void* frame) { struct mGLContext* context = (struct mGLContext*) v; if (layer >= VIDEO_LAYER_MAX) { @@ -206,16 +249,24 @@ void mGLContextPostFrame(struct VideoBackend* v, enum VideoLayer layer, const vo } else { glBindTexture(GL_TEXTURE_2D, context->layers[layer]); } + + int width = context->imageSizes[layer].width; + int height = context->imageSizes[layer].height; + + if (width <= 0 || height <= 0) { + width = context->layerDims[layer].width; + height = context->layerDims[layer].height; + } #ifdef COLOR_16_BIT #ifdef COLOR_5_6_5 - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, context->layerDims[layer].width, context->layerDims[layer].height, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, frame); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, frame); #else - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, context->layerDims[layer].width, context->layerDims[layer].height, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, frame); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, frame); #endif #elif defined(__BIG_ENDIAN__) - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, context->layerDims[layer].width, context->layerDims[layer].height, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, frame); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, frame); #else - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, context->layerDims[layer].width, context->layerDims[layer].height, GL_RGBA, GL_UNSIGNED_BYTE, frame); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, frame); #endif } @@ -227,6 +278,8 @@ void mGLContextCreate(struct mGLContext* context) { context->d.contextResized = mGLContextResized; context->d.swap = NULL; context->d.clear = mGLContextClear; + context->d.setImageSize = mGLContextSetImageSize; + context->d.imageSize = mGLContextImageSize; context->d.setImage = mGLContextPostFrame; context->d.drawFrame = mGLContextDrawFrame; } diff --git a/src/platform/opengl/gl.h b/src/platform/opengl/gl.h index c819e2f37..ae3dc27fa 100644 --- a/src/platform/opengl/gl.h +++ b/src/platform/opengl/gl.h @@ -30,6 +30,7 @@ struct mGLContext { GLuint tex[2]; GLuint layers[VIDEO_LAYER_MAX]; struct Rectangle layerDims[VIDEO_LAYER_MAX]; + struct Size imageSizes[VIDEO_LAYER_MAX]; }; void mGLContextCreate(struct mGLContext*); diff --git a/src/platform/opengl/gles2.c b/src/platform/opengl/gles2.c index 43d9a772b..dbe22650c 100644 --- a/src/platform/opengl/gles2.c +++ b/src/platform/opengl/gles2.c @@ -180,6 +180,20 @@ static void mGLES2ContextInit(struct VideoBackend* v, WHandle handle) { context->finalShader.tex = 0; } +static inline void _setTexDims(int width, int height) { +#ifdef COLOR_16_BIT +#ifdef COLOR_5_6_5 + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0); +#else + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, 0); +#endif +#elif defined(__BIG_ENDIAN__) + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, 0); +#else + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); +#endif +} + static void mGLES2ContextSetLayerDimensions(struct VideoBackend* v, enum VideoLayer layer, const struct Rectangle* dims) { struct mGLES2Context* context = (struct mGLES2Context*) v; if (layer >= VIDEO_LAYER_MAX) { @@ -190,17 +204,9 @@ static void mGLES2ContextSetLayerDimensions(struct VideoBackend* v, enum VideoLa context->layerDims[layer].height = dims->height; glBindTexture(GL_TEXTURE_2D, context->tex[layer]); -#ifdef COLOR_16_BIT -#ifdef COLOR_5_6_5 - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dims->width, dims->height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0); -#else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dims->width, dims->height, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, 0); -#endif -#elif defined(__BIG_ENDIAN__) - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dims->width, dims->height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, 0); -#else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dims->width, dims->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); -#endif + if (context->imageSizes[layer].width <= 0 || context->imageSizes[layer].height <= 0) { + _setTexDims(dims->width, dims->height); + } } context->layerDims[layer].x = dims->x; @@ -455,22 +461,64 @@ void mGLES2ContextDrawFrame(struct VideoBackend* v) { #endif } +static void mGLES2ContextSetImageSize(struct VideoBackend* v, enum VideoLayer layer, int width, int height) { + struct mGLES2Context* context = (struct mGLES2Context*) v; + if (layer >= VIDEO_LAYER_MAX) { + return; + } + + glBindTexture(GL_TEXTURE_2D, context->tex[layer]); + if (width <= 0 || height <= 0) { + context->imageSizes[layer].width = -1; + context->imageSizes[layer].height = -1; + width = context->layerDims[layer].width; + height = context->layerDims[layer].height; + } else { + context->imageSizes[layer].width = width; + context->imageSizes[layer].height = height; + } + _setTexDims(width, height); +} + +static void mGLES2ContextImageSize(struct VideoBackend* v, enum VideoLayer layer, int* width, int* height) { + struct mGLES2Context* context = (struct mGLES2Context*) v; + if (layer >= VIDEO_LAYER_MAX) { + return; + } + + if (context->imageSizes[layer].width <= 0 || context->imageSizes[layer].height <= 0) { + *width = context->layerDims[layer].width; + *height = context->layerDims[layer].height; + } else { + *width = context->imageSizes[layer].width; + *height = context->imageSizes[layer].height; + } +} + void mGLES2ContextPostFrame(struct VideoBackend* v, enum VideoLayer layer, const void* frame) { struct mGLES2Context* context = (struct mGLES2Context*) v; if (layer >= VIDEO_LAYER_MAX) { return; } + + int width = context->imageSizes[layer].width; + int height = context->imageSizes[layer].height; + + if (width <= 0 || height <= 0) { + width = context->layerDims[layer].width; + height = context->layerDims[layer].height; + } glBindTexture(GL_TEXTURE_2D, context->tex[layer]); #ifdef COLOR_16_BIT #ifdef COLOR_5_6_5 - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, context->layerDims[layer].width, context->layerDims[layer].height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, frame); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, frame); #else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, context->layerDims[layer].width, context->layerDims[layer].height, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, frame); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, frame); #endif #elif defined(__BIG_ENDIAN__) - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, context->layerDims[layer].width, context->layerDims[layer].height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, frame); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, frame); #else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, context->layerDims[layer].width, context->layerDims[layer].height, 0, GL_RGBA, GL_UNSIGNED_BYTE, frame); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, frame); #endif } @@ -482,6 +530,8 @@ void mGLES2ContextCreate(struct mGLES2Context* context) { context->d.contextResized = mGLES2ContextResized; context->d.swap = NULL; context->d.clear = mGLES2ContextClear; + context->d.setImageSize = mGLES2ContextSetImageSize; + context->d.imageSize = mGLES2ContextImageSize; context->d.setImage = mGLES2ContextPostFrame; context->d.drawFrame = mGLES2ContextDrawFrame; context->shaders = 0; diff --git a/src/platform/opengl/gles2.h b/src/platform/opengl/gles2.h index ade50e455..5cbc53acc 100644 --- a/src/platform/opengl/gles2.h +++ b/src/platform/opengl/gles2.h @@ -83,6 +83,7 @@ struct mGLES2Context { GLuint vbo; struct Rectangle layerDims[VIDEO_LAYER_MAX]; + struct Size imageSizes[VIDEO_LAYER_MAX]; unsigned width; unsigned height; diff --git a/src/platform/qt/CoreController.cpp b/src/platform/qt/CoreController.cpp index c3f12a19f..8b7929024 100644 --- a/src/platform/qt/CoreController.cpp +++ b/src/platform/qt/CoreController.cpp @@ -271,6 +271,10 @@ QSize CoreController::screenDimensions() const { return QSize(width, height); } +unsigned CoreController::videoScale() const { + return m_threadContext.core->videoScale(m_threadContext.core); +} + void CoreController::loadConfig(ConfigController* config) { Interrupter interrupter(this); m_loadStateFlags = config->getOption("loadStateExtdata", m_loadStateFlags).toInt(); diff --git a/src/platform/qt/CoreController.h b/src/platform/qt/CoreController.h index e2cd03c12..9e64bb838 100644 --- a/src/platform/qt/CoreController.h +++ b/src/platform/qt/CoreController.h @@ -94,6 +94,7 @@ public: mPlatform platform() const; QSize screenDimensions() const; + unsigned videoScale() const; bool supportsFeature(Feature feature) const { return m_threadContext.core->supportsFeature(m_threadContext.core, static_cast(feature)); } bool hardwareAccelerated() const { return m_hwaccel; } diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index 162160121..a666156fc 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -648,13 +648,23 @@ void PainterGL::setMessagePainter(MessagePainter* messagePainter) { } void PainterGL::recenterLayers() { + if (!m_context) { + return; + } const static std::initializer_list centeredLayers{VIDEO_LAYER_BACKGROUND, VIDEO_LAYER_IMAGE}; Rectangle frame = {0}; + unsigned scale = std::max(1U, m_context->videoScale()); for (VideoLayer l : centeredLayers) { - Rectangle dims; - m_backend->layerDimensions(m_backend, l, &dims); - dims.x = 0; - dims.y = 0; + Rectangle dims{}; + int width, height; + m_backend->imageSize(m_backend, l, &width, &height); + dims.width = width; + dims.height = height; + if (l != VIDEO_LAYER_IMAGE) { + dims.width *= scale; + dims.height *= scale; + m_backend->setLayerDimensions(m_backend, l, &dims); + } RectangleUnion(&frame, &dims); } for (VideoLayer l : centeredLayers) { @@ -1035,12 +1045,7 @@ void PainterGL::setBackgroundImage(const QImage& image) { makeCurrent(); } - Rectangle dims = {0, 0, 0, 0}; - if (!image.isNull()) { - dims.width = static_cast(image.width()); - dims.height = static_cast(image.height()); - } - m_backend->setLayerDimensions(m_backend, VIDEO_LAYER_BACKGROUND, &dims); + m_backend->setImageSize(m_backend, VIDEO_LAYER_BACKGROUND, image.width(), image.height()); recenterLayers(); if (!image.isNull()) { diff --git a/src/platform/video-backend.h b/src/platform/video-backend.h index 5a3319721..993e8b0e3 100644 --- a/src/platform/video-backend.h +++ b/src/platform/video-backend.h @@ -35,6 +35,8 @@ struct VideoBackend { void (*swap)(struct VideoBackend*); void (*clear)(struct VideoBackend*); void (*contextResized)(struct VideoBackend*, unsigned w, unsigned h); + void (*setImageSize)(struct VideoBackend*, enum VideoLayer, int w, int h); + void (*imageSize)(struct VideoBackend*, enum VideoLayer, int* w, int* h); void (*setImage)(struct VideoBackend*, enum VideoLayer, const void* frame); void (*drawFrame)(struct VideoBackend*);