diff --git a/CMakeLists.txt b/CMakeLists.txt index aebbfecae..5869e2840 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -740,6 +740,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/include/mgba-util/geometry.h b/include/mgba-util/geometry.h new file mode 100644 index 000000000..344eb074d --- /dev/null +++ b/include/mgba-util/geometry.h @@ -0,0 +1,30 @@ +/* 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 Size { + int width; + int height; +}; + +struct Rectangle { + int x; + int y; + 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/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 fc6f7ee6c..fd3fd64e3 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/opengl/gl.c b/src/platform/opengl/gl.c index 76089c4c0..fea950c17 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[] = { @@ -21,76 +21,102 @@ 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)); + memset(context->imageSizes, -1, sizeof(context->imageSizes)); 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(int width, int height) { +#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 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 (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); + } + } +} - 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) { unsigned drawW = w; unsigned drawH = h; + + unsigned maxW; + unsigned maxH; + VideoBackendGetFrameSize(v, &maxW, &maxH); + if (v->lockAspectRatio) { - lockAspectRatioUInt(v->width, v->height, &drawW, &drawH); + lockAspectRatioUInt(maxW, maxH, &drawW, &drawH); } if (v->lockIntegerScaling) { - lockIntegerRatioUInt(v->width, &drawW); - lockIntegerRatioUInt(v->height, &drawH); + lockIntegerRatioUInt(maxW, &drawW); + lockIntegerRatioUInt(maxH, &drawH); } glMatrixMode(GL_MODELVIEW); glLoadIdentity(); @@ -105,33 +131,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, v->width, v->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); @@ -139,36 +139,147 @@ 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); } -void mGLContextPostFrame(struct VideoBackend* v, const void* frame) { +static void mGLContextSetImageSize(struct VideoBackend* v, enum VideoLayer layer, int width, int height) { struct mGLContext* context = (struct mGLContext*) v; - context->activeTex ^= 1; - glBindTexture(GL_TEXTURE_2D, context->tex[context->activeTex]); + 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) { + 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]); + } + + 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, v->width, v->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, v->width, v->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, v->width, v->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, v->width, v->height, GL_RGBA, GL_UNSIGNED_BYTE, frame); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, 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.setImageSize = mGLContextSetImageSize; + context->d.imageSize = mGLContextImageSize; + 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..ae3dc27fa 100644 --- a/src/platform/opengl/gl.h +++ b/src/platform/opengl/gl.h @@ -26,8 +26,11 @@ 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]; + 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 ffbac5a04..dbe22650c 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,15 +180,7 @@ static void mGLES2ContextInit(struct VideoBackend* v, WHandle handle) { context->finalShader.tex = 0; } -static void mGLES2ContextSetDimensions(struct VideoBackend* v, unsigned width, unsigned height) { - struct mGLES2Context* context = (struct mGLES2Context*) v; - if (width == v->width && height == v->height) { - return; - } - v->width = width; - v->height = height; - - glBindTexture(GL_TEXTURE_2D, context->tex); +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); @@ -197,20 +192,54 @@ static void mGLES2ContextSetDimensions(struct VideoBackend* v, unsigned width, u #else glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, 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; +static void mGLES2ContextSetLayerDimensions(struct VideoBackend* v, enum VideoLayer layer, const struct Rectangle* dims) { + struct mGLES2Context* context = (struct mGLES2Context*) v; + if (layer >= VIDEO_LAYER_MAX) { + return; + } + 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]); + if (context->imageSizes[layer].width <= 0 || context->imageSizes[layer].height <= 0) { + _setTexDims(dims->width, dims->height); } } - context->initialShader.dirty = true; - context->interframeShader.dirty = true; + + context->layerDims[layer].x = dims->x; + context->layerDims[layer].y = dims->y; + + 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) { + context->shaders[n].dirty = true; + } + } + context->initialShader.dirty = true; + context->interframeShader.dirty = true; + context->width = newW; + context->height = newH; + } +} + +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); @@ -222,12 +251,16 @@ static void mGLES2ContextResized(struct VideoBackend* v, unsigned w, unsigned h) struct mGLES2Context* context = (struct mGLES2Context*) v; unsigned drawW = w; unsigned drawH = h; + + unsigned maxW = context->width; + unsigned maxH = context->height; + if (v->lockAspectRatio) { - lockAspectRatioUInt(v->width, v->height, &drawW, &drawH); + lockAspectRatioUInt(maxW, maxH, &drawW, &drawH); } if (v->lockIntegerScaling) { - lockIntegerRatioUInt(v->width, &drawW); - lockIntegerRatioUInt(v->height, &drawH); + lockIntegerRatioUInt(maxW, &drawW); + lockIntegerRatioUInt(maxH, &drawH); } size_t n; for (n = 0; n < context->nShaders; ++n) { @@ -249,7 +282,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; @@ -260,19 +293,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->width * -shader->width; } if (!drawH) { drawH = viewport[3]; padH = viewport[1]; } else if (shader->height < 0) { - drawH = context->d.height * -shader->height; + drawH = context->height * -shader->height; } if (shader->integerScaling) { padW = 0; padH = 0; - drawW -= drawW % context->d.width; - drawH -= drawH % context->d.height; + drawW -= drawW % context->width; + drawH -= drawH % context->height; } if (shader->dirty) { @@ -286,22 +319,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->d.width, context->d.height); + glUniform2f(shader->texSizeLocation, context->width, context->height); glUniform2f(shader->outputSizeLocation, drawW, drawH); #ifdef BUILD_GLES3 if (shader->vao != (GLuint) -1) { @@ -368,21 +408,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); 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]); @@ -392,8 +447,8 @@ void mGLES2ContextDrawFrame(struct VideoBackend* v) { _drawShader(context, &context->finalShader); if (v->interframeBlending) { context->interframeShader.blend = false; - glBindTexture(GL_TEXTURE_2D, context->tex); - _drawShader(context, &context->initialShader); + glBindTexture(GL_TEXTURE_2D, context->tex[VIDEO_LAYER_IMAGE]); + _drawShaderEx(context, &context->initialShader, VIDEO_LAYER_IMAGE); glViewport(0, 0, viewport[2], viewport[3]); _drawShader(context, &context->interframeShader); } @@ -406,33 +461,79 @@ void mGLES2ContextDrawFrame(struct VideoBackend* v) { #endif } -void mGLES2ContextPostFrame(struct VideoBackend* v, const void* frame) { +static void mGLES2ContextSetImageSize(struct VideoBackend* v, enum VideoLayer layer, int width, int height) { 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]); + 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, v->width, v->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, v->width, v->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, v->width, v->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, v->width, v->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 } 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.setImageSize = mGLES2ContextSetImageSize; + context->d.imageSize = mGLES2ContextImageSize; + 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..5cbc53acc 100644 --- a/src/platform/opengl/gles2.h +++ b/src/platform/opengl/gles2.h @@ -79,9 +79,14 @@ struct mGLES2Shader { struct mGLES2Context { struct VideoBackend d; - GLuint tex; + GLuint tex[VIDEO_LAYER_MAX]; GLuint vbo; + struct Rectangle layerDims[VIDEO_LAYER_MAX]; + struct Size imageSizes[VIDEO_LAYER_MAX]; + unsigned width; + unsigned height; + struct mGLES2Shader initialShader; struct mGLES2Shader finalShader; struct mGLES2Shader interframeShader; 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..8b7929024 100644 --- a/src/platform/qt/CoreController.cpp +++ b/src/platform/qt/CoreController.cpp @@ -266,11 +266,15 @@ 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); } +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(); @@ -1186,7 +1190,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/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/Display.h b/src/platform/qt/Display.h index 574a0e8e9..4fc100799 100644 --- a/src/platform/qt/Display.h +++ b/src/platform/qt/Display.h @@ -54,6 +54,8 @@ public: virtual VideoShader* shaders() = 0; 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 9d93a6b57..a454e26a6 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -283,6 +283,8 @@ void DisplayGL::startThread(int from) { show(); m_gl->reset(); } + + QTimer::singleShot(8, this, &DisplayGL::updateContentSize); } bool DisplayGL::supportsFormat(const QSurfaceFormat& format) { @@ -370,12 +372,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); } } @@ -438,6 +442,11 @@ void DisplayGL::setVideoScale(int scale) { QMetaObject::invokeMethod(m_painter.get(), "resizeContext"); } +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) { Display::resizeEvent(event); resizePainter(); @@ -491,6 +500,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(); } @@ -669,13 +682,44 @@ 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); + recenterLayers(); } void PainterGL::setMessagePainter(MessagePainter* messagePainter) { m_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{}; + 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) { + 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; @@ -844,9 +888,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 && m_paintDev && !glContextHasBug(OpenGLBug::IG4ICD_CRASH)) { @@ -902,7 +946,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 } @@ -996,11 +1040,18 @@ 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()) { mGLES2Context* gl2Backend = reinterpret_cast(m_backend); - return gl2Backend->tex; + return gl2Backend->tex[VIDEO_LAYER_IMAGE]; } #endif #ifdef BUILD_GL @@ -1036,6 +1087,27 @@ void PainterGL::updateFramebufferHandle() { m_context->setFramebufferHandle(m_bridgeTexIn); } +void PainterGL::setBackgroundImage(const QImage& image) { + if (!m_started) { + makeCurrent(); + } + + m_backend->setImageSize(m_backend, VIDEO_LAYER_BACKGROUND, image.width(), image.height()); + 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 129d8adc4..759789609 100644 --- a/src/platform/qt/DisplayGL.h +++ b/src/platform/qt/DisplayGL.h @@ -94,6 +94,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&); @@ -113,6 +114,7 @@ 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(); } @@ -121,6 +123,7 @@ protected: private slots: void startThread(int); void setupProxyThread(); + void updateContentSize(); private: void resizePainter(); @@ -138,6 +141,7 @@ private: mGLWidget* m_gl; QOffscreenSurface m_proxySurface; std::unique_ptr m_proxyContext; + QSize m_cachedContentSize; }; class PainterGL : public QObject { @@ -184,10 +188,12 @@ public slots: void filter(bool filter); void resizeContext(); void updateFramebufferHandle(); + void setBackgroundImage(const QImage&); void setShaders(struct VDir*); void clearShaders(); VideoShader* shaders(); + QSize contentSize() const; signals: void created(); @@ -202,6 +208,7 @@ private: void performDraw(); void dequeue(); void dequeueAll(bool keep = false); + void recenterLayers(); std::array, 3> m_buffers; QList m_free; @@ -220,6 +227,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/qt/DisplayQt.cpp b/src/platform/qt/DisplayQt.cpp index 20ec44f0d..dee0d1244 100644 --- a/src/platform/qt/DisplayQt.cpp +++ b/src/platform/qt/DisplayQt.cpp @@ -92,21 +92,103 @@ 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 = contentSize(); + + if (bgRect.width() > imRect.width()) { + imRect.moveLeft(bgRect.width() - imRect.width()); + } else { + bgRect.moveLeft(imRect.width() - bgRect.width()); + } + + if (bgRect.height() > imRect.height()) { + imRect.moveTop(bgRect.height() - imRect.height()); + } else { + bgRect.moveTop(imRect.height() - bgRect.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); } } + +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 269848229..8ea6cc80a 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; @@ -36,6 +37,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 +48,7 @@ private: int m_height = -1; QImage m_backing{nullptr}; QImage m_oldBacking{nullptr}; + QImage m_background; std::shared_ptr m_context = nullptr; }; 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/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..083732cd8 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() { @@ -1486,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; @@ -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) { diff --git a/src/platform/sdl/gl-common.c b/src/platform/sdl/gl-common.c index f0073a904..53219a7bb 100644 --- a/src/platform/sdl/gl-common.c +++ b/src/platform/sdl/gl-common.c @@ -5,10 +5,17 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "main.h" +#include +#include #include +#ifdef USE_PNG +#include +#include +#endif + 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); @@ -24,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); @@ -66,3 +127,61 @@ 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; + + 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); + // 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..f52cfd633 100644 --- a/src/platform/sdl/gl-common.h +++ b/src/platform/sdl/gl-common.h @@ -15,6 +15,8 @@ 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); +bool mSDLGLCommonLoadBackground(struct VideoBackend* context); CXX_GUARD_END diff --git a/src/platform/sdl/gl-sdl.c b/src/platform/sdl/gl-sdl.c index 6c1508d33..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) { @@ -37,48 +35,18 @@ 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; } -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->desiredVideoDimensions(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); - } - - if (mCoreSyncWaitFrameStart(&context->impl->sync)) { - v->postFrame(v, 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 7237b6273..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) { @@ -50,43 +49,19 @@ 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; } -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->postFrame(v, 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.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/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; 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/video-backend.c b/src/platform/video-backend.c new file mode 100644 index 000000000..998764d0d --- /dev/null +++ b/src/platform/video-backend.c @@ -0,0 +1,23 @@ +/* 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 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); + 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 6d714629a..993e8b0e3 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,34 @@ typedef HWND WHandle; typedef void* WHandle; #endif +enum VideoLayer { + VIDEO_LAYER_BACKGROUND = 0, + VIDEO_LAYER_BEZEL, + 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 (*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*); - 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 { @@ -48,6 +58,9 @@ 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 #endif 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; 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), +)