Merge branch 'master' (early part) into medusa

This commit is contained in:
Vicki Pfau 2024-06-21 00:46:28 -07:00
commit 1ec984f4fa
110 changed files with 8958 additions and 3208 deletions

8
.gitignore vendored
View File

@ -1,12 +1,15 @@
# Generic files
*.user* *.user*
*~ *~
*.swp *.swp
*.pyc *.pyc
# Build directories
/build /build
/build-* /build-*
/.vs /.vs
# Build files
*.a *.a
*.dylib *.dylib
*.dll *.dll
@ -18,4 +21,9 @@ CMakeCache.txt
CMakeFiles CMakeFiles
CMakeSettings.json CMakeSettings.json
cmake_install.cmake cmake_install.cmake
hle-bios.bin
version.c version.c
# Runtime generated cruft
*.sav
*.ss0

30
CHANGES
View File

@ -46,19 +46,36 @@ Features:
- New unlicensed GB mappers: NT (older types 1 and 2), Li Cheng, GGB-81 - New unlicensed GB mappers: NT (older types 1 and 2), Li Cheng, GGB-81
- Debugger: Add range watchpoints - Debugger: Add range watchpoints
Emulation fixes: Emulation fixes:
- GB Audio: Fix channels 1/2 staying muted if restarted after long silence
- GB Serialize: Add missing Pocket Cam state to savestates
- GB Video: Implement DMG-style sprite ordering - GB Video: Implement DMG-style sprite ordering
- GBA BIOS: Fix clobbering registers with word-sized CpuSet
- GBA Video: Disable BG target 1 blending when OBJ blending (fixes mgba.io/i/2722)
Other fixes:
- Qt: Fix savestate preview sizes with different scales (fixes mgba.io/i/2560)
Misc:
- Core: Handle relative paths for saves, screenshots, etc consistently (fixes mgba.io/i/2826)
- GB Serialize: Add missing savestate support for MBC6 and NT (newer)
- GBA: Improve detection of valid ELF ROMs
- mGUI: Enable auto-softpatching (closes mgba.io/i/2899)
- Scripting: Add `callbacks:oneshot` for single-call callbacks
0.10.2: (2023-04-23)
Emulation fixes:
- GBA Audio: Fix improperly deserializing GB audio registers (fixes mgba.io/i/2793) - GBA Audio: Fix improperly deserializing GB audio registers (fixes mgba.io/i/2793)
- GBA Audio: Clear GB audio state when disabled
- GBA Memory: Make VRAM access stalls only apply to BG RAM - GBA Memory: Make VRAM access stalls only apply to BG RAM
- GBA Overrides: Fix saving in PMD:RRT (JP) (fixes mgba.io/i/2862)
- GBA SIO: Fix SIOCNT SI pin value after attaching player 2 (fixes mgba.io/i/2805) - GBA SIO: Fix SIOCNT SI pin value after attaching player 2 (fixes mgba.io/i/2805)
- GBA SIO: Fix unconnected normal mode SIOCNT SI bit (fixes mgba.io/i/2810) - GBA SIO: Fix unconnected normal mode SIOCNT SI bit (fixes mgba.io/i/2810)
- GBA SIO: Normal mode transfers with no clock should not finish (fixes mgba.io/i/2811) - GBA SIO: Normal mode transfers with no clock should not finish (fixes mgba.io/i/2811)
- GBA Timers: Cascading timers don't tick when disabled (fixes mgba.io/i/2812) - GBA Timers: Cascading timers don't tick when disabled (fixes mgba.io/i/2812)
- GBA Video: Disable BG target 1 blending when OBJ blending (fixes mgba.io/i/2722) - GBA Video: Fix interpolation issues with OpenGL renderer
Other fixes: Other fixes:
- Core: Allow sending thread requests to a crashed core (fixes mgba.io/i/2784) - Core: Allow sending thread requests to a crashed core (fixes mgba.io/i/2784)
- FFmpeg: Force lower sample rate for codecs not supporting high rates (fixes mgba.io/i/2869)
- Qt: Fix crash when attempting to use OpenGL 2.1 to 3.1 (fixes mgba.io/i/2794) - Qt: Fix crash when attempting to use OpenGL 2.1 to 3.1 (fixes mgba.io/i/2794)
- Qt: Disable sync while running scripts from main thread (fixes mgba.io/i/2738) - Qt: Disable sync while running scripts from main thread (fixes mgba.io/i/2738)
- Qt: Fix savestate preview sizes with different scales (fixes mgba.io/i/2560)
- Qt: Properly cap number of attached players by platform (fixes mgba.io/i/2807) - Qt: Properly cap number of attached players by platform (fixes mgba.io/i/2807)
- Qt: Disable attempted linking betwen incompatible platforms (fixes mgba.io/i/2702) - Qt: Disable attempted linking betwen incompatible platforms (fixes mgba.io/i/2702)
- Qt: Fix modifier key names in shortcut editor (fixes mgba.io/i/2817) - Qt: Fix modifier key names in shortcut editor (fixes mgba.io/i/2817)
@ -67,16 +84,17 @@ Other fixes:
- Qt: Fix crash if loading a shader fails - Qt: Fix crash if loading a shader fails
- Qt: Fix black screen when starting with a game (fixes mgba.io/i/2781) - Qt: Fix black screen when starting with a game (fixes mgba.io/i/2781)
- Qt: Fix OSD on modern macOS (fixes mgba.io/i/2736) - Qt: Fix OSD on modern macOS (fixes mgba.io/i/2736)
- Qt: Fix checked state of mute menu option at load (fixes mgba.io/i/2701)
- Qt: Remove OpenGL proxy thread and override SwapInterval directly instead
- Scripting: Fix receiving packets for client sockets - Scripting: Fix receiving packets for client sockets
- Scripting: Fix empty receive calls returning unknown error on Windows - Scripting: Fix empty receive calls returning unknown error on Windows
- Scripting: Return proper callback ID from socket.add
- Vita: Work around broken mktime implementation in Vita SDK (fixes mgba.io/i/2876)
Misc: Misc:
- Core: Handle relative paths for saves, screenshots, etc consistently (fixes mgba.io/i/2826)
- GB Serialize: Add missing savestate support for MBC6 and NT (newer)
- GBA: Improve detection of valid ELF ROMs
- Qt: Include wayland QPA in AppImage (fixes mgba.io/i/2796) - Qt: Include wayland QPA in AppImage (fixes mgba.io/i/2796)
- Qt: Stop eating boolean action key events (fixes mgba.io/i/2636) - Qt: Stop eating boolean action key events (fixes mgba.io/i/2636)
- Qt: Automatically change video file extension as appropriate - Qt: Automatically change video file extension as appropriate
- Scripting: Add `callbacks:oneshot` for single-call callbacks - Qt: Swap P1 and other player's save if P1 loaded it first (closes mgba.io/i/2750)
0.10.1: (2023-01-10) 0.10.1: (2023-01-10)
Emulation fixes: Emulation fixes:

View File

@ -33,7 +33,7 @@ if(NOT MSVC)
# mingw32 likes to complain about using the "wrong" format strings despite them actually working # mingw32 likes to complain about using the "wrong" format strings despite them actually working
set(WARNING_FLAGS "${WARNING_FLAGS} -Wno-format") set(WARNING_FLAGS "${WARNING_FLAGS} -Wno-format")
endif() endif()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WARNING_FLAGS} -Werror=implicit-function-declaration -Werror=implicit-int") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WARNING_FLAGS} -Werror=implicit-function-declaration -Werror=implicit-int -fwrapv")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WARNING_FLAGS} -Woverloaded-virtual") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WARNING_FLAGS} -Woverloaded-virtual")
else() else()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_CRT_SECURE_NO_WARNINGS /wd4003 /wd4244 /wd4146 /wd4267 /Zc:preprocessor-") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_CRT_SECURE_NO_WARNINGS /wd4003 /wd4244 /wd4146 /wd4267 /Zc:preprocessor-")
@ -426,6 +426,13 @@ if(BUILD_GL)
elseif(UNIX AND NOT APPLE AND TARGET OpenGL::GL) elseif(UNIX AND NOT APPLE AND TARGET OpenGL::GL)
set(OPENGL_LIBRARY OpenGL::GL) set(OPENGL_LIBRARY OpenGL::GL)
endif() endif()
if(OpenGL_GLX_FOUND)
list(APPEND FEATURES GLX)
endif()
if(OpenGL_EGL_FOUND)
list(APPEND FEATURES EGL)
list(APPEND OPENGL_LIBRARY ${OPENGL_egl_LIBRARY})
endif()
endif() endif()
if(BUILD_GL) if(BUILD_GL)
list(APPEND FEATURE_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/opengl/gl.c) list(APPEND FEATURE_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/opengl/gl.c)
@ -757,11 +764,6 @@ elseif(BUILD_GLES2)
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libgles2") set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libgles2")
endif() 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)) if(WIN32 AND NOT (LIBMGBA_ONLY OR SKIP_LIBRARY OR USE_EPOXY))
message(FATAL_ERROR "Windows requires epoxy module!") message(FATAL_ERROR "Windows requires epoxy module!")
endif() endif()

View File

@ -10,20 +10,21 @@
CXX_GUARD_START CXX_GUARD_START
struct Size { struct mSize {
int width; int width;
int height; int height;
}; };
struct Rectangle { struct mRectangle {
int x; int x;
int y; int y;
int width; int width;
int height; int height;
}; };
void RectangleUnion(struct Rectangle* dst, const struct Rectangle* add); void mRectangleUnion(struct mRectangle* dst, const struct mRectangle* add);
void RectangleCenter(const struct Rectangle* ref, struct Rectangle* rect); bool mRectangleIntersection(struct mRectangle* dst, const struct mRectangle* add);
void mRectangleCenter(const struct mRectangle* ref, struct mRectangle* rect);
CXX_GUARD_END CXX_GUARD_END

326
include/mgba-util/image.h Normal file
View File

@ -0,0 +1,326 @@
/* Copyright (c) 2013-2015 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 M_IMAGE_H
#define M_IMAGE_H
#include <mgba-util/common.h>
CXX_GUARD_START
#ifdef COLOR_16_BIT
typedef uint16_t color_t;
#define BYTES_PER_PIXEL 2
#else
typedef uint32_t color_t;
#define BYTES_PER_PIXEL 4
#endif
#define M_R5(X) ((X) & 0x1F)
#define M_G5(X) (((X) >> 5) & 0x1F)
#define M_B5(X) (((X) >> 10) & 0x1F)
#define M_R8(X) ((M_R5(X) * 0x21) >> 2)
#define M_G8(X) ((M_G5(X) * 0x21) >> 2)
#define M_B8(X) ((M_B5(X) * 0x21) >> 2)
#define M_RGB5_TO_BGR8(X) ((M_R5(X) << 3) | (M_G5(X) << 11) | (M_B5(X) << 19))
#define M_RGB5_TO_RGB8(X) ((M_R5(X) << 19) | (M_G5(X) << 11) | (M_B5(X) << 3))
#define M_RGB8_TO_BGR5(X) ((((X) & 0xF8) >> 3) | (((X) & 0xF800) >> 6) | (((X) & 0xF80000) >> 9))
#define M_RGB8_TO_RGB5(X) ((((X) & 0xF8) << 7) | (((X) & 0xF800) >> 6) | (((X) & 0xF80000) >> 19))
#ifndef COLOR_16_BIT
#define M_COLOR_RED 0x000000FF
#define M_COLOR_GREEN 0x0000FF00
#define M_COLOR_BLUE 0x00FF0000
#define M_COLOR_ALPHA 0xFF000000
#define M_COLOR_WHITE 0x00FFFFFF
#define M_RGB8_TO_NATIVE(X) (((X) & 0x00FF00) | (((X) & 0x0000FF) << 16) | (((X) & 0xFF0000) >> 16))
#elif defined(COLOR_5_6_5)
#define M_COLOR_RED 0x001F
#define M_COLOR_GREEN 0x07E0
#define M_COLOR_BLUE 0xF800
#define M_COLOR_ALPHA 0x0000
#define M_COLOR_WHITE 0xFFDF
#define M_RGB8_TO_NATIVE(X) ((((X) & 0xF8) << 8) | (((X) & 0xFC00) >> 5) | (((X) & 0xF80000) >> 19))
#else
#define M_COLOR_RED 0x001F
#define M_COLOR_GREEN 0x03E0
#define M_COLOR_BLUE 0x7C00
#define M_COLOR_ALPHA 0x1000
#define M_COLOR_WHITE 0x7FFF
#define M_RGB8_TO_NATIVE(X) M_RGB8_TO_BGR5(X)
#endif
enum mColorFormat {
mCOLOR_XBGR8 = 0x00001,
mCOLOR_XRGB8 = 0x00002,
mCOLOR_BGRX8 = 0x00004,
mCOLOR_RGBX8 = 0x00008,
mCOLOR_ABGR8 = 0x00010,
mCOLOR_ARGB8 = 0x00020,
mCOLOR_BGRA8 = 0x00040,
mCOLOR_RGBA8 = 0x00080,
mCOLOR_RGB5 = 0x00100,
mCOLOR_BGR5 = 0x00200,
mCOLOR_RGB565 = 0x00400,
mCOLOR_BGR565 = 0x00800,
mCOLOR_ARGB5 = 0x01000,
mCOLOR_ABGR5 = 0x02000,
mCOLOR_RGBA5 = 0x04000,
mCOLOR_BGRA5 = 0x08000,
mCOLOR_RGB8 = 0x10000,
mCOLOR_BGR8 = 0x20000,
mCOLOR_L8 = 0x40000,
mCOLOR_PAL8 = 0x80000,
mCOLOR_ANY = -1
};
#ifndef COLOR_16_BIT
#define mCOLOR_NATIVE mCOLOR_XBGR8
#elif !defined(COLOR_5_6_5)
#define mCOLOR_NATIVE mCOLOR_BGR5
#else
#define mCOLOR_NATIVE mCOLOR_RGB565
#endif
struct mImage {
void* data;
uint32_t* palette;
unsigned width;
unsigned height;
unsigned stride;
unsigned depth;
unsigned palSize;
enum mColorFormat format;
};
struct VFile;
struct mImage* mImageCreate(unsigned width, unsigned height, enum mColorFormat format);
struct mImage* mImageCreateWithStride(unsigned width, unsigned height, unsigned stride, enum mColorFormat format);
struct mImage* mImageCreateFromConstBuffer(unsigned width, unsigned height, unsigned stride, enum mColorFormat format, const void* pixels);
struct mImage* mImageLoad(const char* path);
struct mImage* mImageLoadVF(struct VFile* vf);
struct mImage* mImageConvertToFormat(const struct mImage*, enum mColorFormat format);
void mImageDestroy(struct mImage*);
bool mImageSave(const struct mImage*, const char* path, const char* format);
bool mImageSaveVF(const struct mImage*, struct VFile* vf, const char* format);
uint32_t mImageGetPixel(const struct mImage* image, unsigned x, unsigned y);
uint32_t mImageGetPixelRaw(const struct mImage* image, unsigned x, unsigned y);
void mImageSetPixel(struct mImage* image, unsigned x, unsigned y, uint32_t color);
void mImageSetPixelRaw(struct mImage* image, unsigned x, unsigned y, uint32_t color);
void mImageSetPaletteSize(struct mImage* image, unsigned count);
void mImageSetPaletteEntry(struct mImage* image, unsigned index, uint32_t color);
void mImageBlit(struct mImage* image, const struct mImage* source, int x, int y);
void mImageComposite(struct mImage* image, const struct mImage* source, int x, int y);
void mImageCompositeWithAlpha(struct mImage* image, const struct mImage* source, int x, int y, float alpha);
uint32_t mColorConvert(uint32_t color, enum mColorFormat from, enum mColorFormat to);
uint32_t mImageColorConvert(uint32_t color, const struct mImage* from, enum mColorFormat to);
#ifndef PYCPARSE
static inline unsigned mColorFormatBytes(enum mColorFormat format) {
switch (format) {
case mCOLOR_XBGR8:
case mCOLOR_XRGB8:
case mCOLOR_BGRX8:
case mCOLOR_RGBX8:
case mCOLOR_ABGR8:
case mCOLOR_ARGB8:
case mCOLOR_BGRA8:
case mCOLOR_RGBA8:
return 4;
case mCOLOR_RGB5:
case mCOLOR_BGR5:
case mCOLOR_RGB565:
case mCOLOR_BGR565:
case mCOLOR_ARGB5:
case mCOLOR_ABGR5:
case mCOLOR_RGBA5:
case mCOLOR_BGRA5:
return 2;
case mCOLOR_RGB8:
case mCOLOR_BGR8:
return 3;
case mCOLOR_L8:
case mCOLOR_PAL8:
return 1;
case mCOLOR_ANY:
break;
}
return 0;
}
static inline bool mColorFormatHasAlpha(enum mColorFormat format) {
switch (format) {
case mCOLOR_XBGR8:
case mCOLOR_XRGB8:
case mCOLOR_BGRX8:
case mCOLOR_RGBX8:
case mCOLOR_RGB5:
case mCOLOR_BGR5:
case mCOLOR_RGB565:
case mCOLOR_BGR565:
case mCOLOR_RGB8:
case mCOLOR_BGR8:
case mCOLOR_L8:
return false;
case mCOLOR_ABGR8:
case mCOLOR_ARGB8:
case mCOLOR_BGRA8:
case mCOLOR_RGBA8:
case mCOLOR_ARGB5:
case mCOLOR_ABGR5:
case mCOLOR_RGBA5:
case mCOLOR_BGRA5:
case mCOLOR_PAL8:
return true;
case mCOLOR_ANY:
break;
}
return false;
}
static inline color_t mColorFrom555(uint16_t value) {
#ifdef COLOR_16_BIT
#ifdef COLOR_5_6_5
color_t color = 0;
color |= (value & 0x001F) << 11;
color |= (value & 0x03E0) << 1;
color |= (value & 0x7C00) >> 10;
#else
color_t color = value;
#endif
#else
color_t color = M_RGB5_TO_BGR8(value);
color |= (color >> 5) & 0x070707;
#endif
return color;
}
ATTRIBUTE_UNUSED static unsigned mColorMix5Bit(int weightA, unsigned colorA, int weightB, unsigned colorB) {
unsigned c = 0;
unsigned a, b;
#ifdef COLOR_16_BIT
#ifdef COLOR_5_6_5
a = colorA & 0xF81F;
b = colorB & 0xF81F;
a |= (colorA & 0x7C0) << 16;
b |= (colorB & 0x7C0) << 16;
c = ((a * weightA + b * weightB) / 16);
if (c & 0x08000000) {
c = (c & ~0x0FC00000) | 0x07C00000;
}
if (c & 0x0020) {
c = (c & ~0x003F) | 0x001F;
}
if (c & 0x10000) {
c = (c & ~0x1F800) | 0xF800;
}
c = (c & 0xF81F) | ((c >> 16) & 0x07C0);
#else
a = colorA & 0x7C1F;
b = colorB & 0x7C1F;
a |= (colorA & 0x3E0) << 16;
b |= (colorB & 0x3E0) << 16;
c = ((a * weightA + b * weightB) / 16);
if (c & 0x04000000) {
c = (c & ~0x07E00000) | 0x03E00000;
}
if (c & 0x0020) {
c = (c & ~0x003F) | 0x001F;
}
if (c & 0x8000) {
c = (c & ~0xF800) | 0x7C00;
}
c = (c & 0x7C1F) | ((c >> 16) & 0x03E0);
#endif
#else
a = colorA & 0xFF;
b = colorB & 0xFF;
c |= ((a * weightA + b * weightB) / 16) & 0x1FF;
if (c & 0x00000100) {
c = 0x000000FF;
}
a = colorA & 0xFF00;
b = colorB & 0xFF00;
c |= ((a * weightA + b * weightB) / 16) & 0x1FF00;
if (c & 0x00010000) {
c = (c & 0x000000FF) | 0x0000FF00;
}
a = colorA & 0xFF0000;
b = colorB & 0xFF0000;
c |= ((a * weightA + b * weightB) / 16) & 0x1FF0000;
if (c & 0x01000000) {
c = (c & 0x0000FFFF) | 0x00FF0000;
}
#endif
return c;
}
ATTRIBUTE_UNUSED static uint32_t mColorMixARGB8(uint32_t colorA, uint32_t colorB) {
uint32_t alpha = colorA >> 24;
if (!alpha) {
return colorB;
}
uint32_t color = 0;
uint32_t a, b;
a = colorA & 0xFF00FF;
a *= alpha + 1;
color += (a >> 8) & 0xFF00FF;
a = colorB & 0xFF00FF;
a *= 0x100 - alpha;
color += (a >> 8) & 0xFF00FF;
if (color & 0x100) {
color &= ~0xFF;
color |= 0xFF;
}
if (color & 0x1000000) {
color &= ~0xFF0000;
color |= 0xFF0000;
}
b = 0;
a = colorA & 0xFF00;
a *= alpha + 1;
b += a & 0xFF0000;
a = colorB & 0xFF00;
a *= 0x100 - alpha;
b += a & 0xFF0000;
if (b & 0x1000000) {
b &= ~0xFF0000;
b |= 0xFF0000;
}
color |= b >> 8;
alpha += colorB >> 24;
if (alpha > 0xFF) {
color |= 0xFF000000;
} else {
color |= alpha << 24;
}
return color;
}
#endif
CXX_GUARD_END
#endif

View File

@ -1,10 +1,10 @@
/* Copyright (c) 2013-2015 Jeffrey Pfau /* Copyright (c) 2013-2023 Jeffrey Pfau
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef EXPORT_H #ifndef M_IMAGE_EXPORT_H
#define EXPORT_H #define M_IMAGE_EXPORT_H
#include <mgba-util/common.h> #include <mgba-util/common.h>
@ -12,8 +12,8 @@ CXX_GUARD_START
struct VFile; struct VFile;
bool exportPaletteRIFF(struct VFile* vf, size_t entries, const uint16_t* colors); bool mPaletteExportRIFF(struct VFile* vf, size_t entries, const uint16_t* colors);
bool exportPaletteACT(struct VFile* vf, size_t entries, const uint16_t* colors); bool mPaletteExportACT(struct VFile* vf, size_t entries, const uint16_t* colors);
CXX_GUARD_END CXX_GUARD_END

View File

@ -12,6 +12,8 @@ CXX_GUARD_START
#ifdef USE_PNG #ifdef USE_PNG
#include <mgba-util/image.h>
// png.h defines its own version of restrict which conflicts with mGBA's. // png.h defines its own version of restrict which conflicts with mGBA's.
#ifdef restrict #ifdef restrict
#undef restrict #undef restrict
@ -25,13 +27,10 @@ enum {
}; };
png_structp PNGWriteOpen(struct VFile* source); png_structp PNGWriteOpen(struct VFile* source);
png_infop PNGWriteHeader(png_structp png, unsigned width, unsigned height); png_infop PNGWriteHeader(png_structp png, unsigned width, unsigned height, enum mColorFormat);
png_infop PNGWriteHeaderA(png_structp png, unsigned width, unsigned height); png_infop PNGWriteHeaderPalette(png_structp png, unsigned width, unsigned height, const uint32_t* palette, unsigned entries);
png_infop PNGWriteHeader8(png_structp png, unsigned width, unsigned height); bool PNGWritePixels(png_structp png, unsigned width, unsigned height, unsigned stride, const void* pixels, enum mColorFormat);
bool PNGWritePalette(png_structp png, png_infop info, const uint32_t* palette, unsigned entries); bool PNGWritePixelsPalette(png_structp png, unsigned width, unsigned height, unsigned stride, const void* pixels);
bool PNGWritePixels(png_structp png, unsigned width, unsigned height, unsigned stride, const void* pixels);
bool PNGWritePixelsA(png_structp png, unsigned width, unsigned height, unsigned stride, const void* pixels);
bool PNGWritePixels8(png_structp png, unsigned width, unsigned height, unsigned stride, const void* pixels);
bool PNGWriteCustomChunk(png_structp png, const char* name, size_t size, void* data); bool PNGWriteCustomChunk(png_structp png, const char* name, size_t size, void* data);
void PNGWriteClose(png_structp png, png_infop info); void PNGWriteClose(png_structp png, png_infop info);

View File

@ -16,13 +16,13 @@
#define _mCALL_0(FN, ...) #define _mCALL_0(FN, ...)
#define _mCALL_1(FN, A) FN(A) #define _mCALL_1(FN, A) FN(A)
#define _mCALL_2(FN, A, B) FN(A), FN(B) #define _mCALL_2(FN, A, B) FN(A), FN(B)
#define _mCALL_3(FN, A, ...) FN(A), _mCALL_2(FN, __VA_ARGS__) #define _mCALL_3(FN, A, B, C) FN(A), FN(B), FN(C)
#define _mCALL_4(FN, A, ...) FN(A), _mCALL_3(FN, __VA_ARGS__) #define _mCALL_4(FN, A, B, C, D) FN(A), FN(B), FN(C), FN(D)
#define _mCALL_5(FN, A, ...) FN(A), _mCALL_4(FN, __VA_ARGS__) #define _mCALL_5(FN, A, B, C, D, E) FN(A), FN(B), FN(C), FN(D), FN(E)
#define _mCALL_6(FN, A, ...) FN(A), _mCALL_5(FN, __VA_ARGS__) #define _mCALL_6(FN, A, B, C, D, E, F) FN(A), FN(B), FN(C), FN(D), FN(E), FN(F)
#define _mCALL_7(FN, A, ...) FN(A), _mCALL_6(FN, __VA_ARGS__) #define _mCALL_7(FN, A, B, C, D, E, F, G) FN(A), FN(B), FN(C), FN(D), FN(E), FN(F), FN(G)
#define _mCALL_8(FN, A, ...) FN(A), _mCALL_7(FN, __VA_ARGS__) #define _mCALL_8(FN, A, B, C, D, E, F, G, H) FN(A), FN(B), FN(C), FN(D), FN(E), FN(F), FN(G), FN(H)
#define _mCALL_9(FN, A, ...) FN(A), _mCALL_8(FN, __VA_ARGS__) #define _mCALL_9(FN, A, B, C, D, E, F, G, H, I) FN(A), FN(B), FN(C), FN(D), FN(E), FN(F), FN(G), FN(H), FN(I)
#define _mCOMMA_0(N, ...) N #define _mCOMMA_0(N, ...) N
#define _mCOMMA_1(N, ...) N, __VA_ARGS__ #define _mCOMMA_1(N, ...) N, __VA_ARGS__
@ -37,25 +37,25 @@
#define _mEVEN_0(...) #define _mEVEN_0(...)
#define _mEVEN_1(A, B, ...) A #define _mEVEN_1(A, B, ...) A
#define _mEVEN_2(A, B, ...) A, _mIDENT(_mEVEN_1(__VA_ARGS__)) #define _mEVEN_2(A, B, C, D, ...) A, C
#define _mEVEN_3(A, B, ...) A, _mIDENT(_mEVEN_2(__VA_ARGS__)) #define _mEVEN_3(A, B, C, D, E, F, ...) A, C, E
#define _mEVEN_4(A, B, ...) A, _mIDENT(_mEVEN_3(__VA_ARGS__)) #define _mEVEN_4(A, B, C, D, E, F, G, H, ...) A, C, E, G
#define _mEVEN_5(A, B, ...) A, _mIDENT(_mEVEN_4(__VA_ARGS__)) #define _mEVEN_5(A, B, C, D, E, F, G, H, I, J, ...) A, C, E, G, I
#define _mEVEN_6(A, B, ...) A, _mIDENT(_mEVEN_5(__VA_ARGS__)) #define _mEVEN_6(A, B, ...) A, _mIDENT(_mEVEN_5(__VA_ARGS__))
#define _mEVEN_7(A, B, ...) A, _mIDENT(_mEVEN_6(__VA_ARGS__)) #define _mEVEN_7(A, B, C, D, ...) A, C, _mIDENT(_mEVEN_5(__VA_ARGS__))
#define _mEVEN_8(A, B, ...) A, _mIDENT(_mEVEN_7(__VA_ARGS__)) #define _mEVEN_8(A, B, C, D, E, F, ...) A, C, E, _mIDENT(_mEVEN_5(__VA_ARGS__))
#define _mEVEN_9(A, B, ...) A, _mIDENT(_mEVEN_7(__VA_ARGS__)) #define _mEVEN_9(A, B, C, D, E, F, G, H, ...) A, C, E, G, _mIDENT(_mEVEN_5(__VA_ARGS__))
#define _mODD_0(...) #define _mODD_0(...)
#define _mODD_1(A, B, ...) B #define _mODD_1(A, B, ...) B
#define _mODD_2(A, B, ...) B, _mIDENT(_mODD_1(__VA_ARGS__)) #define _mODD_2(A, B, C, D, ...) B, D
#define _mODD_3(A, B, ...) B, _mIDENT(_mODD_2(__VA_ARGS__)) #define _mODD_3(A, B, C, D, E, F, ...) B, D, F
#define _mODD_4(A, B, ...) B, _mIDENT(_mODD_3(__VA_ARGS__)) #define _mODD_4(A, B, C, D, E, F, G, H, ...) B, D, F, H
#define _mODD_5(A, B, ...) B, _mIDENT(_mODD_4(__VA_ARGS__)) #define _mODD_5(A, B, C, D, E, F, G, H, I, J, ...) B, D, F, H, J
#define _mODD_6(A, B, ...) B, _mIDENT(_mODD_5(__VA_ARGS__)) #define _mODD_6(A, B, ...) B, _mIDENT(_mODD_5(__VA_ARGS__))
#define _mODD_7(A, B, ...) B, _mIDENT(_mODD_6(__VA_ARGS__)) #define _mODD_7(A, B, C, D, ...) B, D, _mIDENT(_mODD_5(__VA_ARGS__))
#define _mODD_8(A, B, ...) B, _mIDENT(_mODD_7(__VA_ARGS__)) #define _mODD_8(A, B, C, D, E, F, ...) B, D, F, _mIDENT(_mODD_5(__VA_ARGS__))
#define _mODD_9(A, B, ...) B, _mIDENT(_mODD_7(__VA_ARGS__)) #define _mODD_9(A, B, C, D, E, F, G, H, ...) B, D, F, H, _mIDENT(_mODD_5(__VA_ARGS__))
#define _mIF0_0(...) __VA_ARGS__ #define _mIF0_0(...) __VA_ARGS__
#define _mIF0_1(...) #define _mIF0_1(...)

View File

@ -33,6 +33,7 @@ struct mCoreOptions {
int frameskip; int frameskip;
bool rewindEnable; bool rewindEnable;
int rewindBufferCapacity; int rewindBufferCapacity;
int rewindBufferInterval;
float fpsTarget; float fpsTarget;
size_t audioBuffers; size_t audioBuffers;
unsigned sampleRate; unsigned sampleRate;

View File

@ -10,165 +10,14 @@
CXX_GUARD_START CXX_GUARD_START
#include <mgba-util/image.h>
#include <mgba-util/vector.h> #include <mgba-util/vector.h>
struct mCore; struct mCore;
struct mStateExtdataItem; struct mStateExtdataItem;
#ifdef COLOR_16_BIT
typedef uint16_t color_t;
#define BYTES_PER_PIXEL 2
#else
typedef uint32_t color_t;
#define BYTES_PER_PIXEL 4
#endif
#define M_R5(X) ((X) & 0x1F)
#define M_G5(X) (((X) >> 5) & 0x1F)
#define M_B5(X) (((X) >> 10) & 0x1F)
#define M_R8(X) (((((X) << 3) & 0xF8) * 0x21) >> 2)
#define M_G8(X) (((((X) >> 2) & 0xF8) * 0x21) >> 2)
#define M_B8(X) (((((X) >> 7) & 0xF8) * 0x21) >> 2)
#define M_RGB5_TO_BGR8(X) ((M_R5(X) << 3) | (M_G5(X) << 11) | (M_B5(X) << 19))
#define M_RGB5_TO_RGB8(X) ((M_R5(X) << 19) | (M_G5(X) << 11) | (M_B5(X) << 3))
#define M_RGB8_TO_BGR5(X) ((((X) & 0xF8) >> 3) | (((X) & 0xF800) >> 6) | (((X) & 0xF80000) >> 9))
#define M_RGB8_TO_RGB5(X) ((((X) & 0xF8) << 7) | (((X) & 0xF800) >> 6) | (((X) & 0xF80000) >> 19))
#ifndef COLOR_16_BIT
#define M_COLOR_RED 0x000000FF
#define M_COLOR_GREEN 0x0000FF00
#define M_COLOR_BLUE 0x00FF0000
#define M_COLOR_ALPHA 0xFF000000
#define M_COLOR_WHITE 0x00FFFFFF
#define M_RGB8_TO_NATIVE(X) (((X) & 0x00FF00) | (((X) & 0x0000FF) << 16) | (((X) & 0xFF0000) >> 16))
#elif defined(COLOR_5_6_5)
#define M_COLOR_RED 0x001F
#define M_COLOR_GREEN 0x07E0
#define M_COLOR_BLUE 0xF800
#define M_COLOR_ALPHA 0x0000
#define M_COLOR_WHITE 0xFFDF
#define M_RGB8_TO_NATIVE(X) ((((X) & 0xF8) << 8) | (((X) & 0xFC00) >> 5) | (((X) & 0xF80000) >> 19))
#else
#define M_COLOR_RED 0x001F
#define M_COLOR_GREEN 0x03E0
#define M_COLOR_BLUE 0x7C00
#define M_COLOR_ALPHA 0x1000
#define M_COLOR_WHITE 0x7FFF
#define M_RGB8_TO_NATIVE(X) M_RGB8_TO_BGR5(X)
#endif
#ifndef PYCPARSE
static inline color_t mColorFrom555(uint16_t value) {
#ifdef COLOR_16_BIT
#ifdef COLOR_5_6_5
color_t color = 0;
color |= (value & 0x001F) << 11;
color |= (value & 0x03E0) << 1;
color |= (value & 0x7C00) >> 10;
#else
color_t color = value;
#endif
#else
color_t color = M_RGB5_TO_BGR8(value);
color |= (color >> 5) & 0x070707;
#endif
return color;
}
ATTRIBUTE_UNUSED static unsigned mColorMix5Bit(int weightA, unsigned colorA, int weightB, unsigned colorB) {
unsigned c = 0;
unsigned a, b;
#ifdef COLOR_16_BIT
#ifdef COLOR_5_6_5
a = colorA & 0xF81F;
b = colorB & 0xF81F;
a |= (colorA & 0x7C0) << 16;
b |= (colorB & 0x7C0) << 16;
c = ((a * weightA + b * weightB) / 16);
if (c & 0x08000000) {
c = (c & ~0x0FC00000) | 0x07C00000;
}
if (c & 0x0020) {
c = (c & ~0x003F) | 0x001F;
}
if (c & 0x10000) {
c = (c & ~0x1F800) | 0xF800;
}
c = (c & 0xF81F) | ((c >> 16) & 0x07C0);
#else
a = colorA & 0x7C1F;
b = colorB & 0x7C1F;
a |= (colorA & 0x3E0) << 16;
b |= (colorB & 0x3E0) << 16;
c = ((a * weightA + b * weightB) / 16);
if (c & 0x04000000) {
c = (c & ~0x07E00000) | 0x03E00000;
}
if (c & 0x0020) {
c = (c & ~0x003F) | 0x001F;
}
if (c & 0x8000) {
c = (c & ~0xF800) | 0x7C00;
}
c = (c & 0x7C1F) | ((c >> 16) & 0x03E0);
#endif
#else
a = colorA & 0xFF;
b = colorB & 0xFF;
c |= ((a * weightA + b * weightB) / 16) & 0x1FF;
if (c & 0x00000100) {
c = 0x000000FF;
}
a = colorA & 0xFF00;
b = colorB & 0xFF00;
c |= ((a * weightA + b * weightB) / 16) & 0x1FF00;
if (c & 0x00010000) {
c = (c & 0x000000FF) | 0x0000FF00;
}
a = colorA & 0xFF0000;
b = colorB & 0xFF0000;
c |= ((a * weightA + b * weightB) / 16) & 0x1FF0000;
if (c & 0x01000000) {
c = (c & 0x0000FFFF) | 0x00FF0000;
}
#endif
return c;
}
#endif
struct blip_t; struct blip_t;
enum mColorFormat {
mCOLOR_XBGR8 = 0x00001,
mCOLOR_XRGB8 = 0x00002,
mCOLOR_BGRX8 = 0x00004,
mCOLOR_RGBX8 = 0x00008,
mCOLOR_ABGR8 = 0x00010,
mCOLOR_ARGB8 = 0x00020,
mCOLOR_BGRA8 = 0x00040,
mCOLOR_RGBA8 = 0x00080,
mCOLOR_RGB5 = 0x00100,
mCOLOR_BGR5 = 0x00200,
mCOLOR_RGB565 = 0x00400,
mCOLOR_BGR565 = 0x00800,
mCOLOR_ARGB5 = 0x01000,
mCOLOR_ABGR5 = 0x02000,
mCOLOR_RGBA5 = 0x04000,
mCOLOR_BGRA5 = 0x08000,
mCOLOR_RGB8 = 0x10000,
mCOLOR_BGR8 = 0x20000,
mCOLOR_L8 = 0x40000,
mCOLOR_ANY = -1
};
enum mCoreFeature { enum mCoreFeature {
mCORE_FEATURE_OPENGL = 1, mCORE_FEATURE_OPENGL = 1,
}; };

View File

@ -24,6 +24,7 @@ struct mCoreRewindContext {
size_t size; size_t size;
struct VFile* previousState; struct VFile* previousState;
struct VFile* currentState; struct VFile* currentState;
int rewindFrameCounter;
#ifndef DISABLE_THREADING #ifndef DISABLE_THREADING
bool onThread; bool onThread;

View File

@ -30,8 +30,8 @@ enum VideoLayer {
struct VideoBackend { struct VideoBackend {
void (*init)(struct VideoBackend*, WHandle handle); void (*init)(struct VideoBackend*, WHandle handle);
void (*deinit)(struct VideoBackend*); void (*deinit)(struct VideoBackend*);
void (*setLayerDimensions)(struct VideoBackend*, enum VideoLayer, const struct Rectangle*); void (*setLayerDimensions)(struct VideoBackend*, enum VideoLayer, const struct mRectangle*);
void (*layerDimensions)(const struct VideoBackend*, enum VideoLayer, struct Rectangle*); void (*layerDimensions)(const struct VideoBackend*, enum VideoLayer, struct mRectangle*);
void (*swap)(struct VideoBackend*); void (*swap)(struct VideoBackend*);
void (*clear)(struct VideoBackend*); void (*clear)(struct VideoBackend*);
void (*contextResized)(struct VideoBackend*, unsigned w, unsigned h); void (*contextResized)(struct VideoBackend*, unsigned w, unsigned h);
@ -58,7 +58,7 @@ struct VideoShader {
size_t nPasses; size_t nPasses;
}; };
void VideoBackendGetFrame(const struct VideoBackend*, struct Rectangle* frame); void VideoBackendGetFrame(const struct VideoBackend*, struct mRectangle* frame);
void VideoBackendGetFrameSize(const struct VideoBackend*, unsigned* width, unsigned* height); void VideoBackendGetFrameSize(const struct VideoBackend*, unsigned* width, unsigned* height);
CXX_GUARD_END CXX_GUARD_END

View File

@ -14,7 +14,7 @@ CXX_GUARD_START
#include <mgba-util/socket.h> #include <mgba-util/socket.h>
#define GDB_STUB_MAX_LINE 1200 #define GDB_STUB_MAX_LINE 1400
#define GDB_STUB_INTERVAL 32 #define GDB_STUB_INTERVAL 32
enum GDBStubAckState { enum GDBStubAckState {

View File

@ -419,6 +419,9 @@ struct GBSerializedState {
uint8_t locked; uint8_t locked;
uint8_t bank0; uint8_t bank0;
} mmm01; } mmm01;
struct {
uint8_t registersActive;
} pocketCam;
struct { struct {
uint64_t lastLatch; uint64_t lastLatch;
uint8_t reg; uint8_t reg;
@ -484,6 +487,7 @@ struct GBSerializedState {
union { union {
uint8_t huc3Registers[0x80]; uint8_t huc3Registers[0x80];
uint8_t pocketCamRegisters[0x36];
struct { struct {
uint8_t registers[4]; uint8_t registers[4];
uint8_t reserved[4]; uint8_t reserved[4];

16
include/mgba/script.h Normal file
View File

@ -0,0 +1,16 @@
/* 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 M_SCRIPT_H
#define M_SCRIPT_H
#include <mgba/script/base.h>
#include <mgba/script/context.h>
#include <mgba/script/input.h>
#include <mgba/script/macros.h>
#include <mgba/script/storage.h>
#include <mgba/script/types.h>
#endif

View File

@ -0,0 +1,25 @@
/* 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 M_SCRIPT_BASE_H
#define M_SCRIPT_BASE_H
#include <mgba-util/common.h>
CXX_GUARD_START
#include <mgba/script/macros.h>
#include <mgba-util/image.h>
mSCRIPT_DECLARE_STRUCT(mImage)
struct mScriptContext;
void mScriptContextAttachImage(struct mScriptContext* context);
void mScriptContextAttachStdlib(struct mScriptContext* context);
void mScriptContextAttachSocket(struct mScriptContext* context);
CXX_GUARD_END
#endif

View File

@ -91,8 +91,6 @@ struct mScriptValue* mScriptContextAccessWeakref(struct mScriptContext*, struct
void mScriptContextClearWeakref(struct mScriptContext*, uint32_t weakref); void mScriptContextClearWeakref(struct mScriptContext*, uint32_t weakref);
void mScriptContextDisownWeakref(struct mScriptContext*, uint32_t weakref); void mScriptContextDisownWeakref(struct mScriptContext*, uint32_t weakref);
void mScriptContextAttachStdlib(struct mScriptContext* context);
void mScriptContextAttachSocket(struct mScriptContext* context);
void mScriptContextExportConstants(struct mScriptContext* context, const char* nspace, struct mScriptKVPair* constants); void mScriptContextExportConstants(struct mScriptContext* context, const char* nspace, struct mScriptKVPair* constants);
void mScriptContextExportNamespace(struct mScriptContext* context, const char* nspace, struct mScriptKVPair* value); void mScriptContextExportNamespace(struct mScriptContext* context, const char* nspace, struct mScriptKVPair* value);

View File

@ -212,20 +212,30 @@ CXX_GUARD_START
} \ } \
}, },
#define mSCRIPT_DEFINE_STRUCT_MEMBER_NAMED(STRUCT, TYPE, EXPORTED_NAME, NAME) { \ #define _mSCRIPT_DEFINE_STRUCT_MEMBER(STRUCT, TYPE, EXPORTED_NAME, NAME, RO) { \
.type = mSCRIPT_CLASS_INIT_INSTANCE_MEMBER, \ .type = mSCRIPT_CLASS_INIT_INSTANCE_MEMBER, \
.info = { \ .info = { \
.member = { \ .member = { \
.name = #EXPORTED_NAME, \ .name = #EXPORTED_NAME, \
.type = mSCRIPT_TYPE_MS_ ## TYPE, \ .type = mSCRIPT_TYPE_MS_ ## TYPE, \
.offset = offsetof(struct STRUCT, NAME) \ .offset = offsetof(struct STRUCT, NAME), \
.readonly = RO \
} \ } \
} \ } \
}, },
#define mSCRIPT_DEFINE_STRUCT_MEMBER_NAMED(STRUCT, TYPE, EXPORTED_NAME, NAME) \
_mSCRIPT_DEFINE_STRUCT_MEMBER(STRUCT, TYPE, EXPORTED_NAME, NAME, false)
#define mSCRIPT_DEFINE_STRUCT_CONST_MEMBER_NAMED(STRUCT, TYPE, EXPORTED_NAME, NAME) \
_mSCRIPT_DEFINE_STRUCT_MEMBER(STRUCT, TYPE, EXPORTED_NAME, NAME, true)
#define mSCRIPT_DEFINE_STRUCT_MEMBER(STRUCT, TYPE, NAME) \ #define mSCRIPT_DEFINE_STRUCT_MEMBER(STRUCT, TYPE, NAME) \
mSCRIPT_DEFINE_STRUCT_MEMBER_NAMED(STRUCT, TYPE, NAME, NAME) mSCRIPT_DEFINE_STRUCT_MEMBER_NAMED(STRUCT, TYPE, NAME, NAME)
#define mSCRIPT_DEFINE_STRUCT_CONST_MEMBER(STRUCT, TYPE, NAME) \
mSCRIPT_DEFINE_STRUCT_CONST_MEMBER_NAMED(STRUCT, TYPE, NAME, NAME)
#define mSCRIPT_DEFINE_INHERIT(PARENT) { \ #define mSCRIPT_DEFINE_INHERIT(PARENT) { \
.type = mSCRIPT_CLASS_INIT_INHERIT, \ .type = mSCRIPT_CLASS_INIT_INHERIT, \
.info = { \ .info = { \
@ -393,6 +403,9 @@ CXX_GUARD_START
static const struct mScriptValue _mSTStructBindingDefaults_doc_ ## TYPE ## _ ## NAME[mSCRIPT_PARAMS_MAX]; \ static const struct mScriptValue _mSTStructBindingDefaults_doc_ ## TYPE ## _ ## NAME[mSCRIPT_PARAMS_MAX]; \
_mSCRIPT_DECLARE_DOC_STRUCT_METHOD(SCOPE, TYPE, NAME, S, 0, 0, NPARAMS, _mIDENT(_mSTStructBindingDefaults_doc_ ## TYPE ## _ ## NAME), __VA_ARGS__) \ _mSCRIPT_DECLARE_DOC_STRUCT_METHOD(SCOPE, TYPE, NAME, S, 0, 0, NPARAMS, _mIDENT(_mSTStructBindingDefaults_doc_ ## TYPE ## _ ## NAME), __VA_ARGS__) \
#define mSCRIPT_DEFINE_FUNCTION_BINDING_DEFAULTS(NAME) \
static const struct mScriptValue _bindingDefaults_ ## NAME[mSCRIPT_PARAMS_MAX] = {
#define mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(TYPE, NAME) \ #define mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(TYPE, NAME) \
static const struct mScriptValue _mSTStructBindingDefaults_ ## TYPE ## _ ## NAME[mSCRIPT_PARAMS_MAX] = { \ static const struct mScriptValue _mSTStructBindingDefaults_ ## TYPE ## _ ## NAME[mSCRIPT_PARAMS_MAX] = { \
mSCRIPT_NO_DEFAULT, mSCRIPT_NO_DEFAULT,
@ -439,7 +452,7 @@ CXX_GUARD_START
#define mSCRIPT_DEFINE_END { .type = mSCRIPT_CLASS_INIT_END } } } #define mSCRIPT_DEFINE_END { .type = mSCRIPT_CLASS_INIT_END } } }
#define _mSCRIPT_BIND_FUNCTION(NAME, NRET, RETURN, NPARAMS, ...) \ #define _mSCRIPT_BIND_FUNCTION(NAME, NRET, RETURN, DEFAULTS, NPARAMS, ...) \
static struct mScriptFunction _function_ ## NAME = { \ static struct mScriptFunction _function_ ## NAME = { \
.call = _binding_ ## NAME \ .call = _binding_ ## NAME \
}; \ }; \
@ -456,6 +469,7 @@ CXX_GUARD_START
.count = NPARAMS, \ .count = NPARAMS, \
.entries = { _mCALL(_mIF0_ ## NPARAMS, 0) _mCALL(mSCRIPT_PREFIX_ ## NPARAMS, mSCRIPT_TYPE_MS_, _mEVEN_ ## NPARAMS(__VA_ARGS__)) }, \ .entries = { _mCALL(_mIF0_ ## NPARAMS, 0) _mCALL(mSCRIPT_PREFIX_ ## NPARAMS, mSCRIPT_TYPE_MS_, _mEVEN_ ## NPARAMS(__VA_ARGS__)) }, \
.names = { _mCALL(_mIF0_ ## NPARAMS, 0) _mCALL(_mCALL_ ## NPARAMS, _mSTRINGIFY, _mODD_ ## NPARAMS(__VA_ARGS__)) }, \ .names = { _mCALL(_mIF0_ ## NPARAMS, 0) _mCALL(_mCALL_ ## NPARAMS, _mSTRINGIFY, _mODD_ ## NPARAMS(__VA_ARGS__)) }, \
.defaults = DEFAULTS, \
}, \ }, \
.returnType = { \ .returnType = { \
.count = NRET, \ .count = NRET, \
@ -472,7 +486,7 @@ CXX_GUARD_START
} \ } \
} }
#define mSCRIPT_BIND_FUNCTION(NAME, RETURN, FUNCTION, NPARAMS, ...) \ #define _mSCRIPT_BIND_N_FUNCTION(NAME, RETURN, FUNCTION, DEFAULTS, NPARAMS, ...) \
static bool _binding_ ## NAME(struct mScriptFrame* frame, void* ctx) { \ static bool _binding_ ## NAME(struct mScriptFrame* frame, void* ctx) { \
UNUSED(ctx); \ UNUSED(ctx); \
_mCALL(mSCRIPT_POP_ ## NPARAMS, &frame->arguments, _mEVEN_ ## NPARAMS(__VA_ARGS__)); \ _mCALL(mSCRIPT_POP_ ## NPARAMS, &frame->arguments, _mEVEN_ ## NPARAMS(__VA_ARGS__)); \
@ -482,9 +496,9 @@ CXX_GUARD_START
_mSCRIPT_CALL(RETURN, FUNCTION, NPARAMS); \ _mSCRIPT_CALL(RETURN, FUNCTION, NPARAMS); \
return true; \ return true; \
} \ } \
_mSCRIPT_BIND_FUNCTION(NAME, 1, mSCRIPT_TYPE_MS_ ## RETURN, NPARAMS, __VA_ARGS__) _mSCRIPT_BIND_FUNCTION(NAME, 1, mSCRIPT_TYPE_MS_ ## RETURN, DEFAULTS, NPARAMS, __VA_ARGS__)
#define mSCRIPT_BIND_VOID_FUNCTION(NAME, FUNCTION, NPARAMS, ...) \ #define _mSCRIPT_BIND_VOID_FUNCTION(NAME, FUNCTION, DEFAULTS, NPARAMS, ...) \
static bool _binding_ ## NAME(struct mScriptFrame* frame, void* ctx) { \ static bool _binding_ ## NAME(struct mScriptFrame* frame, void* ctx) { \
UNUSED(ctx); \ UNUSED(ctx); \
_mCALL(mSCRIPT_POP_ ## NPARAMS, &frame->arguments, _mEVEN_ ## NPARAMS(__VA_ARGS__)); \ _mCALL(mSCRIPT_POP_ ## NPARAMS, &frame->arguments, _mEVEN_ ## NPARAMS(__VA_ARGS__)); \
@ -494,7 +508,21 @@ CXX_GUARD_START
_mSCRIPT_CALL_VOID(FUNCTION, NPARAMS); \ _mSCRIPT_CALL_VOID(FUNCTION, NPARAMS); \
return true; \ return true; \
} \ } \
_mSCRIPT_BIND_FUNCTION(NAME, 0, 0, NPARAMS, __VA_ARGS__) _mSCRIPT_BIND_FUNCTION(NAME, 0, 0, NULL, NPARAMS, __VA_ARGS__)
#define mSCRIPT_BIND_FUNCTION(NAME, RETURN, FUNCTION, NPARAMS, ...) \
_mSCRIPT_BIND_N_FUNCTION(NAME, RETURN, FUNCTION, NULL, NPARAMS, __VA_ARGS__)
#define mSCRIPT_BIND_VOID_FUNCTION(NAME, FUNCTION, NPARAMS, ...) \
_mSCRIPT_BIND_VOID_FUNCTION(NAME, FUNCTION, NULL, NPARAMS, __VA_ARGS__)
#define mSCRIPT_BIND_FUNCTION_WITH_DEFAULTS(NAME, RETURN, FUNCTION, NPARAMS, ...) \
static const struct mScriptValue _bindingDefaults_ ## NAME[mSCRIPT_PARAMS_MAX]; \
_mSCRIPT_BIND_N_FUNCTION(NAME, RETURN, FUNCTION, _mIDENT(_bindingDefaults_ ## NAME), NPARAMS, __VA_ARGS__)
#define mSCRIPT_BIND_VOID_FUNCTION_WITH_DEFAULTS(NAME, FUNCTION, NPARAMS, ...) \
static const struct mScriptValue _bindingDefaults_ ## _ ## NAME[mSCRIPT_PARAMS_MAX]; \
_mSCRIPT_BIND_VOID_FUNCTION(NAME, FUNCTION, _mIDENT(_bindingDefaults_ ## NAME), NPARAMS, __VA_ARGS__)
#define _mSCRIPT_DEFINE_DOC_FUNCTION(SCOPE, NAME, NRET, RETURN, NPARAMS, ...) \ #define _mSCRIPT_DEFINE_DOC_FUNCTION(SCOPE, NAME, NRET, RETURN, NPARAMS, ...) \
static const struct mScriptType _mScriptDocType_ ## NAME = { \ static const struct mScriptType _mScriptDocType_ ## NAME = { \

View File

@ -164,7 +164,8 @@ enum mScriptClassInitType {
}; };
enum { enum {
mSCRIPT_VALUE_FLAG_FREE_BUFFER = 1 mSCRIPT_VALUE_FLAG_FREE_BUFFER = 1,
mSCRIPT_VALUE_FLAG_DEINIT = 2,
}; };
struct mScriptType; struct mScriptType;
@ -231,6 +232,7 @@ struct mScriptClassMember {
const char* docstring; const char* docstring;
const struct mScriptType* type; const struct mScriptType* type;
size_t offset; size_t offset;
bool readonly;
}; };
struct mScriptClassCastMember { struct mScriptClassCastMember {

View File

@ -32,6 +32,8 @@
<string>${MACOSX_BUNDLE_COPYRIGHT}</string> <string>${MACOSX_BUNDLE_COPYRIGHT}</string>
<key>NSSupportsAutomaticGraphicsSwitching</key> <key>NSSupportsAutomaticGraphicsSwitching</key>
<true/> <true/>
<key>NSCameraUsageDescription</key>
<string>Only used when Game Boy Camera is selected and a physical camera is set</string>
<key>CFBundleDocumentTypes</key> <key>CFBundleDocumentTypes</key>
<array> <array>
<dict> <dict>

File diff suppressed because it is too large Load Diff

View File

@ -8,9 +8,8 @@ gocha
Jaime J. Denizard Jaime J. Denizard
MichaelK__ MichaelK__
Miras Absar Miras Absar
Nic Losby
Petru-Sebastian Toader Petru-Sebastian Toader
Stevoisiak Stevoisiak
Tyler Jenkins
William K. Leung William K. Leung
Zach
Zhongchao Qian Zhongchao Qian

View File

@ -0,0 +1,77 @@
local state = {}
state.period = 4
state.phase = 0
state.x = 0
state.y = 0
function state.update()
state.phase = state.phase + 1
if state.phase == state.period then
state.phase = 0
end
if state.phase == 0 then
if input.activeGamepad then
local x = input.activeGamepad.axes[1] / 30000
local y = input.activeGamepad.axes[2] / 30000
-- Map the circle onto a square, since we don't
-- want to have a duty of 1/sqrt(2) on the angles
local theta = math.atan(y, x)
local r = math.sqrt(x * x + y * y)
if theta < math.pi * -3 / 4 then
r = -r / math.cos(theta)
elseif theta < math.pi * -1 / 4 then
r = -r / math.sin(theta)
elseif theta < math.pi * 1 / 4 then
r = r / math.cos(theta)
elseif theta < math.pi * 3 / 4 then
r = r / math.sin(theta)
elseif theta < math.pi * 5 / 4 then
r = -r / math.cos(theta)
end
state.x = math.cos(theta) * r
state.y = math.sin(theta) * r
else
state.x = 0
state.y = 0
end
end
end
function state.read()
emu:clearKeys(0xF0)
if math.floor(math.abs(state.x) * state.period) > state.phase then
if state.x > 0 then
emu:addKey(C.GB_KEY.RIGHT)
else
emu:addKey(C.GB_KEY.LEFT)
end
end
if math.floor(math.abs(state.y) * state.period) > state.phase then
if state.y > 0 then
emu:addKey(C.GB_KEY.DOWN)
else
emu:addKey(C.GB_KEY.UP)
end
end
-- The duty cycle approach can confuse menus and the like,
-- so the POV hat setting should force a direction on
if input.activeGamepad and #input.activeGamepad.hats > 0 then
local hat = input.activeGamepad.hats[1]
if hat & C.INPUT_DIR.UP ~= 0 then
emu:addKey(C.GB_KEY.UP)
end
if hat & C.INPUT_DIR.DOWN ~= 0 then
emu:addKey(C.GB_KEY.DOWN)
end
if hat & C.INPUT_DIR.LEFT ~= 0 then
emu:addKey(C.GB_KEY.LEFT)
end
if hat & C.INPUT_DIR.RIGHT ~= 0 then
emu:addKey(C.GB_KEY.RIGHT)
end
end
end
callbacks:add("frame", state.update)
callbacks:add("keysRead", state.read)

View File

@ -174,7 +174,7 @@ function Generation1En._readPartyMon(game, address, nameAddress, otAddress)
return mon return mon
end end
function Generation2En._readBoxMon(game, address, nameAddress, otAddress) function Generation2En._readBoxMon(game, address, nameAddress, otAddress)
local mon = {} local mon = {}
mon.species = emu:read8(address + 0) mon.species = emu:read8(address + 0)
mon.item = emu:read8(address + 1) mon.item = emu:read8(address + 1)

View File

@ -34,12 +34,15 @@ void mCacheSetDeinit(struct mCacheSet* cache) {
for (i = 0; i < mMapCacheSetSize(&cache->maps); ++i) { for (i = 0; i < mMapCacheSetSize(&cache->maps); ++i) {
mMapCacheDeinit(mMapCacheSetGetPointer(&cache->maps, i)); mMapCacheDeinit(mMapCacheSetGetPointer(&cache->maps, i));
} }
mMapCacheSetDeinit(&cache->maps);
for (i = 0; i < mBitmapCacheSetSize(&cache->bitmaps); ++i) { for (i = 0; i < mBitmapCacheSetSize(&cache->bitmaps); ++i) {
mBitmapCacheDeinit(mBitmapCacheSetGetPointer(&cache->bitmaps, i)); mBitmapCacheDeinit(mBitmapCacheSetGetPointer(&cache->bitmaps, i));
} }
mBitmapCacheSetDeinit(&cache->bitmaps);
for (i = 0; i < mTileCacheSetSize(&cache->tiles); ++i) { for (i = 0; i < mTileCacheSetSize(&cache->tiles); ++i) {
mTileCacheDeinit(mTileCacheSetGetPointer(&cache->tiles, i)); mTileCacheDeinit(mTileCacheSetGetPointer(&cache->tiles, i));
} }
mTileCacheSetDeinit(&cache->tiles);
} }
void mCacheSetAssignVRAM(struct mCacheSet* cache, void* vram) { void mCacheSetAssignVRAM(struct mCacheSet* cache, void* vram) {

View File

@ -279,8 +279,7 @@ void mCoreConfigDirectory(char* out, size_t outLength) {
void mCoreConfigPortablePath(char* out, size_t outLength) { void mCoreConfigPortablePath(char* out, size_t outLength) {
#ifdef _WIN32 #ifdef _WIN32
wchar_t wpath[MAX_PATH]; wchar_t wpath[MAX_PATH];
HMODULE hModule = GetModuleHandleW(NULL); GetModuleFileNameW(NULL, wpath, MAX_PATH);
GetModuleFileNameW(hModule, wpath, MAX_PATH);
PathRemoveFileSpecW(wpath); PathRemoveFileSpecW(wpath);
if (PATH_SEP[0] != '\\') { if (PATH_SEP[0] != '\\') {
WCHAR* pathSep; WCHAR* pathSep;
@ -407,6 +406,7 @@ void mCoreConfigMap(const struct mCoreConfig* config, struct mCoreOptions* opts)
_lookupIntValue(config, "frameskip", &opts->frameskip); _lookupIntValue(config, "frameskip", &opts->frameskip);
_lookupIntValue(config, "volume", &opts->volume); _lookupIntValue(config, "volume", &opts->volume);
_lookupIntValue(config, "rewindBufferCapacity", &opts->rewindBufferCapacity); _lookupIntValue(config, "rewindBufferCapacity", &opts->rewindBufferCapacity);
_lookupIntValue(config, "rewindBufferInterval", &opts->rewindBufferInterval);
_lookupFloatValue(config, "fpsTarget", &opts->fpsTarget); _lookupFloatValue(config, "fpsTarget", &opts->fpsTarget);
unsigned audioBuffers; unsigned audioBuffers;
if (_lookupUIntValue(config, "audioBuffers", &audioBuffers)) { if (_lookupUIntValue(config, "audioBuffers", &audioBuffers)) {
@ -449,6 +449,7 @@ void mCoreConfigLoadDefaults(struct mCoreConfig* config, const struct mCoreOptio
ConfigurationSetIntValue(&config->defaultsTable, 0, "frameskip", opts->frameskip); ConfigurationSetIntValue(&config->defaultsTable, 0, "frameskip", opts->frameskip);
ConfigurationSetIntValue(&config->defaultsTable, 0, "rewindEnable", opts->rewindEnable); ConfigurationSetIntValue(&config->defaultsTable, 0, "rewindEnable", opts->rewindEnable);
ConfigurationSetIntValue(&config->defaultsTable, 0, "rewindBufferCapacity", opts->rewindBufferCapacity); ConfigurationSetIntValue(&config->defaultsTable, 0, "rewindBufferCapacity", opts->rewindBufferCapacity);
ConfigurationSetIntValue(&config->defaultsTable, 0, "rewindBufferInterval", opts->rewindBufferInterval);
ConfigurationSetFloatValue(&config->defaultsTable, 0, "fpsTarget", opts->fpsTarget); ConfigurationSetFloatValue(&config->defaultsTable, 0, "fpsTarget", opts->fpsTarget);
ConfigurationSetUIntValue(&config->defaultsTable, 0, "audioBuffers", opts->audioBuffers); ConfigurationSetUIntValue(&config->defaultsTable, 0, "audioBuffers", opts->audioBuffers);
ConfigurationSetUIntValue(&config->defaultsTable, 0, "sampleRate", opts->sampleRate); ConfigurationSetUIntValue(&config->defaultsTable, 0, "sampleRate", opts->sampleRate);

View File

@ -94,7 +94,7 @@ struct mCore* mCoreCreate(enum mPlatform platform) {
} }
#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2 #if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2
#include <mgba-util/png-io.h> #include <mgba-util/image/png-io.h>
#ifdef PSP2 #ifdef PSP2
#include <psp2/photoexport.h> #include <psp2/photoexport.h>
@ -377,8 +377,8 @@ bool mCoreTakeScreenshotVF(struct mCore* core, struct VFile* vf) {
core->currentVideoSize(core, &width, &height); core->currentVideoSize(core, &width, &height);
core->getPixels(core, &pixels, &stride); core->getPixels(core, &pixels, &stride);
png_structp png = PNGWriteOpen(vf); png_structp png = PNGWriteOpen(vf);
png_infop info = PNGWriteHeader(png, width, height); png_infop info = PNGWriteHeader(png, width, height, mCOLOR_NATIVE);
bool success = PNGWritePixels(png, width, height, stride, pixels); bool success = PNGWritePixels(png, width, height, stride, pixels, mCOLOR_NATIVE);
PNGWriteClose(png, info); PNGWriteClose(png, info);
return success; return success;
#else #else

View File

@ -30,6 +30,7 @@ void mCoreRewindContextInit(struct mCoreRewindContext* context, size_t entries,
context->previousState = VFileMemChunk(0, 0); context->previousState = VFileMemChunk(0, 0);
context->currentState = VFileMemChunk(0, 0); context->currentState = VFileMemChunk(0, 0);
context->size = 0; context->size = 0;
context->rewindFrameCounter = 0;
#ifndef DISABLE_THREADING #ifndef DISABLE_THREADING
context->onThread = onThread; context->onThread = onThread;
context->ready = false; context->ready = false;

View File

@ -7,6 +7,7 @@
#include <mgba/core/core.h> #include <mgba/core/core.h>
#include <mgba/core/serialize.h> #include <mgba/core/serialize.h>
#include <mgba/script/base.h>
#include <mgba/script/context.h> #include <mgba/script/context.h>
#include <mgba-util/table.h> #include <mgba-util/table.h>
#include <mgba-util/vfs.h> #include <mgba-util/vfs.h>
@ -399,6 +400,7 @@ static int _mScriptCoreLoadStateFile(struct mCore* core, const char* path, int f
vf->close(vf); vf->close(vf);
return ok; return ok;
} }
static void _mScriptCoreTakeScreenshot(struct mCore* core, const char* filename) { static void _mScriptCoreTakeScreenshot(struct mCore* core, const char* filename) {
if (filename) { if (filename) {
struct VFile* vf = VFileOpen(filename, O_WRONLY | O_CREAT | O_TRUNC); struct VFile* vf = VFileOpen(filename, O_WRONLY | O_CREAT | O_TRUNC);
@ -412,6 +414,29 @@ static void _mScriptCoreTakeScreenshot(struct mCore* core, const char* filename)
} }
} }
static struct mScriptValue* _mScriptCoreTakeScreenshotToImage(struct mCore* core) {
size_t stride;
const void* pixels = 0;
unsigned width, height;
core->currentVideoSize(core, &width, &height);
core->getPixels(core, &pixels, &stride);
if (!pixels) {
return NULL;
}
#ifndef COLOR_16_BIT
struct mImage* image = mImageCreateFromConstBuffer(width, height, stride, mCOLOR_XBGR8, pixels);
#elif COLOR_5_6_5
struct mImage* image = mImageCreateFromConstBuffer(width, height, stride, mCOLOR_RGB565, pixels);
#else
struct mImage* image = mImageCreateFromConstBuffer(width, height, stride, mCOLOR_BGR5, pixels);
#endif
struct mScriptValue* result = mScriptValueAlloc(mSCRIPT_TYPE_MS_S(mImage));
result->value.opaque = image;
result->flags = mSCRIPT_VALUE_FLAG_DEINIT;
return result;
}
// Loading functions // Loading functions
mSCRIPT_DECLARE_STRUCT_METHOD(mCore, BOOL, loadFile, mCoreLoadFile, 1, CHARP, path); mSCRIPT_DECLARE_STRUCT_METHOD(mCore, BOOL, loadFile, mCoreLoadFile, 1, CHARP, path);
mSCRIPT_DECLARE_STRUCT_METHOD(mCore, BOOL, autoloadSave, mCoreAutoloadSave, 0); mSCRIPT_DECLARE_STRUCT_METHOD(mCore, BOOL, autoloadSave, mCoreAutoloadSave, 0);
@ -464,6 +489,7 @@ mSCRIPT_DECLARE_STRUCT_METHOD_WITH_DEFAULTS(mCore, BOOL, loadStateFile, _mScript
// Miscellaneous functions // Miscellaneous functions
mSCRIPT_DECLARE_STRUCT_VOID_METHOD_WITH_DEFAULTS(mCore, screenshot, _mScriptCoreTakeScreenshot, 1, CHARP, filename); mSCRIPT_DECLARE_STRUCT_VOID_METHOD_WITH_DEFAULTS(mCore, screenshot, _mScriptCoreTakeScreenshot, 1, CHARP, filename);
mSCRIPT_DECLARE_STRUCT_METHOD(mCore, W(mImage), screenshotToImage, _mScriptCoreTakeScreenshotToImage, 0);
mSCRIPT_DEFINE_STRUCT(mCore) mSCRIPT_DEFINE_STRUCT(mCore)
mSCRIPT_DEFINE_CLASS_DOCSTRING( mSCRIPT_DEFINE_CLASS_DOCSTRING(
@ -549,8 +575,10 @@ mSCRIPT_DEFINE_STRUCT(mCore)
mSCRIPT_DEFINE_DOCSTRING("Load state from the given path. See C.SAVESTATE for possible values for `flags`") mSCRIPT_DEFINE_DOCSTRING("Load state from the given path. See C.SAVESTATE for possible values for `flags`")
mSCRIPT_DEFINE_STRUCT_METHOD(mCore, loadStateFile) mSCRIPT_DEFINE_STRUCT_METHOD(mCore, loadStateFile)
mSCRIPT_DEFINE_DOCSTRING("Save a screenshot") mSCRIPT_DEFINE_DOCSTRING("Save a screenshot to a file")
mSCRIPT_DEFINE_STRUCT_METHOD(mCore, screenshot) mSCRIPT_DEFINE_STRUCT_METHOD(mCore, screenshot)
mSCRIPT_DEFINE_DOCSTRING("Get a screenshot in an struct::mImage")
mSCRIPT_DEFINE_STRUCT_METHOD(mCore, screenshotToImage)
mSCRIPT_DEFINE_END; mSCRIPT_DEFINE_END;
mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(mCore, checksum) mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(mCore, checksum)

View File

@ -13,7 +13,7 @@
#include <mgba-util/vfs.h> #include <mgba-util/vfs.h>
#ifdef USE_PNG #ifdef USE_PNG
#include <mgba-util/png-io.h> #include <mgba-util/image/png-io.h>
#include <png.h> #include <png.h>
#include <zlib.h> #include <zlib.h>
#endif #endif
@ -177,13 +177,13 @@ static bool _savePNGState(struct mCore* core, struct VFile* vf, struct mStateExt
unsigned width, height; unsigned width, height;
core->currentVideoSize(core, &width, &height); core->currentVideoSize(core, &width, &height);
png_structp png = PNGWriteOpen(vf); png_structp png = PNGWriteOpen(vf);
png_infop info = PNGWriteHeader(png, width, height); png_infop info = PNGWriteHeader(png, width, height, mCOLOR_NATIVE);
if (!png || !info) { if (!png || !info) {
PNGWriteClose(png, info); PNGWriteClose(png, info);
free(buffer); free(buffer);
return false; return false;
} }
PNGWritePixels(png, width, height, stride, pixels); PNGWritePixels(png, width, height, stride, pixels, mCOLOR_NATIVE);
PNGWriteCustomChunk(png, "gbAs", len, buffer); PNGWriteCustomChunk(png, "gbAs", len, buffer);
if (extdata) { if (extdata) {
uint32_t i; uint32_t i;
@ -453,13 +453,14 @@ bool mCoreSaveStateNamed(struct mCore* core, struct VFile* vf, int flags) {
#endif #endif
vf->truncate(vf, stateSize); vf->truncate(vf, stateSize);
void* state = vf->map(vf, stateSize, MAP_WRITE); void* state = vf->map(vf, stateSize, MAP_WRITE);
if (!state || !core->saveState(core, state)) { if (!state) {
mStateExtdataDeinit(&extdata); mStateExtdataDeinit(&extdata);
if (cheatVf) { if (cheatVf) {
cheatVf->close(cheatVf); cheatVf->close(cheatVf);
} }
return false; return false;
} }
core->saveState(core, state);
vf->unmap(vf, state, stateSize); vf->unmap(vf, state, stateSize);
vf->seek(vf, stateSize, SEEK_SET); vf->seek(vf, stateSize, SEEK_SET);
mStateExtdataSerialize(&extdata, vf); mStateExtdataSerialize(&extdata, vf);

View File

@ -310,6 +310,24 @@ M_TEST_DEFINE(logging) {
mScriptContextDeinit(&context); mScriptContextDeinit(&context);
} }
M_TEST_DEFINE(screenshot) {
SETUP_LUA;
CREATE_CORE;
color_t* buffer = malloc(240 * 160 * sizeof(color_t));
core->setVideoBuffer(core, buffer, 240);
core->reset(core);
core->runFrame(core);
TEST_PROGRAM("im = emu:screenshotToImage()");
TEST_PROGRAM("assert(im)");
TEST_PROGRAM("assert(im.width >= 160)");
TEST_PROGRAM("assert(im.height >= 144)");
TEARDOWN_CORE;
free(buffer);
mScriptContextDeinit(&context);
}
M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptCore, M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptCore,
cmocka_unit_test(globals), cmocka_unit_test(globals),
cmocka_unit_test(infoFuncs), cmocka_unit_test(infoFuncs),
@ -318,4 +336,5 @@ M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptCore,
cmocka_unit_test(memoryRead), cmocka_unit_test(memoryRead),
cmocka_unit_test(memoryWrite), cmocka_unit_test(memoryWrite),
cmocka_unit_test(logging), cmocka_unit_test(logging),
cmocka_unit_test(screenshot),
) )

View File

@ -157,7 +157,11 @@ void _frameStarted(void* context) {
} }
if (thread->core->opts.rewindEnable && thread->core->opts.rewindBufferCapacity > 0) { if (thread->core->opts.rewindEnable && thread->core->opts.rewindBufferCapacity > 0) {
if (!thread->impl->rewinding || !mCoreRewindRestore(&thread->impl->rewind, thread->core)) { if (!thread->impl->rewinding || !mCoreRewindRestore(&thread->impl->rewind, thread->core)) {
mCoreRewindAppend(&thread->impl->rewind, thread->core); if (thread->impl->rewind.rewindFrameCounter == 0) {
mCoreRewindAppend(&thread->impl->rewind, thread->core);
thread->impl->rewind.rewindFrameCounter = thread->core->opts.rewindBufferInterval;
}
thread->impl->rewind.rewindFrameCounter--;
} }
} }
} }

View File

@ -809,10 +809,14 @@ static void _listWatchpoints(struct CLIDebugger* debugger, struct CLIDebugVector
} }
static void _trace(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { static void _trace(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
if (!dv || dv->type != CLIDV_INT_TYPE) { if (!dv) {
debugger->backend->printf(debugger->backend, "%s\n", ERROR_MISSING_ARGS); debugger->backend->printf(debugger->backend, "%s\n", ERROR_MISSING_ARGS);
return; return;
} }
if (dv->type != CLIDV_INT_TYPE || dv->intValue < 0) {
debugger->backend->printf(debugger->backend, "%s\n", ERROR_INVALID_ARGS);
return;
}
debugger->traceRemaining = dv->intValue; debugger->traceRemaining = dv->intValue;
if (debugger->traceVf) { if (debugger->traceVf) {

View File

@ -50,7 +50,17 @@ static const char* TARGET_XML = "<target version=\"1.0\">"
"<reg name=\"sp\" bitsize=\"32\" type=\"data_ptr\"/>" "<reg name=\"sp\" bitsize=\"32\" type=\"data_ptr\"/>"
"<reg name=\"lr\" bitsize=\"32\"/>" "<reg name=\"lr\" bitsize=\"32\"/>"
"<reg name=\"pc\" bitsize=\"32\" type=\"code_ptr\"/>" "<reg name=\"pc\" bitsize=\"32\" type=\"code_ptr\"/>"
"<reg name=\"cpsr\" bitsize=\"32\" regnum=\"25\"/>" "<flags id=\"cpsr_flags\" size=\"4\">"
"<field name=\"N\" start=\"31\" end=\"31\"/>"
"<field name=\"Z\" start=\"30\" end=\"30\"/>"
"<field name=\"C\" start=\"29\" end=\"29\"/>"
"<field name=\"V\" start=\"28\" end=\"28\"/>"
"<field name=\"I\" start=\"7\" end=\"7\"/>"
"<field name=\"F\" start=\"6\" end=\"6\"/>"
"<field name=\"T\" start=\"5\" end=\"5\"/>"
"<field name=\"M\" start=\"0\" end=\"4\"/>"
"</flags>"
"<reg name=\"cpsr\" bitsize=\"32\" regnum=\"25\" type=\"cpsr_flags\"/>"
"</feature>" "</feature>"
"</target>"; "</target>";

View File

@ -3,6 +3,7 @@ set(SOURCE_FILES
commandline.c commandline.c
thread-proxy.c thread-proxy.c
updater.c updater.c
video-backend.c
video-logger.c) video-logger.c)
set(GUI_FILES set(GUI_FILES

View File

@ -158,19 +158,35 @@ bool FFmpegEncoderSetAudio(struct FFmpegEncoder* encoder, const char* acodec, un
} }
encoder->sampleRate = encoder->isampleRate; encoder->sampleRate = encoder->isampleRate;
if (codec->supported_samplerates) { if (codec->supported_samplerates) {
bool gotSampleRate = false;
int highestSampleRate = 0;
for (i = 0; codec->supported_samplerates[i]; ++i) { for (i = 0; codec->supported_samplerates[i]; ++i) {
if (codec->supported_samplerates[i] > highestSampleRate) {
highestSampleRate = codec->supported_samplerates[i];
}
if (codec->supported_samplerates[i] < encoder->isampleRate) { if (codec->supported_samplerates[i] < encoder->isampleRate) {
continue; continue;
} }
if (encoder->sampleRate == encoder->isampleRate || encoder->sampleRate > codec->supported_samplerates[i]) { if (!gotSampleRate || encoder->sampleRate > codec->supported_samplerates[i]) {
encoder->sampleRate = codec->supported_samplerates[i]; encoder->sampleRate = codec->supported_samplerates[i];
gotSampleRate = true;
} }
} }
if (!gotSampleRate) {
// There are no available sample rates that are higher than the input sample rate
// Let's use the highest available instead
encoder->sampleRate = highestSampleRate;
}
} else if (codec->id == AV_CODEC_ID_FLAC) { } else if (codec->id == AV_CODEC_ID_FLAC) {
// HACK: FLAC doesn't support > 65535Hz unless it's divisible by 10 // HACK: FLAC doesn't support > 65535Hz unless it's divisible by 10
if (encoder->sampleRate >= 65535) { if (encoder->sampleRate >= 65535) {
encoder->sampleRate -= encoder->isampleRate % 10; encoder->sampleRate -= encoder->isampleRate % 10;
} }
} else if (codec->id == AV_CODEC_ID_VORBIS) {
// HACK: FLAC doesn't support > 48000Hz but doesn't tell us
if (encoder->sampleRate > 48000) {
encoder->sampleRate = 48000;
}
} else if (codec->id == AV_CODEC_ID_AAC) { } else if (codec->id == AV_CODEC_ID_AAC) {
// HACK: AAC doesn't support 32768Hz (it rounds to 32000), but libfaac doesn't tell us that // HACK: AAC doesn't support 32768Hz (it rounds to 32000), but libfaac doesn't tell us that
encoder->sampleRate = 48000; encoder->sampleRate = 48000;
@ -890,7 +906,7 @@ void FFmpegEncoderSetInputSampleRate(struct FFmpegEncoder* encoder, int sampleRa
} }
void _ffmpegOpenResampleContext(struct FFmpegEncoder* encoder) { void _ffmpegOpenResampleContext(struct FFmpegEncoder* encoder) {
encoder->audioBufferSize = av_rescale_q(encoder->audioFrame->nb_samples, (AVRational) { 4, encoder->sampleRate }, (AVRational) { 1, encoder->isampleRate }); encoder->audioBufferSize = av_rescale_q(encoder->audioFrame->nb_samples, (AVRational) { 1, encoder->sampleRate }, (AVRational) { 1, encoder->isampleRate }) * 4;
encoder->audioBuffer = av_malloc(encoder->audioBufferSize); encoder->audioBuffer = av_malloc(encoder->audioBufferSize);
#ifdef USE_LIBAVRESAMPLE #ifdef USE_LIBAVRESAMPLE
encoder->resampleContext = avresample_alloc_context(); encoder->resampleContext = avresample_alloc_context();

View File

@ -15,8 +15,8 @@
#include <mgba-util/gui/file-select.h> #include <mgba-util/gui/file-select.h>
#include <mgba-util/gui/font.h> #include <mgba-util/gui/font.h>
#include <mgba-util/gui/menu.h> #include <mgba-util/gui/menu.h>
#include <mgba-util/image/png-io.h>
#include <mgba-util/memory.h> #include <mgba-util/memory.h>
#include <mgba-util/png-io.h>
#include <mgba-util/vfs.h> #include <mgba-util/vfs.h>
#ifdef PSP2 #ifdef PSP2
@ -456,6 +456,7 @@ void mGUIRun(struct mGUIRunner* runner, const char* path) {
mLOG(GUI_RUNNER, DEBUG, "Loading save..."); mLOG(GUI_RUNNER, DEBUG, "Loading save...");
mCoreAutoloadSave(runner->core); mCoreAutoloadSave(runner->core);
mCoreAutoloadCheats(runner->core); mCoreAutoloadCheats(runner->core);
mCoreAutoloadPatch(runner->core);
if (runner->setup) { if (runner->setup) {
mLOG(GUI_RUNNER, DEBUG, "Setting up runner..."); mLOG(GUI_RUNNER, DEBUG, "Setting up runner...");
runner->setup(runner); runner->setup(runner);

View File

@ -3,20 +3,20 @@
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "video-backend.h" #include <mgba/feature/video-backend.h>
void VideoBackendGetFrame(const struct VideoBackend* v, struct Rectangle* frame) { void VideoBackendGetFrame(const struct VideoBackend* v, struct mRectangle* frame) {
memset(frame, 0, sizeof(*frame)); memset(frame, 0, sizeof(*frame));
int i; int i;
for (i = 0; i < VIDEO_LAYER_MAX; ++i) { for (i = 0; i < VIDEO_LAYER_MAX; ++i) {
struct Rectangle dims; struct mRectangle dims;
v->layerDimensions(v, i, &dims); v->layerDimensions(v, i, &dims);
RectangleUnion(frame, &dims); mRectangleUnion(frame, &dims);
} }
} }
void VideoBackendGetFrameSize(const struct VideoBackend* v, unsigned* width, unsigned* height) { void VideoBackendGetFrameSize(const struct VideoBackend* v, unsigned* width, unsigned* height) {
struct Rectangle frame; struct mRectangle frame;
VideoBackendGetFrame(v, &frame); VideoBackendGetFrame(v, &frame);
*width = frame.width; *width = frame.width;
*height = frame.height; *height = frame.height;

View File

@ -138,6 +138,9 @@ void GBAudioReset(struct GBAudio* audio) {
} }
void GBAudioResizeBuffer(struct GBAudio* audio, size_t samples) { void GBAudioResizeBuffer(struct GBAudio* audio, size_t samples) {
if (samples > BLIP_BUFFER_SIZE / 2) {
samples = BLIP_BUFFER_SIZE / 2;
}
mCoreSyncLockAudio(audio->p->sync); mCoreSyncLockAudio(audio->p->sync);
audio->samples = samples; audio->samples = samples;
blip_clear(audio->left); blip_clear(audio->left);
@ -199,6 +202,7 @@ void GBAudioWriteNR14(struct GBAudio* audio, uint8_t value) {
--audio->ch1.control.length; --audio->ch1.control.length;
} }
} }
audio->ch1.lastUpdate = mTimingCurrentTime(audio->timing);
_updateSquareSample(&audio->ch1); _updateSquareSample(&audio->ch1);
} }
*audio->nr52 &= ~0x0001; *audio->nr52 &= ~0x0001;
@ -246,6 +250,7 @@ void GBAudioWriteNR24(struct GBAudio* audio, uint8_t value) {
--audio->ch2.control.length; --audio->ch2.control.length;
} }
} }
audio->ch2.lastUpdate = mTimingCurrentTime(audio->timing);
_updateSquareSample(&audio->ch2); _updateSquareSample(&audio->ch2);
} }
*audio->nr52 &= ~0x0002; *audio->nr52 &= ~0x0002;

View File

@ -227,7 +227,7 @@ void _GBNTOld2(struct GB* gb, uint16_t address, uint8_t value) {
mbcState->rumble = !!(value & 0x80); mbcState->rumble = !!(value & 0x80);
} }
if (mbcState->rumble) { if (mbcState->rumble && memory->rumble) {
memory->rumble->setRumble(memory->rumble, !!(mbcState->swapped ? value & 0x08 : value & 0x02)); memory->rumble->setRumble(memory->rumble, !!(mbcState->swapped ? value & 0x08 : value & 0x02));
} }
break; break;

View File

@ -799,6 +799,10 @@ void GBMemorySerialize(const struct GB* gb, struct GBSerializedState* state) {
state->huc3Registers[i] |= memory->mbcState.huc3.registers[i * 2 + 1] << 4; state->huc3Registers[i] |= memory->mbcState.huc3.registers[i * 2 + 1] << 4;
} }
break; break;
case GB_POCKETCAM:
state->memory.pocketCam.registersActive = memory->mbcState.pocketCam.registersActive;
memcpy(state->pocketCamRegisters, memory->mbcState.pocketCam.registers, sizeof(memory->mbcState.pocketCam.registers));
break;
case GB_MMM01: case GB_MMM01:
state->memory.mmm01.locked = memory->mbcState.mmm01.locked; state->memory.mmm01.locked = memory->mbcState.mmm01.locked;
state->memory.mmm01.bank0 = memory->mbcState.mmm01.currentBank0; state->memory.mmm01.bank0 = memory->mbcState.mmm01.currentBank0;
@ -950,6 +954,10 @@ void GBMemoryDeserialize(struct GB* gb, const struct GBSerializedState* state) {
memory->mbcState.huc3.registers[i * 2 + 1] = state->huc3Registers[i] >> 4; memory->mbcState.huc3.registers[i * 2 + 1] = state->huc3Registers[i] >> 4;
} }
break; break;
case GB_POCKETCAM:
memory->mbcState.pocketCam.registersActive = state->memory.pocketCam.registersActive;
memcpy(memory->mbcState.pocketCam.registers, state->pocketCamRegisters, sizeof(memory->mbcState.pocketCam.registers));
break;
case GB_MMM01: case GB_MMM01:
memory->mbcState.mmm01.locked = state->memory.mmm01.locked; memory->mbcState.mmm01.locked = state->memory.mmm01.locked;
memory->mbcState.mmm01.currentBank0 = state->memory.mmm01.bank0; memory->mbcState.mmm01.currentBank0 = state->memory.mmm01.bank0;

View File

@ -106,6 +106,9 @@ void GBAAudioDeinit(struct GBAAudio* audio) {
} }
void GBAAudioResizeBuffer(struct GBAAudio* audio, size_t samples) { void GBAAudioResizeBuffer(struct GBAAudio* audio, size_t samples) {
if (samples > 0x2000) {
samples = 0x2000;
}
mCoreSyncLockAudio(audio->p->sync); mCoreSyncLockAudio(audio->p->sync);
audio->samples = samples; audio->samples = samples;
blip_clear(audio->psg.left); blip_clear(audio->psg.left);
@ -231,8 +234,22 @@ void GBAAudioWriteSOUNDCNT_HI(struct GBAAudio* audio, uint16_t value) {
} }
void GBAAudioWriteSOUNDCNT_X(struct GBAAudio* audio, uint16_t value) { void GBAAudioWriteSOUNDCNT_X(struct GBAAudio* audio, uint16_t value) {
GBAAudioSample(audio, mTimingCurrentTime(&audio->p->timing));
audio->enable = GBAudioEnableGetEnable(value); audio->enable = GBAudioEnableGetEnable(value);
GBAudioWriteNR52(&audio->psg, value); GBAudioWriteNR52(&audio->psg, value);
if (!audio->enable) {
int i;
for (i = REG_SOUND1CNT_LO; i < REG_SOUNDCNT_HI; i += 2) {
audio->p->memory.io[i >> 1] = 0;
}
audio->psg.ch3.size = 0;
audio->psg.ch3.bank = 0;
audio->psg.ch3.volume = 0;
audio->volume = 0;
audio->volumeChA = 0;
audio->volumeChB = 0;
audio->p->memory.io[REG_SOUNDCNT_HI >> 1] &= 0xFF00;
}
} }
void GBAAudioWriteSOUNDBIAS(struct GBAAudio* audio, uint16_t value) { void GBAAudioWriteSOUNDBIAS(struct GBAAudio* audio, uint16_t value) {

View File

@ -13,7 +13,7 @@
#ifdef USE_FFMPEG #ifdef USE_FFMPEG
#include <mgba-util/convolve.h> #include <mgba-util/convolve.h>
#ifdef USE_PNG #ifdef USE_PNG
#include <mgba-util/png-io.h> #include <mgba-util/image/png-io.h>
#include <mgba-util/vfs.h> #include <mgba-util/vfs.h>
#endif #endif
@ -888,7 +888,11 @@ struct EReaderScan* EReaderScanLoadImagePNG(const char* filename) {
} }
png_infop info = png_create_info_struct(png); png_infop info = png_create_info_struct(png);
png_infop end = png_create_info_struct(png); png_infop end = png_create_info_struct(png);
PNGReadHeader(png, info); if (!PNGReadHeader(png, info)) {
PNGReadClose(png, info, end);
vf->close(vf);
return NULL;
}
unsigned height = png_get_image_height(png, info); unsigned height = png_get_image_height(png, info);
unsigned width = png_get_image_width(png, info); unsigned width = png_get_image_width(png, info);
int type = png_get_color_type(png, info); int type = png_get_color_type(png, info);
@ -900,19 +904,34 @@ struct EReaderScan* EReaderScanLoadImagePNG(const char* filename) {
break; break;
} }
image = malloc(height * width * 3); image = malloc(height * width * 3);
PNGReadPixels(png, info, image, width, height, width); if (!image) {
goto out;
}
if (!PNGReadPixels(png, info, image, width, height, width)) {
free(image);
image = NULL;
goto out;
}
break; break;
case PNG_COLOR_TYPE_RGBA: case PNG_COLOR_TYPE_RGBA:
if (depth != 8) { if (depth != 8) {
break; break;
} }
image = malloc(height * width * 4); image = malloc(height * width * 4);
PNGReadPixelsA(png, info, image, width, height, width); if (!image) {
goto out;
}
if (!PNGReadPixelsA(png, info, image, width, height, width)) {
free(image);
image = NULL;
goto out;
}
break; break;
default: default:
break; break;
} }
PNGReadFooter(png, end); PNGReadFooter(png, end);
out:
PNGReadClose(png, info, end); PNGReadClose(png, info, end);
vf->close(vf); vf->close(vf);
if (!image) { if (!image) {

View File

@ -50,13 +50,13 @@ const uint8_t hleBios[GBA_SIZE_BIOS] = {
0x0c, 0x80, 0xbd, 0xe8, 0x30, 0x40, 0x2d, 0xe9, 0x02, 0x46, 0xa0, 0xe1, 0x0c, 0x80, 0xbd, 0xe8, 0x30, 0x40, 0x2d, 0xe9, 0x02, 0x46, 0xa0, 0xe1,
0x00, 0xc0, 0xa0, 0xe1, 0x01, 0x50, 0xa0, 0xe1, 0x01, 0x04, 0x12, 0xe3, 0x00, 0xc0, 0xa0, 0xe1, 0x01, 0x50, 0xa0, 0xe1, 0x01, 0x04, 0x12, 0xe3,
0x0f, 0x00, 0x00, 0x0a, 0x01, 0x03, 0x12, 0xe3, 0x05, 0x00, 0x00, 0x0a, 0x0f, 0x00, 0x00, 0x0a, 0x01, 0x03, 0x12, 0xe3, 0x05, 0x00, 0x00, 0x0a,
0x24, 0x45, 0x85, 0xe0, 0x08, 0x00, 0xbc, 0xe8, 0x04, 0x00, 0x55, 0xe1, 0x24, 0x45, 0x85, 0xe0, 0x08, 0x00, 0xb0, 0xe8, 0x04, 0x00, 0x51, 0xe1,
0x08, 0x00, 0xa5, 0xb8, 0xfc, 0xff, 0xff, 0xba, 0x14, 0x00, 0x00, 0xea, 0x08, 0x00, 0xa1, 0xb8, 0xfc, 0xff, 0xff, 0xba, 0x14, 0x00, 0x00, 0xea,
0x01, 0xc0, 0xcc, 0xe3, 0x01, 0x50, 0xc5, 0xe3, 0xa4, 0x45, 0x85, 0xe0, 0x01, 0xc0, 0xcc, 0xe3, 0x01, 0x50, 0xc5, 0xe3, 0xa4, 0x45, 0x85, 0xe0,
0xb0, 0x30, 0xdc, 0xe1, 0x04, 0x00, 0x55, 0xe1, 0xb2, 0x30, 0xc5, 0xb0, 0xb0, 0x30, 0xdc, 0xe1, 0x04, 0x00, 0x55, 0xe1, 0xb2, 0x30, 0xc5, 0xb0,
0xfc, 0xff, 0xff, 0xba, 0x0c, 0x00, 0x00, 0xea, 0x01, 0x03, 0x12, 0xe3, 0xfc, 0xff, 0xff, 0xba, 0x0c, 0x00, 0x00, 0xea, 0x01, 0x03, 0x12, 0xe3,
0x05, 0x00, 0x00, 0x0a, 0x24, 0x45, 0x85, 0xe0, 0x04, 0x00, 0x55, 0xe1, 0x05, 0x00, 0x00, 0x0a, 0x24, 0x45, 0x85, 0xe0, 0x04, 0x00, 0x51, 0xe1,
0x08, 0x00, 0xbc, 0xb8, 0x08, 0x00, 0xa5, 0xb8, 0xfb, 0xff, 0xff, 0xba, 0x08, 0x00, 0xb0, 0xb8, 0x08, 0x00, 0xa1, 0xb8, 0xfb, 0xff, 0xff, 0xba,
0x04, 0x00, 0x00, 0xea, 0xa4, 0x45, 0x85, 0xe0, 0x04, 0x00, 0x55, 0xe1, 0x04, 0x00, 0x00, 0xea, 0xa4, 0x45, 0x85, 0xe0, 0x04, 0x00, 0x55, 0xe1,
0xb2, 0x30, 0xdc, 0xb0, 0xb2, 0x30, 0xc5, 0xb0, 0xfb, 0xff, 0xff, 0xba, 0xb2, 0x30, 0xdc, 0xb0, 0xb2, 0x30, 0xc5, 0xb0, 0xfb, 0xff, 0xff, 0xba,
0x17, 0x3e, 0xa0, 0xe3, 0x30, 0x80, 0xbd, 0xe8, 0xf0, 0x47, 0x2d, 0xe9, 0x17, 0x3e, 0xa0, 0xe3, 0x30, 0x80, 0xbd, 0xe8, 0xf0, 0x47, 0x2d, 0xe9,

View File

@ -209,10 +209,10 @@ tst r2, #0x04000000
beq 1f beq 1f
@ Word @ Word
add r4, r5, r4, lsr #10 add r4, r5, r4, lsr #10
ldmia r12!, {r3} ldmia r0!, {r3}
2: 2:
cmp r5, r4 cmp r1, r4
stmltia r5!, {r3} stmltia r1!, {r3}
blt 2b blt 2b
b 3f b 3f
@ Halfword @ Halfword
@ -233,9 +233,9 @@ beq 1f
@ Word @ Word
add r4, r5, r4, lsr #10 add r4, r5, r4, lsr #10
2: 2:
cmp r5, r4 cmp r1, r4
ldmltia r12!, {r3} ldmltia r0!, {r3}
stmltia r5!, {r3} stmltia r1!, {r3}
blt 2b blt 2b
b 3f b 3f
@ Halfword @ Halfword

View File

@ -1747,15 +1747,13 @@ int32_t GBAMemoryStall(struct ARMCore* cpu, int32_t wait) {
maxLoads -= previousLoads; maxLoads -= previousLoads;
} }
int32_t s = cpu->memory.activeSeqCycles16;
int32_t n2s = cpu->memory.activeNonseqCycles16 - cpu->memory.activeSeqCycles16 + 1;
// Figure out how many sequential loads we can jam in // Figure out how many sequential loads we can jam in
int32_t s = cpu->memory.activeSeqCycles16;
int32_t stall = s + 1; int32_t stall = s + 1;
int32_t loads = 1; int32_t loads = 1;
while (stall < wait && loads < maxLoads) { while (stall < wait && loads < maxLoads) {
stall += s; stall += s + 1;
++loads; ++loads;
} }
memory->lastPrefetchedPc = cpu->gprs[ARM_PC] + WORD_SIZE_THUMB * (loads + previousLoads - 1); memory->lastPrefetchedPc = cpu->gprs[ARM_PC] + WORD_SIZE_THUMB * (loads + previousLoads - 1);
@ -1766,10 +1764,10 @@ int32_t GBAMemoryStall(struct ARMCore* cpu, int32_t wait) {
} }
// This instruction used to have an N, convert it to an S. // This instruction used to have an N, convert it to an S.
wait -= n2s; wait -= cpu->memory.activeNonseqCycles16 - s;
// The next |loads|S waitstates disappear entirely, so long as they're all in a row // The next |loads|S waitstates disappear entirely, so long as they're all in a row
wait -= stall - 1; wait -= stall;
return wait; return wait;
} }

View File

@ -123,10 +123,8 @@ static const struct GBACartridgeOverride _overrides[] = {
{ "BPEF", SAVEDATA_FLASH1M, HW_RTC, 0x80008C6, false }, { "BPEF", SAVEDATA_FLASH1M, HW_RTC, 0x80008C6, false },
// Pokemon Mystery Dungeon // Pokemon Mystery Dungeon
{ "B24J", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE, false },
{ "B24E", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE, false }, { "B24E", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE, false },
{ "B24P", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE, false }, { "B24P", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE, false },
{ "B24U", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE, false },
// Pokemon FireRed // Pokemon FireRed
{ "BPRJ", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE, false }, { "BPRJ", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE, false },

View File

@ -233,6 +233,29 @@ static const char* const _interpolate =
" aff[2] = transform[start + 2].zw;\n" " aff[2] = transform[start + 2].zw;\n"
" mat[3] = transform[start + 3].xy;\n" " mat[3] = transform[start + 3].xy;\n"
" aff[3] = transform[start + 3].zw;\n" " aff[3] = transform[start + 3].zw;\n"
"}\n"
"ivec2 affineInterpolate() {\n"
" ivec2 mat[4];\n"
" ivec2 offset[4];\n"
" vec2 incoord = texCoord;\n"
" if (mosaic.x > 1) {\n"
" incoord.x = float(MOSAIC(incoord.x, mosaic.x));\n"
" }\n"
" if (mosaic.y > 1) {\n"
" incoord.y = float(MOSAIC(incoord.y, mosaic.y));\n"
" }\n"
" loadAffine(int(incoord.y), mat, offset);\n"
" float y = fract(incoord.y);\n"
" float start = 2. / 3.;\n"
" if (int(incoord.y) - range.x < 4) {\n"
" y = incoord.y - float(range.x);\n"
" start -= 1.;\n"
" }\n"
" float lin = start + y / 3.;\n"
" vec2 mixedTransform = interpolate(mat, lin);\n"
" vec2 mixedOffset = interpolate(offset, lin);\n"
" return ivec2(mixedTransform * incoord.x + mixedOffset);\n"
"}\n"; "}\n";
static const char* const _renderMode2 = static const char* const _renderMode2 =
@ -250,8 +273,7 @@ static const char* const _renderMode2 =
"OUT(0) out vec4 color;\n" "OUT(0) out vec4 color;\n"
"int fetchTile(ivec2 coord);\n" "int fetchTile(ivec2 coord);\n"
"vec2 interpolate(ivec2 arr[4], float x);\n" "ivec2 affineInterpolate();\n"
"void loadAffine(int y, out ivec2 mat[4], out ivec2 aff[4]);\n"
"int renderTile(ivec2 coord) {\n" "int renderTile(ivec2 coord) {\n"
" int map = (coord.x >> 11) + (((coord.y >> 7) & 0x7F0) << size);\n" " int map = (coord.x >> 11) + (((coord.y >> 7) & 0x7F0) << size);\n"
@ -278,26 +300,7 @@ static const char* const _renderMode2 =
"}\n" "}\n"
"void main() {\n" "void main() {\n"
" ivec2 mat[4];\n" " int paletteEntry = fetchTile(affineInterpolate());\n"
" ivec2 offset[4];\n"
" vec2 incoord = texCoord;\n"
" if (mosaic.x > 1) {\n"
" incoord.x = float(MOSAIC(incoord.x, mosaic.x));\n"
" }\n"
" if (mosaic.y > 1) {\n"
" incoord.y = float(MOSAIC(incoord.y, mosaic.y));\n"
" }\n"
" loadAffine(int(incoord.y), mat, offset);\n"
" float y = fract(incoord.y);\n"
" float start = 0.75;\n"
" if (int(incoord.y) - range.x < 4) {\n"
" y = incoord.y - float(range.x);\n"
" start = 0.;\n"
" }\n"
" float lin = start + y * 0.25;\n"
" vec2 mixedTransform = interpolate(mat, lin);\n"
" vec2 mixedOffset = interpolate(offset, lin);\n"
" int paletteEntry = fetchTile(ivec2(mixedTransform * incoord.x + mixedOffset));\n"
" color = texelFetch(palette, ivec2(paletteEntry, int(texCoord.y)), 0);\n" " color = texelFetch(palette, ivec2(paletteEntry, int(texCoord.y)), 0);\n"
"}"; "}";
@ -325,30 +328,10 @@ static const char* const _renderMode35 =
"uniform ivec2 mosaic;\n" "uniform ivec2 mosaic;\n"
"OUT(0) out vec4 color;\n" "OUT(0) out vec4 color;\n"
"vec2 interpolate(ivec2 arr[4], float x);\n" "ivec2 affineInterpolate();\n"
"void loadAffine(int y, out ivec2 mat[4], out ivec2 aff[4]);\n"
"void main() {\n" "void main() {\n"
" ivec2 mat[4];\n" " ivec2 coord = affineInterpolate();\n"
" ivec2 offset[4];\n"
" vec2 incoord = texCoord;\n"
" if (mosaic.x > 1) {\n"
" incoord.x = float(MOSAIC(incoord.x, mosaic.x));\n"
" }\n"
" if (mosaic.y > 1) {\n"
" incoord.y = float(MOSAIC(incoord.y, mosaic.y));\n"
" }\n"
" loadAffine(int(incoord.y), mat, offset);\n"
" float y = fract(incoord.y);\n"
" float start = 0.75;\n"
" if (int(incoord.y) - range.x < 4) {\n"
" y = incoord.y - float(range.x);\n"
" start = 0.;\n"
" }\n"
" float lin = start + y * 0.25;\n"
" vec2 mixedTransform = interpolate(mat, lin);\n"
" vec2 mixedOffset = interpolate(offset, lin);\n"
" ivec2 coord = ivec2(mixedTransform * incoord.x + mixedOffset);\n"
" if (coord.x < 0 || coord.x >= (size.x << 8)) {\n" " if (coord.x < 0 || coord.x >= (size.x << 8)) {\n"
" discard;\n" " discard;\n"
" }\n" " }\n"
@ -386,30 +369,10 @@ static const char* const _renderMode4 =
"uniform ivec2 mosaic;\n" "uniform ivec2 mosaic;\n"
"OUT(0) out vec4 color;\n" "OUT(0) out vec4 color;\n"
"vec2 interpolate(ivec2 arr[4], float x);\n" "ivec2 affineInterpolate();\n"
"void loadAffine(int y, out ivec2 mat[4], out ivec2 aff[4]);\n"
"void main() {\n" "void main() {\n"
" ivec2 mat[4];\n" " ivec2 coord = affineInterpolate();\n"
" ivec2 offset[4];\n"
" vec2 incoord = texCoord;\n"
" if (mosaic.x > 1) {\n"
" incoord.x = float(MOSAIC(incoord.x, mosaic.x));\n"
" }\n"
" if (mosaic.y > 1) {\n"
" incoord.y = float(MOSAIC(incoord.y, mosaic.y));\n"
" }\n"
" loadAffine(int(incoord.y), mat, offset);\n"
" float y = fract(incoord.y);\n"
" float start = 0.75;\n"
" if (int(incoord.y) - range.x < 4) {\n"
" y = incoord.y - float(range.x);\n"
" start = 0.;\n"
" }\n"
" float lin = start + y * 0.25;\n"
" vec2 mixedTransform = interpolate(mat, lin);\n"
" vec2 mixedOffset = interpolate(offset, lin);\n"
" ivec2 coord = ivec2(mixedTransform * incoord.x + mixedOffset);\n"
" if (coord.x < 0 || coord.x >= (size.x << 8)) {\n" " if (coord.x < 0 || coord.x >= (size.x << 8)) {\n"
" discard;\n" " discard;\n"
" }\n" " }\n"

View File

@ -13,6 +13,10 @@
#include <mgba-util/memory.h> #include <mgba-util/memory.h>
#include <mgba-util/vfs.h> #include <mgba-util/vfs.h>
#ifdef PSP2
#include <psp2/rtc.h>
#endif
#include <errno.h> #include <errno.h>
#include <fcntl.h> #include <fcntl.h>
@ -637,6 +641,9 @@ void GBASavedataRTCRead(struct GBASavedata* savedata) {
} }
LOAD_64LE(savedata->gpio->rtc.lastLatch, 0, &buffer.lastLatch); LOAD_64LE(savedata->gpio->rtc.lastLatch, 0, &buffer.lastLatch);
time_t rtcTime;
#ifndef PSP2
struct tm date; struct tm date;
date.tm_year = _unBCD(savedata->gpio->rtc.time[0]) + 100; date.tm_year = _unBCD(savedata->gpio->rtc.time[0]) + 100;
date.tm_mon = _unBCD(savedata->gpio->rtc.time[1]) - 1; date.tm_mon = _unBCD(savedata->gpio->rtc.time[1]) - 1;
@ -645,8 +652,40 @@ void GBASavedataRTCRead(struct GBASavedata* savedata) {
date.tm_min = _unBCD(savedata->gpio->rtc.time[5]); date.tm_min = _unBCD(savedata->gpio->rtc.time[5]);
date.tm_sec = _unBCD(savedata->gpio->rtc.time[6]); date.tm_sec = _unBCD(savedata->gpio->rtc.time[6]);
date.tm_isdst = -1; date.tm_isdst = -1;
rtcTime = mktime(&date);
#else
struct SceDateTime date;
date.year = _unBCD(savedata->gpio->rtc.time[0]) + 2000;
date.month = _unBCD(savedata->gpio->rtc.time[1]);
date.day = _unBCD(savedata->gpio->rtc.time[2]);
date.hour = _unBCD(savedata->gpio->rtc.time[4]);
date.minute = _unBCD(savedata->gpio->rtc.time[5]);
date.second = _unBCD(savedata->gpio->rtc.time[6]);
date.microsecond = 0;
savedata->gpio->rtc.offset = savedata->gpio->rtc.lastLatch - mktime(&date); struct SceRtcTick tick;
int res;
res = sceRtcConvertDateTimeToTick(&date, &tick);
if (res < 0) {
mLOG(GBA_SAVE, ERROR, "sceRtcConvertDateTimeToTick %lx", res);
}
res = sceRtcConvertLocalTimeToUtc(&tick, &tick);
if (res < 0) {
mLOG(GBA_SAVE, ERROR, "sceRtcConvertUtcToLocalTime %lx", res);
}
res = sceRtcConvertTickToDateTime(&tick, &date);
if (res < 0) {
mLOG(GBA_SAVE, ERROR, "sceRtcConvertTickToDateTime %lx", res);
}
res = sceRtcConvertDateTimeToTime_t(&date, &rtcTime);
if (res < 0) {
mLOG(GBA_SAVE, ERROR, "sceRtcConvertDateTimeToTime_t %lx", res);
}
#endif
savedata->gpio->rtc.offset = savedata->gpio->rtc.lastLatch - rtcTime;
mLOG(GBA_SAVE, ERROR, "Savegame time offset set to %li", savedata->gpio->rtc.offset);
} }
void GBASavedataSerialize(const struct GBASavedata* savedata, struct GBASerializedState* state) { void GBASavedataSerialize(const struct GBASavedata* savedata, struct GBASerializedState* state) {

View File

@ -5,7 +5,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <mgba-util/gui/font.h> #include <mgba-util/gui/font.h>
#include <mgba-util/gui/font-metrics.h> #include <mgba-util/gui/font-metrics.h>
#include <mgba-util/png-io.h> #include <mgba-util/image/png-io.h>
#include <mgba-util/vfs.h> #include <mgba-util/vfs.h>
#include "icons.h" #include "icons.h"

View File

@ -873,7 +873,7 @@ int main(int argc, char* argv[]) {
u8 model = 0; u8 model = 0;
cfguInit(); cfguInit();
CFGU_GetSystemModel(&model); CFGU_GetSystemModel(&model);
if (model != 3 /* o2DS */) { if (model != CFG_MODEL_2DS) {
gfxSetWide(true); gfxSetWide(true);
} }
cfguExit(); cfguExit();

View File

@ -63,7 +63,7 @@ static inline void _setTexDims(int width, int height) {
#endif #endif
} }
static void mGLContextSetLayerDimensions(struct VideoBackend* v, enum VideoLayer layer, const struct Rectangle* dims) { static void mGLContextSetLayerDimensions(struct VideoBackend* v, enum VideoLayer layer, const struct mRectangle* dims) {
struct mGLContext* context = (struct mGLContext*) v; struct mGLContext* context = (struct mGLContext*) v;
if (layer >= VIDEO_LAYER_MAX) { if (layer >= VIDEO_LAYER_MAX) {
return; return;
@ -89,7 +89,7 @@ static void mGLContextSetLayerDimensions(struct VideoBackend* v, enum VideoLayer
} }
} }
static void mGLContextLayerDimensions(const struct VideoBackend* v, enum VideoLayer layer, struct Rectangle* dims) { static void mGLContextLayerDimensions(const struct VideoBackend* v, enum VideoLayer layer, struct mRectangle* dims) {
struct mGLContext* context = (struct mGLContext*) v; struct mGLContext* context = (struct mGLContext*) v;
if (layer >= VIDEO_LAYER_MAX) { if (layer >= VIDEO_LAYER_MAX) {
return; return;
@ -141,13 +141,13 @@ static void _setFilter(struct VideoBackend* v) {
} }
} }
static void _setFrame(struct Rectangle* dims, int frameW, int frameH) { static void _setFrame(struct mRectangle* dims, struct mRectangle* frame) {
GLint viewport[4]; GLint viewport[4];
glGetIntegerv(GL_VIEWPORT, viewport); glGetIntegerv(GL_VIEWPORT, viewport);
glScissor(viewport[0] + dims->x * viewport[2] / frameW, glScissor(viewport[0] + (dims->x - frame->x) * viewport[2] / frame->width,
viewport[1] + dims->y * viewport[3] / frameH, viewport[1] + (dims->y - frame->y) * viewport[3] / frame->height,
dims->width * viewport[2] / frameW, dims->width * viewport[2] / frame->width,
dims->height * viewport[3] / frameH); dims->height * viewport[3] / frame->height);
glTranslatef(dims->x, dims->y, 0); glTranslatef(dims->x, dims->y, 0);
glScalef(toPow2(dims->width), toPow2(dims->height), 1); glScalef(toPow2(dims->width), toPow2(dims->height), 1);
} }
@ -162,9 +162,9 @@ void mGLContextDrawFrame(struct VideoBackend* v) {
glTexCoordPointer(2, GL_INT, 0, _glTexCoords); glTexCoordPointer(2, GL_INT, 0, _glTexCoords);
glMatrixMode(GL_PROJECTION); glMatrixMode(GL_PROJECTION);
glLoadIdentity(); glLoadIdentity();
unsigned frameW, frameH; struct mRectangle frame;
VideoBackendGetFrameSize(v, &frameW, &frameH); VideoBackendGetFrame(v, &frame);
glOrtho(0, frameW, frameH, 0, 0, 1); glOrtho(frame.x, frame.x + frame.width, frame.y + frame.height, frame.y, 0, 1);
glMatrixMode(GL_MODELVIEW); glMatrixMode(GL_MODELVIEW);
glLoadIdentity(); glLoadIdentity();
@ -178,12 +178,12 @@ void mGLContextDrawFrame(struct VideoBackend* v) {
glBindTexture(GL_TEXTURE_2D, context->layers[layer]); glBindTexture(GL_TEXTURE_2D, context->layers[layer]);
_setFilter(v); _setFilter(v);
glPushMatrix(); glPushMatrix();
_setFrame(&context->layerDims[layer], frameW, frameH); _setFrame(&context->layerDims[layer], &frame);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4); glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
glPopMatrix(); glPopMatrix();
} }
_setFrame(&context->layerDims[VIDEO_LAYER_IMAGE], frameW, frameH); _setFrame(&context->layerDims[VIDEO_LAYER_IMAGE], &frame);
if (v->interframeBlending) { if (v->interframeBlending) {
glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA); glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA);
glBlendColor(1, 1, 1, 0.5); glBlendColor(1, 1, 1, 0.5);

View File

@ -21,7 +21,7 @@ CXX_GUARD_START
#include <GL/gl.h> #include <GL/gl.h>
#endif #endif
#include "platform/video-backend.h" #include <mgba/feature/video-backend.h>
struct mGLContext { struct mGLContext {
struct VideoBackend d; struct VideoBackend d;
@ -29,8 +29,8 @@ struct mGLContext {
int activeTex; int activeTex;
GLuint tex[2]; GLuint tex[2];
GLuint layers[VIDEO_LAYER_MAX]; GLuint layers[VIDEO_LAYER_MAX];
struct Rectangle layerDims[VIDEO_LAYER_MAX]; struct mRectangle layerDims[VIDEO_LAYER_MAX];
struct Size imageSizes[VIDEO_LAYER_MAX]; struct mSize imageSizes[VIDEO_LAYER_MAX];
}; };
void mGLContextCreate(struct mGLContext*); void mGLContextCreate(struct mGLContext*);

View File

@ -194,7 +194,7 @@ static inline void _setTexDims(int width, int height) {
#endif #endif
} }
static void mGLES2ContextSetLayerDimensions(struct VideoBackend* v, enum VideoLayer layer, const struct Rectangle* dims) { static void mGLES2ContextSetLayerDimensions(struct VideoBackend* v, enum VideoLayer layer, const struct mRectangle* dims) {
struct mGLES2Context* context = (struct mGLES2Context*) v; struct mGLES2Context* context = (struct mGLES2Context*) v;
if (layer >= VIDEO_LAYER_MAX) { if (layer >= VIDEO_LAYER_MAX) {
return; return;
@ -212,10 +212,9 @@ static void mGLES2ContextSetLayerDimensions(struct VideoBackend* v, enum VideoLa
context->layerDims[layer].x = dims->x; context->layerDims[layer].x = dims->x;
context->layerDims[layer].y = dims->y; context->layerDims[layer].y = dims->y;
unsigned newW; struct mRectangle frame;
unsigned newH; VideoBackendGetFrame(v, &frame);
VideoBackendGetFrameSize(v, &newW, &newH); if (frame.width != context->width || frame.height != context->height) {
if (newW != context->width || newH != context->height) {
size_t n; size_t n;
for (n = 0; n < context->nShaders; ++n) { for (n = 0; n < context->nShaders; ++n) {
if (context->shaders[n].width < 0 || context->shaders[n].height < 0) { if (context->shaders[n].width < 0 || context->shaders[n].height < 0) {
@ -224,12 +223,14 @@ static void mGLES2ContextSetLayerDimensions(struct VideoBackend* v, enum VideoLa
} }
context->initialShader.dirty = true; context->initialShader.dirty = true;
context->interframeShader.dirty = true; context->interframeShader.dirty = true;
context->width = newW; context->width = frame.width;
context->height = newH; context->height = frame.height;
} }
context->x = frame.x;
context->y = frame.y;
} }
static void mGLES2ContextLayerDimensions(const struct VideoBackend* v, enum VideoLayer layer, struct Rectangle* dims) { static void mGLES2ContextLayerDimensions(const struct VideoBackend* v, enum VideoLayer layer, struct mRectangle* dims) {
struct mGLES2Context* context = (struct mGLES2Context*) v; struct mGLES2Context* context = (struct mGLES2Context*) v;
if (layer >= VIDEO_LAYER_MAX) { if (layer >= VIDEO_LAYER_MAX) {
return; return;
@ -320,7 +321,7 @@ static void _drawShaderEx(struct mGLES2Context* context, struct mGLES2Shader* sh
} }
if (layer >= 0 && layer < VIDEO_LAYER_MAX) { if (layer >= 0 && layer < VIDEO_LAYER_MAX) {
glViewport(context->layerDims[layer].x, context->layerDims[layer].y, context->layerDims[layer].width, context->layerDims[layer].height); glViewport(context->layerDims[layer].x - context->x, context->layerDims[layer].y - context->y, context->layerDims[layer].width, context->layerDims[layer].height);
} else { } else {
glViewport(padW, padH, drawW, drawH); glViewport(padW, padH, drawW, drawH);
} }

View File

@ -24,7 +24,7 @@ CXX_GUARD_START
#include <GLES2/gl2.h> #include <GLES2/gl2.h>
#endif #endif
#include "platform/video-backend.h" #include <mgba/feature/video-backend.h>
union mGLES2UniformValue { union mGLES2UniformValue {
GLfloat f; GLfloat f;
@ -82,8 +82,10 @@ struct mGLES2Context {
GLuint tex[VIDEO_LAYER_MAX]; GLuint tex[VIDEO_LAYER_MAX];
GLuint vbo; GLuint vbo;
struct Rectangle layerDims[VIDEO_LAYER_MAX]; struct mRectangle layerDims[VIDEO_LAYER_MAX];
struct Size imageSizes[VIDEO_LAYER_MAX]; struct mSize imageSizes[VIDEO_LAYER_MAX];
int x;
int y;
unsigned width; unsigned width;
unsigned height; unsigned height;

View File

@ -28,6 +28,7 @@ set(OS_LIB -lvita2d -l${M_LIBRARY}
-lScePgf_stub -lScePgf_stub
-lScePhotoExport_stub -lScePhotoExport_stub
-lScePower_stub -lScePower_stub
-lSceRtc_stub
-lSceSysmodule_stub -lSceSysmodule_stub
-lSceTouch_stub) -lSceTouch_stub)
set(OS_LIB ${OS_LIB} PARENT_SCOPE) set(OS_LIB ${OS_LIB} PARENT_SCOPE)

View File

@ -53,7 +53,7 @@ void free(void*);
#undef PYEXPORT #undef PYEXPORT
#ifdef USE_PNG #ifdef USE_PNG
#include <mgba-util/png-io.h> #include <mgba-util/image/png-io.h>
#endif #endif
#ifdef M_CORE_GBA #ifdef M_CORE_GBA
#include <mgba/gba/interface.h> #include <mgba/gba/interface.h>

View File

@ -42,7 +42,7 @@ ffi.set_source("mgba._pylib", """
#include <mgba/internal/sm83/sm83.h> #include <mgba/internal/sm83/sm83.h>
#include <mgba/internal/gb/gb.h> #include <mgba/internal/gb/gb.h>
#include <mgba/internal/gb/renderers/cache-set.h> #include <mgba/internal/gb/renderers/cache-set.h>
#include <mgba-util/png-io.h> #include <mgba-util/image/png-io.h>
#include <mgba-util/vfs.h> #include <mgba-util/vfs.h>
#define PYEXPORT #define PYEXPORT

View File

@ -21,20 +21,16 @@ class PNG:
def write_header(self, image): def write_header(self, image):
self._png = lib.PNGWriteOpen(self._vfile.handle) self._png = lib.PNGWriteOpen(self._vfile.handle)
if self.mode == MODE_RGB: if self.mode == MODE_RGB:
self._info = lib.PNGWriteHeader(self._png, image.width, image.height) self._info = lib.PNGWriteHeader(self._png, image.width, image.height, lib.mCOLOR_XBGR8)
if self.mode == MODE_RGBA: if self.mode == MODE_RGBA:
self._info = lib.PNGWriteHeaderA(self._png, image.width, image.height) self._info = lib.PNGWriteHeader(self._png, image.width, image.height, lib.mCOLOR_ABGR8)
if self.mode == MODE_INDEX:
self._info = lib.PNGWriteHeader8(self._png, image.width, image.height)
return self._info != ffi.NULL return self._info != ffi.NULL
def write_pixels(self, image): def write_pixels(self, image):
if self.mode == MODE_RGB: if self.mode == MODE_RGB:
return lib.PNGWritePixels(self._png, image.width, image.height, image.stride, image.buffer) return lib.PNGWritePixels(self._png, image.width, image.height, image.stride, image.buffer, lib.mCOLOR_XBGR8)
if self.mode == MODE_RGBA: if self.mode == MODE_RGBA:
return lib.PNGWritePixelsA(self._png, image.width, image.height, image.stride, image.buffer) return lib.PNGWritePixels(self._png, image.width, image.height, image.stride, image.buffer, lib.mCOLOR_ABGR8)
if self.mode == MODE_INDEX:
return lib.PNGWritePixels8(self._png, image.width, image.height, image.stride, image.buffer)
return False return False
def write_close(self): def write_close(self):

View File

@ -125,6 +125,7 @@ ConfigController::ConfigController(QObject* parent)
m_opts.logLevel = mLOG_WARN | mLOG_ERROR | mLOG_FATAL; m_opts.logLevel = mLOG_WARN | mLOG_ERROR | mLOG_FATAL;
m_opts.rewindEnable = false; m_opts.rewindEnable = false;
m_opts.rewindBufferCapacity = 300; m_opts.rewindBufferCapacity = 300;
m_opts.rewindBufferInterval = 1;
m_opts.useBios = true; m_opts.useBios = true;
m_opts.suspendScreensaver = true; m_opts.suspendScreensaver = true;
m_opts.lockAspectRatio = true; m_opts.lockAspectRatio = true;

View File

@ -297,13 +297,6 @@ void CoreController::loadConfig(ConfigController* config) {
mCoreConfigCopyValue(&m_threadContext.core->config, config->config(), "mute"); mCoreConfigCopyValue(&m_threadContext.core->config, config->config(), "mute");
m_preload = config->getOption("preload").toInt(); m_preload = config->getOption("preload").toInt();
int playerId = m_multiplayer->playerId(this) + 1;
QVariant savePlayerId = config->getOption("savePlayerId");
if (m_multiplayer->attached() < 2 && savePlayerId.canConvert<int>()) {
playerId = savePlayerId.toInt();
}
mCoreConfigSetOverrideIntValue(&m_threadContext.core->config, "savePlayerId", playerId);
QSize sizeBefore = screenDimensions(); QSize sizeBefore = screenDimensions();
m_activeBuffer.resize(256 * 224 * sizeof(color_t)); m_activeBuffer.resize(256 * 224 * sizeof(color_t));
m_threadContext.core->setVideoBuffer(m_threadContext.core, reinterpret_cast<color_t*>(m_activeBuffer.data()), sizeBefore.width()); m_threadContext.core->setVideoBuffer(m_threadContext.core, reinterpret_cast<color_t*>(m_activeBuffer.data()), sizeBefore.width());
@ -1000,9 +993,9 @@ void CoreController::attachPrinter() {
} }
GB* gb = static_cast<GB*>(m_threadContext.core->board); GB* gb = static_cast<GB*>(m_threadContext.core->board);
clearMultiplayerController(); clearMultiplayerController();
GBPrinterCreate(&m_printer.d); GBPrinterCreate(&m_printer);
m_printer.parent = this; m_printer.parent = this;
m_printer.d.print = [](GBPrinter* printer, int height, const uint8_t* data) { m_printer.print = [](GBPrinter* printer, int height, const uint8_t* data) {
QGBPrinter* qPrinter = reinterpret_cast<QGBPrinter*>(printer); QGBPrinter* qPrinter = reinterpret_cast<QGBPrinter*>(printer);
QImage image(GB_VIDEO_HORIZONTAL_PIXELS, height, QImage::Format_Indexed8); QImage image(GB_VIDEO_HORIZONTAL_PIXELS, height, QImage::Format_Indexed8);
QVector<QRgb> colors; QVector<QRgb> colors;
@ -1023,7 +1016,7 @@ void CoreController::attachPrinter() {
QMetaObject::invokeMethod(qPrinter->parent, "imagePrinted", Q_ARG(const QImage&, image)); QMetaObject::invokeMethod(qPrinter->parent, "imagePrinted", Q_ARG(const QImage&, image));
}; };
Interrupter interrupter(this); Interrupter interrupter(this);
GBSIOSetDriver(&gb->sio, &m_printer.d.d); GBSIOSetDriver(&gb->sio, &m_printer.d);
} }
void CoreController::detachPrinter() { void CoreController::detachPrinter() {
@ -1032,7 +1025,7 @@ void CoreController::detachPrinter() {
} }
Interrupter interrupter(this); Interrupter interrupter(this);
GB* gb = static_cast<GB*>(m_threadContext.core->board); GB* gb = static_cast<GB*>(m_threadContext.core->board);
GBPrinterDonePrinting(&m_printer.d); GBPrinterDonePrinting(&m_printer);
GBSIOSetDriver(&gb->sio, nullptr); GBSIOSetDriver(&gb->sio, nullptr);
} }
@ -1041,7 +1034,7 @@ void CoreController::endPrint() {
return; return;
} }
Interrupter interrupter(this); Interrupter interrupter(this);
GBPrinterDonePrinting(&m_printer.d); GBPrinterDonePrinting(&m_printer);
} }
#endif #endif
@ -1199,7 +1192,12 @@ void CoreController::updatePlayerSave() {
int savePlayerId = 0; int savePlayerId = 0;
mCoreConfigGetIntValue(&m_threadContext.core->config, "savePlayerId", &savePlayerId); mCoreConfigGetIntValue(&m_threadContext.core->config, "savePlayerId", &savePlayerId);
if (savePlayerId == 0 || m_multiplayer->attached() > 1) { if (savePlayerId == 0 || m_multiplayer->attached() > 1) {
savePlayerId = m_multiplayer->playerId(this) + 1; if (savePlayerId == m_multiplayer->playerId(this) + 1) {
// Player 1 is using our save, so let's use theirs, at least for now.
savePlayerId = 1;
} else {
savePlayerId = m_multiplayer->playerId(this) + 1;
}
} }
QString saveSuffix; QString saveSuffix;

View File

@ -313,8 +313,7 @@ private:
VFile* m_vlVf = nullptr; VFile* m_vlVf = nullptr;
#ifdef M_CORE_GB #ifdef M_CORE_GB
struct QGBPrinter { struct QGBPrinter : public GBPrinter {
GBPrinter d;
CoreController* parent; CoreController* parent;
} m_printer; } m_printer;
#endif #endif

View File

@ -19,18 +19,18 @@ using namespace QGBA;
DebuggerConsoleController::DebuggerConsoleController(QObject* parent) DebuggerConsoleController::DebuggerConsoleController(QObject* parent)
: DebuggerController(&m_cliDebugger.d, parent) : DebuggerController(&m_cliDebugger.d, parent)
{ {
m_backend.d.printf = printf; m_backend.printf = printf;
m_backend.d.init = init; m_backend.init = init;
m_backend.d.deinit = deinit; m_backend.deinit = deinit;
m_backend.d.readline = readLine; m_backend.readline = readLine;
m_backend.d.lineAppend = lineAppend; m_backend.lineAppend = lineAppend;
m_backend.d.historyLast = historyLast; m_backend.historyLast = historyLast;
m_backend.d.historyAppend = historyAppend; m_backend.historyAppend = historyAppend;
m_backend.d.interrupt = interrupt; m_backend.interrupt = interrupt;
m_backend.self = this; m_backend.self = this;
CLIDebuggerCreate(&m_cliDebugger); CLIDebuggerCreate(&m_cliDebugger);
CLIDebuggerAttachBackend(&m_cliDebugger, &m_backend.d); CLIDebuggerAttachBackend(&m_cliDebugger, &m_backend);
} }
void DebuggerConsoleController::enterLine(const QString& line) { void DebuggerConsoleController::enterLine(const QString& line) {
@ -60,7 +60,7 @@ void DebuggerConsoleController::attachInternal() {
CoreController::Interrupter interrupter(m_gameController); CoreController::Interrupter interrupter(m_gameController);
QMutexLocker lock(&m_mutex); QMutexLocker lock(&m_mutex);
mCore* core = m_gameController->thread()->core; mCore* core = m_gameController->thread()->core;
CLIDebuggerAttachBackend(&m_cliDebugger, &m_backend.d); CLIDebuggerAttachBackend(&m_cliDebugger, &m_backend);
CLIDebuggerAttachSystem(&m_cliDebugger, core->cliDebuggerSystem(core)); CLIDebuggerAttachSystem(&m_cliDebugger, core->cliDebuggerSystem(core));
} }

View File

@ -56,8 +56,7 @@ private:
QStringList m_lines; QStringList m_lines;
QByteArray m_last; QByteArray m_last;
struct Backend { struct Backend : public CLIDebuggerBackend {
CLIDebuggerBackend d;
DebuggerConsoleController* self; DebuggerConsoleController* self;
} m_backend; } m_backend;
}; };

View File

@ -129,6 +129,7 @@ void QGBA::Display::configure(ConfigController* config) {
filter(opts->resampleVideo); filter(opts->resampleVideo);
config->updateOption("showOSD"); config->updateOption("showOSD");
config->updateOption("showFrameCounter"); config->updateOption("showFrameCounter");
config->updateOption("videoSync");
#if defined(BUILD_GL) || defined(BUILD_GLES2) || defined(BUILD_GLES3) #if defined(BUILD_GL) || defined(BUILD_GLES2) || defined(BUILD_GLES3)
if (opts->shader && supportsShaders()) { if (opts->shader && supportsShaders()) {
struct VDir* shader = VDirOpen(opts->shader); struct VDir* shader = VDirOpen(opts->shader);

View File

@ -78,6 +78,7 @@ public slots:
virtual void showOSDMessages(bool enable); virtual void showOSDMessages(bool enable);
virtual void showFrameCounter(bool enable); virtual void showFrameCounter(bool enable);
virtual void filter(bool filter); virtual void filter(bool filter);
virtual void swapInterval(int interval) = 0;
virtual void framePosted() = 0; virtual void framePosted() = 0;
virtual void setShaders(struct VDir*) = 0; virtual void setShaders(struct VDir*) = 0;
virtual void clearShaders() = 0; virtual void clearShaders() = 0;

View File

@ -38,6 +38,21 @@ using QOpenGLFunctions_Baseline = QOpenGLFunctions_3_2_Core;
#endif #endif
#endif #endif
#ifdef _WIN32
#include <windows.h>
#elif defined(Q_OS_MAC)
#include <OpenGL/OpenGL.h>
#endif
#ifdef USE_GLX
#define GLX_GLXEXT_PROTOTYPES
typedef struct _XDisplay Display;
#include <GL/glx.h>
#include <GL/glxext.h>
#endif
#ifdef USE_EGL
#include <EGL/egl.h>
#endif
#ifdef _WIN32 #ifdef _WIN32
#define OVERHEAD_NSEC 1000000 #define OVERHEAD_NSEC 1000000
#else #else
@ -48,11 +63,6 @@ using QOpenGLFunctions_Baseline = QOpenGLFunctions_3_2_Core;
using namespace QGBA; using namespace QGBA;
enum ThreadStartFrom {
START = 1,
PROXY = 2,
};
QHash<QSurfaceFormat, bool> DisplayGL::s_supports; QHash<QSurfaceFormat, bool> DisplayGL::s_supports;
uint qHash(const QSurfaceFormat& format, uint seed) { uint qHash(const QSurfaceFormat& format, uint seed) {
@ -206,11 +216,6 @@ DisplayGL::DisplayGL(const QSurfaceFormat& format, QWidget* parent)
m_drawThread.setObjectName("Painter Thread"); m_drawThread.setObjectName("Painter Thread");
m_painter->setThread(&m_drawThread); m_painter->setThread(&m_drawThread);
m_proxyThread.setObjectName("OpenGL Proxy Thread");
m_proxyContext = std::make_unique<QOpenGLContext>();
m_proxyContext->setFormat(format);
connect(m_painter.get(), &PainterGL::created, this, &DisplayGL::setupProxyThread);
connect(&m_drawThread, &QThread::started, m_painter.get(), &PainterGL::create); connect(&m_drawThread, &QThread::started, m_painter.get(), &PainterGL::create);
connect(m_painter.get(), &PainterGL::started, this, [this] { connect(m_painter.get(), &PainterGL::started, this, [this] {
m_hasStarted = true; m_hasStarted = true;
@ -225,11 +230,6 @@ DisplayGL::~DisplayGL() {
QMetaObject::invokeMethod(m_painter.get(), "destroy", Qt::BlockingQueuedConnection); QMetaObject::invokeMethod(m_painter.get(), "destroy", Qt::BlockingQueuedConnection);
m_drawThread.exit(); m_drawThread.exit();
m_drawThread.wait(); m_drawThread.wait();
if (m_proxyThread.isRunning()) {
m_proxyThread.exit();
m_proxyThread.wait();
}
} }
bool DisplayGL::supportsShaders() const { bool DisplayGL::supportsShaders() const {
@ -253,6 +253,9 @@ void DisplayGL::startDrawing(std::shared_ptr<CoreController> controller) {
m_painter->setContext(controller); m_painter->setContext(controller);
m_painter->setMessagePainter(messagePainter()); m_painter->setMessagePainter(messagePainter());
m_context = controller; m_context = controller;
if (videoProxy()) {
videoProxy()->moveToThread(&m_drawThread);
}
lockAspectRatio(isAspectRatioLocked()); lockAspectRatio(isAspectRatioLocked());
lockIntegerScaling(isIntegerScalingLocked()); lockIntegerScaling(isIntegerScalingLocked());
@ -267,15 +270,6 @@ void DisplayGL::startDrawing(std::shared_ptr<CoreController> controller) {
messagePainter()->resize(size(), devicePixelRatio()); messagePainter()->resize(size(), devicePixelRatio());
#endif #endif
startThread(ThreadStartFrom::START);
}
void DisplayGL::startThread(int from) {
m_threadStartPending |= from;
if (m_threadStartPending < 3) {
return;
}
CoreController::Interrupter interrupter(m_context); CoreController::Interrupter interrupter(m_context);
QMetaObject::invokeMethod(m_painter.get(), "start"); QMetaObject::invokeMethod(m_painter.get(), "start");
if (!m_gl) { if (!m_gl) {
@ -353,7 +347,6 @@ void DisplayGL::stopDrawing() {
hide(); hide();
} }
setUpdatesEnabled(true); setUpdatesEnabled(true);
m_threadStartPending &= ~1;
} }
m_context.reset(); m_context.reset();
} }
@ -419,6 +412,10 @@ void DisplayGL::filter(bool filter) {
QMetaObject::invokeMethod(m_painter.get(), "filter", Q_ARG(bool, filter)); QMetaObject::invokeMethod(m_painter.get(), "filter", Q_ARG(bool, filter));
} }
void DisplayGL::swapInterval(int interval) {
QMetaObject::invokeMethod(m_painter.get(), "swapInterval", Q_ARG(int, interval));
}
void DisplayGL::framePosted() { void DisplayGL::framePosted() {
m_painter->enqueue(m_context->drawContext()); m_painter->enqueue(m_context->drawContext());
QMetaObject::invokeMethod(m_painter.get(), "draw"); QMetaObject::invokeMethod(m_painter.get(), "draw");
@ -474,35 +471,11 @@ bool DisplayGL::shouldDisableUpdates() {
void DisplayGL::setVideoProxy(std::shared_ptr<VideoProxy> proxy) { void DisplayGL::setVideoProxy(std::shared_ptr<VideoProxy> proxy) {
Display::setVideoProxy(proxy); Display::setVideoProxy(proxy);
if (proxy) { if (proxy) {
proxy->moveToThread(&m_proxyThread); proxy->moveToThread(&m_drawThread);
} }
m_painter->setVideoProxy(proxy); m_painter->setVideoProxy(proxy);
} }
void DisplayGL::setupProxyThread() {
m_proxyContext->moveToThread(&m_proxyThread);
m_proxySurface.create();
connect(&m_proxyThread, &QThread::started, m_proxyContext.get(), [this]() {
m_proxyContext->setShareContext(m_painter->shareContext());
m_proxyContext->create();
m_proxyContext->makeCurrent(&m_proxySurface);
#if defined(_WIN32) && defined(USE_EPOXY)
epoxy_handle_external_wglMakeCurrent();
#endif
QMetaObject::invokeMethod(this, "startThread", Q_ARG(int, ThreadStartFrom::PROXY));
});
connect(m_painter.get(), &PainterGL::texSwapped, m_proxyContext.get(), [this]() {
if (!m_context->hardwareAccelerated()) {
return;
}
if (videoProxy()) {
videoProxy()->processData();
}
m_painter->updateFramebufferHandle();
}, Qt::BlockingQueuedConnection);
m_proxyThread.start();
}
void DisplayGL::updateContentSize() { void DisplayGL::updateContentSize() {
QMetaObject::invokeMethod(m_painter.get(), "contentSize", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QSize, m_cachedContentSize)); QMetaObject::invokeMethod(m_painter.get(), "contentSize", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QSize, m_cachedContentSize));
} }
@ -577,12 +550,6 @@ void PainterGL::create() {
gl2Backend = static_cast<mGLES2Context*>(malloc(sizeof(mGLES2Context))); gl2Backend = static_cast<mGLES2Context*>(malloc(sizeof(mGLES2Context)));
mGLES2ContextCreate(gl2Backend); mGLES2ContextCreate(gl2Backend);
m_backend = &gl2Backend->d; m_backend = &gl2Backend->d;
QOpenGLFunctions* fn = m_gl->functions();
fn->glGenTextures(m_bridgeTexes.size(), m_bridgeTexes.data());
for (auto tex : m_bridgeTexes) {
m_freeTex.enqueue(tex);
}
m_bridgeTexIn = m_freeTex.dequeue();
} }
#endif #endif
@ -650,11 +617,9 @@ void PainterGL::destroy() {
} }
makeCurrent(); makeCurrent();
#if defined(BUILD_GLES2) || defined(BUILD_GLES3) #if defined(BUILD_GLES2) || defined(BUILD_GLES3)
QOpenGLFunctions* fn = m_gl->functions();
if (m_shader.passes) { if (m_shader.passes) {
mGLES2ShaderFree(&m_shader); mGLES2ShaderFree(&m_shader);
} }
fn->glDeleteTextures(m_bridgeTexes.size(), m_bridgeTexes.data());
#endif #endif
m_backend->deinit(m_backend); m_backend->deinit(m_backend);
m_gl->doneCurrent(); m_gl->doneCurrent();
@ -686,7 +651,7 @@ void PainterGL::resizeContext() {
} }
dequeueAll(false); dequeueAll(false);
Rectangle dims = {0, 0, size.width(), size.height()}; mRectangle dims = {0, 0, size.width(), size.height()};
m_backend->setLayerDimensions(m_backend, VIDEO_LAYER_IMAGE, &dims); m_backend->setLayerDimensions(m_backend, VIDEO_LAYER_IMAGE, &dims);
recenterLayers(); recenterLayers();
} }
@ -699,26 +664,20 @@ void PainterGL::recenterLayers() {
if (!m_context) { if (!m_context) {
return; return;
} }
const static std::initializer_list<VideoLayer> centeredLayers{VIDEO_LAYER_BACKGROUND, VIDEO_LAYER_IMAGE}; const static std::initializer_list<VideoLayer> centeredLayers{VIDEO_LAYER_BACKGROUND};
Rectangle frame = {0}; int width, height;
mRectangle frame = {0};
m_backend->imageSize(m_backend, VIDEO_LAYER_IMAGE, &width, &height);
frame.width = width;
frame.height = height;
unsigned scale = std::max(1U, m_context->videoScale()); unsigned scale = std::max(1U, m_context->videoScale());
for (VideoLayer l : centeredLayers) { for (VideoLayer l : centeredLayers) {
Rectangle dims{}; mRectangle dims{};
int width, height;
m_backend->imageSize(m_backend, l, &width, &height); m_backend->imageSize(m_backend, l, &width, &height);
dims.width = width; dims.width = width * scale;
dims.height = height; dims.height = height * scale;
if (l != VIDEO_LAYER_IMAGE) { mRectangleCenter(&frame, &dims);
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); m_backend->setLayerDimensions(m_backend, l, &dims);
} }
} }
@ -764,6 +723,32 @@ void PainterGL::filter(bool filter) {
} }
} }
void PainterGL::swapInterval(int interval) {
if (!m_started) {
return;
}
m_swapInterval = interval;
#ifdef Q_OS_WIN
wglSwapIntervalEXT(interval);
#elif defined(Q_OS_MAC)
CGLSetParameter(CGLGetCurrentContext(), kCGLCPSwapInterval, &interval);
#else
#ifdef USE_GLX
if (QGuiApplication::platformName() == "xcb") {
::Display* display = glXGetCurrentDisplay();
GLXDrawable drawable = glXGetCurrentDrawable();
glXSwapIntervalEXT(display, drawable, interval);
}
#endif
#ifdef USE_EGL
if (QGuiApplication::platformName().contains("egl") || QGuiApplication::platformName() == "wayland") {
EGLDisplay display = eglGetCurrentDisplay();
eglSwapInterval(display, interval);
}
#endif
#endif
}
#ifndef GL_DEBUG_OUTPUT_SYNCHRONOUS #ifndef GL_DEBUG_OUTPUT_SYNCHRONOUS
#define GL_DEBUG_OUTPUT_SYNCHRONOUS 0x8242 #define GL_DEBUG_OUTPUT_SYNCHRONOUS 0x8242
#endif #endif
@ -785,16 +770,16 @@ void PainterGL::start() {
} }
#endif #endif
resizeContext(); resizeContext();
m_context->addFrameAction(std::bind(&PainterGL::swapTex, this));
m_buffer = nullptr; m_buffer = nullptr;
m_active = true; m_active = true;
m_started = true; m_started = true;
swapInterval(1);
emit started(); emit started();
} }
void PainterGL::draw() { void PainterGL::draw() {
if (!m_started || (m_queue.isEmpty() && m_queueTex.isEmpty())) { if (!m_started || m_queue.isEmpty()) {
return; return;
} }
@ -820,12 +805,16 @@ void PainterGL::draw() {
} }
return; return;
} }
int wantSwap = sync->audioWait || sync->videoFrameWait;
if (m_swapInterval != wantSwap) {
swapInterval(wantSwap);
}
dequeue(); dequeue();
bool forceRedraw = true; bool forceRedraw = true;
if (!m_delayTimer.isValid()) { if (!m_delayTimer.isValid()) {
m_delayTimer.start(); m_delayTimer.start();
} else { } else {
if (sync->audioWait || sync->videoFrameWait) { if (wantSwap) {
while (m_delayTimer.nsecsElapsed() + OVERHEAD_NSEC < 1000000000 / sync->fpsTarget) { while (m_delayTimer.nsecsElapsed() + OVERHEAD_NSEC < 1000000000 / sync->fpsTarget) {
QThread::usleep(500); QThread::usleep(500);
} }
@ -877,6 +866,11 @@ void PainterGL::doStop() {
} }
m_backend->clear(m_backend); m_backend->clear(m_backend);
m_backend->swap(m_backend); m_backend->swap(m_backend);
if (m_videoProxy) {
m_videoProxy->reset();
m_videoProxy->moveToThread(m_window->thread());
m_videoProxy.reset();
}
} }
void PainterGL::pause() { void PainterGL::pause() {
@ -904,33 +898,22 @@ void PainterGL::performDraw() {
} }
void PainterGL::enqueue(const uint32_t* backing) { void PainterGL::enqueue(const uint32_t* backing) {
if (!backing) {
return;
}
QMutexLocker locker(&m_mutex); QMutexLocker locker(&m_mutex);
uint32_t* buffer = nullptr; uint32_t* buffer = nullptr;
if (m_free.isEmpty()) { if (backing) {
buffer = m_queue.dequeue(); if (m_free.isEmpty()) {
} else { buffer = m_queue.dequeue();
buffer = m_free.takeLast(); } else {
} buffer = m_free.takeLast();
if (buffer) { }
QSize size = m_context->screenDimensions(); if (buffer) {
memcpy(buffer, backing, size.width() * size.height() * BYTES_PER_PIXEL); QSize size = m_context->screenDimensions();
memcpy(buffer, backing, size.width() * size.height() * BYTES_PER_PIXEL);
}
} }
m_queue.enqueue(buffer); m_queue.enqueue(buffer);
} }
void PainterGL::enqueue(GLuint tex) {
QMutexLocker locker(&m_mutex);
if (m_freeTex.isEmpty()) {
m_bridgeTexIn = m_queueTex.dequeue();
} else {
m_bridgeTexIn = m_freeTex.takeLast();
}
m_queueTex.enqueue(tex);
}
void PainterGL::dequeue() { void PainterGL::dequeue() {
QMutexLocker locker(&m_mutex); QMutexLocker locker(&m_mutex);
if (!m_queue.isEmpty()) { if (!m_queue.isEmpty()) {
@ -940,19 +923,6 @@ void PainterGL::dequeue() {
} }
m_buffer = buffer; m_buffer = buffer;
} }
if (!m_queueTex.isEmpty()) {
if (m_bridgeTexOut != std::numeric_limits<GLuint>::max()) {
m_freeTex.enqueue(m_bridgeTexOut);
}
m_bridgeTexOut = m_queueTex.dequeue();
#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
if (supportsShaders()) {
mGLES2Context* gl2Backend = reinterpret_cast<mGLES2Context*>(m_backend);
gl2Backend->tex[VIDEO_LAYER_IMAGE] = m_bridgeTexOut;
}
#endif
}
} }
void PainterGL::dequeueAll(bool keep) { void PainterGL::dequeueAll(bool keep) {
@ -973,19 +943,6 @@ void PainterGL::dequeueAll(bool keep) {
m_free.append(m_buffer); m_free.append(m_buffer);
m_buffer = nullptr; m_buffer = nullptr;
} }
m_queueTex.clear();
m_freeTex.clear();
for (auto tex : m_bridgeTexes) {
if (keep && tex == m_bridgeTexIn) {
continue;
}
m_freeTex.enqueue(tex);
}
if (!keep) {
m_bridgeTexIn = m_freeTex.dequeue();
m_bridgeTexOut = std::numeric_limits<GLuint>::max();
}
} }
void PainterGL::setVideoProxy(std::shared_ptr<VideoProxy> proxy) { void PainterGL::setVideoProxy(std::shared_ptr<VideoProxy> proxy) {
@ -1073,23 +1030,6 @@ QOpenGLContext* PainterGL::shareContext() {
} }
} }
void PainterGL::updateFramebufferHandle() {
QOpenGLFunctions* fn = m_gl->functions();
// TODO: Figure out why glFlush doesn't work here on Intel/Windows
if (glContextHasBug(OpenGLBug::CROSS_THREAD_FLUSH)) {
fn->glFinish();
} else {
fn->glFlush();
}
CoreController::Interrupter interrupter(m_context);
if (!m_context->hardwareAccelerated()) {
return;
}
enqueue(m_bridgeTexIn);
m_context->setFramebufferHandle(m_bridgeTexIn);
}
void PainterGL::setBackgroundImage(const QImage& image) { void PainterGL::setBackgroundImage(const QImage& image) {
if (!m_started) { if (!m_started) {
makeCurrent(); makeCurrent();
@ -1111,14 +1051,4 @@ void PainterGL::setBackgroundImage(const QImage& image) {
} }
} }
void PainterGL::swapTex() {
if (!m_started) {
return;
}
CoreController::Interrupter interrupter(m_context);
emit texSwapped();
m_context->addFrameAction(std::bind(&PainterGL::swapTex, this));
}
#endif #endif

View File

@ -37,7 +37,7 @@
#include "CoreController.h" #include "CoreController.h"
#include "VideoProxy.h" #include "VideoProxy.h"
#include "platform/video-backend.h" #include <mgba/feature/video-backend.h>
class QOpenGLPaintDevice; class QOpenGLPaintDevice;
class QOpenGLWidget; class QOpenGLWidget;
@ -109,6 +109,7 @@ public slots:
void showOSDMessages(bool enable) override; void showOSDMessages(bool enable) override;
void showFrameCounter(bool enable) override; void showFrameCounter(bool enable) override;
void filter(bool filter) override; void filter(bool filter) override;
void swapInterval(int interval) override;
void framePosted() override; void framePosted() override;
void setShaders(struct VDir*) override; void setShaders(struct VDir*) override;
void clearShaders() override; void clearShaders() override;
@ -121,8 +122,6 @@ protected:
virtual void resizeEvent(QResizeEvent*) override; virtual void resizeEvent(QResizeEvent*) override;
private slots: private slots:
void startThread(int);
void setupProxyThread();
void updateContentSize(); void updateContentSize();
private: private:
@ -133,14 +132,10 @@ private:
bool m_isDrawing = false; bool m_isDrawing = false;
bool m_hasStarted = false; bool m_hasStarted = false;
int m_threadStartPending = 0;
std::unique_ptr<PainterGL> m_painter; std::unique_ptr<PainterGL> m_painter;
QThread m_drawThread; QThread m_drawThread;
QThread m_proxyThread;
std::shared_ptr<CoreController> m_context; std::shared_ptr<CoreController> m_context;
mGLWidget* m_gl; mGLWidget* m_gl;
QOffscreenSurface m_proxySurface;
std::unique_ptr<QOpenGLContext> m_proxyContext;
QSize m_cachedContentSize; QSize m_cachedContentSize;
}; };
@ -155,7 +150,6 @@ public:
void setContext(std::shared_ptr<CoreController>); void setContext(std::shared_ptr<CoreController>);
void setMessagePainter(MessagePainter*); void setMessagePainter(MessagePainter*);
void enqueue(const uint32_t* backing); void enqueue(const uint32_t* backing);
void enqueue(GLuint tex);
void stop(); void stop();
@ -167,9 +161,6 @@ public:
void setVideoProxy(std::shared_ptr<VideoProxy>); void setVideoProxy(std::shared_ptr<VideoProxy>);
void interrupt(); void interrupt();
// Run on main thread
void swapTex();
public slots: public slots:
void create(); void create();
void destroy(); void destroy();
@ -186,8 +177,8 @@ public slots:
void showOSD(bool enable); void showOSD(bool enable);
void showFrameCounter(bool enable); void showFrameCounter(bool enable);
void filter(bool filter); void filter(bool filter);
void swapInterval(int interval);
void resizeContext(); void resizeContext();
void updateFramebufferHandle();
void setBackgroundImage(const QImage&); void setBackgroundImage(const QImage&);
void setShaders(struct VDir*); void setShaders(struct VDir*);
@ -215,13 +206,6 @@ private:
QQueue<uint32_t*> m_queue; QQueue<uint32_t*> m_queue;
uint32_t* m_buffer = nullptr; uint32_t* m_buffer = nullptr;
std::array<GLuint, 3> m_bridgeTexes;
QQueue<GLuint> m_freeTex;
QQueue<GLuint> m_queueTex;
GLuint m_bridgeTexIn = std::numeric_limits<GLuint>::max();
GLuint m_bridgeTexOut = std::numeric_limits<GLuint>::max();
QPainter m_painter; QPainter m_painter;
QMutex m_mutex; QMutex m_mutex;
QWindow* m_window; QWindow* m_window;
@ -248,6 +232,7 @@ private:
MessagePainter* m_messagePainter = nullptr; MessagePainter* m_messagePainter = nullptr;
QElapsedTimer m_delayTimer; QElapsedTimer m_delayTimer;
std::shared_ptr<VideoProxy> m_videoProxy; std::shared_ptr<VideoProxy> m_videoProxy;
int m_swapInterval = -1;
}; };
} }

View File

@ -32,6 +32,7 @@ public slots:
void lockAspectRatio(bool lock) override; void lockAspectRatio(bool lock) override;
void lockIntegerScaling(bool lock) override; void lockIntegerScaling(bool lock) override;
void interframeBlending(bool enable) override; void interframeBlending(bool enable) override;
void swapInterval(int) override {};
void filter(bool filter) override; void filter(bool filter) override;
void framePosted() override; void framePosted() override;
void setShaders(struct VDir*) override {} void setShaders(struct VDir*) override {}

View File

@ -548,6 +548,11 @@ void FrameView::newVl() {
m_vl->deinit(m_vl); m_vl->deinit(m_vl);
} }
m_vl = mCoreFindVF(m_currentFrame); m_vl = mCoreFindVF(m_currentFrame);
if (!m_vl) {
m_currentFrame->close(m_currentFrame);
m_currentFrame = nullptr;
return;
}
m_vl->init(m_vl); m_vl->init(m_vl);
m_vl->loadROM(m_vl, m_currentFrame); m_vl->loadROM(m_vl, m_currentFrame);
m_currentFrame = nullptr; m_currentFrame = nullptr;

View File

@ -9,7 +9,7 @@
#include "GBAApp.h" #include "GBAApp.h"
#include "LogController.h" #include "LogController.h"
#include <mgba-util/png-io.h> #include <mgba-util/image/png-io.h>
#include <mgba-util/vfs.h> #include <mgba-util/vfs.h>
#ifdef M_CORE_GBA #ifdef M_CORE_GBA
#include <mgba/internal/gba/gba.h> #include <mgba/internal/gba/gba.h>

View File

@ -19,7 +19,7 @@
#ifdef M_CORE_GB #ifdef M_CORE_GB
#include <mgba/internal/gb/gb.h> #include <mgba/internal/gb/gb.h>
#endif #endif
#include <mgba-util/export.h> #include <mgba-util/image/export.h>
#include <mgba-util/vfs.h> #include <mgba-util/vfs.h>
using namespace QGBA; using namespace QGBA;
@ -145,9 +145,9 @@ void PaletteView::exportPalette(int start, int length) {
return; return;
} }
if (filename.endsWith(".pal", Qt::CaseInsensitive)) { if (filename.endsWith(".pal", Qt::CaseInsensitive)) {
exportPaletteRIFF(vf, length, &static_cast<GBA*>(m_controller->thread()->core->board)->video.palette[start]); mPaletteExportRIFF(vf, length, &static_cast<GBA*>(m_controller->thread()->core->board)->video.palette[start]);
} else if (filename.endsWith(".act", Qt::CaseInsensitive)) { } else if (filename.endsWith(".act", Qt::CaseInsensitive)) {
exportPaletteACT(vf, length, &static_cast<GBA*>(m_controller->thread()->core->board)->video.palette[start]); mPaletteExportACT(vf, length, &static_cast<GBA*>(m_controller->thread()->core->board)->video.palette[start]);
} }
vf->close(vf); vf->close(vf);
} }

View File

@ -15,7 +15,7 @@
#include <mgba/core/cheats.h> #include <mgba/core/cheats.h>
#include <mgba/core/serialize.h> #include <mgba/core/serialize.h>
#include <mgba/core/version.h> #include <mgba/core/version.h>
#include <mgba-util/png-io.h> #include <mgba-util/image/png-io.h>
#include <mgba-util/vfs.h> #include <mgba-util/vfs.h>
#include "CoreController.h" #include "CoreController.h"
@ -127,6 +127,7 @@ void ReportView::generateReport() {
swReport << QString("Build architecture: %1").arg(QSysInfo::buildCpuArchitecture()); swReport << QString("Build architecture: %1").arg(QSysInfo::buildCpuArchitecture());
swReport << QString("Run architecture: %1").arg(QSysInfo::currentCpuArchitecture()); swReport << QString("Run architecture: %1").arg(QSysInfo::currentCpuArchitecture());
swReport << QString("Qt version: %1").arg(QLatin1String(qVersion())); swReport << QString("Qt version: %1").arg(QLatin1String(qVersion()));
swReport << QString("Qt QPA platform: %1").arg(QGuiApplication::platformName());
#ifdef USE_FFMPEG #ifdef USE_FFMPEG
QStringList libavVers; QStringList libavVers;
libavVers << QLatin1String(LIBAVCODEC_IDENT); libavVers << QLatin1String(LIBAVCODEC_IDENT);

View File

@ -483,6 +483,7 @@ void SettingsView::updateConfig() {
saveSetting("fastForwardMute", m_ui.muteFf); saveSetting("fastForwardMute", m_ui.muteFf);
saveSetting("rewindEnable", m_ui.rewind); saveSetting("rewindEnable", m_ui.rewind);
saveSetting("rewindBufferCapacity", m_ui.rewindCapacity); saveSetting("rewindBufferCapacity", m_ui.rewindCapacity);
saveSetting("rewindBufferInterval", m_ui.rewindBufferInterval);
saveSetting("resampleVideo", m_ui.resampleVideo); saveSetting("resampleVideo", m_ui.resampleVideo);
saveSetting("allowOpposingDirections", m_ui.allowOpposingDirections); saveSetting("allowOpposingDirections", m_ui.allowOpposingDirections);
saveSetting("suspendScreensaver", m_ui.suspendScreensaver); saveSetting("suspendScreensaver", m_ui.suspendScreensaver);
@ -716,6 +717,7 @@ void SettingsView::reloadConfig() {
loadSetting("fastForwardMute", m_ui.muteFf, m_ui.mute->isChecked()); loadSetting("fastForwardMute", m_ui.muteFf, m_ui.mute->isChecked());
loadSetting("rewindEnable", m_ui.rewind); loadSetting("rewindEnable", m_ui.rewind);
loadSetting("rewindBufferCapacity", m_ui.rewindCapacity); loadSetting("rewindBufferCapacity", m_ui.rewindCapacity);
loadSetting("rewindBufferInterval", m_ui.rewindBufferInterval);
loadSetting("resampleVideo", m_ui.resampleVideo); loadSetting("resampleVideo", m_ui.resampleVideo);
loadSetting("allowOpposingDirections", m_ui.allowOpposingDirections); loadSetting("allowOpposingDirections", m_ui.allowOpposingDirections);
loadSetting("suspendScreensaver", m_ui.suspendScreensaver); loadSetting("suspendScreensaver", m_ui.suspendScreensaver);

View File

@ -1195,21 +1195,51 @@
</item> </item>
</layout> </layout>
</item> </item>
<item row="6" column="0" colspan="2"> <item row="6" column="0">
<widget class="QLabel" name="label_54">
<property name="text">
<string>Rewind speed:</string>
</property>
</widget>
</item>
<item row="6" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_23">
<item>
<widget class="QSpinBox" name="rewindBufferInterval">
<property name="suffix">
<string notr="true">×</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>10</number>
</property>
<property name="singleStep">
<number>1</number>
</property>
<property name="value">
<number>1</number>
</property>
</widget>
</item>
</layout>
</item>
<item row="7" column="0" colspan="2">
<widget class="Line" name="line_3"> <widget class="Line" name="line_3">
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
</property> </property>
</widget> </widget>
</item> </item>
<item row="7" column="0"> <item row="8" column="0">
<widget class="QLabel" name="label_15"> <widget class="QLabel" name="label_15">
<property name="text"> <property name="text">
<string>Idle loops:</string> <string>Idle loops:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="7" column="1"> <item row="8" column="1">
<widget class="QComboBox" name="idleOptimization"> <widget class="QComboBox" name="idleOptimization">
<item> <item>
<property name="text"> <property name="text">
@ -1228,21 +1258,21 @@
</item> </item>
</widget> </widget>
</item> </item>
<item row="8" column="1"> <item row="9" column="1">
<widget class="QCheckBox" name="preload"> <widget class="QCheckBox" name="preload">
<property name="text"> <property name="text">
<string>Preload entire ROM into memory</string> <string>Preload entire ROM into memory</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="9" column="1"> <item row="10" column="1">
<widget class="QCheckBox" name="forceGbp"> <widget class="QCheckBox" name="forceGbp">
<property name="text"> <property name="text">
<string>Enable Game Boy Player features by default</string> <string>Enable Game Boy Player features by default</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="10" column="1"> <item row="11" column="1">
<widget class="QCheckBox" name="vbaBugCompat"> <widget class="QCheckBox" name="vbaBugCompat">
<property name="text"> <property name="text">
<string>Enable VBA bug compatibility in ROM hacks</string> <string>Enable VBA bug compatibility in ROM hacks</string>

View File

@ -19,8 +19,8 @@
#include <QSpinBox> #include <QSpinBox>
#include <mgba/core/version.h> #include <mgba/core/version.h>
#include <mgba/feature/video-backend.h>
#include <mgba-util/vfs.h> #include <mgba-util/vfs.h>
#include "platform/video-backend.h"
#if defined(BUILD_GL) || defined(BUILD_GLES2) #if defined(BUILD_GL) || defined(BUILD_GLES2)

View File

@ -12,39 +12,40 @@
using namespace QGBA; using namespace QGBA;
VideoProxy::VideoProxy() { VideoProxy::VideoProxy() {
mVideoLoggerRendererCreate(&m_logger.d, false); mVideoLoggerRendererCreate(&m_logger, false);
m_logger.d.block = true; m_logger.p = this;
m_logger.d.waitOnFlush = true; m_logger.block = true;
m_logger.waitOnFlush = true;
m_logger.d.init = &cbind<&VideoProxy::init>; m_logger.init = &cbind<&VideoProxy::init>;
m_logger.d.reset = &cbind<&VideoProxy::reset>; m_logger.reset = &cbind<&VideoProxy::reset>;
m_logger.d.deinit = &cbind<&VideoProxy::deinit>; m_logger.deinit = &cbind<&VideoProxy::deinit>;
m_logger.d.lock = &cbind<&VideoProxy::lock>; m_logger.lock = &cbind<&VideoProxy::lock>;
m_logger.d.unlock = &cbind<&VideoProxy::unlock>; m_logger.unlock = &cbind<&VideoProxy::unlock>;
m_logger.d.wait = &cbind<&VideoProxy::wait>; m_logger.wait = &cbind<&VideoProxy::wait>;
m_logger.d.wake = &callback<void, int>::func<&VideoProxy::wake>; m_logger.wake = &callback<void, int>::func<&VideoProxy::wake>;
m_logger.d.writeData = &callback<bool, const void*, size_t>::func<&VideoProxy::writeData>; m_logger.writeData = &callback<bool, const void*, size_t>::func<&VideoProxy::writeData>;
m_logger.d.readData = &callback<bool, void*, size_t, bool>::func<&VideoProxy::readData>; m_logger.readData = &callback<bool, void*, size_t, bool>::func<&VideoProxy::readData>;
m_logger.d.postEvent = &callback<void, enum mVideoLoggerEvent>::func<&VideoProxy::postEvent>; m_logger.postEvent = &callback<void, enum mVideoLoggerEvent>::func<&VideoProxy::postEvent>;
connect(this, &VideoProxy::dataAvailable, this, &VideoProxy::processData); connect(this, &VideoProxy::dataAvailable, this, &VideoProxy::processData);
} }
void VideoProxy::attach(CoreController* controller) { void VideoProxy::attach(CoreController* controller) {
CoreController::Interrupter interrupter(controller); CoreController::Interrupter interrupter(controller);
controller->thread()->core->videoLogger = &m_logger.d; controller->thread()->core->videoLogger = &m_logger;
} }
void VideoProxy::detach(CoreController* controller) { void VideoProxy::detach(CoreController* controller) {
CoreController::Interrupter interrupter(controller); CoreController::Interrupter interrupter(controller);
if (controller->thread()->core->videoLogger == &m_logger.d) { if (controller->thread()->core->videoLogger == &m_logger) {
controller->thread()->core->videoLogger = nullptr; controller->thread()->core->videoLogger = nullptr;
} }
} }
void VideoProxy::processData() { void VideoProxy::processData() {
mVideoLoggerRendererRun(&m_logger.d, false); mVideoLoggerRendererRun(&m_logger, false);
m_fromThreadCond.wakeAll(); m_fromThreadCond.wakeAll();
} }
@ -67,7 +68,7 @@ bool VideoProxy::writeData(const void* data, size_t length) {
while (!RingFIFOWrite(&m_dirtyQueue, data, length)) { while (!RingFIFOWrite(&m_dirtyQueue, data, length)) {
if (QThread::currentThread() == thread()) { if (QThread::currentThread() == thread()) {
// We're on the main thread // We're on the main thread
mVideoLoggerRendererRun(&m_logger.d, false); mVideoLoggerRendererRun(&m_logger, false);
} else { } else {
emit dataAvailable(); emit dataAvailable();
m_mutex.lock(); m_mutex.lock();
@ -105,7 +106,7 @@ void VideoProxy::postEvent(enum mVideoLoggerEvent event) {
void VideoProxy::handleEvent(int event) { void VideoProxy::handleEvent(int event) {
m_mutex.lock(); m_mutex.lock();
m_logger.d.handleEvent(&m_logger.d, static_cast<enum mVideoLoggerEvent>(event)); m_logger.handleEvent(&m_logger, static_cast<enum mVideoLoggerEvent>(event));
m_mutex.unlock(); m_mutex.unlock();
} }
@ -122,7 +123,7 @@ void VideoProxy::wait() {
while (RingFIFOSize(&m_dirtyQueue)) { while (RingFIFOSize(&m_dirtyQueue)) {
if (QThread::currentThread() == thread()) { if (QThread::currentThread() == thread()) {
// We're on the main thread // We're on the main thread
mVideoLoggerRendererRun(&m_logger.d, false); mVideoLoggerRendererRun(&m_logger, false);
} else { } else {
emit dataAvailable(); emit dataAvailable();
m_toThreadCond.wakeAll(); m_toThreadCond.wakeAll();

View File

@ -24,7 +24,7 @@ public:
void attach(CoreController*); void attach(CoreController*);
void detach(CoreController*); void detach(CoreController*);
void setBlocking(bool block) { m_logger.d.waitOnFlush = block; } void setBlocking(bool block) { m_logger.waitOnFlush = block; }
signals: signals:
void dataAvailable(); void dataAvailable();
@ -51,17 +51,16 @@ private:
using type = T (VideoProxy::*)(A...); using type = T (VideoProxy::*)(A...);
template<type F> static T func(mVideoLogger* logger, A... args) { template<type F> static T func(mVideoLogger* logger, A... args) {
VideoProxy* proxy = reinterpret_cast<Logger*>(logger)->p; VideoProxy* proxy = static_cast<Logger*>(logger)->p;
return (proxy->*F)(args...); return (proxy->*F)(args...);
} }
}; };
template<void (VideoProxy::*F)()> static void cbind(mVideoLogger* logger) { callback<void>::func<F>(logger); } template<void (VideoProxy::*F)()> static void cbind(mVideoLogger* logger) { callback<void>::func<F>(logger); }
struct Logger { struct Logger : public mVideoLogger {
mVideoLogger d;
VideoProxy* p; VideoProxy* p;
} m_logger = {{}, this}; } m_logger;
RingFIFO m_dirtyQueue; RingFIFO m_dirtyQueue;
QMutex m_mutex; QMutex m_mutex;

View File

@ -1493,6 +1493,20 @@ void Window::setupMenu(QMenuBar* menubar) {
} }
m_config->updateOption("fastForwardRatio"); m_config->updateOption("fastForwardRatio");
addGameAction(tr("Increase fast forward speed"), "fastForwardUp", [this] {
float newRatio = m_config->getOption("fastForwardRatio", 1.0f).toFloat() + 1.0f;
if (newRatio >= 3.0f) {
m_config->setOption("fastForwardRatio", QVariant(newRatio));
}
}, "emu");
addGameAction(tr("Decrease fast forward speed"), "fastForwardDown", [this] {
float newRatio = m_config->getOption("fastForwardRatio").toFloat() - 1.0f;
if (newRatio >= 2.0f) {
m_config->setOption("fastForwardRatio", QVariant(newRatio));
}
}, "emu");
Action* rewindHeld = m_actions.addHeldAction(tr("Rewind (held)"), "holdRewind", [this](bool held) { Action* rewindHeld = m_actions.addHeldAction(tr("Rewind (held)"), "holdRewind", [this](bool held) {
if (m_controller) { if (m_controller) {
m_controller->setRewinding(held); m_controller->setRewinding(held);
@ -1634,7 +1648,8 @@ void Window::setupMenu(QMenuBar* menubar) {
m_actions.addSeparator("av"); m_actions.addSeparator("av");
ConfigOption* mute = m_config->addOption("mute"); ConfigOption* mute = m_config->addOption("mute");
mute->addBoolean(tr("Mute"), &m_actions, "av"); Action* muteAction = mute->addBoolean(tr("Mute"), &m_actions, "av");
muteAction->setActive(m_config->getOption("mute").toInt());
mute->connect([this](const QVariant& value) { mute->connect([this](const QVariant& value) {
m_config->setOption("fastForwardMute", static_cast<bool>(value.toInt())); m_config->setOption("fastForwardMute", static_cast<bool>(value.toInt()));
reloadConfig(); reloadConfig();
@ -1774,7 +1789,7 @@ void Window::setupMenu(QMenuBar* menubar) {
void Window::setupOptions() { void Window::setupOptions() {
ConfigOption* videoSync = m_config->addOption("videoSync"); ConfigOption* videoSync = m_config->addOption("videoSync");
videoSync->connect([this](const QVariant&) { videoSync->connect([this](const QVariant& variant) {
reloadConfig(); reloadConfig();
}, this); }, this);
@ -1828,6 +1843,11 @@ void Window::setupOptions() {
reloadConfig(); reloadConfig();
}, this); }, this);
ConfigOption* rewindBufferInterval = m_config->addOption("rewindBufferInterval");
rewindBufferInterval->connect([this](const QVariant&) {
reloadConfig();
}, this);
ConfigOption* allowOpposingDirections = m_config->addOption("allowOpposingDirections"); ConfigOption* allowOpposingDirections = m_config->addOption("allowOpposingDirections");
allowOpposingDirections->connect([this](const QVariant&) { allowOpposingDirections->connect([this](const QVariant&) {
reloadConfig(); reloadConfig();

View File

@ -19,8 +19,7 @@
#include "scripting/ScriptingTextBuffer.h" #include "scripting/ScriptingTextBuffer.h"
#include "scripting/ScriptingTextBufferModel.h" #include "scripting/ScriptingTextBufferModel.h"
#include <mgba/script/input.h> #include <mgba/script.h>
#include <mgba/script/storage.h>
#include <mgba-util/math.h> #include <mgba-util/math.h>
#include <mgba-util/string.h> #include <mgba-util/string.h>

View File

@ -10,7 +10,7 @@
#include <mgba/core/version.h> #include <mgba/core/version.h>
#ifdef USE_PNG #ifdef USE_PNG
#include <mgba-util/png-io.h> #include <mgba-util/image/png-io.h>
#include <mgba-util/vfs.h> #include <mgba-util/vfs.h>
#endif #endif
@ -66,7 +66,7 @@ bool mSDLGLCommonLoadBackground(struct VideoBackend* context) {
goto done; goto done;
} }
struct Rectangle dims = { struct mRectangle dims = {
.width = width, .width = width,
.height = height .height = height
}; };
@ -136,13 +136,13 @@ void mSDLGLCommonRunloop(struct mSDLRenderer* renderer, void* user) {
if (mSDLGLCommonLoadBackground(v)) { if (mSDLGLCommonLoadBackground(v)) {
renderer->player.windowUpdated = true; renderer->player.windowUpdated = true;
struct Rectangle frame; struct mRectangle frame;
VideoBackendGetFrame(v, &frame); v->layerDimensions(v, VIDEO_LAYER_IMAGE, &frame);
int i; int i;
for (i = 0; i <= VIDEO_LAYER_IMAGE; ++i) { for (i = 0; i < VIDEO_LAYER_IMAGE; ++i) {
struct Rectangle dims; struct mRectangle dims;
v->layerDimensions(v, i, &dims); v->layerDimensions(v, i, &dims);
RectangleCenter(&frame, &dims); mRectangleCenter(&frame, &dims);
v->setLayerDimensions(v, i, &dims); v->setLayerDimensions(v, i, &dims);
} }
@ -168,7 +168,7 @@ void mSDLGLCommonRunloop(struct mSDLRenderer* renderer, void* user) {
} }
} }
renderer->core->currentVideoSize(renderer->core, &renderer->width, &renderer->height); renderer->core->currentVideoSize(renderer->core, &renderer->width, &renderer->height);
struct Rectangle dims; struct mRectangle dims;
v->layerDimensions(v, VIDEO_LAYER_IMAGE, &dims); v->layerDimensions(v, VIDEO_LAYER_IMAGE, &dims);
if (renderer->width != dims.width || renderer->height != dims.height) { if (renderer->width != dims.width || renderer->height != dims.height) {
renderer->core->setVideoBuffer(renderer->core, renderer->outputBuffer, renderer->width); renderer->core->setVideoBuffer(renderer->core, renderer->outputBuffer, renderer->width);

View File

@ -35,7 +35,7 @@ bool mSDLGLInit(struct mSDLRenderer* renderer) {
renderer->gl.d.filter = renderer->filter; renderer->gl.d.filter = renderer->filter;
renderer->gl.d.swap = mSDLGLCommonSwap; renderer->gl.d.swap = mSDLGLCommonSwap;
renderer->gl.d.init(&renderer->gl.d, 0); renderer->gl.d.init(&renderer->gl.d, 0);
struct Rectangle dims = { struct mRectangle dims = {
.x = 0, .x = 0,
.y = 0, .y = 0,
.width = renderer->width, .width = renderer->width,

View File

@ -50,7 +50,7 @@ bool mSDLGLES2Init(struct mSDLRenderer* renderer) {
#endif #endif
renderer->gl2.d.init(&renderer->gl2.d, 0); renderer->gl2.d.init(&renderer->gl2.d, 0);
struct Rectangle dims = { struct mRectangle dims = {
.x = 0, .x = 0,
.y = 0, .y = 0,
.width = renderer->width, .width = renderer->width,

View File

@ -61,6 +61,7 @@ int main(int argc, char** argv) {
.useBios = true, .useBios = true,
.rewindEnable = true, .rewindEnable = true,
.rewindBufferCapacity = 600, .rewindBufferCapacity = 600,
.rewindBufferInterval = 1,
.audioBuffers = 1024, .audioBuffers = 1024,
.videoSync = false, .videoSync = false,
.audioSync = true, .audioSync = true,

View File

@ -5,7 +5,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <mgba-util/gui/font.h> #include <mgba-util/gui/font.h>
#include <mgba-util/gui/font-metrics.h> #include <mgba-util/gui/font-metrics.h>
#include <mgba-util/png-io.h> #include <mgba-util/image/png-io.h>
#include <mgba-util/string.h> #include <mgba-util/string.h>
#include <mgba-util/vfs.h> #include <mgba-util/vfs.h>

View File

@ -10,7 +10,7 @@
#include <mgba/feature/commandline.h> #include <mgba/feature/commandline.h>
#include <mgba/feature/video-logger.h> #include <mgba/feature/video-logger.h>
#include <mgba-util/png-io.h> #include <mgba-util/image/png-io.h>
#include <mgba-util/string.h> #include <mgba-util/string.h>
#include <mgba-util/table.h> #include <mgba-util/table.h>
#include <mgba-util/threading.h> #include <mgba-util/threading.h>
@ -732,8 +732,8 @@ static struct VDir* _makeOutDir(const char* testName) {
static void _writeImage(struct VFile* vf, const struct CInemaImage* image) { static void _writeImage(struct VFile* vf, const struct CInemaImage* image) {
png_structp png = PNGWriteOpen(vf); png_structp png = PNGWriteOpen(vf);
png_infop info = PNGWriteHeader(png, image->width, image->height); png_infop info = PNGWriteHeader(png, image->width, image->height, mCOLOR_NATIVE);
if (!PNGWritePixels(png, image->width, image->height, image->stride, image->data)) { if (!PNGWritePixels(png, image->width, image->height, image->stride, image->data, mCOLOR_NATIVE)) {
CIerr(0, "Could not write output image\n"); CIerr(0, "Could not write output image\n");
} }
PNGWriteClose(png, info); PNGWriteClose(png, info);

View File

@ -2,6 +2,7 @@ include(ExportDirectory)
set(SOURCE_FILES set(SOURCE_FILES
context.c context.c
input.c input.c
image.c
socket.c socket.c
stdlib.c stdlib.c
types.c) types.c)
@ -18,6 +19,7 @@ if(USE_LUA)
list(APPEND SOURCE_FILES engines/lua.c) list(APPEND SOURCE_FILES engines/lua.c)
list(APPEND TEST_FILES list(APPEND TEST_FILES
test/context.c test/context.c
test/image.c
test/input.c test/input.c
test/lua.c test/lua.c
test/stdlib.c) test/stdlib.c)

View File

@ -356,6 +356,7 @@ void mScriptEngineExportDocNamespace(struct mScriptEngineContext* ctx, const cha
struct mScriptValue* key = mScriptStringCreateFromUTF8(values[i].key); struct mScriptValue* key = mScriptStringCreateFromUTF8(values[i].key);
mScriptTableInsert(table, key, values[i].value); mScriptTableInsert(table, key, values[i].value);
mScriptValueDeref(key); mScriptValueDeref(key);
mScriptValueDeref(values[i].value);
} }
HashTableInsert(&ctx->docroot, nspace, table); HashTableInsert(&ctx->docroot, nspace, table);
} }

View File

@ -7,9 +7,7 @@
#include <mgba/core/scripting.h> #include <mgba/core/scripting.h>
#include <mgba/core/version.h> #include <mgba/core/version.h>
#include <mgba/internal/script/types.h> #include <mgba/internal/script/types.h>
#include <mgba/script/context.h> #include <mgba/script.h>
#include <mgba/script/input.h>
#include <mgba/script/storage.h>
#include <mgba-util/string.h> #include <mgba-util/string.h>
struct mScriptContext context; struct mScriptContext context;
@ -188,6 +186,9 @@ void explainClass(struct mScriptTypeClass* cls, int level) {
} }
docstring = NULL; docstring = NULL;
} }
if (details->info.member.readonly) {
fprintf(out, "%s readonly: true\n", indent);
}
fprintf(out, "%s type: %s\n", indent, details->info.member.type->name); fprintf(out, "%s type: %s\n", indent, details->info.member.type->name);
break; break;
case mSCRIPT_CLASS_INIT_END: case mSCRIPT_CLASS_INIT_END:
@ -469,9 +470,10 @@ int main(int argc, char* argv[]) {
mScriptContextInit(&context); mScriptContextInit(&context);
mScriptContextAttachStdlib(&context); mScriptContextAttachStdlib(&context);
mScriptContextAttachImage(&context);
mScriptContextAttachInput(&context);
mScriptContextAttachSocket(&context); mScriptContextAttachSocket(&context);
mScriptContextAttachStorage(&context); mScriptContextAttachStorage(&context);
mScriptContextAttachInput(&context);
mScriptContextSetTextBufferFactory(&context, NULL, NULL); mScriptContextSetTextBufferFactory(&context, NULL, NULL);
initTypes(); initTypes();

View File

@ -60,6 +60,7 @@ static int _luaGetList(lua_State* lua);
static int _luaLenList(lua_State* lua); static int _luaLenList(lua_State* lua);
static int _luaRequireShim(lua_State* lua); static int _luaRequireShim(lua_State* lua);
static int _luaPrintShim(lua_State* lua);
static const char* _socketLuaSource = static const char* _socketLuaSource =
"socket = {\n" "socket = {\n"
@ -101,7 +102,7 @@ static const char* _socketLuaSource =
" local cbid = self._nextCallback\n" " local cbid = self._nextCallback\n"
" self._nextCallback = cbid + 1\n" " self._nextCallback = cbid + 1\n"
" self._callbacks[event][cbid] = callback\n" " self._callbacks[event][cbid] = callback\n"
" return id\n" " return cbid\n"
" end,\n" " end,\n"
" remove = function(self, cbid)\n" " remove = function(self, cbid)\n"
" for _, group in pairs(self._callbacks) do\n" " for _, group in pairs(self._callbacks) do\n"
@ -414,6 +415,14 @@ struct mScriptEngineContext* _luaCreate(struct mScriptEngine2* engine, struct mS
lua_getglobal(luaContext->lua, "require"); lua_getglobal(luaContext->lua, "require");
luaContext->require = luaL_ref(luaContext->lua, LUA_REGISTRYINDEX); luaContext->require = luaL_ref(luaContext->lua, LUA_REGISTRYINDEX);
lua_pushliteral(luaContext->lua, "log");
lua_pushcclosure(luaContext->lua, _luaPrintShim, 1);
lua_setglobal(luaContext->lua, "print");
lua_pushliteral(luaContext->lua, "warn");
lua_pushcclosure(luaContext->lua, _luaPrintShim, 1);
lua_setglobal(luaContext->lua, "warn");
HashTableInit(&luaContext->d.docroot, 0, (void (*)(void*)) mScriptValueDeref); HashTableInit(&luaContext->d.docroot, 0, (void (*)(void*)) mScriptValueDeref);
int status = luaL_dostring(luaContext->lua, _socketLuaSource); int status = luaL_dostring(luaContext->lua, _socketLuaSource);
@ -449,7 +458,6 @@ struct mScriptEngineContext* _luaCreate(struct mScriptEngine2* engine, struct mS
mSCRIPT_KV_PAIR(connect, mSCRIPT_VALUE_DOC_FUNCTION(socket_connect)), mSCRIPT_KV_PAIR(connect, mSCRIPT_VALUE_DOC_FUNCTION(socket_connect)),
mSCRIPT_KV_SENTINEL mSCRIPT_KV_SENTINEL
}); });
mScriptValueDeref(errors);
mScriptEngineSetDocstring(&luaContext->d, "socket", "A basic TCP socket library"); mScriptEngineSetDocstring(&luaContext->d, "socket", "A basic TCP socket library");
mScriptEngineSetDocstring(&luaContext->d, "socket.ERRORS", mScriptEngineSetDocstring(&luaContext->d, "socket.ERRORS",
"Error strings corresponding to the C.SOCKERR error codes, indexed both by name and by value"); "Error strings corresponding to the C.SOCKERR error codes, indexed both by name and by value");
@ -464,6 +472,16 @@ struct mScriptEngineContext* _luaCreate(struct mScriptEngine2* engine, struct mS
"the connection either succeeds or fails"); "the connection either succeeds or fails");
} }
mScriptEngineExportDocNamespace(&luaContext->d, "script", (struct mScriptKVPair[]) {
mSCRIPT_KV_PAIR(dir, mScriptStringCreateFromASCII("/")),
mSCRIPT_KV_PAIR(path, mScriptStringCreateFromASCII("/lua")),
mSCRIPT_KV_SENTINEL
});
mScriptEngineSetDocstring(&luaContext->d, "script", "Information about the currently loaded script");
mScriptEngineSetDocstring(&luaContext->d, "script.dir", "The path to the directory containing the script");
mScriptEngineSetDocstring(&luaContext->d, "script.path", "The path of the current script file");
return &luaContext->d; return &luaContext->d;
} }
@ -621,6 +639,14 @@ struct mScriptValue* _luaCoerceTable(struct mScriptEngineContextLua* luaContext,
mScriptValueDeref(list); mScriptValueDeref(list);
return table; return table;
} }
for (i = 0; i < mScriptListSize(list->value.list); ++i) {
struct mScriptValue* value = mScriptListGetPointer(list->value.list, i);
if (value->type->base != mSCRIPT_TYPE_WRAPPER) {
continue;
}
value = mScriptValueUnwrap(value);
mScriptValueRef(value);
}
mScriptValueDeref(table); mScriptValueDeref(table);
return list; return list;
} }
@ -885,7 +911,9 @@ bool _luaLoad(struct mScriptEngineContext* ctx, const char* filename, struct VFi
luaContext->lastError = NULL; luaContext->lastError = NULL;
} }
char name[PATH_MAX + 1]; char name[PATH_MAX + 1];
char dirname[PATH_MAX] = {0}; char dirname[PATH_MAX];
name[0] = '\0';
dirname[0] = '\0';
if (filename) { if (filename) {
if (*filename == '*') { if (*filename == '*') {
snprintf(name, sizeof(name), "=%s", filename + 1); snprintf(name, sizeof(name), "=%s", filename + 1);
@ -900,7 +928,11 @@ bool _luaLoad(struct mScriptEngineContext* ctx, const char* filename, struct VFi
lastSlash = lastBackslash; lastSlash = lastBackslash;
} }
if (lastSlash) { if (lastSlash) {
strncpy(dirname, filename, lastSlash - filename); size_t len = lastSlash - filename + 1;
if (sizeof(dirname) < len) {
len = sizeof(dirname);
}
strlcpy(dirname, filename, len);
} }
snprintf(name, sizeof(name), "@%s", filename); snprintf(name, sizeof(name), "@%s", filename);
} }
@ -913,14 +945,43 @@ bool _luaLoad(struct mScriptEngineContext* ctx, const char* filename, struct VFi
#endif #endif
switch (ret) { switch (ret) {
case LUA_OK: case LUA_OK:
// Create new _ENV
lua_newtable(luaContext->lua);
// Make the old _ENV the __index in the metatable
lua_newtable(luaContext->lua);
lua_pushliteral(luaContext->lua, "__index");
lua_getupvalue(luaContext->lua, -4, 1);
lua_rawset(luaContext->lua, -3);
lua_pushliteral(luaContext->lua, "__newindex");
lua_getupvalue(luaContext->lua, -4, 1);
lua_rawset(luaContext->lua, -3);
lua_setmetatable(luaContext->lua, -2);
lua_pushliteral(luaContext->lua, "script");
lua_newtable(luaContext->lua);
if (dirname[0]) { if (dirname[0]) {
lua_getupvalue(luaContext->lua, -1, 1);
lua_pushliteral(luaContext->lua, "require"); lua_pushliteral(luaContext->lua, "require");
lua_pushstring(luaContext->lua, dirname); lua_pushstring(luaContext->lua, dirname);
lua_pushcclosure(luaContext->lua, _luaRequireShim, 1); lua_pushcclosure(luaContext->lua, _luaRequireShim, 1);
lua_rawset(luaContext->lua, -5);
lua_pushliteral(luaContext->lua, "dir");
lua_pushstring(luaContext->lua, dirname);
lua_rawset(luaContext->lua, -3); lua_rawset(luaContext->lua, -3);
lua_pop(luaContext->lua, 1);
} }
if (name[0] == '@') {
lua_pushliteral(luaContext->lua, "path");
lua_pushstring(luaContext->lua, &name[1]);
lua_rawset(luaContext->lua, -3);
}
lua_rawset(luaContext->lua, -3);
lua_setupvalue(luaContext->lua, -2, 1);
luaContext->func = luaL_ref(luaContext->lua, LUA_REGISTRYINDEX); luaContext->func = luaL_ref(luaContext->lua, LUA_REGISTRYINDEX);
return true; return true;
case LUA_ERRSYNTAX: case LUA_ERRSYNTAX:
@ -1237,7 +1298,7 @@ int _luaGetTable(lua_State* lua) {
} }
lua_pop(lua, 2); lua_pop(lua, 2);
obj = mScriptContextAccessWeakref(luaContext->d.context, obj); obj = mScriptContextAccessWeakref(luaContext->d.context, obj);
if (obj->type->base == mSCRIPT_TYPE_WRAPPER) { if (obj->type->base == mSCRIPT_TYPE_WRAPPER) {
obj = mScriptValueUnwrap(obj); obj = mScriptValueUnwrap(obj);
} }
@ -1475,3 +1536,37 @@ static int _luaRequireShim(lua_State* lua) {
int newtop = lua_gettop(luaContext->lua); int newtop = lua_gettop(luaContext->lua);
return newtop - oldtop + 1; return newtop - oldtop + 1;
} }
static int _luaPrintShim(lua_State* lua) {
int n = lua_gettop(lua);
lua_getglobal(lua, "console");
lua_insert(lua, 1);
// The first upvalue is either "log" or "warn"
lua_getglobal(lua, "console");
lua_pushvalue(lua, lua_upvalueindex(1));
lua_gettable(lua, -2);
lua_insert(lua, 1);
lua_pop(lua, 1);
// TODO when console:log is variadic and stringifies by itself:
// lua_call(lua, n + 1, 0);
// Until then, stringify and concatenate:
for (int i = 0; i < n; i++) {
luaL_tolstring(lua, i * 2 + 3, NULL);
lua_replace(lua, i * 2 + 3);
if (i == 0) {
lua_pushliteral(lua, "");
} else {
lua_pushliteral(lua, "\t");
}
lua_insert(lua, i * 2 + 3);
}
n = n * 2 - 1;
lua_concat(lua, n + 1);
lua_call(lua, 2, 0);
return 0;
}

85
src/script/image.c Normal file
View File

@ -0,0 +1,85 @@
/* 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 <mgba/script.h>
static struct mScriptValue* _mImageNew(unsigned width, unsigned height) {
// For various reasons, it's probably a good idea to limit the maximum image size scripts can make
if (width >= 10000 || height >= 10000) {
return NULL;
}
struct mImage* image = mImageCreate(width, height, mCOLOR_ABGR8);
if (!image) {
return NULL;
}
struct mScriptValue* result = mScriptValueAlloc(mSCRIPT_TYPE_MS_S(mImage));
result->value.opaque = image;
result->flags = mSCRIPT_VALUE_FLAG_DEINIT;
return result;
}
static struct mScriptValue* _mImageLoad(const char* path) {
struct mImage* image = mImageLoad(path);
if (!image) {
return NULL;
}
struct mScriptValue* result = mScriptValueAlloc(mSCRIPT_TYPE_MS_S(mImage));
result->value.opaque = image;
result->flags = mSCRIPT_VALUE_FLAG_DEINIT;
return result;
}
mSCRIPT_DECLARE_STRUCT_C_METHOD(mImage, U32, getPixel, mImageGetPixel, 2, U32, x, U32, y);
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mImage, setPixel, mImageSetPixel, 3, U32, x, U32, y, U32, color);
mSCRIPT_DECLARE_STRUCT_C_METHOD_WITH_DEFAULTS(mImage, BOOL, save, mImageSave, 2, CHARP, path, CHARP, format);
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mImage, _deinit, mImageDestroy, 0);
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mImage, drawImageOpaque, mImageBlit, 3, CS(mImage), image, U32, x, U32, y);
mSCRIPT_DECLARE_STRUCT_VOID_METHOD_WITH_DEFAULTS(mImage, drawImage, mImageCompositeWithAlpha, 4, CS(mImage), image, U32, x, U32, y, F32, alpha);
mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(mImage, save)
mSCRIPT_NO_DEFAULT,
mSCRIPT_CHARP("PNG")
mSCRIPT_DEFINE_DEFAULTS_END;
mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(mImage, drawImage)
mSCRIPT_NO_DEFAULT,
mSCRIPT_NO_DEFAULT,
mSCRIPT_NO_DEFAULT,
mSCRIPT_F32(1.0f)
mSCRIPT_DEFINE_DEFAULTS_END;
mSCRIPT_DEFINE_STRUCT(mImage)
mSCRIPT_DEFINE_CLASS_DOCSTRING(
"A single, static image."
)
mSCRIPT_DEFINE_STRUCT_DEINIT(mImage)
mSCRIPT_DEFINE_DOCSTRING("Save the image to a file. Currently, only `PNG` format is supported")
mSCRIPT_DEFINE_STRUCT_METHOD(mImage, save)
mSCRIPT_DEFINE_DOCSTRING("Get the ARGB value of the pixel at a given coordinate")
mSCRIPT_DEFINE_STRUCT_METHOD(mImage, getPixel)
mSCRIPT_DEFINE_DOCSTRING("Set the ARGB value of the pixel at a given coordinate")
mSCRIPT_DEFINE_STRUCT_METHOD(mImage, setPixel)
mSCRIPT_DEFINE_DOCSTRING("Draw another image onto this image without any alpha blending, overwriting what was already there")
mSCRIPT_DEFINE_STRUCT_METHOD(mImage, drawImageOpaque)
mSCRIPT_DEFINE_DOCSTRING("Draw another image onto this image with alpha blending as needed, optionally specifying a coefficient for adjusting the opacity")
mSCRIPT_DEFINE_STRUCT_METHOD(mImage, drawImage)
mSCRIPT_DEFINE_DOCSTRING("The width of the image, in pixels")
mSCRIPT_DEFINE_STRUCT_CONST_MEMBER(mImage, U32, width)
mSCRIPT_DEFINE_DOCSTRING("The height of the image, in pixels")
mSCRIPT_DEFINE_STRUCT_CONST_MEMBER(mImage, U32, height)
mSCRIPT_DEFINE_END;
mSCRIPT_BIND_FUNCTION(mImageNew_Binding, W(mImage), _mImageNew, 2, U32, width, U32, height);
mSCRIPT_BIND_FUNCTION(mImageLoad_Binding, W(mImage), _mImageLoad, 1, CHARP, path);
void mScriptContextAttachImage(struct mScriptContext* context) {
mScriptContextExportNamespace(context, "image", (struct mScriptKVPair[]) {
mSCRIPT_KV_PAIR(new, &mImageNew_Binding),
mSCRIPT_KV_PAIR(load, &mImageLoad_Binding),
mSCRIPT_KV_SENTINEL
});
mScriptContextSetDocstring(context, "image", "Methods for creating struct::mImage instances");
mScriptContextSetDocstring(context, "image.new", "Create a new image with the given dimensions");
mScriptContextSetDocstring(context, "image.load", "Load an image from a path. Currently, only `PNG` format is supported");
}

View File

@ -3,10 +3,12 @@
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <mgba/script/context.h> #include <mgba/script/base.h>
#include <mgba/core/core.h> #include <mgba/core/core.h>
#include <mgba/core/serialize.h> #include <mgba/core/serialize.h>
#include <mgba/core/version.h>
#include <mgba/script/context.h>
#include <mgba/script/macros.h> #include <mgba/script/macros.h>
#ifdef M_CORE_GBA #ifdef M_CORE_GBA
#include <mgba/internal/gba/input.h> #include <mgba/internal/gba/input.h>
@ -189,4 +191,27 @@ void mScriptContextAttachStdlib(struct mScriptContext* context) {
mScriptContextSetDocstring(context, "util", "Basic utility library"); mScriptContextSetDocstring(context, "util", "Basic utility library");
mScriptContextSetDocstring(context, "util.makeBitmask", "Compile a list of bit indices into a bitmask"); mScriptContextSetDocstring(context, "util.makeBitmask", "Compile a list of bit indices into a bitmask");
mScriptContextSetDocstring(context, "util.expandBitmask", "Expand a bitmask into a list of bit indices"); mScriptContextSetDocstring(context, "util.expandBitmask", "Expand a bitmask into a list of bit indices");
struct mScriptValue* systemVersion = mScriptStringCreateFromUTF8(projectVersion);
struct mScriptValue* systemProgram = mScriptStringCreateFromUTF8(projectName);
struct mScriptValue* systemBranch = mScriptStringCreateFromUTF8(gitBranch);
struct mScriptValue* systemCommit = mScriptStringCreateFromUTF8(gitCommit);
struct mScriptValue* systemRevision = mScriptValueAlloc(mSCRIPT_TYPE_MS_S32);
systemRevision->value.s32 = gitRevision;
mScriptContextExportNamespace(context, "system", (struct mScriptKVPair[]) {
mSCRIPT_KV_PAIR(version, systemVersion),
mSCRIPT_KV_PAIR(program, systemProgram),
mSCRIPT_KV_PAIR(branch, systemBranch),
mSCRIPT_KV_PAIR(commit, systemCommit),
mSCRIPT_KV_PAIR(revision, systemRevision),
mSCRIPT_KV_SENTINEL
});
mScriptContextSetDocstring(context, "system", "Information about the system the script is running under");
mScriptContextSetDocstring(context, "system.version", "The current version of this build of the program");
mScriptContextSetDocstring(context, "system.program", "The name of the program. Generally this will be \"mGBA\", but forks may change it to differentiate");
mScriptContextSetDocstring(context, "system.branch", "The current git branch of this build of the program, if known");
mScriptContextSetDocstring(context, "system.commit", "The current git commit hash of this build of the program, if known");
mScriptContextSetDocstring(context, "system.revision", "The current git revision number of this build of the program, or -1 if unknown");
} }

View File

@ -5,9 +5,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "util/test/suite.h" #include "util/test/suite.h"
#include <mgba/script/context.h> #include <mgba/script.h>
#include <mgba/script/macros.h>
#include <mgba/script/types.h>
struct TestA { struct TestA {
int32_t i; int32_t i;
@ -54,6 +52,11 @@ struct TestG {
const char* c; const char* c;
}; };
struct TestH {
int32_t i;
int32_t j;
};
static int32_t testAi0(struct TestA* a) { static int32_t testAi0(struct TestA* a) {
return a->i; return a->i;
} }
@ -198,6 +201,12 @@ mSCRIPT_DEFINE_STRUCT(TestG)
mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(TestG, setC) mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(TestG, setC)
mSCRIPT_DEFINE_END; mSCRIPT_DEFINE_END;
mSCRIPT_DEFINE_STRUCT(TestH)
mSCRIPT_DEFINE_STRUCT_MEMBER(TestH, S32, i)
mSCRIPT_DEFINE_STRUCT_CONST_MEMBER(TestH, S32, j)
mSCRIPT_DEFINE_END;
M_TEST_DEFINE(testALayout) { M_TEST_DEFINE(testALayout) {
struct mScriptTypeClass* cls = mSCRIPT_TYPE_MS_S(TestA)->details.cls; struct mScriptTypeClass* cls = mSCRIPT_TYPE_MS_S(TestA)->details.cls;
assert_false(cls->init); assert_false(cls->init);
@ -1134,6 +1143,47 @@ M_TEST_DEFINE(testGSet) {
assert_false(cls->init); assert_false(cls->init);
} }
M_TEST_DEFINE(testHSet) {
struct mScriptTypeClass* cls = mSCRIPT_TYPE_MS_S(TestH)->details.cls;
struct TestH s = {
.i = 1,
.j = 2,
};
struct mScriptValue sval = mSCRIPT_MAKE_S(TestH, &s);
struct mScriptValue val;
struct mScriptValue compare;
compare = mSCRIPT_MAKE_S32(1);
assert_true(mScriptObjectGet(&sval, "i", &val));
assert_true(compare.type->equal(&compare, &val));
compare = mSCRIPT_MAKE_S32(2);
assert_true(mScriptObjectGet(&sval, "j", &val));
assert_true(compare.type->equal(&compare, &val));
val = mSCRIPT_MAKE_S32(3);
assert_true(mScriptObjectSet(&sval, "i", &val));
assert_int_equal(s.i, 3);
val = mSCRIPT_MAKE_S32(4);
assert_false(mScriptObjectSet(&sval, "j", &val));
assert_int_equal(s.j, 2);
compare = mSCRIPT_MAKE_S32(3);
assert_true(mScriptObjectGet(&sval, "i", &val));
assert_true(compare.type->equal(&compare, &val));
compare = mSCRIPT_MAKE_S32(2);
assert_true(mScriptObjectGet(&sval, "j", &val));
assert_true(compare.type->equal(&compare, &val));
assert_true(cls->init);
mScriptClassDeinit(cls);
assert_false(cls->init);
}
M_TEST_SUITE_DEFINE(mScriptClasses, M_TEST_SUITE_DEFINE(mScriptClasses,
cmocka_unit_test(testALayout), cmocka_unit_test(testALayout),
cmocka_unit_test(testASignatures), cmocka_unit_test(testASignatures),
@ -1150,4 +1200,5 @@ M_TEST_SUITE_DEFINE(mScriptClasses,
cmocka_unit_test(testEGet), cmocka_unit_test(testEGet),
cmocka_unit_test(testFDeinit), cmocka_unit_test(testFDeinit),
cmocka_unit_test(testGSet), cmocka_unit_test(testGSet),
cmocka_unit_test(testHSet),
) )

View File

@ -5,9 +5,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "util/test/suite.h" #include "util/test/suite.h"
#include <mgba/script/context.h> #include <mgba/script.h>
#include <mgba/script/macros.h>
#include <mgba/script/types.h>
M_TEST_DEFINE(weakrefBasic) { M_TEST_DEFINE(weakrefBasic) {
struct mScriptContext context; struct mScriptContext context;

111
src/script/test/image.c Normal file
View File

@ -0,0 +1,111 @@
/* 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 <mgba/internal/script/lua.h>
#include <mgba/script.h>
#include "script/test.h"
#define SETUP_LUA \
struct mScriptContext context; \
mScriptContextInit(&context); \
struct mScriptEngineContext* lua = mScriptContextRegisterEngine(&context, mSCRIPT_ENGINE_LUA); \
mScriptContextAttachStdlib(&context); \
mScriptContextAttachImage(&context)
M_TEST_SUITE_SETUP(mScriptImage) {
if (mSCRIPT_ENGINE_LUA->init) {
mSCRIPT_ENGINE_LUA->init(mSCRIPT_ENGINE_LUA);
}
return 0;
}
M_TEST_SUITE_TEARDOWN(mScriptImage) {
if (mSCRIPT_ENGINE_LUA->deinit) {
mSCRIPT_ENGINE_LUA->deinit(mSCRIPT_ENGINE_LUA);
}
return 0;
}
M_TEST_DEFINE(members) {
SETUP_LUA;
TEST_PROGRAM("assert(image)");
TEST_PROGRAM("assert(image.new)");
TEST_PROGRAM("assert(image.load)");
TEST_PROGRAM("im = image.new(1, 1)");
TEST_PROGRAM("assert(im)");
TEST_PROGRAM("assert(im.width == 1)");
TEST_PROGRAM("assert(im.height == 1)");
TEST_PROGRAM("assert(im.save)");
TEST_PROGRAM("assert(im.save)");
TEST_PROGRAM("assert(im.getPixel)");
TEST_PROGRAM("assert(im.setPixel)");
mScriptContextDeinit(&context);
}
M_TEST_DEFINE(zeroDim) {
SETUP_LUA;
TEST_PROGRAM("im = image.new(0, 0)");
TEST_PROGRAM("assert(not im)");
TEST_PROGRAM("im = image.new(1, 0)");
TEST_PROGRAM("assert(not im)");
TEST_PROGRAM("im = image.new(0, 1)");
TEST_PROGRAM("assert(not im)");
TEST_PROGRAM("im = image.new(1, 1)");
TEST_PROGRAM("assert(im)");
mScriptContextDeinit(&context);
}
M_TEST_DEFINE(pixelColorDefault) {
SETUP_LUA;
TEST_PROGRAM("im = image.new(1, 1)");
TEST_PROGRAM("assert(im:getPixel(0, 0) == 0)");
mScriptContextDeinit(&context);
}
M_TEST_DEFINE(pixelColorRoundTrip) {
SETUP_LUA;
TEST_PROGRAM("im = image.new(1, 1)");
TEST_PROGRAM("im:setPixel(0, 0, 0xFF123456)");
TEST_PROGRAM("assert(im:getPixel(0, 0) == 0xFF123456)");
mScriptContextDeinit(&context);
}
#ifdef USE_PNG
M_TEST_DEFINE(saveLoadRoundTrip) {
SETUP_LUA;
unlink("tmp.png");
TEST_PROGRAM("im = image.new(1, 1)");
TEST_PROGRAM("im:setPixel(0, 0, 0xFF123456)");
TEST_PROGRAM("assert(im:save('tmp.png'))");
TEST_PROGRAM("im = image.load('tmp.png')");
TEST_PROGRAM("assert(im)");
TEST_PROGRAM("assert(im:getPixel(0, 0) == 0xFF123456)");
unlink("tmp.png");
mScriptContextDeinit(&context);
}
#endif
M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptImage,
cmocka_unit_test(members),
cmocka_unit_test(zeroDim),
cmocka_unit_test(pixelColorDefault),
cmocka_unit_test(pixelColorRoundTrip),
#ifdef USE_PNG
cmocka_unit_test(saveLoadRoundTrip),
#endif
)

View File

@ -6,9 +6,7 @@
#include "util/test/suite.h" #include "util/test/suite.h"
#include <mgba/internal/script/lua.h> #include <mgba/internal/script/lua.h>
#include <mgba/script/context.h> #include <mgba/script.h>
#include <mgba/script/input.h>
#include <mgba/script/types.h>
#include "script/test.h" #include "script/test.h"

View File

@ -6,9 +6,7 @@
#include "util/test/suite.h" #include "util/test/suite.h"
#include <mgba/internal/script/lua.h> #include <mgba/internal/script/lua.h>
#include <mgba/script/context.h> #include <mgba/script.h>
#include <mgba/script/macros.h>
#include <mgba/script/types.h>
#include "script/test.h" #include "script/test.h"

View File

@ -6,8 +6,7 @@
#include "util/test/suite.h" #include "util/test/suite.h"
#include <mgba/internal/script/lua.h> #include <mgba/internal/script/lua.h>
#include <mgba/script/storage.h> #include <mgba/script.h>
#include <mgba/script/types.h>
#include "script/test.h" #include "script/test.h"

View File

@ -5,9 +5,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "util/test/suite.h" #include "util/test/suite.h"
#include <mgba/script/context.h> #include <mgba/script.h>
#include <mgba/script/macros.h>
#include <mgba/script/types.h>
struct Test { struct Test {
int32_t a; int32_t a;
@ -96,6 +94,12 @@ mSCRIPT_BIND_FUNCTION(boundIsHello, S32, isHello, 1, CHARP, str);
mSCRIPT_BIND_FUNCTION(boundIsSequential, S32, isSequential, 1, LIST, list); mSCRIPT_BIND_FUNCTION(boundIsSequential, S32, isSequential, 1, LIST, list);
mSCRIPT_BIND_FUNCTION(boundIsNullCharp, BOOL, isNullCharp, 1, CHARP, arg); mSCRIPT_BIND_FUNCTION(boundIsNullCharp, BOOL, isNullCharp, 1, CHARP, arg);
mSCRIPT_BIND_FUNCTION(boundIsNullStruct, BOOL, isNullStruct, 1, S(Test), arg); mSCRIPT_BIND_FUNCTION(boundIsNullStruct, BOOL, isNullStruct, 1, S(Test), arg);
mSCRIPT_BIND_FUNCTION_WITH_DEFAULTS(boundAddIntWithDefaults, S32, addInts, 2, S32, a, S32, b);
mSCRIPT_DEFINE_FUNCTION_BINDING_DEFAULTS(boundAddIntWithDefaults)
mSCRIPT_NO_DEFAULT,
mSCRIPT_S32(0)
mSCRIPT_DEFINE_DEFAULTS_END;
M_TEST_DEFINE(voidArgs) { M_TEST_DEFINE(voidArgs) {
struct mScriptFrame frame; struct mScriptFrame frame;
@ -172,6 +176,30 @@ M_TEST_DEFINE(addS32) {
mScriptFrameDeinit(&frame); mScriptFrameDeinit(&frame);
} }
M_TEST_DEFINE(addS32Defaults) {
struct mScriptFrame frame;
int32_t val;
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, S32, 1);
mSCRIPT_PUSH(&frame.arguments, S32, 2);
assert_true(mScriptInvoke(&boundAddIntWithDefaults, &frame));
assert_true(mScriptPopS32(&frame.returnValues, &val));
assert_int_equal(val, 3);
mScriptFrameDeinit(&frame);
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, S32, 1);
assert_true(mScriptInvoke(&boundAddIntWithDefaults, &frame));
assert_true(mScriptPopS32(&frame.returnValues, &val));
assert_int_equal(val, 1);
mScriptFrameDeinit(&frame);
mScriptFrameInit(&frame);
assert_false(mScriptInvoke(&boundAddIntWithDefaults, &frame));
mScriptFrameDeinit(&frame);
}
M_TEST_DEFINE(subS32) { M_TEST_DEFINE(subS32) {
struct mScriptFrame frame; struct mScriptFrame frame;
mScriptFrameInit(&frame); mScriptFrameInit(&frame);
@ -1316,6 +1344,7 @@ M_TEST_SUITE_DEFINE(mScript,
cmocka_unit_test(identityFunctionF32), cmocka_unit_test(identityFunctionF32),
cmocka_unit_test(identityFunctionStruct), cmocka_unit_test(identityFunctionStruct),
cmocka_unit_test(addS32), cmocka_unit_test(addS32),
cmocka_unit_test(addS32Defaults),
cmocka_unit_test(subS32), cmocka_unit_test(subS32),
cmocka_unit_test(wrongArgCountLo), cmocka_unit_test(wrongArgCountLo),
cmocka_unit_test(wrongArgCountHi), cmocka_unit_test(wrongArgCountHi),

View File

@ -364,24 +364,6 @@ static uint32_t _hashString(const struct mScriptValue* val) {
return hash32(buffer, size, 0); return hash32(buffer, size, 0);
} }
uint32_t _hashScalar(const struct mScriptValue* val) {
// From https://stackoverflow.com/questions/664014/what-integer-hash-function-are-good-that-accepts-an-integer-hash-key
uint32_t x = 0;
switch (val->type->base) {
case mSCRIPT_TYPE_SINT:
x = val->value.s32;
break;
case mSCRIPT_TYPE_UINT:
default:
x = val->value.u32;
break;
}
x = ((x >> 16) ^ x) * 0x45D9F3B;
x = ((x >> 16) ^ x) * 0x45D9F3B;
x = (x >> 16) ^ x;
return x;
}
bool _wstrCast(const struct mScriptValue* input, const struct mScriptType* type, struct mScriptValue* output) { bool _wstrCast(const struct mScriptValue* input, const struct mScriptType* type, struct mScriptValue* output) {
if (input->type->base != mSCRIPT_TYPE_WRAPPER) { if (input->type->base != mSCRIPT_TYPE_WRAPPER) {
return false; return false;
@ -519,6 +501,16 @@ bool _castScalar(const struct mScriptValue* input, const struct mScriptType* typ
return true; return true;
} }
uint32_t _hashScalar(const struct mScriptValue* val) {
// From https://stackoverflow.com/questions/664014/what-integer-hash-function-are-good-that-accepts-an-integer-hash-key
uint32_t x = 0;
_asUInt32(val, &x);
x = ((x >> 16) ^ x) * 0x45D9F3B;
x = ((x >> 16) ^ x) * 0x45D9F3B;
x = (x >> 16) ^ x;
return x;
}
uint32_t _valHash(const void* val, size_t len, uint32_t seed) { uint32_t _valHash(const void* val, size_t len, uint32_t seed) {
UNUSED(len); UNUSED(len);
const struct mScriptValue* value = val; const struct mScriptValue* value = val;
@ -1480,6 +1472,10 @@ bool mScriptObjectSet(struct mScriptValue* obj, const char* member, struct mScri
return true; return true;
} }
if (m->readonly) {
return false;
}
void* rawMember = (void *)((uintptr_t) obj->value.opaque + m->offset); void* rawMember = (void *)((uintptr_t) obj->value.opaque + m->offset);
if (m->type != val->type) { if (m->type != val->type) {
if (!mScriptCast(m->type, val, val)) { if (!mScriptCast(m->type, val, val)) {
@ -1572,7 +1568,7 @@ void mScriptObjectFree(struct mScriptValue* value) {
if (value->type->base != mSCRIPT_TYPE_OBJECT) { if (value->type->base != mSCRIPT_TYPE_OBJECT) {
return; return;
} }
if (value->flags & mSCRIPT_VALUE_FLAG_FREE_BUFFER) { if (value->flags & (mSCRIPT_VALUE_FLAG_DEINIT | mSCRIPT_VALUE_FLAG_FREE_BUFFER)) {
mScriptClassInit(value->type->details.cls); mScriptClassInit(value->type->details.cls);
if (value->type->details.cls->free) { if (value->type->details.cls->free) {
struct mScriptValue deinitMember; struct mScriptValue deinitMember;
@ -1588,6 +1584,8 @@ void mScriptObjectFree(struct mScriptValue* value) {
mScriptFrameDeinit(&frame); mScriptFrameDeinit(&frame);
} }
} }
}
if (value->flags & mSCRIPT_VALUE_FLAG_FREE_BUFFER) {
free(value->value.opaque); free(value->value.opaque);
} }
} }

Some files were not shown because too many files have changed in this diff Show More