diff --git a/CHANGES b/CHANGES index a1c80068c..cf70c4f93 100644 --- a/CHANGES +++ b/CHANGES @@ -35,6 +35,7 @@ Emulation fixes: - GB MBC: Fix MBC1 RAM enable bit selection - GB MBC: Fix MBC2 bit selection - GB Video: Fix state after skipping BIOS (fixes mgba.io/i/1715 and mgba.io/i/1716) + - GB Video: Always initialize palette - GBA: Fix timing advancing too quickly in rare cases - GBA BIOS: Implement dummy sound driver calls - GBA BIOS: Improve HLE BIOS timing @@ -46,6 +47,7 @@ Emulation fixes: - GBA Video: Latch scanline at end of Hblank (fixes mgba.io/i/1319) - GBA Video: Fix Hblank timing - GBA Video: Fix invalid read in mode 4 mosaic + - GBA Video: Fix color of disabled screen - SM83: Emulate HALT bug Other fixes: - All: Improve export headers (fixes mgba.io/i/1738) @@ -55,6 +57,7 @@ Other fixes: - Core: Ensure ELF regions can be written before trying - Debugger: Don't skip undefined instructions when debugger attached - FFmpeg: Fix some small memory leaks + - FFmpeg: Fix encoding of time base - GB Core: Fix extracting SRAM when none is present - GBA Savedata: Fix extracting save when not yet configured in-game - Qt: Force OpenGL paint engine creation thread (fixes mgba.io/i/1642) diff --git a/CMakeLists.txt b/CMakeLists.txt index ed768e365..5aaf9bd2c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,6 +60,7 @@ if(NOT LIBMGBA_ONLY) set(BUILD_PERF OFF CACHE BOOL "Build performance profiling tool") set(BUILD_TEST OFF CACHE BOOL "Build testing harness") set(BUILD_SUITE OFF CACHE BOOL "Build test suite") + set(BUILD_CINEMA OFF CACHE BOOL "Build video tests suite") set(BUILD_EXAMPLE OFF CACHE BOOL "Build example frontends") set(BUILD_PYTHON OFF CACHE BOOL "Build Python bindings") set(BUILD_STATIC OFF CACHE BOOL "Build a static library") @@ -1250,6 +1251,7 @@ if(NOT QUIET AND NOT LIBMGBA_ONLY) message(STATUS " Profiling: ${BUILD_PERF}") message(STATUS " Test harness: ${BUILD_TEST}") message(STATUS " Test suite: ${BUILD_SUITE}") + message(STATUS " Video test suite: ${BUILD_CINEMA}") message(STATUS " Python bindings: ${BUILD_PYTHON}") message(STATUS " Examples: ${BUILD_EXAMPLE}") message(STATUS "Cores:") diff --git a/cinema/gb/mooneye-gb/madness/mgb_oam_dma_halt_sprites/baseline_0000.png b/cinema/gb/mooneye-gb/madness/mgb_oam_dma_halt_sprites/baseline_0000.png deleted file mode 100644 index 78773f1a8..000000000 Binary files a/cinema/gb/mooneye-gb/madness/mgb_oam_dma_halt_sprites/baseline_0000.png and /dev/null differ diff --git a/cinema/gb/mooneye-gb/manual-only/sprite_priority/baseline_0000.png b/cinema/gb/mooneye-gb/manual-only/sprite_priority/baseline_0000.png index e35683164..64aac5965 100644 Binary files a/cinema/gb/mooneye-gb/manual-only/sprite_priority/baseline_0000.png and b/cinema/gb/mooneye-gb/manual-only/sprite_priority/baseline_0000.png differ diff --git a/cinema/gb/window/007wne-hud/baseline_0000.png b/cinema/gb/window/007wne-hud/baseline_0000.png index 9ae2a2a5c..6fb7af9b1 100644 Binary files a/cinema/gb/window/007wne-hud/baseline_0000.png and b/cinema/gb/window/007wne-hud/baseline_0000.png differ diff --git a/cinema/gb/window/007wne-hud/baseline_0001.png b/cinema/gb/window/007wne-hud/baseline_0001.png index 9ae2a2a5c..6fb7af9b1 100644 Binary files a/cinema/gb/window/007wne-hud/baseline_0001.png and b/cinema/gb/window/007wne-hud/baseline_0001.png differ diff --git a/cinema/gb/window/007wne-hud/baseline_0002.png b/cinema/gb/window/007wne-hud/baseline_0002.png index 9ae2a2a5c..6fb7af9b1 100644 Binary files a/cinema/gb/window/007wne-hud/baseline_0002.png and b/cinema/gb/window/007wne-hud/baseline_0002.png differ diff --git a/cinema/gb/window/007wne-hud/baseline_0003.png b/cinema/gb/window/007wne-hud/baseline_0003.png index 9ae2a2a5c..6fb7af9b1 100644 Binary files a/cinema/gb/window/007wne-hud/baseline_0003.png and b/cinema/gb/window/007wne-hud/baseline_0003.png differ diff --git a/cinema/gb/window/ccmmr-hud/baseline_0000.png b/cinema/gb/window/ccmmr-hud/baseline_0000.png index 75fc1974e..e2cb469e9 100644 Binary files a/cinema/gb/window/ccmmr-hud/baseline_0000.png and b/cinema/gb/window/ccmmr-hud/baseline_0000.png differ diff --git a/cinema/gb/window/ccmmr-hud/baseline_0001.png b/cinema/gb/window/ccmmr-hud/baseline_0001.png index 75fc1974e..e2cb469e9 100644 Binary files a/cinema/gb/window/ccmmr-hud/baseline_0001.png and b/cinema/gb/window/ccmmr-hud/baseline_0001.png differ diff --git a/cinema/gb/window/ccmmr-hud/baseline_0002.png b/cinema/gb/window/ccmmr-hud/baseline_0002.png index 75fc1974e..e2cb469e9 100644 Binary files a/cinema/gb/window/ccmmr-hud/baseline_0002.png and b/cinema/gb/window/ccmmr-hud/baseline_0002.png differ diff --git a/cinema/gb/window/ccmmr-hud/baseline_0003.png b/cinema/gb/window/ccmmr-hud/baseline_0003.png index b64e44668..22ab091b2 100644 Binary files a/cinema/gb/window/ccmmr-hud/baseline_0003.png and b/cinema/gb/window/ccmmr-hud/baseline_0003.png differ diff --git a/cinema/gb/window/ccmmr-hud/baseline_0004.png b/cinema/gb/window/ccmmr-hud/baseline_0004.png deleted file mode 100644 index b64e44668..000000000 Binary files a/cinema/gb/window/ccmmr-hud/baseline_0004.png and /dev/null differ diff --git a/cinema/gb/window/ccmmr-hud/baseline_0005.png b/cinema/gb/window/ccmmr-hud/baseline_0005.png deleted file mode 100644 index 9209c6402..000000000 Binary files a/cinema/gb/window/ccmmr-hud/baseline_0005.png and /dev/null differ diff --git a/cinema/gb/window/kdt-battle/baseline_0000.png b/cinema/gb/window/kdt-battle/baseline_0000.png index 2ba1aa28b..b750cca02 100644 Binary files a/cinema/gb/window/kdt-battle/baseline_0000.png and b/cinema/gb/window/kdt-battle/baseline_0000.png differ diff --git a/cinema/gb/window/kdt-battle/baseline_0001.png b/cinema/gb/window/kdt-battle/baseline_0001.png index 2ba1aa28b..b750cca02 100644 Binary files a/cinema/gb/window/kdt-battle/baseline_0001.png and b/cinema/gb/window/kdt-battle/baseline_0001.png differ diff --git a/cinema/gb/window/kdt-battle/baseline_0002.png b/cinema/gb/window/kdt-battle/baseline_0002.png index 2ba1aa28b..b750cca02 100644 Binary files a/cinema/gb/window/kdt-battle/baseline_0002.png and b/cinema/gb/window/kdt-battle/baseline_0002.png differ diff --git a/cinema/gb/window/kdt-battle/baseline_0003.png b/cinema/gb/window/kdt-battle/baseline_0003.png index 2ba1aa28b..b750cca02 100644 Binary files a/cinema/gb/window/kdt-battle/baseline_0003.png and b/cinema/gb/window/kdt-battle/baseline_0003.png differ diff --git a/cinema/gb/window/kdt-battle/baseline_0004.png b/cinema/gb/window/kdt-battle/baseline_0004.png index 2ba1aa28b..b750cca02 100644 Binary files a/cinema/gb/window/kdt-battle/baseline_0004.png and b/cinema/gb/window/kdt-battle/baseline_0004.png differ diff --git a/cinema/gb/window/kdt-battle/baseline_0005.png b/cinema/gb/window/kdt-battle/baseline_0005.png index 2ba1aa28b..b750cca02 100644 Binary files a/cinema/gb/window/kdt-battle/baseline_0005.png and b/cinema/gb/window/kdt-battle/baseline_0005.png differ diff --git a/cinema/gb/window/kdt-battle/baseline_0006.png b/cinema/gb/window/kdt-battle/baseline_0006.png index 2ba1aa28b..b750cca02 100644 Binary files a/cinema/gb/window/kdt-battle/baseline_0006.png and b/cinema/gb/window/kdt-battle/baseline_0006.png differ diff --git a/cinema/gb/window/kdt-battle/baseline_0007.png b/cinema/gb/window/kdt-battle/baseline_0007.png index 2491849a8..28e473277 100644 Binary files a/cinema/gb/window/kdt-battle/baseline_0007.png and b/cinema/gb/window/kdt-battle/baseline_0007.png differ diff --git a/cinema/gb/window/kdt-battle/baseline_0008.png b/cinema/gb/window/kdt-battle/baseline_0008.png index 2491849a8..28e473277 100644 Binary files a/cinema/gb/window/kdt-battle/baseline_0008.png and b/cinema/gb/window/kdt-battle/baseline_0008.png differ diff --git a/cinema/gb/window/kdt-battle/baseline_0009.png b/cinema/gb/window/kdt-battle/baseline_0009.png index 2491849a8..28e473277 100644 Binary files a/cinema/gb/window/kdt-battle/baseline_0009.png and b/cinema/gb/window/kdt-battle/baseline_0009.png differ diff --git a/cinema/gb/window/rfs-hud/baseline_0000.png b/cinema/gb/window/rfs-hud/baseline_0000.png index a4199c67a..4236bc539 100644 Binary files a/cinema/gb/window/rfs-hud/baseline_0000.png and b/cinema/gb/window/rfs-hud/baseline_0000.png differ diff --git a/cinema/gb/window/rfs-hud/baseline_0001.png b/cinema/gb/window/rfs-hud/baseline_0001.png deleted file mode 100644 index a4199c67a..000000000 Binary files a/cinema/gb/window/rfs-hud/baseline_0001.png and /dev/null differ diff --git a/cinema/gb/window/rfs-hud/baseline_0002.png b/cinema/gb/window/rfs-hud/baseline_0002.png deleted file mode 100644 index d8c767203..000000000 Binary files a/cinema/gb/window/rfs-hud/baseline_0002.png and /dev/null differ diff --git a/cinema/gb/window/rfs-hud/baseline_0003.png b/cinema/gb/window/rfs-hud/baseline_0003.png deleted file mode 100644 index d8c767203..000000000 Binary files a/cinema/gb/window/rfs-hud/baseline_0003.png and /dev/null differ diff --git a/cinema/gb/window/rfs-hud/baseline_0004.png b/cinema/gb/window/rfs-hud/baseline_0004.png deleted file mode 100644 index d8c767203..000000000 Binary files a/cinema/gb/window/rfs-hud/baseline_0004.png and /dev/null differ diff --git a/cinema/gb/window/rfs-hud/baseline_0005.png b/cinema/gb/window/rfs-hud/baseline_0005.png deleted file mode 100644 index d8c767203..000000000 Binary files a/cinema/gb/window/rfs-hud/baseline_0005.png and /dev/null differ diff --git a/cinema/gb/window/rfs-hud/baseline_0006.png b/cinema/gb/window/rfs-hud/baseline_0006.png deleted file mode 100644 index 7489e9768..000000000 Binary files a/cinema/gb/window/rfs-hud/baseline_0006.png and /dev/null differ diff --git a/cinema/gb/window/rfs-hud/baseline_0007.png b/cinema/gb/window/rfs-hud/baseline_0007.png deleted file mode 100644 index 7489e9768..000000000 Binary files a/cinema/gb/window/rfs-hud/baseline_0007.png and /dev/null differ diff --git a/cinema/gb/window/rfs-hud/baseline_0008.png b/cinema/gb/window/rfs-hud/baseline_0008.png deleted file mode 100644 index 7489e9768..000000000 Binary files a/cinema/gb/window/rfs-hud/baseline_0008.png and /dev/null differ diff --git a/cinema/gb/window/rfs-hud/baseline_0009.png b/cinema/gb/window/rfs-hud/baseline_0009.png deleted file mode 100644 index 7489e9768..000000000 Binary files a/cinema/gb/window/rfs-hud/baseline_0009.png and /dev/null differ diff --git a/cinema/gb/window/rfs-hud/baseline_0010.png b/cinema/gb/window/rfs-hud/baseline_0010.png deleted file mode 100644 index 334806217..000000000 Binary files a/cinema/gb/window/rfs-hud/baseline_0010.png and /dev/null differ diff --git a/cinema/gb/window/rfs-hud/baseline_0011.png b/cinema/gb/window/rfs-hud/baseline_0011.png deleted file mode 100644 index 334806217..000000000 Binary files a/cinema/gb/window/rfs-hud/baseline_0011.png and /dev/null differ diff --git a/cinema/gb/window/rfs-hud/baseline_0012.png b/cinema/gb/window/rfs-hud/baseline_0012.png deleted file mode 100644 index 334806217..000000000 Binary files a/cinema/gb/window/rfs-hud/baseline_0012.png and /dev/null differ diff --git a/cinema/gb/window/rfs-hud/baseline_0013.png b/cinema/gb/window/rfs-hud/baseline_0013.png deleted file mode 100644 index 334806217..000000000 Binary files a/cinema/gb/window/rfs-hud/baseline_0013.png and /dev/null differ diff --git a/cinema/gb/window/rfs-hud/baseline_0014.png b/cinema/gb/window/rfs-hud/baseline_0014.png deleted file mode 100644 index 7c444e005..000000000 Binary files a/cinema/gb/window/rfs-hud/baseline_0014.png and /dev/null differ diff --git a/cinema/gb/window/wmm-hud/baseline_0000.png b/cinema/gb/window/wmm-hud/baseline_0000.png index bf01a3469..3a7096b58 100644 Binary files a/cinema/gb/window/wmm-hud/baseline_0000.png and b/cinema/gb/window/wmm-hud/baseline_0000.png differ diff --git a/cinema/gb/window/zin-head/baseline_0000.png b/cinema/gb/window/zin-head/baseline_0000.png index d3c2c0f9d..41cd69861 100644 Binary files a/cinema/gb/window/zin-head/baseline_0000.png and b/cinema/gb/window/zin-head/baseline_0000.png differ diff --git a/cinema/gb/window/zin-head/baseline_0001.png b/cinema/gb/window/zin-head/baseline_0001.png index 5d583d227..f8e0b3711 100644 Binary files a/cinema/gb/window/zin-head/baseline_0001.png and b/cinema/gb/window/zin-head/baseline_0001.png differ diff --git a/cinema/gb/window/zin-head/baseline_0002.png b/cinema/gb/window/zin-head/baseline_0002.png index 5d583d227..f8e0b3711 100644 Binary files a/cinema/gb/window/zin-head/baseline_0002.png and b/cinema/gb/window/zin-head/baseline_0002.png differ diff --git a/cinema/gb/window/zin-head/baseline_0003.png b/cinema/gb/window/zin-head/baseline_0003.png index 5d583d227..f8e0b3711 100644 Binary files a/cinema/gb/window/zin-head/baseline_0003.png and b/cinema/gb/window/zin-head/baseline_0003.png differ diff --git a/cinema/gb/window/zin-head/baseline_0004.png b/cinema/gb/window/zin-head/baseline_0004.png index 5d583d227..f8e0b3711 100644 Binary files a/cinema/gb/window/zin-head/baseline_0004.png and b/cinema/gb/window/zin-head/baseline_0004.png differ diff --git a/cinema/gb/window/zin-head/baseline_0005.png b/cinema/gb/window/zin-head/baseline_0005.png index d3c2c0f9d..41cd69861 100644 Binary files a/cinema/gb/window/zin-head/baseline_0005.png and b/cinema/gb/window/zin-head/baseline_0005.png differ diff --git a/include/mgba/feature/video-logger.h b/include/mgba/feature/video-logger.h index b466e4b6c..2530d3fa2 100644 --- a/include/mgba/feature/video-logger.h +++ b/include/mgba/feature/video-logger.h @@ -10,6 +10,8 @@ CXX_GUARD_START +#include + #include #define mVL_MAX_CHANNELS 32 @@ -115,7 +117,7 @@ void mVideoLogContextSetOutput(struct mVideoLogContext*, struct VFile*); void mVideoLogContextWriteHeader(struct mVideoLogContext*, struct mCore* core); bool mVideoLogContextLoad(struct mVideoLogContext*, struct VFile*); -void mVideoLogContextDestroy(struct mCore* core, struct mVideoLogContext*); +void mVideoLogContextDestroy(struct mCore* core, struct mVideoLogContext*, bool closeVF); void mVideoLogContextRewind(struct mVideoLogContext*, struct mCore*); void* mVideoLogContextInitialState(struct mVideoLogContext*, size_t* size); @@ -128,6 +130,7 @@ void mVideoLoggerInjectVideoRegister(struct mVideoLogger* logger, uint32_t addre void mVideoLoggerInjectPalette(struct mVideoLogger* logger, uint32_t address, uint16_t value); void mVideoLoggerInjectOAM(struct mVideoLogger* logger, uint32_t address, uint16_t value); +enum mPlatform mVideoLogIsCompatible(struct VFile*); struct mCore* mVideoLogCoreFind(struct VFile*); CXX_GUARD_END diff --git a/include/mgba/internal/gba/renderers/video-software.h b/include/mgba/internal/gba/renderers/video-software.h index a546a4bf0..ee4af4fc3 100644 --- a/include/mgba/internal/gba/renderers/video-software.h +++ b/include/mgba/internal/gba/renderers/video-software.h @@ -56,7 +56,7 @@ enum { GBA_COLOR_WHITE = 0x7FFF, #endif #else - GBA_COLOR_WHITE = 0x00F8F8F8, + GBA_COLOR_WHITE = 0x00FFFFFF, #endif OFFSET_PRIORITY = 30, OFFSET_INDEX = 28, diff --git a/src/feature/ffmpeg/ffmpeg-encoder.c b/src/feature/ffmpeg/ffmpeg-encoder.c index 95ac403c8..5e86b8712 100644 --- a/src/feature/ffmpeg/ffmpeg-encoder.c +++ b/src/feature/ffmpeg/ffmpeg-encoder.c @@ -369,6 +369,8 @@ bool FFmpegEncoderOpen(struct FFmpegEncoder* encoder, const char* outfile) { encoder->video->height = encoder->height; encoder->video->time_base = (AVRational) { encoder->frameCycles * encoder->frameskip, encoder->cycles }; encoder->video->framerate = (AVRational) { encoder->cycles, encoder->frameCycles * encoder->frameskip }; + encoder->videoStream->time_base = encoder->video->time_base; + encoder->videoStream->avg_frame_rate = encoder->video->framerate; encoder->video->pix_fmt = encoder->pixFormat; encoder->video->gop_size = 60; encoder->video->max_b_frames = 3; diff --git a/src/feature/video-logger.c b/src/feature/video-logger.c index a240d2c33..e25b1a44c 100644 --- a/src/feature/video-logger.c +++ b/src/feature/video-logger.c @@ -5,7 +5,6 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include -#include #include #include #include @@ -724,7 +723,7 @@ static void _flushBuffer(struct mVideoLogContext* context) { } } -void mVideoLogContextDestroy(struct mCore* core, struct mVideoLogContext* context) { +void mVideoLogContextDestroy(struct mCore* core, struct mVideoLogContext* context, bool closeVF) { if (context->write) { _flushBuffer(context); @@ -752,6 +751,10 @@ void mVideoLogContextDestroy(struct mCore* core, struct mVideoLogContext* contex #endif } + if (closeVF && context->backing) { + context->backing->close(context->backing); + } + free(context); } @@ -1033,7 +1036,7 @@ static ssize_t mVideoLoggerWriteChannel(struct mVideoLogChannel* channel, const return read; } -struct mCore* mVideoLogCoreFind(struct VFile* vf) { +static const struct mVLDescriptor* _mVideoLogDescriptor(struct VFile* vf) { if (!vf) { return NULL; } @@ -1055,6 +1058,25 @@ struct mCore* mVideoLogCoreFind(struct VFile* vf) { break; } } + if (descriptor->platform == PLATFORM_NONE) { + return NULL; + } + return descriptor; +} + +enum mPlatform mVideoLogIsCompatible(struct VFile* vf) { + const struct mVLDescriptor* descriptor = _mVideoLogDescriptor(vf); + if (descriptor) { + return descriptor->platform; + } + return PLATFORM_NONE; +} + +struct mCore* mVideoLogCoreFind(struct VFile* vf) { + const struct mVLDescriptor* descriptor = _mVideoLogDescriptor(vf); + if (!descriptor) { + return NULL; + } struct mCore* core = NULL; if (descriptor->open) { core = descriptor->open(); diff --git a/src/gb/core.c b/src/gb/core.c index 590957691..0ac21e045 100644 --- a/src/gb/core.c +++ b/src/gb/core.c @@ -1091,7 +1091,7 @@ static bool _GBVLPInit(struct mCore* core) { static void _GBVLPDeinit(struct mCore* core) { struct GBCore* gbcore = (struct GBCore*) core; if (gbcore->logContext) { - mVideoLogContextDestroy(core, gbcore->logContext); + mVideoLogContextDestroy(core, gbcore->logContext, true); } _GBCoreDeinit(core); } @@ -1120,7 +1120,7 @@ static bool _GBVLPLoadROM(struct mCore* core, struct VFile* vf) { struct GBCore* gbcore = (struct GBCore*) core; gbcore->logContext = mVideoLogContextCreate(NULL); if (!mVideoLogContextLoad(gbcore->logContext, vf)) { - mVideoLogContextDestroy(core, gbcore->logContext); + mVideoLogContextDestroy(core, gbcore->logContext, false); gbcore->logContext = NULL; return false; } diff --git a/src/gb/renderers/software.c b/src/gb/renderers/software.c index 4d3f8b284..637bae496 100644 --- a/src/gb/renderers/software.c +++ b/src/gb/renderers/software.c @@ -213,6 +213,8 @@ static void GBVideoSoftwareRendererInit(struct GBVideoRenderer* renderer, enum G softwareRenderer->lookup[i] = i; softwareRenderer->lookup[i] = i; } + + memset(softwareRenderer->palette, 0, sizeof(softwareRenderer->palette)); } static void GBVideoSoftwareRendererDeinit(struct GBVideoRenderer* renderer) { diff --git a/src/gb/video.c b/src/gb/video.c index 575bdeb2f..fa5404c5a 100644 --- a/src/gb/video.c +++ b/src/gb/video.c @@ -894,6 +894,9 @@ void GBVideoDeserialize(struct GBVideo* video, const struct GBSerializedState* s mTimingSchedule(&video->p->timing, &video->frameEvent, when); } + video->renderer->deinit(video->renderer); + video->renderer->init(video->renderer, video->p->model, video->sgbBorders); + size_t i; for (i = 0; i < 64; ++i) { LOAD_16LE(video->palette[i], i * 2, state->video.palette); @@ -905,7 +908,4 @@ void GBVideoDeserialize(struct GBVideo* video, const struct GBSerializedState* s _cleanOAM(video, video->ly); GBVideoSwitchBank(video, video->vramCurrentBank); - - video->renderer->deinit(video->renderer); - video->renderer->init(video->renderer, video->p->model, video->sgbBorders); } diff --git a/src/gba/core.c b/src/gba/core.c index 703cf6a0e..cbf9ba7aa 100644 --- a/src/gba/core.c +++ b/src/gba/core.c @@ -1248,7 +1248,7 @@ static bool _GBAVLPInit(struct mCore* core) { static void _GBAVLPDeinit(struct mCore* core) { struct GBACore* gbacore = (struct GBACore*) core; if (gbacore->logContext) { - mVideoLogContextDestroy(core, gbacore->logContext); + mVideoLogContextDestroy(core, gbacore->logContext, true); } _GBACoreDeinit(core); } @@ -1277,7 +1277,7 @@ static bool _GBAVLPLoadROM(struct mCore* core, struct VFile* vf) { struct GBACore* gbacore = (struct GBACore*) core; gbacore->logContext = mVideoLogContextCreate(NULL); if (!mVideoLogContextLoad(gbacore->logContext, vf)) { - mVideoLogContextDestroy(core, gbacore->logContext); + mVideoLogContextDestroy(core, gbacore->logContext, false); gbacore->logContext = NULL; return false; } diff --git a/src/platform/python/cinema/movie.py b/src/platform/python/cinema/movie.py index d1a8d9f60..5f5402c99 100644 --- a/src/platform/python/cinema/movie.py +++ b/src/platform/python/cinema/movie.py @@ -8,15 +8,17 @@ Output = namedtuple('Output', ['video']) class Tracer(object): def __init__(self, core): self.core = core - self.framebuffer = Image(*core.desired_video_dimensions()) - self.core.set_video_buffer(self.framebuffer) self._video_fifo = [] def yield_frames(self, skip=0, limit=None): + self.framebuffer = Image(*self.core.desired_video_dimensions()) + self.core.set_video_buffer(self.framebuffer) self.core.reset() skip = (skip or 0) + 1 while skip > 0: frame = self.core.frame_counter + self.framebuffer = Image(*self.core.desired_video_dimensions()) + self.core.set_video_buffer(self.framebuffer) self.core.run_frame() skip -= 1 while frame <= self.core.frame_counter and limit != 0: diff --git a/src/platform/python/cinema/test.py b/src/platform/python/cinema/test.py index dd47708f9..fdd333fd5 100644 --- a/src/platform/python/cinema/test.py +++ b/src/platform/python/cinema/test.py @@ -3,7 +3,6 @@ import os.path import mgba.core import mgba.image import cinema.movie -import itertools import glob import re from copy import deepcopy @@ -73,7 +72,7 @@ class VideoTest(CinemaTest): self.tracer = cinema.movie.Tracer(self.core) def generate_frames(self): - for i, frame in zip(itertools.count(), self.tracer.video(**self.output_settings())): + for i, frame in enumerate(self.tracer.video(**self.output_settings())): try: baseline = VideoFrame.load(os.path.join(self.path, self.BASELINE % i)) yield baseline, frame, VideoFrame.diff(baseline, frame) @@ -85,7 +84,7 @@ class VideoTest(CinemaTest): assert not any(any(diffs[0].image.convert("L").point(bool).getdata()) for diffs in self.diffs) def generate_baseline(self): - for i, frame in zip(itertools.count(), self.tracer.video(**self.output_settings())): + for i, frame in enumerate(self.tracer.video(**self.output_settings())): frame.save(os.path.join(self.path, self.BASELINE % i)) diff --git a/src/platform/qt/CoreController.cpp b/src/platform/qt/CoreController.cpp index da8b25349..4d761d0c2 100644 --- a/src/platform/qt/CoreController.cpp +++ b/src/platform/qt/CoreController.cpp @@ -859,9 +859,8 @@ void CoreController::endVideoLog(bool closeVf) { } Interrupter interrupter(this); - mVideoLogContextDestroy(m_threadContext.core, m_vl); - if (m_vlVf && closeVf) { - m_vlVf->close(m_vlVf); + mVideoLogContextDestroy(m_threadContext.core, m_vl, closeVf); + if (closeVf) { m_vlVf = nullptr; } m_vl = nullptr; diff --git a/src/platform/qt/ts/mgba-zh_CN.ts b/src/platform/qt/ts/mgba-zh_CN.ts index 031a1f643..8a8a1a623 100644 --- a/src/platform/qt/ts/mgba-zh_CN.ts +++ b/src/platform/qt/ts/mgba-zh_CN.ts @@ -126,7 +126,7 @@ Game Boy Advance 是任天堂有限公司(Nintendo Co., Ltd.)的注册商标 - BattleChipView + BattleChipView BattleChip Gate @@ -531,7 +531,7 @@ Game Boy Advance 是任天堂有限公司(Nintendo Co., Ltd.)的注册商标 2 2 - + Cancel @@ -904,7 +904,7 @@ Game Boy Advance 是任天堂有限公司(Nintendo Co., Ltd.)的注册商标 Address 地址 - + 0x07000000 0x07000000 @@ -1512,11 +1512,6 @@ Game Boy Advance 是任天堂有限公司(Nintendo Co., Ltd.)的注册商标 Add CodeBreaker 添加 CodeBreaker - - - Add GameShark - 添加 GameShark - Add GameGenie @@ -1562,7 +1557,7 @@ Game Boy Advance 是任天堂有限公司(Nintendo Co., Ltd.)的注册商标 Could not load game. Are you sure it's in the correct format? - 无法载入游戏。请确认游戏格式是否无误。 + 无法载入游戏。是否确认游戏格式无误? @@ -1696,8 +1691,8 @@ Game Boy Advance 是任天堂有限公司(Nintendo Co., Ltd.)的注册商标 - Graphics Interchange Format (*.gif);;Animated Portable Network Graphics (*.png *.apng)" - 图形交换格式 (*.gif);;动画便携式网络图形 (*.png *.apng)" + Graphics Interchange Format (*.gif);;Animated Portable Network Graphics (*.png *.apng)" + 图形交换格式 (*.gif);;动画便携式网络图形 (*.png *.apng)" @@ -3821,7 +3816,7 @@ Game Boy Advance 是任天堂有限公司(Nintendo Co., Ltd.)的注册商标 %1 - %2 - %3 - %1 - %2 - %3 + %1 - %2 - %3 @@ -3947,7 +3942,7 @@ Game Boy Advance 是任天堂有限公司(Nintendo Co., Ltd.)的注册商标 State &%1 - 即时存档 1(&1) + 即时存档 (&%1) @@ -4098,7 +4093,7 @@ Game Boy Advance 是任天堂有限公司(Nintendo Co., Ltd.)的注册商标 BattleChip Gate... BattleChip Gate... - + Audio/&Video @@ -5318,21 +5313,11 @@ Game Boy Advance 是任天堂有限公司(Nintendo Co., Ltd.)的注册商标 MKV MKV - - - WebM - WebM - AVI AVI - - - MP4 - MP4 - h.264 @@ -5407,7 +5392,7 @@ Game Boy Advance 是任天堂有限公司(Nintendo Co., Ltd.)的注册商标 Bitrate (kbps) - 比特率 (kbps) + 比特率 (kbps) diff --git a/src/platform/test/CMakeLists.txt b/src/platform/test/CMakeLists.txt index 62e6b0ac2..8882b8d94 100644 --- a/src/platform/test/CMakeLists.txt +++ b/src/platform/test/CMakeLists.txt @@ -36,4 +36,11 @@ if(BUILD_SUITE) set_target_properties(test-${TEST_NAME} PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES};${FUNCTION_DEFINES}") add_test(${TEST_NAME} test-${TEST_NAME}) endforeach() -endif() \ No newline at end of file +endif() + +if(BUILD_CINEMA) + enable_testing() + add_executable(${BINARY_NAME}-cinema ${CMAKE_CURRENT_SOURCE_DIR}/cinema-main.c) + target_link_libraries(${BINARY_NAME}-cinema ${BINARY_NAME} ${PLATFORM_LIBRARY}) + set_target_properties(${BINARY_NAME}-cinema PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES};${FUNCTION_DEFINES}") +endif() diff --git a/src/platform/test/cinema-main.c b/src/platform/test/cinema-main.c new file mode 100644 index 000000000..83bdb52b2 --- /dev/null +++ b/src/platform/test/cinema-main.c @@ -0,0 +1,850 @@ +/* Copyright (c) 2013-2020 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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#ifdef _MSC_VER +#include +#else +#include +#endif + +#include +#include +#include + +#define MAX_TEST 200 + +static const struct option longOpts[] = { + { "base", required_argument, 0, 'b' }, + { "diffs", no_argument, 0, 'd' }, + { "help", no_argument, 0, 'h' }, + { "dry-run", no_argument, 0, 'n' }, + { "outdir", required_argument, 0, 'o' }, + { "quiet", no_argument, 0, 'q' }, + { "rebaseline", no_argument, 0, 'r' }, + { "verbose", no_argument, 0, 'v' }, + { "version", no_argument, 0, '\0' }, + { 0, 0, 0, 0 } +}; + +static const char shortOpts[] = "b:dhno:qrv"; + +enum CInemaStatus { + CI_PASS, + CI_FAIL, + CI_XPASS, + CI_XFAIL, + CI_ERROR, + CI_SKIP +}; + +struct CInemaTest { + char directory[MAX_TEST]; + char filename[MAX_TEST]; + char name[MAX_TEST]; + enum CInemaStatus status; + unsigned failedFrames; + uint64_t failedPixels; + unsigned totalFrames; + uint64_t totalDistance; + uint64_t totalPixels; +}; + +struct CInemaImage { + void* data; + unsigned width; + unsigned height; + unsigned stride; +}; + +DECLARE_VECTOR(CInemaTestList, struct CInemaTest) +DEFINE_VECTOR(CInemaTestList, struct CInemaTest) + +DECLARE_VECTOR(ImageList, void*) +DEFINE_VECTOR(ImageList, void*) + +static bool showVersion = false; +static bool showUsage = false; +static char base[PATH_MAX] = {0}; +static char outdir[PATH_MAX] = {'.'}; +static bool dryRun = false; +static bool diffs = false; +static bool rebaseline = false; +static int verbosity = 0; + +bool CInemaTestInit(struct CInemaTest*, const char* directory, const char* filename); +void CInemaTestRun(struct CInemaTest*, struct Table* configTree); + +bool CInemaConfigGetUInt(struct Table* configTree, const char* testName, const char* key, unsigned* value); +void CInemaConfigLoad(struct Table* configTree, const char* testName, struct mCore* core); + +static void _log(struct mLogger* log, int category, enum mLogLevel level, const char* format, va_list args); + +ATTRIBUTE_FORMAT(printf, 2, 3) void CIlog(int minlevel, const char* format, ...) { + if (verbosity < minlevel) { + return; + } + va_list args; + va_start(args, format); + vprintf(format, args); + va_end(args); +} + +ATTRIBUTE_FORMAT(printf, 2, 3) void CIerr(int minlevel, const char* format, ...) { + if (verbosity < minlevel) { + return; + } + va_list args; + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); +} + +static bool parseCInemaArgs(int argc, char* const* argv) { + int ch; + int index = 0; + while ((ch = getopt_long(argc, argv, shortOpts, longOpts, &index)) != -1) { + const struct option* opt = &longOpts[index]; + switch (ch) { + case '\0': + if (strcmp(opt->name, "version") == 0) { + showVersion = true; + } else { + return false; + } + break; + case 'b': + strncpy(base, optarg, sizeof(base)); + // TODO: Verify path exists + break; + case 'd': + diffs = true; + break; + case 'h': + showUsage = true; + break; + case 'n': + dryRun = true; + break; + case 'o': + strncpy(outdir, optarg, sizeof(outdir)); + // TODO: Make directory + break; + case 'q': + --verbosity; + break; + case 'r': + rebaseline = true; + break; + case 'v': + ++verbosity; + break; + default: + return false; + } + } + + return true; +} + +static void usageCInema(const char* arg0) { + printf("usage: %s [-dhnqv] [-b BASE] [-o DIR] [--version] [test...]\n", arg0); + puts(" -b, --base [BASE] Path to the CInema base directory"); + puts(" -d, --diffs Output image diffs from failures"); + puts(" -h, --help Print this usage and exit"); + puts(" -n, --dry-run List all collected tests instead of running them"); + puts(" -o, --output [DIR] Path to output applicable results"); + puts(" -q, --quiet Decrease log verbosity (can be repeated)"); + puts(" -r, --rebaseline Rewrite the baseline for failing tests"); + puts(" -v, --verbose Increase log verbosity (can be repeated)"); + puts(" --version Print version and exit"); +} + +static bool determineBase(int argc, char* const* argv) { + // TODO: Better dynamic detection + separatePath(__FILE__, base, NULL, NULL); + strncat(base, PATH_SEP ".." PATH_SEP ".." PATH_SEP ".." PATH_SEP "cinema", sizeof(base) - strlen(base) - 1); + return true; +} + +static bool collectTests(struct CInemaTestList* tests, const char* path) { + CIerr(2, "Considering path %s\n", path); + struct VDir* dir = VDirOpen(path); + if (!dir) { + return false; + } + struct VDirEntry* entry = dir->listNext(dir); + while (entry) { + char subpath[PATH_MAX]; + snprintf(subpath, sizeof(subpath), "%s" PATH_SEP "%s", path, entry->name(entry)); + if (entry->type(entry) == VFS_DIRECTORY && strncmp(entry->name(entry), ".", 2) != 0 && strncmp(entry->name(entry), "..", 3) != 0) { + if (!collectTests(tests, subpath)) { + dir->close(dir); + return false; + } + } else if (entry->type(entry) == VFS_FILE && strncmp(entry->name(entry), "test.", 5) == 0) { + CIerr(3, "Found potential test %s\n", subpath); + struct VFile* vf = dir->openFile(dir, entry->name(entry), O_RDONLY); + if (vf) { + if (mCoreIsCompatible(vf) != PLATFORM_NONE || mVideoLogIsCompatible(vf) != PLATFORM_NONE) { + struct CInemaTest* test = CInemaTestListAppend(tests); + if (!CInemaTestInit(test, path, entry->name(entry))) { + CIerr(3, "Failed to create test\n"); + CInemaTestListResize(tests, -1); + } else { + CIerr(2, "Found test %s\n", test->name); + } + } else { + CIerr(3, "Not a compatible file\n"); + } + vf->close(vf); + } else { + CIerr(3, "Failed to open file\n"); + } + } + entry = dir->listNext(dir); + } + dir->close(dir); + return true; +} + +static int _compareNames(const void* a, const void* b) { + const struct CInemaTest* ta = a; + const struct CInemaTest* tb = b; + + return strncmp(ta->name, tb->name, sizeof(ta->name)); +} + +static void reduceTestList(struct CInemaTestList* tests) { + qsort(CInemaTestListGetPointer(tests, 0), CInemaTestListSize(tests), sizeof(struct CInemaTest), _compareNames); + + size_t i; + for (i = 1; i < CInemaTestListSize(tests);) { + struct CInemaTest* cur = CInemaTestListGetPointer(tests, i); + struct CInemaTest* prev = CInemaTestListGetPointer(tests, i - 1); + if (strncmp(cur->name, prev->name, sizeof(cur->name)) != 0) { + ++i; + continue; + } + CInemaTestListShift(tests, i, 1); + } +} + +static void testToPath(const char* testName, char* path) { + strncpy(path, base, PATH_MAX); + + bool dotSeen = true; + size_t i; + for (i = strlen(path); testName[0] && i < PATH_MAX; ++testName) { + if (testName[0] == '.') { + dotSeen = true; + } else { + if (dotSeen) { + strncpy(&path[i], PATH_SEP, PATH_MAX - i); + i += strlen(PATH_SEP); + dotSeen = false; + if (!i) { + break; + } + } + path[i] = testName[0]; + ++i; + } + } +} + +static void _loadConfigTree(struct Table* configTree, const char* testName) { + char key[MAX_TEST]; + strncpy(key, testName, sizeof(key) - 1); + + struct mCoreConfig* config; + while (!(config = HashTableLookup(configTree, key))) { + char path[PATH_MAX]; + config = malloc(sizeof(*config)); + mCoreConfigInit(config, "cinema"); + testToPath(key, path); + strncat(path, PATH_SEP, sizeof(path) - 1); + strncat(path, "config.ini", sizeof(path) - 1); + mCoreConfigLoadPath(config, path); + HashTableInsert(configTree, key, config); + char* pos = strrchr(key, '.'); + if (pos) { + pos[0] = '\0'; + } else if (key[0]) { + key[0] = '\0'; + } else { + break; + } + } +} + +static void _unloadConfigTree(const char* key, void* value, void* user) { + UNUSED(key); + UNUSED(user); + mCoreConfigDeinit(value); +} + +static const char* _lookupValue(struct Table* configTree, const char* testName, const char* key) { + _loadConfigTree(configTree, testName); + + char testKey[MAX_TEST]; + strncpy(testKey, testName, sizeof(testKey) - 1); + + struct mCoreConfig* config; + while (true) { + config = HashTableLookup(configTree, testKey); + if (!config) { + continue; + } + const char* str = ConfigurationGetValue(&config->configTable, "testinfo", key); + if (str) { + return str; + } + char* pos = strrchr(testKey, '.'); + if (pos) { + pos[0] = '\0'; + } else if (testKey[0]) { + testKey[0] = '\0'; + } else { + break; + } + } + return NULL; +} + +bool CInemaConfigGetUInt(struct Table* configTree, const char* testName, const char* key, unsigned* out) { + const char* charValue = _lookupValue(configTree, testName, key); + if (!charValue) { + return false; + } + char* end; + unsigned long value = strtoul(charValue, &end, 10); + if (*end) { + return false; + } + *out = value; + return true; +} + +void CInemaConfigLoad(struct Table* configTree, const char* testName, struct mCore* core) { + _loadConfigTree(configTree, testName); + + char testKey[MAX_TEST] = {0}; + char* keyEnd = testKey; + const char* pos; + while (true) { + pos = strchr(testName, '.'); + size_t maxlen = sizeof(testKey) - (keyEnd - testKey) - 1; + size_t len; + if (pos) { + len = pos - testName; + } else { + len = strlen(testName); + } + if (len > maxlen) { + len = maxlen; + } + strncpy(keyEnd, testName, len); + keyEnd += len; + + struct mCoreConfig* config = HashTableLookup(configTree, testKey); + if (config) { + core->loadConfig(core, config); + } + if (!pos) { + break; + } + testName = pos + 1; + keyEnd[0] = '.'; + ++keyEnd; + } +} + +bool CInemaTestInit(struct CInemaTest* test, const char* directory, const char* filename) { + if (strncmp(base, directory, strlen(base)) != 0) { + return false; + } + memset(test, 0, sizeof(*test)); + strncpy(test->directory, directory, sizeof(test->directory) - 1); + strncpy(test->filename, filename, sizeof(test->filename) - 1); + directory += strlen(base) + 1; + strncpy(test->name, directory, sizeof(test->name) - 1); + char* str = strstr(test->name, PATH_SEP); + while (str) { + str[0] = '.'; + str = strstr(str, PATH_SEP); + } + return true; +} + +static bool _loadBaseline(struct VDir* dir, struct CInemaImage* image, size_t frame, enum CInemaStatus* status) { + char baselineName[32]; + snprintf(baselineName, sizeof(baselineName), "baseline_%04" PRIz "u.png", frame); + struct VFile* baselineVF = dir->openFile(dir, baselineName, O_RDONLY); + if (!baselineVF) { + if (*status == CI_PASS) { + *status = CI_FAIL; + } + return false; + } + + png_structp png = PNGReadOpen(baselineVF, 0); + png_infop info = png_create_info_struct(png); + png_infop end = png_create_info_struct(png); + if (!png || !info || !end || !PNGReadHeader(png, info)) { + PNGReadClose(png, info, end); + baselineVF->close(baselineVF); + CIerr(1, "Failed to load %s\n", baselineName); + *status = CI_ERROR; + return false; + } + + unsigned pwidth = png_get_image_width(png, info); + unsigned pheight = png_get_image_height(png, info); + if (pheight != image->height || pwidth != image->width) { + PNGReadClose(png, info, end); + baselineVF->close(baselineVF); + CIlog(1, "Size mismatch for %s, expected %ux%u, got %ux%u\n", baselineName, pwidth, pheight, image->width, image->height); + if (*status == CI_PASS) { + *status = CI_FAIL; + } + return false; + } + + image->data = malloc(pwidth * pheight * BYTES_PER_PIXEL); + if (!image->data) { + CIerr(1, "Failed to allocate baseline buffer\n"); + *status = CI_ERROR; + PNGReadClose(png, info, end); + baselineVF->close(baselineVF); + return false; + } + if (!PNGReadPixels(png, info, image->data, pwidth, pheight, pwidth) || !PNGReadFooter(png, end)) { + CIerr(1, "Failed to read %s\n", baselineName); + *status = CI_ERROR; + free(image->data); + return false; + } + PNGReadClose(png, info, end); + baselineVF->close(baselineVF); + image->stride = pwidth; + return true; +} + +static struct VDir* _makeOutDir(const char* testName) { + char path[PATH_MAX] = {0}; + strncpy(path, outdir, sizeof(path) - 1); + char* pathEnd = path + strlen(path); + const char* pos; + while (true) { + pathEnd[0] = PATH_SEP[0]; + ++pathEnd; + pos = strchr(testName, '.'); + size_t maxlen = sizeof(path) - (pathEnd - path) - 1; + size_t len; + if (pos) { + len = pos - testName; + } else { + len = strlen(testName); + } + if (len > maxlen) { + len = maxlen; + } + strncpy(pathEnd, testName, len); + pathEnd += len; + + mkdir(path, 0777); + + if (!pos) { + break; + } + testName = pos + 1; + } + return VDirOpen(path); +} + +static void _writeImage(struct VFile* vf, const struct CInemaImage* image) { + png_structp png = PNGWriteOpen(vf); + png_infop info = PNGWriteHeader(png, image->width, image->height); + if (!PNGWritePixels(png, image->width, image->height, image->stride, image->data)) { + CIerr(0, "Could not write output image\n"); + } + PNGWriteClose(png, info); + + vf->close(vf); +} + +static void _writeDiff(const char* testName, const struct CInemaImage* image, size_t frame, const char* type) { + struct VDir* dir = _makeOutDir(testName); + if (!dir) { + CIerr(0, "Could not open directory for %s\n", testName); + return; + } + char name[32]; + snprintf(name, sizeof(name), "%s_%04" PRIz "u.png", type, frame); + struct VFile* vf = dir->openFile(dir, name, O_CREAT | O_TRUNC | O_WRONLY); + if (!vf) { + CIerr(0, "Could not open output file %s\n", name); + dir->close(dir); + return; + } + _writeImage(vf, image); + dir->close(dir); +} + +static void _writeBaseline(struct VDir* dir, const struct CInemaImage* image, size_t frame) { + char baselineName[32]; + snprintf(baselineName, sizeof(baselineName), "baseline_%04" PRIz "u.png", frame); + struct VFile* baselineVF = dir->openFile(dir, baselineName, O_CREAT | O_TRUNC | O_WRONLY); + if (baselineVF) { + _writeImage(baselineVF, image); + } else { + CIerr(0, "Could not open output file %s\n", baselineName); + } +} + +void CInemaTestRun(struct CInemaTest* test, struct Table* configTree) { + unsigned ignore = 0; + CInemaConfigGetUInt(configTree, test->name, "ignore", &ignore); + if (ignore) { + test->status = CI_SKIP; + return; + } + + struct VDir* dir = VDirOpen(test->directory); + if (!dir) { + CIerr(0, "Failed to open test directory\n"); + test->status = CI_ERROR; + return; + } + struct VFile* rom = dir->openFile(dir, test->filename, O_RDONLY); + if (!rom) { + CIerr(0, "Failed to open test\n"); + test->status = CI_ERROR; + return; + } + struct mCore* core = mCoreFindVF(rom); + if (!core) { + CIerr(0, "Failed to load test\n"); + test->status = CI_ERROR; + rom->close(rom); + return; + } + if (!core->init(core)) { + CIerr(0, "Failed to init test\n"); + test->status = CI_ERROR; + core->deinit(core); + return; + } + struct CInemaImage image; + core->desiredVideoDimensions(core, &image.width, &image.height); + ssize_t bufferSize = image.width * image.height * BYTES_PER_PIXEL; + image.data = malloc(bufferSize); + image.stride = image.width; + if (!image.data) { + CIerr(0, "Failed to allocate video buffer\n"); + test->status = CI_ERROR; + core->deinit(core); + } + core->setVideoBuffer(core, image.data, image.stride); + mCoreConfigInit(&core->config, "cinema"); + + unsigned limit = 9999; + unsigned skip = 0; + unsigned fail = 0; + + CInemaConfigGetUInt(configTree, test->name, "frames", &limit); + CInemaConfigGetUInt(configTree, test->name, "skip", &skip); + CInemaConfigGetUInt(configTree, test->name, "fail", &fail); + CInemaConfigLoad(configTree, test->name, core); + + core->loadROM(core, rom); + core->rtc.override = RTC_FAKE_EPOCH; + core->rtc.value = 1200000000; + core->reset(core); + + test->status = CI_PASS; + + unsigned minFrame = core->frameCounter(core); + size_t frame; + for (frame = 0; frame < skip; ++frame) { + core->runFrame(core); + } + for (frame = 0; limit; ++frame, --limit) { + core->runFrame(core); + ++test->totalFrames; + unsigned frameCounter = core->frameCounter(core); + if (frameCounter <= minFrame) { + break; + } + CIlog(3, "Test frame: %u\n", frameCounter); + core->desiredVideoDimensions(core, &image.width, &image.height); + uint8_t* diff = NULL; + struct CInemaImage expected = { + .data = NULL, + .width = image.width, + .height = image.height, + .stride = image.width, + }; + if (_loadBaseline(dir, &expected, frame, &test->status)) { + uint8_t* testPixels = image.data; + uint8_t* expectPixels = expected.data; + size_t x; + size_t y; + int max = 0; + bool failed = false; + for (y = 0; y < image.height; ++y) { + for (x = 0; x < image.width; ++x) { + size_t pix = expected.stride * y + x; + size_t tpix = image.stride * y + x; + int testR = testPixels[tpix * 4 + 0]; + int testG = testPixels[tpix * 4 + 1]; + int testB = testPixels[tpix * 4 + 2]; + int expectR = expectPixels[pix * 4 + 0]; + int expectG = expectPixels[pix * 4 + 1]; + int expectB = expectPixels[pix * 4 + 2]; + int r = expectR - testR; + int g = expectG - testG; + int b = expectB - testB; + if (r | g | b) { + failed = true; + if (diffs && !diff) { + diff = calloc(expected.width * expected.height, BYTES_PER_PIXEL); + } + CIlog(3, "Frame %u failed at pixel %" PRIz "ux%" PRIz "u with diff %i,%i,%i (expected %02x%02x%02x, got %02x%02x%02x)\n", + frameCounter, x, y, r, g, b, + expectR, expectG, expectB, + testR, testG, testB); + test->status = CI_FAIL; + if (r < 0) { + r = -r; + } + if (g < 0) { + g = -g; + } + if (b < 0) { + b = -b; + } + + if (diff) { + if (r > max) { + max = r; + } + if (g > max) { + max = g; + } + if (b > max) { + max = b; + } + diff[pix * 4 + 0] = r; + diff[pix * 4 + 1] = g; + diff[pix * 4 + 2] = b; + } + + test->totalDistance += r + g + b; + ++test->failedPixels; + } + } + } + if (failed) { + ++test->failedFrames; + } + test->totalPixels += image.height * image.width; + if (rebaseline && failed) { + _writeBaseline(dir, &image, frame); + } + if (diff) { + if (failed) { + struct CInemaImage outdiff = { + .data = diff, + .width = image.width, + .height = image.height, + .stride = image.width, + }; + + _writeDiff(test->name, &image, frame, "result"); + _writeDiff(test->name, &expected, frame, "expected"); + _writeDiff(test->name, &outdiff, frame, "diff"); + + for (y = 0; y < outdiff.height; ++y) { + for (x = 0; x < outdiff.width; ++x) { + size_t pix = outdiff.stride * y + x; + diff[pix * 4 + 0] = diff[pix * 4 + 0] * 255 / max; + diff[pix * 4 + 1] = diff[pix * 4 + 1] * 255 / max; + diff[pix * 4 + 2] = diff[pix * 4 + 2] * 255 / max; + } + } + _writeDiff(test->name, &outdiff, frame, "normalized"); + } + free(diff); + } + free(expected.data); + } else if (test->status == CI_ERROR) { + break; + } else if (rebaseline) { + _writeBaseline(dir, &image, frame); + } + } + + if (fail) { + if (test->status == CI_FAIL) { + test->status = CI_XFAIL; + } else if (test->status == CI_PASS) { + test->status = CI_XPASS; + } + } + + free(image.data); + mCoreConfigDeinit(&core->config); + core->deinit(core); + dir->close(dir); +} + +void _log(struct mLogger* log, int category, enum mLogLevel level, const char* format, va_list args) { + UNUSED(log); + if (verbosity < 0) { + return; + } + int mask = mLOG_FATAL; + if (verbosity >= 1) { + mask |= mLOG_ERROR; + } + if (verbosity >= 2) { + mask |= mLOG_WARN; + } + if (verbosity >= 4) { + mask |= mLOG_INFO; + } + if (verbosity >= 5) { + mask |= mLOG_ALL; + } + if (!(mask & level)) { + return; + } + + char buffer[256]; + vsnprintf(buffer, sizeof(buffer), format, args); + CIerr(0, "[%s] %s\n", mLogCategoryName(category), buffer); +} + +int main(int argc, char** argv) { + int status = 0; + if (!parseCInemaArgs(argc, argv)) { + status = 1; + goto cleanup; + } + + if (showVersion) { + version(argv[0]); + goto cleanup; + } + + if (showUsage) { + usageCInema(argv[0]); + goto cleanup; + } + + argc -= optind; + argv += optind; + + if (!base[0] && !determineBase(argc, argv)) { + CIlog(0, "Could not determine CInema test base. Please specify manually."); + status = 1; + goto cleanup; + } +#ifndef _WIN32 + char* rbase = realpath(base, NULL); + strncpy(base, rbase, PATH_MAX); + free(rbase); +#endif + + struct CInemaTestList tests; + CInemaTestListInit(&tests, 0); + + struct mLogger logger = { .log = _log }; + mLogSetDefaultLogger(&logger); + + if (argc > 0) { + size_t i; + for (i = 0; i < (size_t) argc; ++i) { + char path[PATH_MAX + 1] = {0}; + testToPath(argv[i], path); + + if (!collectTests(&tests, path)) { + status = 1; + break; + } + } + } else if (!collectTests(&tests, base)) { + status = 1; + } + + if (CInemaTestListSize(&tests) == 0) { + CIlog(1, "No tests found."); + status = 1; + } else { + reduceTestList(&tests); + } + + struct Table configTree; + HashTableInit(&configTree, 0, free); + + size_t i; + for (i = 0; i < CInemaTestListSize(&tests); ++i) { + struct CInemaTest* test = CInemaTestListGetPointer(&tests, i); + if (dryRun) { + CIlog(-1, "%s\n", test->name); + } else { + CIlog(1, "%s: ", test->name); + fflush(stdout); + CInemaTestRun(test, &configTree); + switch (test->status) { + case CI_PASS: + CIlog(1, "pass\n"); + break; + case CI_FAIL: + status = 1; + CIlog(1, "fail\n"); + break; + case CI_XPASS: + CIlog(1, "xpass\n"); + break; + case CI_XFAIL: + CIlog(1, "xfail\n"); + break; + case CI_SKIP: + CIlog(1, "skip\n"); + break; + case CI_ERROR: + status = 1; + CIlog(1, "error"); + break; + } + if (test->failedFrames) { + CIlog(2, "\tfailed frames: %u/%u (%1.3g%%)\n", test->failedFrames, test->totalFrames, test->failedFrames / (test->totalFrames * 0.01)); + CIlog(2, "\tfailed pixels: %" PRIu64 "/%" PRIu64 " (%1.3g%%)\n", test->failedPixels, test->totalPixels, test->failedPixels / (test->totalPixels * 0.01)); + CIlog(2, "\tdistance: %" PRIu64 "/%" PRIu64 " (%1.3g%%)\n", test->totalDistance, test->totalPixels * 765, test->totalDistance / (test->totalPixels * 7.65)); + } + } + } + + HashTableEnumerate(&configTree, _unloadConfigTree, NULL); + HashTableDeinit(&configTree); + CInemaTestListDeinit(&tests); + +cleanup: + return status; +} \ No newline at end of file