mirror of https://github.com/mgba-emu/mgba.git
Merge branch 'master' (early part) into medusa
This commit is contained in:
commit
faa0e49563
2
CHANGES
2
CHANGES
|
@ -66,9 +66,11 @@ Other fixes:
|
|||
- Qt: Fix full-buffer rewind
|
||||
- Qt: Fix crash if loading a shader fails
|
||||
- Qt: Fix black screen when starting with a game (fixes mgba.io/i/2781)
|
||||
- Qt: Fix OSD on modern macOS (fixes mgba.io/i/2736)
|
||||
- Scripting: Fix receiving packets for client sockets
|
||||
- Scripting: Fix empty receive calls returning unknown error on Windows
|
||||
Misc:
|
||||
- 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)
|
||||
|
|
|
@ -342,6 +342,8 @@ find_function(popcount32)
|
|||
find_function(futimens)
|
||||
find_function(futimes)
|
||||
|
||||
find_function(realpath)
|
||||
|
||||
if(ANDROID AND ANDROID_NDK_MAJOR GREATER 13)
|
||||
find_function(localtime_r)
|
||||
set(HAVE_STRTOF_L ON)
|
||||
|
@ -755,6 +757,11 @@ elseif(BUILD_GLES2)
|
|||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libgles2")
|
||||
endif()
|
||||
|
||||
if(USE_EPOXY OR BUILD_GL OR BUILD_GLES2)
|
||||
# This file should probably go somewhere else
|
||||
list(APPEND FEATURE_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/video-backend.c)
|
||||
endif()
|
||||
|
||||
if(WIN32 AND NOT (LIBMGBA_ONLY OR SKIP_LIBRARY OR USE_EPOXY))
|
||||
message(FATAL_ERROR "Windows requires epoxy module!")
|
||||
endif()
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
/* Copyright (c) 2013-2023 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#ifndef GEOMETRY_H
|
||||
#define GEOMETRY_H
|
||||
|
||||
#include <mgba-util/common.h>
|
||||
|
||||
CXX_GUARD_START
|
||||
|
||||
struct Size {
|
||||
int width;
|
||||
int height;
|
||||
};
|
||||
|
||||
struct Rectangle {
|
||||
int x;
|
||||
int y;
|
||||
int width;
|
||||
int height;
|
||||
};
|
||||
|
||||
void RectangleUnion(struct Rectangle* dst, const struct Rectangle* add);
|
||||
void RectangleCenter(const struct Rectangle* ref, struct Rectangle* rect);
|
||||
|
||||
CXX_GUARD_END
|
||||
|
||||
#endif
|
|
@ -100,6 +100,9 @@ struct VFile* VFileFromFILE(FILE* file);
|
|||
|
||||
void separatePath(const char* path, char* dirname, char* basename, char* extension);
|
||||
|
||||
bool isAbsolute(const char* path);
|
||||
void makeAbsolute(const char* path, const char* base, char* out);
|
||||
|
||||
struct VFile* VDirFindFirst(struct VDir* dir, bool (*filter)(struct VFile*));
|
||||
struct VFile* VDirFindNextAvailable(struct VDir*, const char* basename, const char* infix, const char* suffix, int mode);
|
||||
|
||||
|
|
|
@ -68,7 +68,11 @@ struct mCore {
|
|||
void (*loadConfig)(struct mCore*, const struct mCoreConfig*);
|
||||
void (*reloadConfigOption)(struct mCore*, const char* option, const struct mCoreConfig*);
|
||||
|
||||
void (*desiredVideoDimensions)(const struct mCore*, unsigned* width, unsigned* height);
|
||||
void (*baseVideoSize)(const struct mCore*, unsigned* width, unsigned* height);
|
||||
void (*currentVideoSize)(const struct mCore*, unsigned* width, unsigned* height);
|
||||
unsigned (*videoScale)(const struct mCore*);
|
||||
size_t (*screenRegions)(const struct mCore*, const struct mCoreScreenRegion**);
|
||||
|
||||
void (*setVideoBuffer)(struct mCore*, color_t* buffer, size_t stride);
|
||||
void (*setVideoGLTex)(struct mCore*, unsigned texid);
|
||||
|
||||
|
|
|
@ -27,9 +27,9 @@ typedef uint32_t color_t;
|
|||
#define M_G5(X) (((X) >> 5) & 0x1F)
|
||||
#define M_B5(X) (((X) >> 10) & 0x1F)
|
||||
|
||||
#define M_R8(X) (((((X) << 3) & 0xF8) * 0x21) >> 5)
|
||||
#define M_G8(X) (((((X) >> 2) & 0xF8) * 0x21) >> 5)
|
||||
#define M_B8(X) (((((X) >> 7) & 0xF8) * 0x21) >> 5)
|
||||
#define M_R8(X) (((((X) << 3) & 0xF8) * 0x21) >> 2)
|
||||
#define M_G8(X) (((((X) >> 2) & 0xF8) * 0x21) >> 2)
|
||||
#define M_B8(X) (((((X) >> 7) & 0xF8) * 0x21) >> 2)
|
||||
|
||||
#define M_RGB5_TO_BGR8(X) ((M_R5(X) << 3) | (M_G5(X) << 11) | (M_B5(X) << 19))
|
||||
#define M_RGB5_TO_RGB8(X) ((M_R5(X) << 19) | (M_G5(X) << 11) | (M_B5(X) << 3))
|
||||
|
@ -294,6 +294,15 @@ struct mCoreMemoryBlock {
|
|||
uint32_t segmentStart;
|
||||
};
|
||||
|
||||
struct mCoreScreenRegion {
|
||||
size_t id;
|
||||
const char* description;
|
||||
int16_t x;
|
||||
int16_t y;
|
||||
int16_t w;
|
||||
int16_t h;
|
||||
};
|
||||
|
||||
enum mCoreRegisterType {
|
||||
mCORE_REGISTER_GPR = 0,
|
||||
mCORE_REGISTER_FPR,
|
||||
|
|
|
@ -22,6 +22,9 @@ enum {
|
|||
GB_VIDEO_VBLANK_PIXELS = 10,
|
||||
GB_VIDEO_VERTICAL_TOTAL_PIXELS = 154,
|
||||
|
||||
SGB_VIDEO_HORIZONTAL_PIXELS = 256,
|
||||
SGB_VIDEO_VERTICAL_PIXELS = 224,
|
||||
|
||||
// TODO: Figure out exact lengths
|
||||
GB_VIDEO_MODE_2_LENGTH = 80,
|
||||
GB_VIDEO_MODE_3_LENGTH_BASE = 172,
|
||||
|
|
|
@ -79,7 +79,7 @@ static const char* _lookupValue(const struct mCoreConfig* config, const char* ke
|
|||
|
||||
static bool _lookupCharValue(const struct mCoreConfig* config, const char* key, char** out) {
|
||||
const char* value = _lookupValue(config, key);
|
||||
if (!value) {
|
||||
if (!value || !value[0]) {
|
||||
return false;
|
||||
}
|
||||
if (*out) {
|
||||
|
|
|
@ -374,7 +374,7 @@ bool mCoreTakeScreenshotVF(struct mCore* core, struct VFile* vf) {
|
|||
size_t stride;
|
||||
const void* pixels = 0;
|
||||
unsigned width, height;
|
||||
core->desiredVideoDimensions(core, &width, &height);
|
||||
core->currentVideoSize(core, &width, &height);
|
||||
core->getPixels(core, &pixels, &stride);
|
||||
png_structp png = PNGWriteOpen(vf);
|
||||
png_infop info = PNGWriteHeader(png, width, height);
|
||||
|
|
|
@ -116,10 +116,15 @@ struct VFile* mDirectorySetOpenSuffix(struct mDirectorySet* dirs, struct VDir* d
|
|||
}
|
||||
|
||||
void mDirectorySetMapOptions(struct mDirectorySet* dirs, const struct mCoreOptions* opts) {
|
||||
char abspath[PATH_MAX + 1];
|
||||
char configDir[PATH_MAX + 1];
|
||||
mCoreConfigDirectory(configDir, sizeof(configDir));
|
||||
|
||||
if (opts->savegamePath) {
|
||||
struct VDir* dir = VDirOpen(opts->savegamePath);
|
||||
if (!dir && VDirCreate(opts->savegamePath)) {
|
||||
dir = VDirOpen(opts->savegamePath);
|
||||
makeAbsolute(opts->savegamePath, configDir, abspath);
|
||||
struct VDir* dir = VDirOpen(abspath);
|
||||
if (!dir && VDirCreate(abspath)) {
|
||||
dir = VDirOpen(abspath);
|
||||
}
|
||||
if (dir) {
|
||||
if (dirs->save && dirs->save != dirs->base) {
|
||||
|
@ -130,9 +135,10 @@ void mDirectorySetMapOptions(struct mDirectorySet* dirs, const struct mCoreOptio
|
|||
}
|
||||
|
||||
if (opts->savestatePath) {
|
||||
struct VDir* dir = VDirOpen(opts->savestatePath);
|
||||
if (!dir && VDirCreate(opts->savestatePath)) {
|
||||
dir = VDirOpen(opts->savestatePath);
|
||||
makeAbsolute(opts->savestatePath, configDir, abspath);
|
||||
struct VDir* dir = VDirOpen(abspath);
|
||||
if (!dir && VDirCreate(abspath)) {
|
||||
dir = VDirOpen(abspath);
|
||||
}
|
||||
if (dir) {
|
||||
if (dirs->state && dirs->state != dirs->base) {
|
||||
|
@ -143,9 +149,10 @@ void mDirectorySetMapOptions(struct mDirectorySet* dirs, const struct mCoreOptio
|
|||
}
|
||||
|
||||
if (opts->screenshotPath) {
|
||||
struct VDir* dir = VDirOpen(opts->screenshotPath);
|
||||
if (!dir && VDirCreate(opts->screenshotPath)) {
|
||||
dir = VDirOpen(opts->screenshotPath);
|
||||
makeAbsolute(opts->screenshotPath, configDir, abspath);
|
||||
struct VDir* dir = VDirOpen(abspath);
|
||||
if (!dir && VDirCreate(abspath)) {
|
||||
dir = VDirOpen(abspath);
|
||||
}
|
||||
if (dir) {
|
||||
if (dirs->screenshot && dirs->screenshot != dirs->base) {
|
||||
|
@ -156,9 +163,10 @@ void mDirectorySetMapOptions(struct mDirectorySet* dirs, const struct mCoreOptio
|
|||
}
|
||||
|
||||
if (opts->patchPath) {
|
||||
struct VDir* dir = VDirOpen(opts->patchPath);
|
||||
if (!dir && VDirCreate(opts->patchPath)) {
|
||||
dir = VDirOpen(opts->patchPath);
|
||||
makeAbsolute(opts->patchPath, configDir, abspath);
|
||||
struct VDir* dir = VDirOpen(abspath);
|
||||
if (!dir && VDirCreate(abspath)) {
|
||||
dir = VDirOpen(abspath);
|
||||
}
|
||||
if (dir) {
|
||||
if (dirs->patch && dirs->patch != dirs->base) {
|
||||
|
@ -169,9 +177,10 @@ void mDirectorySetMapOptions(struct mDirectorySet* dirs, const struct mCoreOptio
|
|||
}
|
||||
|
||||
if (opts->cheatsPath) {
|
||||
struct VDir* dir = VDirOpen(opts->cheatsPath);
|
||||
if (!dir && VDirCreate(opts->cheatsPath)) {
|
||||
dir = VDirOpen(opts->cheatsPath);
|
||||
makeAbsolute(opts->cheatsPath, configDir, abspath);
|
||||
struct VDir* dir = VDirOpen(abspath);
|
||||
if (!dir && VDirCreate(abspath)) {
|
||||
dir = VDirOpen(abspath);
|
||||
}
|
||||
if (dir) {
|
||||
if (dirs->cheats && dirs->cheats != dirs->base) {
|
||||
|
|
|
@ -175,7 +175,7 @@ static bool _savePNGState(struct mCore* core, struct VFile* vf, struct mStateExt
|
|||
mappedMemoryFree(state, stateSize);
|
||||
|
||||
unsigned width, height;
|
||||
core->desiredVideoDimensions(core, &width, &height);
|
||||
core->currentVideoSize(core, &width, &height);
|
||||
png_structp png = PNGWriteOpen(vf);
|
||||
png_infop info = PNGWriteHeader(png, width, height);
|
||||
if (!png || !info) {
|
||||
|
@ -527,7 +527,7 @@ bool mCoreLoadStateNamed(struct mCore* core, struct VFile* vf, int flags) {
|
|||
mappedMemoryFree(state, core->stateSize(core));
|
||||
|
||||
unsigned width, height;
|
||||
core->desiredVideoDimensions(core, &width, &height);
|
||||
core->currentVideoSize(core, &width, &height);
|
||||
|
||||
struct mStateExtdataItem item;
|
||||
if (flags & SAVESTATE_SCREENSHOT && mStateExtdataGet(&extdata, EXTDATA_SCREENSHOT, &item)) {
|
||||
|
|
|
@ -216,7 +216,7 @@ static void _DSCoreReloadConfigOption(struct mCore* core, const char* option, co
|
|||
}
|
||||
}
|
||||
|
||||
static void _DSCoreDesiredVideoDimensions(const struct mCore* core, unsigned* width, unsigned* height) {
|
||||
static void _DSCoreVideoSize(const struct mCore* core, unsigned* width, unsigned* height) {
|
||||
UNUSED(core);
|
||||
*width = DS_VIDEO_HORIZONTAL_PIXELS;
|
||||
*height = DS_VIDEO_VERTICAL_PIXELS * 2;
|
||||
|
@ -713,7 +713,8 @@ struct mCore* DSCoreCreate(void) {
|
|||
core->setSync = _DSCoreSetSync;
|
||||
core->loadConfig = _DSCoreLoadConfig;
|
||||
core->reloadConfigOption = _DSCoreReloadConfigOption;
|
||||
core->desiredVideoDimensions = _DSCoreDesiredVideoDimensions;
|
||||
core->baseVideoSize = _DSCoreVideoSize;
|
||||
core->currentVideoSize = _DSCoreVideoSize;
|
||||
core->setVideoBuffer = _DSCoreSetVideoBuffer;
|
||||
core->getPixels = _DSCoreGetPixels;
|
||||
core->putPixels = _DSCorePutPixels;
|
||||
|
|
|
@ -65,6 +65,15 @@ static const struct mCoreMemoryBlock _GBCMemoryBlocks[] = {
|
|||
{ GB_BASE_HRAM, "hram", "HRAM", "High RAM", GB_BASE_HRAM, GB_BASE_HRAM + GB_SIZE_HRAM, GB_SIZE_HRAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
|
||||
};
|
||||
|
||||
static const struct mCoreScreenRegion _GBScreenRegions[] = {
|
||||
{ 0, "Screen", 0, 0, GB_VIDEO_HORIZONTAL_PIXELS, GB_VIDEO_VERTICAL_PIXELS }
|
||||
};
|
||||
|
||||
static const struct mCoreScreenRegion _SGBScreenRegions[] = {
|
||||
{ 0, "Screen", (SGB_VIDEO_HORIZONTAL_PIXELS - GB_VIDEO_HORIZONTAL_PIXELS) / 2, (SGB_VIDEO_VERTICAL_PIXELS - GB_VIDEO_VERTICAL_PIXELS) / 2, GB_VIDEO_HORIZONTAL_PIXELS, GB_VIDEO_VERTICAL_PIXELS },
|
||||
{ 1, "Border", 0, 0, SGB_VIDEO_HORIZONTAL_PIXELS, SGB_VIDEO_VERTICAL_PIXELS },
|
||||
};
|
||||
|
||||
static const struct mCoreRegisterInfo _GBRegisters[] = {
|
||||
{ "b", NULL, 1, 0xFF, mCORE_REGISTER_GPR },
|
||||
{ "c", NULL, 1, 0xFF, mCORE_REGISTER_GPR },
|
||||
|
@ -364,14 +373,36 @@ static void _GBCoreReloadConfigOption(struct mCore* core, const char* option, co
|
|||
}
|
||||
}
|
||||
|
||||
static void _GBCoreDesiredVideoDimensions(const struct mCore* core, unsigned* width, unsigned* height) {
|
||||
static void _GBCoreBaseVideoSize(const struct mCore* core, unsigned* width, unsigned* height) {
|
||||
UNUSED(core);
|
||||
*width = SGB_VIDEO_HORIZONTAL_PIXELS;
|
||||
*height = SGB_VIDEO_VERTICAL_PIXELS;
|
||||
}
|
||||
|
||||
static void _GBCoreCurrentVideoSize(const struct mCore* core, unsigned* width, unsigned* height) {
|
||||
const struct GB* gb = core->board;
|
||||
if (gb && (!(gb->model & GB_MODEL_SGB) || !gb->video.sgbBorders)) {
|
||||
*width = GB_VIDEO_HORIZONTAL_PIXELS;
|
||||
*height = GB_VIDEO_VERTICAL_PIXELS;
|
||||
} else {
|
||||
*width = 256;
|
||||
*height = 224;
|
||||
*width = SGB_VIDEO_HORIZONTAL_PIXELS;
|
||||
*height = SGB_VIDEO_VERTICAL_PIXELS;
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned _GBCoreVideoScale(const struct mCore* core) {
|
||||
UNUSED(core);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static size_t _GBCoreScreenRegions(const struct mCore* core, const struct mCoreScreenRegion** regions) {
|
||||
const struct GB* gb = core->board;
|
||||
if (gb && (!(gb->model & GB_MODEL_SGB) || !gb->video.sgbBorders)) {
|
||||
*regions = _GBScreenRegions;
|
||||
return 1;
|
||||
} else {
|
||||
*regions = _SGBScreenRegions;
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -433,7 +464,7 @@ static void _GBCoreSetAVStream(struct mCore* core, struct mAVStream* stream) {
|
|||
gb->stream = stream;
|
||||
if (stream && stream->videoDimensionsChanged) {
|
||||
unsigned width, height;
|
||||
core->desiredVideoDimensions(core, &width, &height);
|
||||
core->currentVideoSize(core, &width, &height);
|
||||
stream->videoDimensionsChanged(stream, width, height);
|
||||
}
|
||||
if (stream && stream->videoFrameRateChanged) {
|
||||
|
@ -1255,7 +1286,10 @@ struct mCore* GBCoreCreate(void) {
|
|||
core->setSync = _GBCoreSetSync;
|
||||
core->loadConfig = _GBCoreLoadConfig;
|
||||
core->reloadConfigOption = _GBCoreReloadConfigOption;
|
||||
core->desiredVideoDimensions = _GBCoreDesiredVideoDimensions;
|
||||
core->baseVideoSize = _GBCoreBaseVideoSize;
|
||||
core->currentVideoSize = _GBCoreCurrentVideoSize;
|
||||
core->videoScale = _GBCoreVideoScale;
|
||||
core->screenRegions = _GBCoreScreenRegions;
|
||||
core->setVideoBuffer = _GBCoreSetVideoBuffer;
|
||||
core->setVideoGLTex = _GBCoreSetVideoGLTex;
|
||||
core->getPixels = _GBCoreGetPixels;
|
||||
|
|
|
@ -133,6 +133,10 @@ static const struct mCoreMemoryBlock _GBAMemoryBlocksEEPROM[] = {
|
|||
{ GBA_REGION_SRAM_MIRROR, "eeprom", "EEPROM", "EEPROM (8kiB)", 0, GBA_SIZE_EEPROM, GBA_SIZE_EEPROM, mCORE_MEMORY_RW },
|
||||
};
|
||||
|
||||
static const struct mCoreScreenRegion _GBAScreenRegions[] = {
|
||||
{ 0, "Screen", 0, 0, GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS }
|
||||
};
|
||||
|
||||
static const struct mCoreRegisterInfo _GBARegisters[] = {
|
||||
{ "r0", NULL, 4, 0xFFFFFFFF, mCORE_REGISTER_GPR },
|
||||
{ "r1", NULL, 4, 0xFFFFFFFF, mCORE_REGISTER_GPR },
|
||||
|
@ -430,19 +434,45 @@ static void _GBACoreReloadConfigOption(struct mCore* core, const char* option, c
|
|||
}
|
||||
}
|
||||
|
||||
static void _GBACoreDesiredVideoDimensions(const struct mCore* core, unsigned* width, unsigned* height) {
|
||||
static void _GBACoreBaseVideoSize(const struct mCore* core, unsigned* width, unsigned* height) {
|
||||
UNUSED(core);
|
||||
*width = GBA_VIDEO_HORIZONTAL_PIXELS;
|
||||
*height = GBA_VIDEO_VERTICAL_PIXELS;
|
||||
}
|
||||
|
||||
static void _GBACoreCurrentVideoSize(const struct mCore* core, unsigned* width, unsigned* height) {
|
||||
int scale = 1;
|
||||
#ifdef BUILD_GLES3
|
||||
const struct GBACore* gbacore = (const struct GBACore*) core;
|
||||
int scale = gbacore->glRenderer.scale;
|
||||
if (gbacore->glRenderer.outputTex != (unsigned) -1) {
|
||||
scale = gbacore->glRenderer.scale;
|
||||
}
|
||||
#else
|
||||
UNUSED(core);
|
||||
int scale = 1;
|
||||
#endif
|
||||
|
||||
*width = GBA_VIDEO_HORIZONTAL_PIXELS * scale;
|
||||
*height = GBA_VIDEO_VERTICAL_PIXELS * scale;
|
||||
}
|
||||
|
||||
static unsigned _GBACoreVideoScale(const struct mCore* core) {
|
||||
#ifdef BUILD_GLES3
|
||||
const struct GBACore* gbacore = (const struct GBACore*) core;
|
||||
if (gbacore->glRenderer.outputTex != (unsigned) -1) {
|
||||
return gbacore->glRenderer.scale;
|
||||
}
|
||||
#else
|
||||
UNUSED(core);
|
||||
#endif
|
||||
return 1;
|
||||
}
|
||||
|
||||
static size_t _GBACoreScreenRegions(const struct mCore* core, const struct mCoreScreenRegion** regions) {
|
||||
UNUSED(core);
|
||||
*regions = _GBAScreenRegions;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void _GBACoreSetVideoBuffer(struct mCore* core, color_t* buffer, size_t stride) {
|
||||
struct GBACore* gbacore = (struct GBACore*) core;
|
||||
gbacore->renderer.outputBuffer = buffer;
|
||||
|
@ -508,7 +538,7 @@ static void _GBACoreSetAVStream(struct mCore* core, struct mAVStream* stream) {
|
|||
gba->stream = stream;
|
||||
if (stream && stream->videoDimensionsChanged) {
|
||||
unsigned width, height;
|
||||
core->desiredVideoDimensions(core, &width, &height);
|
||||
core->currentVideoSize(core, &width, &height);
|
||||
stream->videoDimensionsChanged(stream, width, height);
|
||||
}
|
||||
if (stream && stream->videoFrameRateChanged) {
|
||||
|
@ -1380,7 +1410,10 @@ struct mCore* GBACoreCreate(void) {
|
|||
core->setSync = _GBACoreSetSync;
|
||||
core->loadConfig = _GBACoreLoadConfig;
|
||||
core->reloadConfigOption = _GBACoreReloadConfigOption;
|
||||
core->desiredVideoDimensions = _GBACoreDesiredVideoDimensions;
|
||||
core->baseVideoSize = _GBACoreBaseVideoSize;
|
||||
core->currentVideoSize = _GBACoreCurrentVideoSize;
|
||||
core->videoScale = _GBACoreVideoScale;
|
||||
core->screenRegions = _GBACoreScreenRegions;
|
||||
core->setVideoBuffer = _GBACoreSetVideoBuffer;
|
||||
core->setVideoGLTex = _GBACoreSetVideoGLTex;
|
||||
core->getPixels = _GBACoreGetPixels;
|
||||
|
|
|
@ -405,7 +405,7 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) {
|
|||
|
||||
#define LOAD_CART \
|
||||
wait += waitstatesRegion[address >> BASE_OFFSET]; \
|
||||
if ((address & (GBA_SIZE_ROM0 - 1)) < memory->romSize) { \
|
||||
if ((address & (GBA_SIZE_ROM0 - 4)) < memory->romSize) { \
|
||||
LOAD_32(value, address & (GBA_SIZE_ROM0 - 4), memory->rom); \
|
||||
} else if (memory->vfame.cartType) { \
|
||||
value = GBAVFameGetPatternValue(address, 32); \
|
||||
|
@ -570,11 +570,11 @@ uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
|
|||
case GBA_REGION_ROM1_EX:
|
||||
case GBA_REGION_ROM2:
|
||||
wait = memory->waitstatesNonseq16[address >> BASE_OFFSET];
|
||||
if ((address & (GBA_SIZE_ROM0 - 1)) < memory->romSize) {
|
||||
if ((address & (GBA_SIZE_ROM0 - 2)) < memory->romSize) {
|
||||
LOAD_16(value, address & (GBA_SIZE_ROM0 - 2), memory->rom);
|
||||
} else if (memory->vfame.cartType) {
|
||||
value = GBAVFameGetPatternValue(address, 16);
|
||||
} else if ((address & (GBA_SIZE_ROM0 - 1)) >= AGB_PRINT_BASE) {
|
||||
} else if ((address & (GBA_SIZE_ROM0 - 2)) >= AGB_PRINT_BASE) {
|
||||
uint32_t agbPrintAddr = address & 0x00FFFFFF;
|
||||
if (agbPrintAddr == AGB_PRINT_PROTECT) {
|
||||
value = memory->agbPrintProtect;
|
||||
|
@ -595,7 +595,7 @@ uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
|
|||
value = GBASavedataReadEEPROM(&memory->savedata);
|
||||
} else if ((address & 0x0DFC0000) >= 0x0DF80000 && memory->hw.devices & HW_EREADER) {
|
||||
value = GBACartEReaderRead(&memory->ereader, address);
|
||||
} else if ((address & (GBA_SIZE_ROM0 - 1)) < memory->romSize) {
|
||||
} else if ((address & (GBA_SIZE_ROM0 - 2)) < memory->romSize) {
|
||||
LOAD_16(value, address & (GBA_SIZE_ROM0 - 2), memory->rom);
|
||||
} else if (memory->vfame.cartType) {
|
||||
value = GBAVFameGetPatternValue(address, 16);
|
||||
|
@ -1219,7 +1219,7 @@ void GBAPatch32(struct ARMCore* cpu, uint32_t address, int32_t value, int32_t* o
|
|||
mLOG(GBA_MEM, STUB, "Unimplemented memory Patch32: 0x%08X", address);
|
||||
break;
|
||||
case GBA_REGION_PALETTE_RAM:
|
||||
LOAD_32(oldValue, address & (GBA_SIZE_PALETTE_RAM - 1), gba->video.palette);
|
||||
LOAD_32(oldValue, address & (GBA_SIZE_PALETTE_RAM - 4), gba->video.palette);
|
||||
STORE_32(value, address & (GBA_SIZE_PALETTE_RAM - 4), gba->video.palette);
|
||||
gba->video.renderer->writePalette(gba->video.renderer, address & (GBA_SIZE_PALETTE_RAM - 4), value);
|
||||
gba->video.renderer->writePalette(gba->video.renderer, (address & (GBA_SIZE_PALETTE_RAM - 4)) + 2, value >> 16);
|
||||
|
@ -1320,7 +1320,7 @@ void GBAPatch16(struct ARMCore* cpu, uint32_t address, int16_t value, int16_t* o
|
|||
case GBA_REGION_ROM2:
|
||||
case GBA_REGION_ROM2_EX:
|
||||
_pristineCow(gba);
|
||||
if ((address & (GBA_SIZE_ROM0 - 1)) >= gba->memory.romSize) {
|
||||
if ((address & (GBA_SIZE_ROM0 - 2)) >= gba->memory.romSize) {
|
||||
gba->memory.romSize = (address & (GBA_SIZE_ROM0 - 2)) + 2;
|
||||
gba->memory.romMask = toPow2(gba->memory.romSize) - 1;
|
||||
}
|
||||
|
|
|
@ -496,7 +496,7 @@ static void _drawTex(struct mCore* core, bool faded, bool both) {
|
|||
int wide = isWide ? 2 : 1;
|
||||
|
||||
unsigned corew, coreh;
|
||||
core->desiredVideoDimensions(core, &corew, &coreh);
|
||||
core->currentVideoSize(core, &corew, &coreh);
|
||||
|
||||
int w = corew;
|
||||
int h = coreh;
|
||||
|
|
|
@ -83,7 +83,7 @@ bool _mExampleRun(const struct mArguments* args, Socket client) {
|
|||
|
||||
// Get the dimensions required for this core and send them to the client.
|
||||
unsigned width, height;
|
||||
core->desiredVideoDimensions(core, &width, &height);
|
||||
core->baseVideoSize(core, &width, &height);
|
||||
ssize_t bufferSize = width * height * BYTES_PER_PIXEL;
|
||||
uint32_t sendNO;
|
||||
sendNO = htonl(width);
|
||||
|
|
|
@ -414,19 +414,13 @@ void retro_get_system_info(struct retro_system_info* info) {
|
|||
|
||||
void retro_get_system_av_info(struct retro_system_av_info* info) {
|
||||
unsigned width, height;
|
||||
core->desiredVideoDimensions(core, &width, &height);
|
||||
core->currentVideoSize(core, &width, &height);
|
||||
info->geometry.base_width = width;
|
||||
info->geometry.base_height = height;
|
||||
#ifdef M_CORE_GB
|
||||
if (core->platform(core) == mPLATFORM_GB) {
|
||||
info->geometry.max_width = 256;
|
||||
info->geometry.max_height = 224;
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
info->geometry.max_width = width;
|
||||
info->geometry.max_height = height;
|
||||
}
|
||||
|
||||
core->baseVideoSize(core, &width, &height);
|
||||
info->geometry.max_width = width;
|
||||
info->geometry.max_height = height;
|
||||
|
||||
info->geometry.aspect_ratio = width / (double) height;
|
||||
info->timing.fps = core->frequency(core) / (float) core->frameCycles(core);
|
||||
|
@ -615,7 +609,7 @@ void retro_run(void) {
|
|||
|
||||
core->runFrame(core);
|
||||
unsigned width, height;
|
||||
core->desiredVideoDimensions(core, &width, &height);
|
||||
core->currentVideoSize(core, &width, &height);
|
||||
videoCallback(outputBuffer, width, height, BYTES_PER_PIXEL * 256);
|
||||
|
||||
#ifdef M_CORE_GBA
|
||||
|
|
|
@ -68,7 +68,7 @@
|
|||
outputBuffer = nil;
|
||||
|
||||
unsigned width, height;
|
||||
core->desiredVideoDimensions(core, &width, &height);
|
||||
core->baseVideoSize(core, &width, &height);
|
||||
outputBuffer = malloc(width * height * BYTES_PER_PIXEL);
|
||||
core->setVideoBuffer(core, outputBuffer, width);
|
||||
core->setAudioBufferSize(core, SAMPLES);
|
||||
|
@ -143,14 +143,14 @@
|
|||
- (OEIntRect)screenRect
|
||||
{
|
||||
unsigned width, height;
|
||||
core->desiredVideoDimensions(core, &width, &height);
|
||||
core->currentVideoSize(core, &width, &height);
|
||||
return OEIntRectMake(0, 0, width, height);
|
||||
}
|
||||
|
||||
- (OEIntSize)bufferSize
|
||||
{
|
||||
unsigned width, height;
|
||||
core->desiredVideoDimensions(core, &width, &height);
|
||||
core->baseVideoSize(core, &width, &height);
|
||||
return OEIntSizeMake(width, height);
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,13 @@
|
|||
|
||||
#include <mgba-util/math.h>
|
||||
|
||||
static const GLint _glVertices[] = {
|
||||
0, 0,
|
||||
1, 0,
|
||||
1, 1,
|
||||
0, 1
|
||||
};
|
||||
|
||||
static const GLint _glTexCoords[] = {
|
||||
0, 0,
|
||||
1, 0,
|
||||
|
@ -14,85 +21,102 @@ static const GLint _glTexCoords[] = {
|
|||
0, 1
|
||||
};
|
||||
|
||||
static inline void _initTex(void) {
|
||||
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
|
||||
#ifndef _WIN32
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void mGLContextInit(struct VideoBackend* v, WHandle handle) {
|
||||
UNUSED(handle);
|
||||
struct mGLContext* context = (struct mGLContext*) v;
|
||||
v->width = 1;
|
||||
v->height = 1;
|
||||
memset(context->layerDims, 0, sizeof(context->layerDims));
|
||||
memset(context->imageSizes, -1, sizeof(context->imageSizes));
|
||||
glGenTextures(2, context->tex);
|
||||
glBindTexture(GL_TEXTURE_2D, context->tex[0]);
|
||||
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
|
||||
#ifndef _WIN32
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
#endif
|
||||
_initTex();
|
||||
glBindTexture(GL_TEXTURE_2D, context->tex[1]);
|
||||
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
|
||||
#ifndef _WIN32
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
#endif
|
||||
_initTex();
|
||||
context->activeTex = 0;
|
||||
|
||||
glGenTextures(VIDEO_LAYER_MAX, context->tex);
|
||||
int i;
|
||||
for (i = 0; i < VIDEO_LAYER_MAX; ++i) {
|
||||
glBindTexture(GL_TEXTURE_2D, context->layers[i]);
|
||||
_initTex();
|
||||
}
|
||||
}
|
||||
|
||||
static void mGLContextSetDimensions(struct VideoBackend* v, unsigned width, unsigned height) {
|
||||
static inline void _setTexDims(int width, int height) {
|
||||
#ifdef COLOR_16_BIT
|
||||
#ifdef COLOR_5_6_5
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0);
|
||||
#else
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, 0);
|
||||
#endif
|
||||
#elif defined(__BIG_ENDIAN__)
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, 0);
|
||||
#else
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void mGLContextSetLayerDimensions(struct VideoBackend* v, enum VideoLayer layer, const struct Rectangle* dims) {
|
||||
struct mGLContext* context = (struct mGLContext*) v;
|
||||
if (width == v->width && height == v->height) {
|
||||
if (layer >= VIDEO_LAYER_MAX) {
|
||||
return;
|
||||
}
|
||||
v->width = width;
|
||||
v->height = height;
|
||||
context->layerDims[layer].x = dims->x;
|
||||
context->layerDims[layer].y = dims->y;
|
||||
if (dims->width == context->layerDims[layer].width && dims->height == context->layerDims[layer].height) {
|
||||
return;
|
||||
}
|
||||
context->layerDims[layer].width = dims->width;
|
||||
context->layerDims[layer].height = dims->height;
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, context->tex[0]);
|
||||
#ifdef COLOR_16_BIT
|
||||
#ifdef COLOR_5_6_5
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0);
|
||||
#else
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, 0);
|
||||
#endif
|
||||
#elif defined(__BIG_ENDIAN__)
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, 0);
|
||||
#else
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
|
||||
#endif
|
||||
if (context->imageSizes[layer].width <= 0 || context->imageSizes[layer].height <= 0) {
|
||||
if (layer == VIDEO_LAYER_IMAGE) {
|
||||
glBindTexture(GL_TEXTURE_2D, context->tex[0]);
|
||||
_setTexDims(dims->width, dims->height);
|
||||
glBindTexture(GL_TEXTURE_2D, context->tex[1]);
|
||||
_setTexDims(dims->width, dims->height);
|
||||
} else {
|
||||
glBindTexture(GL_TEXTURE_2D, context->layers[layer]);
|
||||
_setTexDims(dims->width, dims->height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, context->tex[1]);
|
||||
#ifdef COLOR_16_BIT
|
||||
#ifdef COLOR_5_6_5
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0);
|
||||
#else
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, 0);
|
||||
#endif
|
||||
#elif defined(__BIG_ENDIAN__)
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, 0);
|
||||
#else
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
|
||||
#endif
|
||||
|
||||
context->vertices[0] = 0;
|
||||
context->vertices[1] = 0;
|
||||
context->vertices[2] = toPow2(width);
|
||||
context->vertices[3] = 0;
|
||||
context->vertices[4] = toPow2(width);
|
||||
context->vertices[5] = toPow2(height);
|
||||
context->vertices[6] = 0;
|
||||
context->vertices[7] = toPow2(height);
|
||||
static void mGLContextLayerDimensions(const struct VideoBackend* v, enum VideoLayer layer, struct Rectangle* dims) {
|
||||
struct mGLContext* context = (struct mGLContext*) v;
|
||||
if (layer >= VIDEO_LAYER_MAX) {
|
||||
return;
|
||||
}
|
||||
memcpy(dims, &context->layerDims[layer], sizeof(*dims));
|
||||
}
|
||||
|
||||
static void mGLContextDeinit(struct VideoBackend* v) {
|
||||
struct mGLContext* context = (struct mGLContext*) v;
|
||||
glDeleteTextures(2, context->tex);
|
||||
glDeleteTextures(VIDEO_LAYER_MAX, context->layers);
|
||||
}
|
||||
|
||||
static void mGLContextResized(struct VideoBackend* v, unsigned w, unsigned h) {
|
||||
unsigned drawW = w;
|
||||
unsigned drawH = h;
|
||||
|
||||
unsigned maxW;
|
||||
unsigned maxH;
|
||||
VideoBackendGetFrameSize(v, &maxW, &maxH);
|
||||
|
||||
if (v->lockAspectRatio) {
|
||||
lockAspectRatioUInt(v->width, v->height, &drawW, &drawH);
|
||||
lockAspectRatioUInt(maxW, maxH, &drawW, &drawH);
|
||||
}
|
||||
if (v->lockIntegerScaling) {
|
||||
lockIntegerRatioUInt(v->width, &drawW);
|
||||
lockIntegerRatioUInt(v->height, &drawH);
|
||||
lockIntegerRatioUInt(maxW, &drawW);
|
||||
lockIntegerRatioUInt(maxH, &drawH);
|
||||
}
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadIdentity();
|
||||
|
@ -107,33 +131,7 @@ static void mGLContextClear(struct VideoBackend* v) {
|
|||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
||||
void mGLContextDrawFrame(struct VideoBackend* v) {
|
||||
struct mGLContext* context = (struct mGLContext*) v;
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
glEnableClientState(GL_VERTEX_ARRAY);
|
||||
glVertexPointer(2, GL_INT, 0, context->vertices);
|
||||
glTexCoordPointer(2, GL_INT, 0, _glTexCoords);
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glLoadIdentity();
|
||||
glOrtho(0, v->width, v->height, 0, 0, 1);
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadIdentity();
|
||||
if (v->interframeBlending) {
|
||||
glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA);
|
||||
glBlendColor(1, 1, 1, 0.5);
|
||||
glBindTexture(GL_TEXTURE_2D, context->tex[context->activeTex ^ 1]);
|
||||
if (v->filter) {
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
} else {
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
}
|
||||
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
|
||||
glEnable(GL_BLEND);
|
||||
}
|
||||
glBindTexture(GL_TEXTURE_2D, context->tex[context->activeTex]);
|
||||
static void _setFilter(struct VideoBackend* v) {
|
||||
if (v->filter) {
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
|
@ -141,36 +139,147 @@ void mGLContextDrawFrame(struct VideoBackend* v) {
|
|||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
}
|
||||
}
|
||||
|
||||
static void _setFrame(struct Rectangle* dims, int frameW, int frameH) {
|
||||
GLint viewport[4];
|
||||
glGetIntegerv(GL_VIEWPORT, viewport);
|
||||
glScissor(viewport[0] + dims->x * viewport[2] / frameW,
|
||||
viewport[1] + dims->y * viewport[3] / frameH,
|
||||
dims->width * viewport[2] / frameW,
|
||||
dims->height * viewport[3] / frameH);
|
||||
glTranslatef(dims->x, dims->y, 0);
|
||||
glScalef(toPow2(dims->width), toPow2(dims->height), 1);
|
||||
}
|
||||
|
||||
void mGLContextDrawFrame(struct VideoBackend* v) {
|
||||
struct mGLContext* context = (struct mGLContext*) v;
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
glEnable(GL_SCISSOR_TEST);
|
||||
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
glEnableClientState(GL_VERTEX_ARRAY);
|
||||
glVertexPointer(2, GL_INT, 0, _glVertices);
|
||||
glTexCoordPointer(2, GL_INT, 0, _glTexCoords);
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glLoadIdentity();
|
||||
unsigned frameW, frameH;
|
||||
VideoBackendGetFrameSize(v, &frameW, &frameH);
|
||||
glOrtho(0, frameW, frameH, 0, 0, 1);
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadIdentity();
|
||||
|
||||
int layer;
|
||||
for (layer = 0; layer < VIDEO_LAYER_IMAGE; ++layer) {
|
||||
if (context->layerDims[layer].width < 1 || context->layerDims[layer].height < 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
glBindTexture(GL_TEXTURE_2D, context->layers[layer]);
|
||||
_setFilter(v);
|
||||
glPushMatrix();
|
||||
_setFrame(&context->layerDims[layer], frameW, frameH);
|
||||
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
|
||||
glPopMatrix();
|
||||
}
|
||||
|
||||
_setFrame(&context->layerDims[VIDEO_LAYER_IMAGE], frameW, frameH);
|
||||
if (v->interframeBlending) {
|
||||
glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA);
|
||||
glBlendColor(1, 1, 1, 0.5);
|
||||
glBindTexture(GL_TEXTURE_2D, context->tex[context->activeTex ^ 1]);
|
||||
_setFilter(v);
|
||||
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
|
||||
glEnable(GL_BLEND);
|
||||
}
|
||||
glBindTexture(GL_TEXTURE_2D, context->tex[context->activeTex]);
|
||||
_setFilter(v);
|
||||
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
|
||||
void mGLContextPostFrame(struct VideoBackend* v, const void* frame) {
|
||||
static void mGLContextSetImageSize(struct VideoBackend* v, enum VideoLayer layer, int width, int height) {
|
||||
struct mGLContext* context = (struct mGLContext*) v;
|
||||
context->activeTex ^= 1;
|
||||
glBindTexture(GL_TEXTURE_2D, context->tex[context->activeTex]);
|
||||
if (layer >= VIDEO_LAYER_MAX) {
|
||||
return;
|
||||
}
|
||||
if (width <= 0 || height <= 0) {
|
||||
context->imageSizes[layer].width = -1;
|
||||
context->imageSizes[layer].height = -1;
|
||||
width = context->layerDims[layer].width;
|
||||
height = context->layerDims[layer].height;
|
||||
} else {
|
||||
context->imageSizes[layer].width = width;
|
||||
context->imageSizes[layer].height = height;
|
||||
}
|
||||
if (layer == VIDEO_LAYER_IMAGE) {
|
||||
glBindTexture(GL_TEXTURE_2D, context->tex[0]);
|
||||
_setTexDims(width, height);
|
||||
glBindTexture(GL_TEXTURE_2D, context->tex[1]);
|
||||
_setTexDims(width, height);
|
||||
} else {
|
||||
glBindTexture(GL_TEXTURE_2D, context->layers[layer]);
|
||||
_setTexDims(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
static void mGLContextImageSize(struct VideoBackend* v, enum VideoLayer layer, int* width, int* height) {
|
||||
struct mGLContext* context = (struct mGLContext*) v;
|
||||
if (layer >= VIDEO_LAYER_MAX) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (context->imageSizes[layer].width <= 0 || context->imageSizes[layer].height <= 0) {
|
||||
*width = context->layerDims[layer].width;
|
||||
*height = context->layerDims[layer].height;
|
||||
} else {
|
||||
*width = context->imageSizes[layer].width;
|
||||
*height = context->imageSizes[layer].height;
|
||||
}
|
||||
}
|
||||
|
||||
void mGLContextPostFrame(struct VideoBackend* v, enum VideoLayer layer, const void* frame) {
|
||||
struct mGLContext* context = (struct mGLContext*) v;
|
||||
if (layer >= VIDEO_LAYER_MAX) {
|
||||
return;
|
||||
}
|
||||
if (layer == VIDEO_LAYER_IMAGE) {
|
||||
context->activeTex ^= 1;
|
||||
glBindTexture(GL_TEXTURE_2D, context->tex[context->activeTex]);
|
||||
} else {
|
||||
glBindTexture(GL_TEXTURE_2D, context->layers[layer]);
|
||||
}
|
||||
|
||||
int width = context->imageSizes[layer].width;
|
||||
int height = context->imageSizes[layer].height;
|
||||
|
||||
if (width <= 0 || height <= 0) {
|
||||
width = context->layerDims[layer].width;
|
||||
height = context->layerDims[layer].height;
|
||||
}
|
||||
#ifdef COLOR_16_BIT
|
||||
#ifdef COLOR_5_6_5
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, v->width, v->height, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, frame);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, frame);
|
||||
#else
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, v->width, v->height, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, frame);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, frame);
|
||||
#endif
|
||||
#elif defined(__BIG_ENDIAN__)
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, v->width, v->height, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, frame);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, frame);
|
||||
#else
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, v->width, v->height, GL_RGBA, GL_UNSIGNED_BYTE, frame);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, frame);
|
||||
#endif
|
||||
}
|
||||
|
||||
void mGLContextCreate(struct mGLContext* context) {
|
||||
context->d.init = mGLContextInit;
|
||||
context->d.deinit = mGLContextDeinit;
|
||||
context->d.setDimensions = mGLContextSetDimensions;
|
||||
context->d.resized = mGLContextResized;
|
||||
context->d.swap = 0;
|
||||
context->d.setLayerDimensions = mGLContextSetLayerDimensions;
|
||||
context->d.layerDimensions = mGLContextLayerDimensions;
|
||||
context->d.contextResized = mGLContextResized;
|
||||
context->d.swap = NULL;
|
||||
context->d.clear = mGLContextClear;
|
||||
context->d.postFrame = mGLContextPostFrame;
|
||||
context->d.setImageSize = mGLContextSetImageSize;
|
||||
context->d.imageSize = mGLContextImageSize;
|
||||
context->d.setImage = mGLContextPostFrame;
|
||||
context->d.drawFrame = mGLContextDrawFrame;
|
||||
context->d.setMessage = 0;
|
||||
context->d.clearMessage = 0;
|
||||
}
|
||||
|
|
|
@ -26,9 +26,11 @@ CXX_GUARD_START
|
|||
struct mGLContext {
|
||||
struct VideoBackend d;
|
||||
|
||||
GLuint tex[2];
|
||||
GLint vertices[8];
|
||||
int activeTex;
|
||||
GLuint tex[2];
|
||||
GLuint layers[VIDEO_LAYER_MAX];
|
||||
struct Rectangle layerDims[VIDEO_LAYER_MAX];
|
||||
struct Size imageSizes[VIDEO_LAYER_MAX];
|
||||
};
|
||||
|
||||
void mGLContextCreate(struct mGLContext*);
|
||||
|
|
|
@ -101,12 +101,15 @@ static const GLfloat _vertices[] = {
|
|||
static void mGLES2ContextInit(struct VideoBackend* v, WHandle handle) {
|
||||
UNUSED(handle);
|
||||
struct mGLES2Context* context = (struct mGLES2Context*) v;
|
||||
v->width = 1;
|
||||
v->height = 1;
|
||||
glGenTextures(1, &context->tex);
|
||||
glBindTexture(GL_TEXTURE_2D, context->tex);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
memset(context->layerDims, 0, sizeof(context->layerDims));
|
||||
|
||||
glGenTextures(VIDEO_LAYER_MAX, context->tex);
|
||||
int i;
|
||||
for (i = 0; i < VIDEO_LAYER_MAX; ++i) {
|
||||
glBindTexture(GL_TEXTURE_2D, context->tex[i]);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
}
|
||||
|
||||
glGenBuffers(1, &context->vbo);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, context->vbo);
|
||||
|
@ -177,15 +180,7 @@ static void mGLES2ContextInit(struct VideoBackend* v, WHandle handle) {
|
|||
context->finalShader.tex = 0;
|
||||
}
|
||||
|
||||
static void mGLES2ContextSetDimensions(struct VideoBackend* v, unsigned width, unsigned height) {
|
||||
struct mGLES2Context* context = (struct mGLES2Context*) v;
|
||||
if (width == v->width && height == v->height) {
|
||||
return;
|
||||
}
|
||||
v->width = width;
|
||||
v->height = height;
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, context->tex);
|
||||
static inline void _setTexDims(int width, int height) {
|
||||
#ifdef COLOR_16_BIT
|
||||
#ifdef COLOR_5_6_5
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0);
|
||||
|
@ -197,20 +192,54 @@ static void mGLES2ContextSetDimensions(struct VideoBackend* v, unsigned width, u
|
|||
#else
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
size_t n;
|
||||
for (n = 0; n < context->nShaders; ++n) {
|
||||
if (context->shaders[n].width < 0 || context->shaders[n].height < 0) {
|
||||
context->shaders[n].dirty = true;
|
||||
static void mGLES2ContextSetLayerDimensions(struct VideoBackend* v, enum VideoLayer layer, const struct Rectangle* dims) {
|
||||
struct mGLES2Context* context = (struct mGLES2Context*) v;
|
||||
if (layer >= VIDEO_LAYER_MAX) {
|
||||
return;
|
||||
}
|
||||
if (dims->width != context->layerDims[layer].width && dims->height != context->layerDims[layer].height) {
|
||||
context->layerDims[layer].width = dims->width;
|
||||
context->layerDims[layer].height = dims->height;
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, context->tex[layer]);
|
||||
if (context->imageSizes[layer].width <= 0 || context->imageSizes[layer].height <= 0) {
|
||||
_setTexDims(dims->width, dims->height);
|
||||
}
|
||||
}
|
||||
context->initialShader.dirty = true;
|
||||
context->interframeShader.dirty = true;
|
||||
|
||||
context->layerDims[layer].x = dims->x;
|
||||
context->layerDims[layer].y = dims->y;
|
||||
|
||||
unsigned newW;
|
||||
unsigned newH;
|
||||
VideoBackendGetFrameSize(v, &newW, &newH);
|
||||
if (newW != context->width || newH != context->height) {
|
||||
size_t n;
|
||||
for (n = 0; n < context->nShaders; ++n) {
|
||||
if (context->shaders[n].width < 0 || context->shaders[n].height < 0) {
|
||||
context->shaders[n].dirty = true;
|
||||
}
|
||||
}
|
||||
context->initialShader.dirty = true;
|
||||
context->interframeShader.dirty = true;
|
||||
context->width = newW;
|
||||
context->height = newH;
|
||||
}
|
||||
}
|
||||
|
||||
static void mGLES2ContextLayerDimensions(const struct VideoBackend* v, enum VideoLayer layer, struct Rectangle* dims) {
|
||||
struct mGLES2Context* context = (struct mGLES2Context*) v;
|
||||
if (layer >= VIDEO_LAYER_MAX) {
|
||||
return;
|
||||
}
|
||||
memcpy(dims, &context->layerDims[layer], sizeof(*dims));
|
||||
}
|
||||
|
||||
static void mGLES2ContextDeinit(struct VideoBackend* v) {
|
||||
struct mGLES2Context* context = (struct mGLES2Context*) v;
|
||||
glDeleteTextures(1, &context->tex);
|
||||
glDeleteTextures(VIDEO_LAYER_MAX, context->tex);
|
||||
glDeleteBuffers(1, &context->vbo);
|
||||
mGLES2ShaderDeinit(&context->initialShader);
|
||||
mGLES2ShaderDeinit(&context->finalShader);
|
||||
|
@ -222,12 +251,16 @@ static void mGLES2ContextResized(struct VideoBackend* v, unsigned w, unsigned h)
|
|||
struct mGLES2Context* context = (struct mGLES2Context*) v;
|
||||
unsigned drawW = w;
|
||||
unsigned drawH = h;
|
||||
|
||||
unsigned maxW = context->width;
|
||||
unsigned maxH = context->height;
|
||||
|
||||
if (v->lockAspectRatio) {
|
||||
lockAspectRatioUInt(v->width, v->height, &drawW, &drawH);
|
||||
lockAspectRatioUInt(maxW, maxH, &drawW, &drawH);
|
||||
}
|
||||
if (v->lockIntegerScaling) {
|
||||
lockIntegerRatioUInt(v->width, &drawW);
|
||||
lockIntegerRatioUInt(v->height, &drawH);
|
||||
lockIntegerRatioUInt(maxW, &drawW);
|
||||
lockIntegerRatioUInt(maxH, &drawH);
|
||||
}
|
||||
size_t n;
|
||||
for (n = 0; n < context->nShaders; ++n) {
|
||||
|
@ -249,7 +282,7 @@ static void mGLES2ContextClear(struct VideoBackend* v) {
|
|||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
||||
void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) {
|
||||
static void _drawShaderEx(struct mGLES2Context* context, struct mGLES2Shader* shader, int layer) {
|
||||
GLint viewport[4];
|
||||
glGetIntegerv(GL_VIEWPORT, viewport);
|
||||
int drawW = shader->width;
|
||||
|
@ -260,19 +293,19 @@ void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) {
|
|||
drawW = viewport[2];
|
||||
padW = viewport[0];
|
||||
} else if (shader->width < 0) {
|
||||
drawW = context->d.width * -shader->width;
|
||||
drawW = context->width * -shader->width;
|
||||
}
|
||||
if (!drawH) {
|
||||
drawH = viewport[3];
|
||||
padH = viewport[1];
|
||||
} else if (shader->height < 0) {
|
||||
drawH = context->d.height * -shader->height;
|
||||
drawH = context->height * -shader->height;
|
||||
}
|
||||
if (shader->integerScaling) {
|
||||
padW = 0;
|
||||
padH = 0;
|
||||
drawW -= drawW % context->d.width;
|
||||
drawH -= drawH % context->d.height;
|
||||
drawW -= drawW % context->width;
|
||||
drawH -= drawH % context->height;
|
||||
}
|
||||
|
||||
if (shader->dirty) {
|
||||
|
@ -286,22 +319,29 @@ void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) {
|
|||
shader->dirty = false;
|
||||
}
|
||||
|
||||
if (layer >= 0 && layer < VIDEO_LAYER_MAX) {
|
||||
glViewport(context->layerDims[layer].x, context->layerDims[layer].y, context->layerDims[layer].width, context->layerDims[layer].height);
|
||||
} else {
|
||||
glViewport(padW, padH, drawW, drawH);
|
||||
}
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, shader->fbo);
|
||||
glViewport(padW, padH, drawW, drawH);
|
||||
if (shader->blend) {
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
} else {
|
||||
glDisable(GL_BLEND);
|
||||
glClearColor(0.f, 0.f, 0.f, 1.f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
if (layer <= VIDEO_LAYER_BACKGROUND) {
|
||||
glClearColor(0.f, 0.f, 0.f, 1.f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
}
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, shader->filter ? GL_LINEAR : GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, shader->filter ? GL_LINEAR : GL_NEAREST);
|
||||
glUseProgram(shader->program);
|
||||
glUniform1i(shader->texLocation, 0);
|
||||
glUniform2f(shader->texSizeLocation, context->d.width, context->d.height);
|
||||
glUniform2f(shader->texSizeLocation, context->width, context->height);
|
||||
glUniform2f(shader->outputSizeLocation, drawW, drawH);
|
||||
#ifdef BUILD_GLES3
|
||||
if (shader->vao != (GLuint) -1) {
|
||||
|
@ -368,21 +408,36 @@ void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) {
|
|||
glBindTexture(GL_TEXTURE_2D, shader->tex);
|
||||
}
|
||||
|
||||
static void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) {
|
||||
_drawShaderEx(context, shader, -1);
|
||||
}
|
||||
|
||||
void mGLES2ContextDrawFrame(struct VideoBackend* v) {
|
||||
struct mGLES2Context* context = (struct mGLES2Context*) v;
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, context->tex);
|
||||
|
||||
GLint viewport[4];
|
||||
glGetIntegerv(GL_VIEWPORT, viewport);
|
||||
|
||||
context->finalShader.filter = v->filter;
|
||||
_drawShader(context, &context->initialShader);
|
||||
if (v->interframeBlending) {
|
||||
context->interframeShader.blend = true;
|
||||
glViewport(0, 0, viewport[2], viewport[3]);
|
||||
_drawShader(context, &context->interframeShader);
|
||||
|
||||
int layer;
|
||||
for (layer = 0; layer <= VIDEO_LAYER_IMAGE; ++layer) {
|
||||
if (context->layerDims[layer].width < 1 || context->layerDims[layer].height < 1) {
|
||||
continue;
|
||||
}
|
||||
glBindTexture(GL_TEXTURE_2D, context->tex[layer]);
|
||||
_drawShaderEx(context, &context->initialShader, layer);
|
||||
if (layer != VIDEO_LAYER_IMAGE) {
|
||||
continue;
|
||||
}
|
||||
if (v->interframeBlending) {
|
||||
context->interframeShader.blend = true;
|
||||
glViewport(0, 0, viewport[2], viewport[3]);
|
||||
_drawShader(context, &context->interframeShader);
|
||||
}
|
||||
}
|
||||
|
||||
size_t n;
|
||||
for (n = 0; n < context->nShaders; ++n) {
|
||||
glViewport(0, 0, viewport[2], viewport[3]);
|
||||
|
@ -392,8 +447,8 @@ void mGLES2ContextDrawFrame(struct VideoBackend* v) {
|
|||
_drawShader(context, &context->finalShader);
|
||||
if (v->interframeBlending) {
|
||||
context->interframeShader.blend = false;
|
||||
glBindTexture(GL_TEXTURE_2D, context->tex);
|
||||
_drawShader(context, &context->initialShader);
|
||||
glBindTexture(GL_TEXTURE_2D, context->tex[VIDEO_LAYER_IMAGE]);
|
||||
_drawShaderEx(context, &context->initialShader, VIDEO_LAYER_IMAGE);
|
||||
glViewport(0, 0, viewport[2], viewport[3]);
|
||||
_drawShader(context, &context->interframeShader);
|
||||
}
|
||||
|
@ -406,33 +461,79 @@ void mGLES2ContextDrawFrame(struct VideoBackend* v) {
|
|||
#endif
|
||||
}
|
||||
|
||||
void mGLES2ContextPostFrame(struct VideoBackend* v, const void* frame) {
|
||||
static void mGLES2ContextSetImageSize(struct VideoBackend* v, enum VideoLayer layer, int width, int height) {
|
||||
struct mGLES2Context* context = (struct mGLES2Context*) v;
|
||||
glBindTexture(GL_TEXTURE_2D, context->tex);
|
||||
if (layer >= VIDEO_LAYER_MAX) {
|
||||
return;
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, context->tex[layer]);
|
||||
if (width <= 0 || height <= 0) {
|
||||
context->imageSizes[layer].width = -1;
|
||||
context->imageSizes[layer].height = -1;
|
||||
width = context->layerDims[layer].width;
|
||||
height = context->layerDims[layer].height;
|
||||
} else {
|
||||
context->imageSizes[layer].width = width;
|
||||
context->imageSizes[layer].height = height;
|
||||
}
|
||||
_setTexDims(width, height);
|
||||
}
|
||||
|
||||
static void mGLES2ContextImageSize(struct VideoBackend* v, enum VideoLayer layer, int* width, int* height) {
|
||||
struct mGLES2Context* context = (struct mGLES2Context*) v;
|
||||
if (layer >= VIDEO_LAYER_MAX) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (context->imageSizes[layer].width <= 0 || context->imageSizes[layer].height <= 0) {
|
||||
*width = context->layerDims[layer].width;
|
||||
*height = context->layerDims[layer].height;
|
||||
} else {
|
||||
*width = context->imageSizes[layer].width;
|
||||
*height = context->imageSizes[layer].height;
|
||||
}
|
||||
}
|
||||
|
||||
void mGLES2ContextPostFrame(struct VideoBackend* v, enum VideoLayer layer, const void* frame) {
|
||||
struct mGLES2Context* context = (struct mGLES2Context*) v;
|
||||
if (layer >= VIDEO_LAYER_MAX) {
|
||||
return;
|
||||
}
|
||||
|
||||
int width = context->imageSizes[layer].width;
|
||||
int height = context->imageSizes[layer].height;
|
||||
|
||||
if (width <= 0 || height <= 0) {
|
||||
width = context->layerDims[layer].width;
|
||||
height = context->layerDims[layer].height;
|
||||
}
|
||||
glBindTexture(GL_TEXTURE_2D, context->tex[layer]);
|
||||
#ifdef COLOR_16_BIT
|
||||
#ifdef COLOR_5_6_5
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, v->width, v->height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, frame);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, frame);
|
||||
#else
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, v->width, v->height, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, frame);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, frame);
|
||||
#endif
|
||||
#elif defined(__BIG_ENDIAN__)
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, v->width, v->height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, frame);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, frame);
|
||||
#else
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, v->width, v->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, frame);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, frame);
|
||||
#endif
|
||||
}
|
||||
|
||||
void mGLES2ContextCreate(struct mGLES2Context* context) {
|
||||
context->d.init = mGLES2ContextInit;
|
||||
context->d.deinit = mGLES2ContextDeinit;
|
||||
context->d.setDimensions = mGLES2ContextSetDimensions;
|
||||
context->d.resized = mGLES2ContextResized;
|
||||
context->d.swap = 0;
|
||||
context->d.setLayerDimensions = mGLES2ContextSetLayerDimensions;
|
||||
context->d.layerDimensions = mGLES2ContextLayerDimensions;
|
||||
context->d.contextResized = mGLES2ContextResized;
|
||||
context->d.swap = NULL;
|
||||
context->d.clear = mGLES2ContextClear;
|
||||
context->d.postFrame = mGLES2ContextPostFrame;
|
||||
context->d.setImageSize = mGLES2ContextSetImageSize;
|
||||
context->d.imageSize = mGLES2ContextImageSize;
|
||||
context->d.setImage = mGLES2ContextPostFrame;
|
||||
context->d.drawFrame = mGLES2ContextDrawFrame;
|
||||
context->d.setMessage = 0;
|
||||
context->d.clearMessage = 0;
|
||||
context->shaders = 0;
|
||||
context->nShaders = 0;
|
||||
}
|
||||
|
|
|
@ -79,9 +79,14 @@ struct mGLES2Shader {
|
|||
struct mGLES2Context {
|
||||
struct VideoBackend d;
|
||||
|
||||
GLuint tex;
|
||||
GLuint tex[VIDEO_LAYER_MAX];
|
||||
GLuint vbo;
|
||||
|
||||
struct Rectangle layerDims[VIDEO_LAYER_MAX];
|
||||
struct Size imageSizes[VIDEO_LAYER_MAX];
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
|
||||
struct mGLES2Shader initialShader;
|
||||
struct mGLES2Shader finalShader;
|
||||
struct mGLES2Shader interframeShader;
|
||||
|
|
|
@ -323,7 +323,7 @@ void mPSP2Setup(struct mGUIRunner* runner) {
|
|||
mInputBindAxis(&runner->core->inputMap, PSP2_INPUT, 1, &desc);
|
||||
|
||||
unsigned width, height;
|
||||
runner->core->desiredVideoDimensions(runner->core, &width, &height);
|
||||
runner->core->baseVideoSize(runner->core, &width, &height);
|
||||
tex[0] = vita2d_create_empty_texture_format(256, toPow2(height), SCE_GXM_TEXTURE_FORMAT_X8U8U8U8_1BGR);
|
||||
tex[1] = vita2d_create_empty_texture_format(256, toPow2(height), SCE_GXM_TEXTURE_FORMAT_X8U8U8U8_1BGR);
|
||||
currentTex = 0;
|
||||
|
@ -614,7 +614,7 @@ void mPSP2Swap(struct mGUIRunner* runner) {
|
|||
|
||||
void mPSP2Draw(struct mGUIRunner* runner, bool faded) {
|
||||
unsigned width, height;
|
||||
runner->core->desiredVideoDimensions(runner->core, &width, &height);
|
||||
runner->core->currentVideoSize(runner->core, &width, &height);
|
||||
if (interframeBlending) {
|
||||
_drawTex(tex[!currentTex], width, height, faded, false);
|
||||
}
|
||||
|
|
|
@ -241,7 +241,7 @@ class Core(object):
|
|||
def desired_video_dimensions(self):
|
||||
width = ffi.new("unsigned*")
|
||||
height = ffi.new("unsigned*")
|
||||
self._core.desiredVideoDimensions(self._core, width, height)
|
||||
self._core.currentVideoSize(self._core, width, height)
|
||||
return width[0], height[0]
|
||||
|
||||
@protected
|
||||
|
|
|
@ -100,6 +100,7 @@ ApplicationUpdater::UpdateInfo ApplicationUpdater::currentVersion() {
|
|||
info.version = QLatin1String(projectVersion);
|
||||
info.rev = gitRevision;
|
||||
info.commit = QLatin1String(gitCommit);
|
||||
info.size = 0;
|
||||
return info;
|
||||
}
|
||||
|
||||
|
|
|
@ -49,10 +49,10 @@ private:
|
|||
BattleChip createChip(int id) const;
|
||||
|
||||
QMap<int, QString> m_chipIdToName;
|
||||
int m_flavor;
|
||||
int m_flavor = 0;
|
||||
int m_scale = 1;
|
||||
|
||||
QList<BattleChip> m_deck;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -266,7 +266,7 @@ mPlatform CoreController::platform() const {
|
|||
|
||||
QSize CoreController::screenDimensions() const {
|
||||
unsigned width, height;
|
||||
m_threadContext.core->desiredVideoDimensions(m_threadContext.core, &width, &height);
|
||||
m_threadContext.core->currentVideoSize(m_threadContext.core, &width, &height);
|
||||
|
||||
return QSize(width, height);
|
||||
}
|
||||
|
@ -275,6 +275,10 @@ QPair<unsigned, unsigned> CoreController::frameRate() const {
|
|||
return qMakePair(m_threadContext.core->frameCycles(m_threadContext.core), m_threadContext.core->frequency(m_threadContext.core));
|
||||
}
|
||||
|
||||
unsigned CoreController::videoScale() const {
|
||||
return m_threadContext.core->videoScale(m_threadContext.core);
|
||||
}
|
||||
|
||||
void CoreController::loadConfig(ConfigController* config) {
|
||||
Interrupter interrupter(this);
|
||||
m_loadStateFlags = config->getOption("loadStateExtdata", m_loadStateFlags).toInt();
|
||||
|
@ -1165,7 +1169,7 @@ void CoreController::updateKeys() {
|
|||
void CoreController::finishFrame() {
|
||||
if (!m_hwaccel) {
|
||||
unsigned width, height;
|
||||
m_threadContext.core->desiredVideoDimensions(m_threadContext.core, &width, &height);
|
||||
m_threadContext.core->currentVideoSize(m_threadContext.core, &width, &height);
|
||||
|
||||
QMutexLocker locker(&m_bufferMutex);
|
||||
memcpy(m_completeBuffer.data(), m_activeBuffer.constData(), width * height * BYTES_PER_PIXEL);
|
||||
|
|
|
@ -95,6 +95,7 @@ public:
|
|||
mPlatform platform() const;
|
||||
QSize screenDimensions() const;
|
||||
QPair<unsigned, unsigned> frameRate() const;
|
||||
unsigned videoScale() const;
|
||||
bool supportsFeature(Feature feature) const { return m_threadContext.core->supportsFeature(m_threadContext.core, static_cast<mCoreFeature>(feature)); }
|
||||
bool hardwareAccelerated() const { return m_hwaccel; }
|
||||
|
||||
|
|
|
@ -54,6 +54,8 @@ public:
|
|||
virtual VideoShader* shaders() = 0;
|
||||
virtual int framebufferHandle() { return -1; }
|
||||
virtual void setVideoScale(int) {}
|
||||
virtual void setBackgroundImage(const QImage&) = 0;
|
||||
virtual QSize contentSize() const = 0;
|
||||
|
||||
QSize viewportSize();
|
||||
|
||||
|
|
|
@ -70,6 +70,10 @@ mGLWidget::mGLWidget(QWidget* parent)
|
|||
connect(&m_refresh, &QTimer::timeout, this, static_cast<void (QWidget::*)()>(&QWidget::update));
|
||||
}
|
||||
|
||||
mGLWidget::~mGLWidget() {
|
||||
// This is needed for unique_ptr<QOpenGLPaintDevice> to work
|
||||
}
|
||||
|
||||
void mGLWidget::initializeGL() {
|
||||
m_vao = std::make_unique<QOpenGLVertexArrayObject>();
|
||||
m_vao->create();
|
||||
|
@ -99,6 +103,8 @@ void mGLWidget::initializeGL() {
|
|||
|
||||
m_vaoDone = false;
|
||||
m_tex = 0;
|
||||
|
||||
m_paintDev = std::make_unique<QOpenGLPaintDevice>();
|
||||
}
|
||||
|
||||
bool mGLWidget::finalizeVAO() {
|
||||
|
@ -150,6 +156,23 @@ void mGLWidget::paintGL() {
|
|||
} else {
|
||||
m_refresh.start(17);
|
||||
}
|
||||
|
||||
if (m_showOSD && m_messagePainter) {
|
||||
qreal r = window()->devicePixelRatio();
|
||||
m_paintDev->setDevicePixelRatio(r);
|
||||
m_paintDev->setSize(size() * r);
|
||||
QPainter painter(m_paintDev.get());
|
||||
m_messagePainter->paint(&painter);
|
||||
painter.end();
|
||||
}
|
||||
}
|
||||
|
||||
void mGLWidget::setMessagePainter(MessagePainter* messagePainter) {
|
||||
m_messagePainter = messagePainter;
|
||||
}
|
||||
|
||||
void mGLWidget::setShowOSD(bool showOSD) {
|
||||
m_showOSD = showOSD;
|
||||
}
|
||||
|
||||
DisplayGL::DisplayGL(const QSurfaceFormat& format, QWidget* parent)
|
||||
|
@ -170,6 +193,7 @@ DisplayGL::DisplayGL(const QSurfaceFormat& format, QWidget* parent)
|
|||
m_gl = new mGLWidget;
|
||||
m_gl->setAttribute(Qt::WA_NativeWindow);
|
||||
m_gl->setFormat(format);
|
||||
m_gl->setMessagePainter(messagePainter());
|
||||
QBoxLayout* layout = new QVBoxLayout;
|
||||
layout->addWidget(m_gl);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
@ -262,6 +286,8 @@ void DisplayGL::startThread(int from) {
|
|||
show();
|
||||
m_gl->reset();
|
||||
}
|
||||
|
||||
QTimer::singleShot(8, this, &DisplayGL::updateContentSize);
|
||||
}
|
||||
|
||||
bool DisplayGL::supportsFormat(const QSurfaceFormat& format) {
|
||||
|
@ -349,12 +375,14 @@ void DisplayGL::unpauseDrawing() {
|
|||
if (!m_gl && shouldDisableUpdates()) {
|
||||
setUpdatesEnabled(false);
|
||||
}
|
||||
QMetaObject::invokeMethod(this, "updateContentSize", Qt::QueuedConnection);
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayGL::forceDraw() {
|
||||
if (m_hasStarted) {
|
||||
QMetaObject::invokeMethod(m_painter.get(), "forceDraw");
|
||||
QMetaObject::invokeMethod(this, "updateContentSize", Qt::QueuedConnection);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -375,6 +403,9 @@ void DisplayGL::interframeBlending(bool enable) {
|
|||
|
||||
void DisplayGL::showOSDMessages(bool enable) {
|
||||
Display::showOSDMessages(enable);
|
||||
if (m_gl) {
|
||||
m_gl->setShowOSD(enable);
|
||||
}
|
||||
QMetaObject::invokeMethod(m_painter.get(), "showOSD", Q_ARG(bool, enable));
|
||||
}
|
||||
|
||||
|
@ -414,6 +445,11 @@ void DisplayGL::setVideoScale(int scale) {
|
|||
QMetaObject::invokeMethod(m_painter.get(), "resizeContext");
|
||||
}
|
||||
|
||||
void DisplayGL::setBackgroundImage(const QImage& image) {
|
||||
QMetaObject::invokeMethod(m_painter.get(), "setBackgroundImage", Q_ARG(const QImage&, image));
|
||||
QMetaObject::invokeMethod(this, "updateContentSize", Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void DisplayGL::resizeEvent(QResizeEvent* event) {
|
||||
Display::resizeEvent(event);
|
||||
resizePainter();
|
||||
|
@ -467,6 +503,10 @@ void DisplayGL::setupProxyThread() {
|
|||
m_proxyThread.start();
|
||||
}
|
||||
|
||||
void DisplayGL::updateContentSize() {
|
||||
QMetaObject::invokeMethod(m_painter.get(), "contentSize", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QSize, m_cachedContentSize));
|
||||
}
|
||||
|
||||
int DisplayGL::framebufferHandle() {
|
||||
return m_painter->glTex();
|
||||
}
|
||||
|
@ -528,7 +568,9 @@ void PainterGL::create() {
|
|||
mGLES2Context* gl2Backend;
|
||||
#endif
|
||||
|
||||
m_paintDev = std::make_unique<QOpenGLPaintDevice>();
|
||||
if (!m_widget) {
|
||||
m_paintDev = std::make_unique<QOpenGLPaintDevice>();
|
||||
}
|
||||
|
||||
#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
|
||||
if (m_supportsShaders) {
|
||||
|
@ -643,18 +685,51 @@ void PainterGL::resizeContext() {
|
|||
return;
|
||||
}
|
||||
dequeueAll(false);
|
||||
m_backend->setDimensions(m_backend, size.width(), size.height());
|
||||
|
||||
Rectangle dims = {0, 0, size.width(), size.height()};
|
||||
m_backend->setLayerDimensions(m_backend, VIDEO_LAYER_IMAGE, &dims);
|
||||
recenterLayers();
|
||||
}
|
||||
|
||||
void PainterGL::setMessagePainter(MessagePainter* messagePainter) {
|
||||
m_messagePainter = messagePainter;
|
||||
}
|
||||
|
||||
void PainterGL::recenterLayers() {
|
||||
if (!m_context) {
|
||||
return;
|
||||
}
|
||||
const static std::initializer_list<VideoLayer> centeredLayers{VIDEO_LAYER_BACKGROUND, VIDEO_LAYER_IMAGE};
|
||||
Rectangle frame = {0};
|
||||
unsigned scale = std::max(1U, m_context->videoScale());
|
||||
for (VideoLayer l : centeredLayers) {
|
||||
Rectangle dims{};
|
||||
int width, height;
|
||||
m_backend->imageSize(m_backend, l, &width, &height);
|
||||
dims.width = width;
|
||||
dims.height = height;
|
||||
if (l != VIDEO_LAYER_IMAGE) {
|
||||
dims.width *= scale;
|
||||
dims.height *= scale;
|
||||
m_backend->setLayerDimensions(m_backend, l, &dims);
|
||||
}
|
||||
RectangleUnion(&frame, &dims);
|
||||
}
|
||||
for (VideoLayer l : centeredLayers) {
|
||||
Rectangle dims;
|
||||
m_backend->layerDimensions(m_backend, l, &dims);
|
||||
RectangleCenter(&frame, &dims);
|
||||
m_backend->setLayerDimensions(m_backend, l, &dims);
|
||||
}
|
||||
}
|
||||
|
||||
void PainterGL::resize(const QSize& size) {
|
||||
qreal r = m_window->devicePixelRatio();
|
||||
m_size = size;
|
||||
m_paintDev->setSize(m_size * r);
|
||||
m_paintDev->setDevicePixelRatio(r);
|
||||
if (m_paintDev) {
|
||||
m_paintDev->setSize(m_size * r);
|
||||
m_paintDev->setDevicePixelRatio(r);
|
||||
}
|
||||
if (m_started && !m_active) {
|
||||
forceDraw();
|
||||
}
|
||||
|
@ -816,12 +891,12 @@ void PainterGL::unpause() {
|
|||
|
||||
void PainterGL::performDraw() {
|
||||
float r = m_window->devicePixelRatio();
|
||||
m_backend->resized(m_backend, m_size.width() * r, m_size.height() * r);
|
||||
m_backend->contextResized(m_backend, m_size.width() * r, m_size.height() * r);
|
||||
if (m_buffer) {
|
||||
m_backend->postFrame(m_backend, m_buffer);
|
||||
m_backend->setImage(m_backend, VIDEO_LAYER_IMAGE, m_buffer);
|
||||
}
|
||||
m_backend->drawFrame(m_backend);
|
||||
if (m_showOSD && m_messagePainter && !glContextHasBug(OpenGLBug::IG4ICD_CRASH)) {
|
||||
if (m_showOSD && m_messagePainter && m_paintDev && !glContextHasBug(OpenGLBug::IG4ICD_CRASH)) {
|
||||
m_painter.begin(m_paintDev.get());
|
||||
m_messagePainter->paint(&m_painter);
|
||||
m_painter.end();
|
||||
|
@ -874,7 +949,7 @@ void PainterGL::dequeue() {
|
|||
#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
|
||||
if (supportsShaders()) {
|
||||
mGLES2Context* gl2Backend = reinterpret_cast<mGLES2Context*>(m_backend);
|
||||
gl2Backend->tex = m_bridgeTexOut;
|
||||
gl2Backend->tex[VIDEO_LAYER_IMAGE] = m_bridgeTexOut;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
@ -968,11 +1043,18 @@ VideoShader* PainterGL::shaders() {
|
|||
return &m_shader;
|
||||
}
|
||||
|
||||
QSize PainterGL::contentSize() const {
|
||||
unsigned width, height;
|
||||
VideoBackendGetFrameSize(m_backend, &width, &height);
|
||||
return {static_cast<int>(width > static_cast<unsigned>(INT_MAX) ? INT_MAX : width),
|
||||
static_cast<int>(height > static_cast<unsigned>(INT_MAX) ? INT_MAX : height)};
|
||||
}
|
||||
|
||||
int PainterGL::glTex() {
|
||||
#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
|
||||
if (supportsShaders()) {
|
||||
mGLES2Context* gl2Backend = reinterpret_cast<mGLES2Context*>(m_backend);
|
||||
return gl2Backend->tex;
|
||||
return gl2Backend->tex[VIDEO_LAYER_IMAGE];
|
||||
}
|
||||
#endif
|
||||
#ifdef BUILD_GL
|
||||
|
@ -1008,6 +1090,27 @@ void PainterGL::updateFramebufferHandle() {
|
|||
m_context->setFramebufferHandle(m_bridgeTexIn);
|
||||
}
|
||||
|
||||
void PainterGL::setBackgroundImage(const QImage& image) {
|
||||
if (!m_started) {
|
||||
makeCurrent();
|
||||
}
|
||||
|
||||
m_backend->setImageSize(m_backend, VIDEO_LAYER_BACKGROUND, image.width(), image.height());
|
||||
recenterLayers();
|
||||
|
||||
if (!image.isNull()) {
|
||||
m_background = image.convertToFormat(QImage::Format_RGB32);
|
||||
m_background = m_background.rgbSwapped();
|
||||
m_backend->setImage(m_backend, VIDEO_LAYER_BACKGROUND, m_background.constBits());
|
||||
} else {
|
||||
m_background = QImage();
|
||||
}
|
||||
|
||||
if (!m_started) {
|
||||
m_gl->doneCurrent();
|
||||
}
|
||||
}
|
||||
|
||||
void PainterGL::swapTex() {
|
||||
if (!m_started) {
|
||||
return;
|
||||
|
|
|
@ -51,9 +51,12 @@ Q_OBJECT
|
|||
|
||||
public:
|
||||
mGLWidget(QWidget* parent = nullptr);
|
||||
~mGLWidget();
|
||||
|
||||
void setTex(GLuint tex) { m_tex = tex; }
|
||||
void setVBO(GLuint vbo) { m_vbo = vbo; }
|
||||
void setMessagePainter(MessagePainter*);
|
||||
void setShowOSD(bool showOSD);
|
||||
bool finalizeVAO();
|
||||
void reset();
|
||||
|
||||
|
@ -72,6 +75,9 @@ private:
|
|||
|
||||
QTimer m_refresh;
|
||||
int m_refreshResidue = 0;
|
||||
std::unique_ptr<QOpenGLPaintDevice> m_paintDev;
|
||||
MessagePainter* m_messagePainter = nullptr;
|
||||
bool m_showOSD = false;
|
||||
};
|
||||
|
||||
class PainterGL;
|
||||
|
@ -88,6 +94,7 @@ public:
|
|||
VideoShader* shaders() override;
|
||||
void setVideoProxy(std::shared_ptr<VideoProxy>) override;
|
||||
int framebufferHandle() override;
|
||||
QSize contentSize() const override { return m_cachedContentSize; }
|
||||
|
||||
static bool supportsFormat(const QSurfaceFormat&);
|
||||
|
||||
|
@ -107,6 +114,7 @@ public slots:
|
|||
void clearShaders() override;
|
||||
void resizeContext() override;
|
||||
void setVideoScale(int scale) override;
|
||||
void setBackgroundImage(const QImage&) override;
|
||||
|
||||
protected:
|
||||
virtual void paintEvent(QPaintEvent*) override { forceDraw(); }
|
||||
|
@ -115,6 +123,7 @@ protected:
|
|||
private slots:
|
||||
void startThread(int);
|
||||
void setupProxyThread();
|
||||
void updateContentSize();
|
||||
|
||||
private:
|
||||
void resizePainter();
|
||||
|
@ -132,6 +141,7 @@ private:
|
|||
mGLWidget* m_gl;
|
||||
QOffscreenSurface m_proxySurface;
|
||||
std::unique_ptr<QOpenGLContext> m_proxyContext;
|
||||
QSize m_cachedContentSize;
|
||||
};
|
||||
|
||||
class PainterGL : public QObject {
|
||||
|
@ -178,10 +188,12 @@ public slots:
|
|||
void filter(bool filter);
|
||||
void resizeContext();
|
||||
void updateFramebufferHandle();
|
||||
void setBackgroundImage(const QImage&);
|
||||
|
||||
void setShaders(struct VDir*);
|
||||
void clearShaders();
|
||||
VideoShader* shaders();
|
||||
QSize contentSize() const;
|
||||
|
||||
signals:
|
||||
void created();
|
||||
|
@ -196,6 +208,7 @@ private:
|
|||
void performDraw();
|
||||
void dequeue();
|
||||
void dequeueAll(bool keep = false);
|
||||
void recenterLayers();
|
||||
|
||||
std::array<std::array<uint32_t, 0x100000>, 3> m_buffers;
|
||||
QList<uint32_t*> m_free;
|
||||
|
@ -214,6 +227,7 @@ private:
|
|||
QWindow* m_window;
|
||||
QSurface* m_surface;
|
||||
QSurfaceFormat m_format;
|
||||
QImage m_background;
|
||||
std::unique_ptr<QOpenGLPaintDevice> m_paintDev;
|
||||
std::unique_ptr<QOpenGLContext> m_gl;
|
||||
int m_finalTexIdx = 0;
|
||||
|
|
|
@ -93,21 +93,103 @@ void DisplayQt::resizeContext() {
|
|||
}
|
||||
}
|
||||
|
||||
void DisplayQt::setBackgroundImage(const QImage& image) {
|
||||
m_background = image;
|
||||
update();
|
||||
}
|
||||
|
||||
void DisplayQt::paintEvent(QPaintEvent*) {
|
||||
QPainter painter(this);
|
||||
painter.fillRect(QRect(QPoint(), size()), Qt::black);
|
||||
if (isFiltered()) {
|
||||
painter.setRenderHint(QPainter::SmoothPixmapTransform);
|
||||
}
|
||||
QRect full(clampSize(QSize(m_width, m_height), size(), isAspectRatioLocked(), isIntegerScalingLocked()));
|
||||
|
||||
QRect bgRect(0, 0, m_background.width(), m_background.height());
|
||||
QRect imRect(0, 0, m_width, m_height);
|
||||
QSize outerFrame = contentSize();
|
||||
|
||||
if (bgRect.width() > imRect.width()) {
|
||||
imRect.moveLeft(bgRect.width() - imRect.width());
|
||||
} else {
|
||||
bgRect.moveLeft(imRect.width() - bgRect.width());
|
||||
}
|
||||
|
||||
if (bgRect.height() > imRect.height()) {
|
||||
imRect.moveTop(bgRect.height() - imRect.height());
|
||||
} else {
|
||||
bgRect.moveTop(imRect.height() - bgRect.height());
|
||||
}
|
||||
|
||||
QRect full(clampSize(outerFrame, size(), isAspectRatioLocked(), isIntegerScalingLocked()));
|
||||
|
||||
if (m_background.isNull()) {
|
||||
imRect = full;
|
||||
} else {
|
||||
if (imRect.x()) {
|
||||
imRect.moveLeft(imRect.x() * full.width() / bgRect.width() / 2);
|
||||
imRect.setWidth(imRect.width() * full.width() / bgRect.width());
|
||||
bgRect.setWidth(full.width());
|
||||
} else {
|
||||
bgRect.moveLeft(bgRect.x() * full.width() / imRect.width() / 2);
|
||||
bgRect.setWidth(bgRect.width() * full.width() / imRect.width());
|
||||
imRect.setWidth(full.width());
|
||||
}
|
||||
if (imRect.y()) {
|
||||
imRect.moveTop(imRect.y() * full.height() / bgRect.height() / 2);
|
||||
imRect.setHeight(imRect.height() * full.height() / bgRect.height());
|
||||
bgRect.setHeight(full.height());
|
||||
} else {
|
||||
bgRect.moveTop(bgRect.y() * full.height() / imRect.height() / 2);
|
||||
bgRect.setHeight(bgRect.height() * full.height() / imRect.height());
|
||||
imRect.setHeight(full.height());
|
||||
}
|
||||
|
||||
if (bgRect.right() > imRect.right()) {
|
||||
if (bgRect.right() < full.right()) {
|
||||
imRect.translate((full.right() - bgRect.right()), 0);
|
||||
bgRect.translate((full.right() - bgRect.right()), 0);
|
||||
}
|
||||
} else {
|
||||
if (imRect.right() < full.right()) {
|
||||
bgRect.translate((full.right() - imRect.right()), 0);
|
||||
imRect.translate((full.right() - imRect.right()), 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (bgRect.bottom() > imRect.bottom()) {
|
||||
if (bgRect.bottom() < full.bottom()) {
|
||||
imRect.translate(0, (full.bottom() - bgRect.bottom()));
|
||||
bgRect.translate(0, (full.bottom() - bgRect.bottom()));
|
||||
}
|
||||
} else {
|
||||
if (imRect.bottom() < full.bottom()) {
|
||||
bgRect.translate(0, (full.bottom() - imRect.bottom()));
|
||||
imRect.translate(0, (full.bottom() - imRect.bottom()));
|
||||
}
|
||||
}
|
||||
painter.drawImage(bgRect, m_background);
|
||||
}
|
||||
|
||||
if (hasInterframeBlending()) {
|
||||
painter.drawImage(full, m_oldBacking, QRect(0, 0, m_width, m_height));
|
||||
painter.drawImage(imRect, m_oldBacking, QRect(0, 0, m_width, m_height));
|
||||
painter.setOpacity(0.5);
|
||||
}
|
||||
painter.drawImage(full, m_backing, QRect(0, 0, m_width, m_height));
|
||||
painter.drawImage(imRect, m_backing, QRect(0, 0, m_width, m_height));
|
||||
painter.setOpacity(1);
|
||||
if (isShowOSD() || isShowFrameCounter()) {
|
||||
messagePainter()->paint(&painter);
|
||||
}
|
||||
}
|
||||
|
||||
QSize DisplayQt::contentSize() const {
|
||||
QSize outerFrame(m_width, m_height);
|
||||
|
||||
if (m_background.width() > outerFrame.width()) {
|
||||
outerFrame.setWidth(m_background.width());
|
||||
}
|
||||
if (m_background.height() > outerFrame.height()) {
|
||||
outerFrame.setHeight(m_background.height());
|
||||
}
|
||||
return outerFrame;
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ public:
|
|||
bool isDrawing() const override { return m_isDrawing; }
|
||||
bool supportsShaders() const override { return false; }
|
||||
VideoShader* shaders() override { return nullptr; }
|
||||
QSize contentSize() const override;
|
||||
|
||||
public slots:
|
||||
void stopDrawing() override;
|
||||
|
@ -36,16 +37,18 @@ public slots:
|
|||
void setShaders(struct VDir*) override {}
|
||||
void clearShaders() override {}
|
||||
void resizeContext() override;
|
||||
void setBackgroundImage(const QImage&) override;
|
||||
|
||||
protected:
|
||||
virtual void paintEvent(QPaintEvent*) override;
|
||||
|
||||
private:
|
||||
bool m_isDrawing = false;
|
||||
int m_width;
|
||||
int m_height;
|
||||
int m_width = -1;
|
||||
int m_height = -1;
|
||||
QImage m_backing{nullptr};
|
||||
QImage m_oldBacking{nullptr};
|
||||
QImage m_background;
|
||||
std::shared_ptr<CoreController> m_context = nullptr;
|
||||
};
|
||||
|
||||
|
|
|
@ -559,7 +559,7 @@ void FrameView::newVl() {
|
|||
}
|
||||
#endif
|
||||
unsigned width, height;
|
||||
m_vl->desiredVideoDimensions(m_vl, &width, &height);
|
||||
m_vl->baseVideoSize(m_vl, &width, &height);
|
||||
m_framebuffer = QImage(width, height, QImage::Format_RGBX8888);
|
||||
m_vl->setVideoBuffer(m_vl, reinterpret_cast<color_t*>(m_framebuffer.bits()), width);
|
||||
m_vl->reset(m_vl);
|
||||
|
|
|
@ -143,6 +143,9 @@ SettingsView::SettingsView(ConfigController* controller, InputController* inputC
|
|||
connect(m_ui.cheatsBrowse, &QAbstractButton::pressed, [this] () {
|
||||
selectPath(m_ui.cheatsPath, m_ui.cheatsSameDir);
|
||||
});
|
||||
connect(m_ui.bgImageBrowse, &QAbstractButton::pressed, [this] () {
|
||||
selectImage(m_ui.bgImage);
|
||||
});
|
||||
connect(m_ui.clearCache, &QAbstractButton::pressed, this, &SettingsView::libraryCleared);
|
||||
|
||||
// TODO: Move to reloadConfig()
|
||||
|
@ -445,6 +448,13 @@ void SettingsView::selectPath(QLineEdit* field, QCheckBox* sameDir) {
|
|||
}
|
||||
}
|
||||
|
||||
void SettingsView::selectImage(QLineEdit* field) {
|
||||
QString path = GBAApp::app()->getOpenFileName(this, tr("Select image"), tr("Image file (*.png *.jpg *.jpeg)"));
|
||||
if (!path.isNull()) {
|
||||
field->setText(makePortablePath(path));
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsView::updateConfig() {
|
||||
saveSetting("gba.bios", m_ui.gbaBios);
|
||||
saveSetting("gb.bios", m_ui.gbBios);
|
||||
|
@ -504,6 +514,7 @@ void SettingsView::updateConfig() {
|
|||
saveSetting("vbaBugCompat", m_ui.vbaBugCompat);
|
||||
saveSetting("updateAutoCheck", m_ui.updateAutoCheck);
|
||||
saveSetting("showFilenameInLibrary", m_ui.showFilenameInLibrary);
|
||||
saveSetting("backgroundImage", m_ui.bgImage);
|
||||
|
||||
if (m_ui.audioBufferSize->currentText().toInt() > 8192) {
|
||||
m_ui.audioBufferSize->setCurrentText("8192");
|
||||
|
@ -733,6 +744,7 @@ void SettingsView::reloadConfig() {
|
|||
loadSetting("vbaBugCompat", m_ui.vbaBugCompat, true);
|
||||
loadSetting("updateAutoCheck", m_ui.updateAutoCheck);
|
||||
loadSetting("showFilenameInLibrary", m_ui.showFilenameInLibrary);
|
||||
loadSetting("backgroundImage", m_ui.bgImage);
|
||||
|
||||
m_ui.libraryStyle->setCurrentIndex(loadSetting("libraryStyle").toInt());
|
||||
|
||||
|
|
|
@ -72,6 +72,7 @@ public slots:
|
|||
private slots:
|
||||
void selectBios(QLineEdit*);
|
||||
void selectPath(QLineEdit*, QCheckBox*);
|
||||
void selectImage(QLineEdit*);
|
||||
void updateConfig();
|
||||
void reloadConfig();
|
||||
void updateChecked();
|
||||
|
|
|
@ -907,6 +907,41 @@
|
|||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="19" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_22">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="bgImage">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="bgImageBrowse">
|
||||
<property name="text">
|
||||
<string>Browse</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="18" column="0" colspan="2">
|
||||
<widget class="Line" name="line_13">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="19" column="0">
|
||||
<widget class="QLabel" name="label_53">
|
||||
<property name="text">
|
||||
<string>Custom border:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="update">
|
||||
|
|
|
@ -1094,6 +1094,8 @@ void Window::reloadDisplayDriver() {
|
|||
#elif defined(M_CORE_GBA)
|
||||
m_display->setMinimumSize(GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS);
|
||||
#endif
|
||||
|
||||
m_display->setBackgroundImage(QImage{m_config->getOption("backgroundImage")});
|
||||
}
|
||||
|
||||
void Window::reloadAudioDriver() {
|
||||
|
@ -1554,8 +1556,8 @@ void Window::setupMenu(QMenuBar* menubar) {
|
|||
Action* setSize = m_frameSizes[i];
|
||||
showNormal();
|
||||
QSize size(GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS);
|
||||
if (m_controller) {
|
||||
size = m_controller->screenDimensions();
|
||||
if (m_display) {
|
||||
size = m_display->contentSize();
|
||||
}
|
||||
size *= i;
|
||||
m_savedScale = i;
|
||||
|
@ -1890,6 +1892,14 @@ void Window::setupOptions() {
|
|||
dynamicTitle->connect([this](const QVariant&) {
|
||||
updateTitle();
|
||||
}, this);
|
||||
|
||||
ConfigOption* backgroundImage = m_config->addOption("backgroundImage");
|
||||
backgroundImage->connect([this](const QVariant& value) {
|
||||
if (m_display) {
|
||||
m_display->setBackgroundImage(QImage{value.toString()});
|
||||
}
|
||||
}, this);
|
||||
m_config->updateOption("backgroundImage");
|
||||
}
|
||||
|
||||
void Window::attachWidget(QWidget* widget) {
|
||||
|
|
|
@ -5,10 +5,17 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "main.h"
|
||||
|
||||
#include <mgba/core/core.h>
|
||||
#include <mgba/core/thread.h>
|
||||
#include <mgba/core/version.h>
|
||||
|
||||
#ifdef USE_PNG
|
||||
#include <mgba-util/png-io.h>
|
||||
#include <mgba-util/vfs.h>
|
||||
#endif
|
||||
|
||||
void mSDLGLDoViewport(int w, int h, struct VideoBackend* v) {
|
||||
v->resized(v, w, h);
|
||||
v->contextResized(v, w, h);
|
||||
v->clear(v);
|
||||
v->swap(v);
|
||||
v->clear(v);
|
||||
|
@ -24,6 +31,60 @@ void mSDLGLCommonSwap(struct VideoBackend* context) {
|
|||
#endif
|
||||
}
|
||||
|
||||
bool mSDLGLCommonLoadBackground(struct VideoBackend* context) {
|
||||
#ifdef USE_PNG
|
||||
struct mSDLRenderer* renderer = context->user;
|
||||
const char* bgImage = mCoreConfigGetValue(&renderer->core->config, "backgroundImage");
|
||||
if (!bgImage) {
|
||||
return false;
|
||||
}
|
||||
struct VFile* vf = VFileOpen(bgImage, O_RDONLY);
|
||||
if (!vf) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ok = false;
|
||||
png_structp png = PNGReadOpen(vf, 0);
|
||||
png_infop info = png_create_info_struct(png);
|
||||
png_infop end = png_create_info_struct(png);
|
||||
if (!png || !info || !end) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (!PNGReadHeader(png, info)) {
|
||||
goto done;
|
||||
}
|
||||
unsigned width = png_get_image_width(png, info);
|
||||
unsigned height = png_get_image_height(png, info);
|
||||
uint32_t* pixels = malloc(width * height * 4);
|
||||
if (!pixels) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (!PNGReadPixels(png, info, pixels, width, height, width) || !PNGReadFooter(png, end)) {
|
||||
free(pixels);
|
||||
goto done;
|
||||
}
|
||||
|
||||
struct Rectangle dims = {
|
||||
.width = width,
|
||||
.height = height
|
||||
};
|
||||
context->setLayerDimensions(context, VIDEO_LAYER_BACKGROUND, &dims);
|
||||
context->setImage(context, VIDEO_LAYER_BACKGROUND, pixels);
|
||||
free(pixels);
|
||||
ok = true;
|
||||
|
||||
done:
|
||||
PNGReadClose(png, info, end);
|
||||
vf->close(vf);
|
||||
return ok;
|
||||
#else
|
||||
UNUSED(context);
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool mSDLGLCommonInit(struct mSDLRenderer* renderer) {
|
||||
#ifndef COLOR_16_BIT
|
||||
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
|
||||
|
@ -66,3 +127,61 @@ bool mSDLGLCommonInit(struct mSDLRenderer* renderer) {
|
|||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
void mSDLGLCommonRunloop(struct mSDLRenderer* renderer, void* user) {
|
||||
struct mCoreThread* context = user;
|
||||
SDL_Event event;
|
||||
struct VideoBackend* v = renderer->backend;
|
||||
|
||||
if (mSDLGLCommonLoadBackground(v)) {
|
||||
renderer->player.windowUpdated = true;
|
||||
|
||||
struct Rectangle frame;
|
||||
VideoBackendGetFrame(v, &frame);
|
||||
int i;
|
||||
for (i = 0; i <= VIDEO_LAYER_IMAGE; ++i) {
|
||||
struct Rectangle dims;
|
||||
v->layerDimensions(v, i, &dims);
|
||||
RectangleCenter(&frame, &dims);
|
||||
v->setLayerDimensions(v, i, &dims);
|
||||
}
|
||||
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
SDL_SetWindowSize(renderer->window, frame.width * renderer->ratio, frame.height * renderer->ratio);
|
||||
#endif
|
||||
}
|
||||
|
||||
while (mCoreThreadIsActive(context)) {
|
||||
while (SDL_PollEvent(&event)) {
|
||||
mSDLHandleEvent(context, &renderer->player, &event);
|
||||
// Event handling can change the size of the screen
|
||||
if (renderer->player.windowUpdated) {
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
SDL_GetWindowSize(renderer->window, &renderer->viewportWidth, &renderer->viewportHeight);
|
||||
#else
|
||||
renderer->viewportWidth = renderer->player.newWidth;
|
||||
renderer->viewportHeight = renderer->player.newHeight;
|
||||
mSDLGLCommonInit(renderer);
|
||||
#endif
|
||||
mSDLGLDoViewport(renderer->viewportWidth, renderer->viewportHeight, v);
|
||||
renderer->player.windowUpdated = 0;
|
||||
}
|
||||
}
|
||||
renderer->core->currentVideoSize(renderer->core, &renderer->width, &renderer->height);
|
||||
struct Rectangle dims;
|
||||
v->layerDimensions(v, VIDEO_LAYER_IMAGE, &dims);
|
||||
if (renderer->width != dims.width || renderer->height != dims.height) {
|
||||
renderer->core->setVideoBuffer(renderer->core, renderer->outputBuffer, renderer->width);
|
||||
dims.width = renderer->width;
|
||||
dims.height = renderer->height;
|
||||
v->setLayerDimensions(v, VIDEO_LAYER_IMAGE, &dims);
|
||||
}
|
||||
|
||||
if (mCoreSyncWaitFrameStart(&context->impl->sync)) {
|
||||
v->setImage(v, VIDEO_LAYER_IMAGE, renderer->outputBuffer);
|
||||
}
|
||||
mCoreSyncWaitFrameEnd(&context->impl->sync);
|
||||
v->drawFrame(v);
|
||||
v->swap(v);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,8 @@ struct mSDLRenderer;
|
|||
void mSDLGLDoViewport(int w, int h, struct VideoBackend* v);
|
||||
void mSDLGLCommonSwap(struct VideoBackend* context);
|
||||
bool mSDLGLCommonInit(struct mSDLRenderer* renderer);
|
||||
void mSDLGLCommonRunloop(struct mSDLRenderer* renderer, void* user);
|
||||
bool mSDLGLCommonLoadBackground(struct VideoBackend* context);
|
||||
|
||||
CXX_GUARD_END
|
||||
|
||||
|
|
|
@ -8,19 +8,17 @@
|
|||
#include "gl-common.h"
|
||||
|
||||
#include <mgba/core/core.h>
|
||||
#include <mgba/core/thread.h>
|
||||
#include <mgba-util/math.h>
|
||||
|
||||
#include "platform/opengl/gl.h"
|
||||
|
||||
static bool mSDLGLInit(struct mSDLRenderer* renderer);
|
||||
static void mSDLGLRunloop(struct mSDLRenderer* renderer, void* user);
|
||||
static void mSDLGLDeinit(struct mSDLRenderer* renderer);
|
||||
|
||||
void mSDLGLCreate(struct mSDLRenderer* renderer) {
|
||||
renderer->init = mSDLGLInit;
|
||||
renderer->deinit = mSDLGLDeinit;
|
||||
renderer->runloop = mSDLGLRunloop;
|
||||
renderer->runloop = mSDLGLCommonRunloop;
|
||||
renderer->backend = &renderer->gl.d;
|
||||
}
|
||||
|
||||
bool mSDLGLInit(struct mSDLRenderer* renderer) {
|
||||
|
@ -37,48 +35,18 @@ bool mSDLGLInit(struct mSDLRenderer* renderer) {
|
|||
renderer->gl.d.filter = renderer->filter;
|
||||
renderer->gl.d.swap = mSDLGLCommonSwap;
|
||||
renderer->gl.d.init(&renderer->gl.d, 0);
|
||||
renderer->gl.d.setDimensions(&renderer->gl.d, renderer->width, renderer->height);
|
||||
struct Rectangle dims = {
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.width = renderer->width,
|
||||
.height = renderer->height
|
||||
};
|
||||
renderer->gl.d.setLayerDimensions(&renderer->gl.d, VIDEO_LAYER_IMAGE, &dims);
|
||||
|
||||
mSDLGLDoViewport(renderer->viewportWidth, renderer->viewportHeight, &renderer->gl.d);
|
||||
return true;
|
||||
}
|
||||
|
||||
void mSDLGLRunloop(struct mSDLRenderer* renderer, void* user) {
|
||||
struct mCoreThread* context = user;
|
||||
SDL_Event event;
|
||||
struct VideoBackend* v = &renderer->gl.d;
|
||||
|
||||
while (mCoreThreadIsActive(context)) {
|
||||
while (SDL_PollEvent(&event)) {
|
||||
mSDLHandleEvent(context, &renderer->player, &event);
|
||||
// Event handling can change the size of the screen
|
||||
if (renderer->player.windowUpdated) {
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
SDL_GetWindowSize(renderer->window, &renderer->viewportWidth, &renderer->viewportHeight);
|
||||
#else
|
||||
renderer->viewportWidth = renderer->player.newWidth;
|
||||
renderer->viewportHeight = renderer->player.newHeight;
|
||||
mSDLGLCommonInit(renderer);
|
||||
#endif
|
||||
mSDLGLDoViewport(renderer->viewportWidth, renderer->viewportHeight, v);
|
||||
renderer->player.windowUpdated = 0;
|
||||
}
|
||||
}
|
||||
renderer->core->desiredVideoDimensions(renderer->core, &renderer->width, &renderer->height);
|
||||
if (renderer->width != v->width || renderer->height != v->height) {
|
||||
renderer->core->setVideoBuffer(renderer->core, renderer->outputBuffer, renderer->width);
|
||||
v->setDimensions(v, renderer->width, renderer->height);
|
||||
}
|
||||
|
||||
if (mCoreSyncWaitFrameStart(&context->impl->sync)) {
|
||||
v->postFrame(v, renderer->outputBuffer);
|
||||
}
|
||||
mCoreSyncWaitFrameEnd(&context->impl->sync);
|
||||
v->drawFrame(v);
|
||||
v->swap(v);
|
||||
}
|
||||
}
|
||||
|
||||
void mSDLGLDeinit(struct mSDLRenderer* renderer) {
|
||||
if (renderer->gl.d.deinit) {
|
||||
renderer->gl.d.deinit(&renderer->gl.d);
|
||||
|
|
|
@ -11,20 +11,19 @@
|
|||
#endif
|
||||
|
||||
#include <mgba/core/core.h>
|
||||
#include <mgba/core/thread.h>
|
||||
|
||||
#ifdef __linux__
|
||||
#include <malloc.h>
|
||||
#endif
|
||||
|
||||
static bool mSDLGLES2Init(struct mSDLRenderer* renderer);
|
||||
static void mSDLGLES2Runloop(struct mSDLRenderer* renderer, void* user);
|
||||
static void mSDLGLES2Deinit(struct mSDLRenderer* renderer);
|
||||
|
||||
void mSDLGLES2Create(struct mSDLRenderer* renderer) {
|
||||
renderer->init = mSDLGLES2Init;
|
||||
renderer->deinit = mSDLGLES2Deinit;
|
||||
renderer->runloop = mSDLGLES2Runloop;
|
||||
renderer->runloop = mSDLGLCommonRunloop;
|
||||
renderer->backend = &renderer->gl2.d;
|
||||
}
|
||||
|
||||
bool mSDLGLES2Init(struct mSDLRenderer* renderer) {
|
||||
|
@ -50,43 +49,19 @@ bool mSDLGLES2Init(struct mSDLRenderer* renderer) {
|
|||
renderer->gl2.d.swap = mSDLGLCommonSwap;
|
||||
#endif
|
||||
renderer->gl2.d.init(&renderer->gl2.d, 0);
|
||||
renderer->gl2.d.setDimensions(&renderer->gl2.d, renderer->width, renderer->height);
|
||||
|
||||
struct Rectangle dims = {
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.width = renderer->width,
|
||||
.height = renderer->height
|
||||
};
|
||||
renderer->gl2.d.setLayerDimensions(&renderer->gl2.d, VIDEO_LAYER_IMAGE, &dims);
|
||||
|
||||
mSDLGLDoViewport(renderer->viewportWidth, renderer->viewportHeight, &renderer->gl2.d);
|
||||
return true;
|
||||
}
|
||||
|
||||
void mSDLGLES2Runloop(struct mSDLRenderer* renderer, void* user) {
|
||||
struct mCoreThread* context = user;
|
||||
SDL_Event event;
|
||||
struct VideoBackend* v = &renderer->gl2.d;
|
||||
|
||||
while (mCoreThreadIsActive(context)) {
|
||||
while (SDL_PollEvent(&event)) {
|
||||
mSDLHandleEvent(context, &renderer->player, &event);
|
||||
// Event handling can change the size of the screen
|
||||
if (renderer->player.windowUpdated) {
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
SDL_GetWindowSize(renderer->window, &renderer->viewportWidth, &renderer->viewportHeight);
|
||||
#else
|
||||
renderer->viewportWidth = renderer->player.newWidth;
|
||||
renderer->viewportHeight = renderer->player.newHeight;
|
||||
mSDLGLCommonInit(renderer);
|
||||
#endif
|
||||
mSDLGLDoViewport(renderer->viewportWidth, renderer->viewportHeight, v);
|
||||
renderer->player.windowUpdated = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (mCoreSyncWaitFrameStart(&context->impl->sync)) {
|
||||
v->postFrame(v, renderer->outputBuffer);
|
||||
}
|
||||
mCoreSyncWaitFrameEnd(&context->impl->sync);
|
||||
v->drawFrame(v);
|
||||
v->swap(v);
|
||||
}
|
||||
}
|
||||
|
||||
void mSDLGLES2Deinit(struct mSDLRenderer* renderer) {
|
||||
if (renderer->gl2.d.deinit) {
|
||||
renderer->gl2.d.deinit(&renderer->gl2.d);
|
||||
|
|
|
@ -107,7 +107,7 @@ int main(int argc, char** argv) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
renderer.core->desiredVideoDimensions(renderer.core, &renderer.width, &renderer.height);
|
||||
renderer.core->baseVideoSize(renderer.core, &renderer.width, &renderer.height);
|
||||
renderer.ratio = graphicsOpts.multiplier;
|
||||
if (renderer.ratio == 0) {
|
||||
renderer.ratio = 1;
|
||||
|
@ -275,7 +275,7 @@ int mSDLRun(struct mSDLRenderer* renderer, struct mArguments* args) {
|
|||
|
||||
if (!didFail) {
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
renderer->core->desiredVideoDimensions(renderer->core, &renderer->width, &renderer->height);
|
||||
renderer->core->currentVideoSize(renderer->core, &renderer->width, &renderer->height);
|
||||
unsigned width = renderer->width * renderer->ratio;
|
||||
unsigned height = renderer->height * renderer->ratio;
|
||||
if (width != (unsigned) renderer->viewportWidth && height != (unsigned) renderer->viewportHeight) {
|
||||
|
|
|
@ -76,6 +76,8 @@ struct mSDLRenderer {
|
|||
struct mGLES2Context gl2;
|
||||
#endif
|
||||
|
||||
struct VideoBackend* backend;
|
||||
|
||||
#ifdef USE_PIXMAN
|
||||
pixman_image_t* pix;
|
||||
pixman_image_t* screenpix;
|
||||
|
|
|
@ -583,7 +583,7 @@ static void _mSDLHandleMouseButton(struct mCore* core, struct mSDLPlayer* sdlCon
|
|||
SDL_GetWindowSize(sdlContext->window, &windowW, &windowH);
|
||||
unsigned coreW;
|
||||
unsigned coreH;
|
||||
core->desiredVideoDimensions(core, &coreW, &coreH);
|
||||
core->baseVideoSize(core, &coreW, &coreH);
|
||||
x = x * coreW / windowW;
|
||||
y = y * coreH / windowH;
|
||||
#endif
|
||||
|
@ -600,7 +600,7 @@ static void _mSDLHandleMouseMotion(struct mCore* core, struct mSDLPlayer* sdlCon
|
|||
SDL_GetWindowSize(sdlContext->window, &windowW, &windowH);
|
||||
unsigned coreW;
|
||||
unsigned coreH;
|
||||
core->desiredVideoDimensions(core, &coreW, &coreH);
|
||||
core->baseVideoSize(core, &coreW, &coreH);
|
||||
x = x * coreW / windowW;
|
||||
y = y * coreH / windowH;
|
||||
#endif
|
||||
|
|
|
@ -28,7 +28,7 @@ bool mSDLSWInit(struct mSDLRenderer* renderer) {
|
|||
SDL_WM_SetCaption(projectName, "");
|
||||
|
||||
unsigned width, height;
|
||||
renderer->core->desiredVideoDimensions(renderer->core, &width, &height);
|
||||
renderer->core->baseVideoSize(renderer->core, &width, &height);
|
||||
SDL_Surface* surface = SDL_GetVideoSurface();
|
||||
SDL_LockSurface(surface);
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ void mSDLSWCreate(struct mSDLRenderer* renderer) {
|
|||
|
||||
bool mSDLSWInit(struct mSDLRenderer* renderer) {
|
||||
unsigned width, height;
|
||||
renderer->core->desiredVideoDimensions(renderer->core, &width, &height);
|
||||
renderer->core->baseVideoSize(renderer->core, &width, &height);
|
||||
renderer->window = SDL_CreateWindow(projectName, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, renderer->viewportWidth, renderer->viewportHeight, SDL_WINDOW_OPENGL | (SDL_WINDOW_FULLSCREEN_DESKTOP * renderer->player.fullscreen));
|
||||
SDL_GetWindowSize(renderer->window, &renderer->viewportWidth, &renderer->viewportHeight);
|
||||
renderer->player.window = renderer->window;
|
||||
|
|
|
@ -477,7 +477,7 @@ static void _drawFrame(struct mGUIRunner* runner, bool faded) {
|
|||
}
|
||||
|
||||
unsigned width, height;
|
||||
runner->core->desiredVideoDimensions(runner->core, &width, &height);
|
||||
runner->core->currentVideoSize(runner->core, &width, &height);
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
if (usePbo) {
|
||||
|
@ -530,7 +530,7 @@ static void _drawScreenshot(struct mGUIRunner* runner, const color_t* pixels, un
|
|||
glBindTexture(GL_TEXTURE_2D, screenshotTex);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
|
||||
|
||||
runner->core->desiredVideoDimensions(runner->core, &width, &height);
|
||||
runner->core->currentVideoSize(runner->core, &width, &height);
|
||||
glDisable(GL_BLEND);
|
||||
bool wasPbo = usePbo;
|
||||
usePbo = false;
|
||||
|
|
|
@ -1033,7 +1033,7 @@ void CInemaTestRun(struct CInemaTest* test) {
|
|||
return;
|
||||
}
|
||||
struct CInemaImage image;
|
||||
core->desiredVideoDimensions(core, &image.width, &image.height);
|
||||
core->baseVideoSize(core, &image.width, &image.height);
|
||||
ssize_t bufferSize = image.width * image.height * BYTES_PER_PIXEL;
|
||||
image.data = malloc(bufferSize);
|
||||
image.stride = image.width;
|
||||
|
@ -1076,7 +1076,7 @@ void CInemaTestRun(struct CInemaTest* test) {
|
|||
for (frame = 0; frame < skip; ++frame) {
|
||||
core->runFrame(core);
|
||||
}
|
||||
core->desiredVideoDimensions(core, &image.width, &image.height);
|
||||
core->currentVideoSize(core, &image.width, &image.height);
|
||||
|
||||
#ifdef USE_FFMPEG
|
||||
struct FFmpegDecoder decoder;
|
||||
|
@ -1141,7 +1141,7 @@ void CInemaTestRun(struct CInemaTest* test) {
|
|||
break;
|
||||
}
|
||||
CIlog(3, "Test frame: %u\n", frameCounter);
|
||||
core->desiredVideoDimensions(core, &image.width, &image.height);
|
||||
core->currentVideoSize(core, &image.width, &image.height);
|
||||
uint8_t* diff = NULL;
|
||||
struct CInemaImage expected = {
|
||||
.data = NULL,
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
/* Copyright (c) 2013-2023 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "video-backend.h"
|
||||
|
||||
void VideoBackendGetFrame(const struct VideoBackend* v, struct Rectangle* frame) {
|
||||
memset(frame, 0, sizeof(*frame));
|
||||
int i;
|
||||
for (i = 0; i < VIDEO_LAYER_MAX; ++i) {
|
||||
struct Rectangle dims;
|
||||
v->layerDimensions(v, i, &dims);
|
||||
RectangleUnion(frame, &dims);
|
||||
}
|
||||
}
|
||||
|
||||
void VideoBackendGetFrameSize(const struct VideoBackend* v, unsigned* width, unsigned* height) {
|
||||
struct Rectangle frame;
|
||||
VideoBackendGetFrame(v, &frame);
|
||||
*width = frame.width;
|
||||
*height = frame.height;
|
||||
}
|
|
@ -10,6 +10,8 @@
|
|||
|
||||
CXX_GUARD_START
|
||||
|
||||
#include <mgba-util/geometry.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
typedef HWND WHandle;
|
||||
|
@ -17,26 +19,34 @@ typedef HWND WHandle;
|
|||
typedef void* WHandle;
|
||||
#endif
|
||||
|
||||
enum VideoLayer {
|
||||
VIDEO_LAYER_BACKGROUND = 0,
|
||||
VIDEO_LAYER_BEZEL,
|
||||
VIDEO_LAYER_IMAGE,
|
||||
VIDEO_LAYER_OVERLAY,
|
||||
VIDEO_LAYER_MAX
|
||||
};
|
||||
|
||||
struct VideoBackend {
|
||||
void (*init)(struct VideoBackend*, WHandle handle);
|
||||
void (*deinit)(struct VideoBackend*);
|
||||
void (*setDimensions)(struct VideoBackend*, unsigned width, unsigned height);
|
||||
void (*setLayerDimensions)(struct VideoBackend*, enum VideoLayer, const struct Rectangle*);
|
||||
void (*layerDimensions)(const struct VideoBackend*, enum VideoLayer, struct Rectangle*);
|
||||
void (*swap)(struct VideoBackend*);
|
||||
void (*clear)(struct VideoBackend*);
|
||||
void (*resized)(struct VideoBackend*, unsigned w, unsigned h);
|
||||
void (*postFrame)(struct VideoBackend*, const void* frame);
|
||||
void (*contextResized)(struct VideoBackend*, unsigned w, unsigned h);
|
||||
void (*setImageSize)(struct VideoBackend*, enum VideoLayer, int w, int h);
|
||||
void (*imageSize)(struct VideoBackend*, enum VideoLayer, int* w, int* h);
|
||||
void (*setImage)(struct VideoBackend*, enum VideoLayer, const void* frame);
|
||||
void (*drawFrame)(struct VideoBackend*);
|
||||
void (*setMessage)(struct VideoBackend*, const char* message);
|
||||
void (*clearMessage)(struct VideoBackend*);
|
||||
|
||||
void* user;
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
|
||||
bool filter;
|
||||
bool lockAspectRatio;
|
||||
bool lockIntegerScaling;
|
||||
bool interframeBlending;
|
||||
enum VideoLayer cropToLayer;
|
||||
};
|
||||
|
||||
struct VideoShader {
|
||||
|
@ -48,6 +58,9 @@ struct VideoShader {
|
|||
size_t nPasses;
|
||||
};
|
||||
|
||||
void VideoBackendGetFrame(const struct VideoBackend*, struct Rectangle* frame);
|
||||
void VideoBackendGetFrameSize(const struct VideoBackend*, unsigned* width, unsigned* height);
|
||||
|
||||
CXX_GUARD_END
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1511,7 +1511,7 @@ void _prepareForFrame(struct mGUIRunner* runner) {
|
|||
}
|
||||
|
||||
void _drawFrame(struct mGUIRunner* runner, bool faded) {
|
||||
runner->core->desiredVideoDimensions(runner->core, &corew, &coreh);
|
||||
runner->core->currentVideoSize(runner->core, &corew, &coreh);
|
||||
uint32_t color = 0xFFFFFF3F;
|
||||
if (!faded) {
|
||||
color |= 0xC0;
|
||||
|
|
|
@ -16,6 +16,7 @@ set(SOURCE_FILES
|
|||
convolve.c
|
||||
elf-read.c
|
||||
export.c
|
||||
geometry.c
|
||||
patch.c
|
||||
patch-fast.c
|
||||
patch-ips.c
|
||||
|
@ -33,6 +34,7 @@ set(GUI_FILES
|
|||
gui/menu.c)
|
||||
|
||||
set(TEST_FILES
|
||||
test/geometry.c
|
||||
test/sfo.c
|
||||
test/string-parser.c
|
||||
test/string-utf8.c
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
/* Copyright (c) 2013-2023 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include <mgba-util/geometry.h>
|
||||
|
||||
void RectangleUnion(struct Rectangle* dst, const struct Rectangle* add) {
|
||||
int x0 = dst->x;
|
||||
int y0 = dst->y;
|
||||
int x1 = dst->x + dst->width;
|
||||
int y1 = dst->y + dst->height;
|
||||
|
||||
if (add->x < x0) {
|
||||
x0 = add->x;
|
||||
}
|
||||
if (add->y < y0) {
|
||||
y0 = add->y;
|
||||
}
|
||||
if (add->x + add->width > x1) {
|
||||
x1 = add->x + add->width;
|
||||
}
|
||||
if (add->y + add->height > y1) {
|
||||
y1 = add->y + add->height;
|
||||
}
|
||||
|
||||
dst->x = x0;
|
||||
dst->y = y0;
|
||||
dst->width = x1 - x0;
|
||||
dst->height = y1 - y0;
|
||||
}
|
||||
|
||||
void RectangleCenter(const struct Rectangle* ref, struct Rectangle* rect) {
|
||||
rect->x = ref->x + (ref->width - rect->width) / 2;
|
||||
rect->y = ref->y + (ref->height - rect->height) / 2;
|
||||
}
|
|
@ -0,0 +1,213 @@
|
|||
/* Copyright (c) 2013-2023 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "util/test/suite.h"
|
||||
|
||||
#include <mgba-util/geometry.h>
|
||||
|
||||
M_TEST_DEFINE(unionRectOrigin) {
|
||||
struct Rectangle a = {
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.width = 1,
|
||||
.height = 1
|
||||
};
|
||||
struct Rectangle b = {
|
||||
.x = 1,
|
||||
.y = 1,
|
||||
.width = 1,
|
||||
.height = 1
|
||||
};
|
||||
RectangleUnion(&a, &b);
|
||||
assert_int_equal(a.x, 0);
|
||||
assert_int_equal(a.y, 0);
|
||||
assert_int_equal(a.width, 2);
|
||||
assert_int_equal(a.height, 2);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(unionRectOriginSwapped) {
|
||||
struct Rectangle a = {
|
||||
.x = 1,
|
||||
.y = 1,
|
||||
.width = 1,
|
||||
.height = 1
|
||||
};
|
||||
struct Rectangle b = {
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.width = 1,
|
||||
.height = 1
|
||||
};
|
||||
RectangleUnion(&a, &b);
|
||||
assert_int_equal(a.x, 0);
|
||||
assert_int_equal(a.y, 0);
|
||||
assert_int_equal(a.width, 2);
|
||||
assert_int_equal(a.height, 2);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(unionRectNonOrigin) {
|
||||
struct Rectangle a = {
|
||||
.x = 1,
|
||||
.y = 1,
|
||||
.width = 1,
|
||||
.height = 1
|
||||
};
|
||||
struct Rectangle b = {
|
||||
.x = 2,
|
||||
.y = 2,
|
||||
.width = 1,
|
||||
.height = 1
|
||||
};
|
||||
RectangleUnion(&a, &b);
|
||||
assert_int_equal(a.x, 1);
|
||||
assert_int_equal(a.y, 1);
|
||||
assert_int_equal(a.width, 2);
|
||||
assert_int_equal(a.height, 2);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(unionRectOverlapping) {
|
||||
struct Rectangle a = {
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.width = 2,
|
||||
.height = 2
|
||||
};
|
||||
struct Rectangle b = {
|
||||
.x = 1,
|
||||
.y = 1,
|
||||
.width = 2,
|
||||
.height = 2
|
||||
};
|
||||
RectangleUnion(&a, &b);
|
||||
assert_int_equal(a.x, 0);
|
||||
assert_int_equal(a.y, 0);
|
||||
assert_int_equal(a.width, 3);
|
||||
assert_int_equal(a.height, 3);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(unionRectSubRect) {
|
||||
struct Rectangle a = {
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.width = 3,
|
||||
.height = 3
|
||||
};
|
||||
struct Rectangle b = {
|
||||
.x = 1,
|
||||
.y = 1,
|
||||
.width = 1,
|
||||
.height = 1
|
||||
};
|
||||
RectangleUnion(&a, &b);
|
||||
assert_int_equal(a.x, 0);
|
||||
assert_int_equal(a.y, 0);
|
||||
assert_int_equal(a.width, 3);
|
||||
assert_int_equal(a.height, 3);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(unionRectNegativeOrigin) {
|
||||
struct Rectangle a = {
|
||||
.x = -1,
|
||||
.y = -1,
|
||||
.width = 1,
|
||||
.height = 1
|
||||
};
|
||||
struct Rectangle b = {
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.width = 1,
|
||||
.height = 1
|
||||
};
|
||||
RectangleUnion(&a, &b);
|
||||
assert_int_equal(a.x, -1);
|
||||
assert_int_equal(a.y, -1);
|
||||
assert_int_equal(a.width, 2);
|
||||
assert_int_equal(a.height, 2);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(centerRectBasic) {
|
||||
struct Rectangle ref = {
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.width = 4,
|
||||
.height = 4
|
||||
};
|
||||
struct Rectangle a = {
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.width = 2,
|
||||
.height = 2
|
||||
};
|
||||
RectangleCenter(&ref, &a);
|
||||
assert_int_equal(a.x, 1);
|
||||
assert_int_equal(a.y, 1);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(centerRectRoundDown) {
|
||||
struct Rectangle ref = {
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.width = 4,
|
||||
.height = 4
|
||||
};
|
||||
struct Rectangle a = {
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.width = 1,
|
||||
.height = 1
|
||||
};
|
||||
RectangleCenter(&ref, &a);
|
||||
assert_int_equal(a.x, 1);
|
||||
assert_int_equal(a.y, 1);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(centerRectRoundDown2) {
|
||||
struct Rectangle ref = {
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.width = 4,
|
||||
.height = 4
|
||||
};
|
||||
struct Rectangle a = {
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.width = 3,
|
||||
.height = 2
|
||||
};
|
||||
RectangleCenter(&ref, &a);
|
||||
assert_int_equal(a.x, 0);
|
||||
assert_int_equal(a.y, 1);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(centerRectOffset) {
|
||||
struct Rectangle ref = {
|
||||
.x = 1,
|
||||
.y = 1,
|
||||
.width = 4,
|
||||
.height = 4
|
||||
};
|
||||
struct Rectangle a = {
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.width = 2,
|
||||
.height = 2
|
||||
};
|
||||
RectangleCenter(&ref, &a);
|
||||
assert_int_equal(a.x, 2);
|
||||
assert_int_equal(a.y, 2);
|
||||
}
|
||||
|
||||
M_TEST_SUITE_DEFINE(Geometry,
|
||||
cmocka_unit_test(unionRectOrigin),
|
||||
cmocka_unit_test(unionRectOriginSwapped),
|
||||
cmocka_unit_test(unionRectNonOrigin),
|
||||
cmocka_unit_test(unionRectOverlapping),
|
||||
cmocka_unit_test(unionRectSubRect),
|
||||
cmocka_unit_test(unionRectNegativeOrigin),
|
||||
cmocka_unit_test(centerRectBasic),
|
||||
cmocka_unit_test(centerRectRoundDown),
|
||||
cmocka_unit_test(centerRectRoundDown2),
|
||||
cmocka_unit_test(centerRectOffset),
|
||||
)
|
|
@ -13,6 +13,10 @@
|
|||
#ifdef __3DS__
|
||||
#include <mgba-util/platform/3ds/3ds-vfs.h>
|
||||
#endif
|
||||
#ifdef _WIN32
|
||||
#include <shlwapi.h>
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
struct VFile* VFileOpen(const char* path, int flags) {
|
||||
#ifdef USE_VFS_FILE
|
||||
|
@ -207,6 +211,41 @@ void separatePath(const char* path, char* dirname, char* basename, char* extensi
|
|||
}
|
||||
}
|
||||
|
||||
bool isAbsolute(const char* path) {
|
||||
// XXX: Is this robust?
|
||||
#ifdef _WIN32
|
||||
WCHAR wpath[PATH_MAX];
|
||||
MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, PATH_MAX);
|
||||
return !PathIsRelativeW(wpath);
|
||||
#else
|
||||
return path[0] == '/';
|
||||
#endif
|
||||
}
|
||||
|
||||
void makeAbsolute(const char* path, const char* base, char* out) {
|
||||
if (isAbsolute(path)) {
|
||||
strncpy(out, path, PATH_MAX);
|
||||
return;
|
||||
}
|
||||
|
||||
char buf[PATH_MAX];
|
||||
snprintf(buf, sizeof(buf), "%s" PATH_SEP "%s", base, path);
|
||||
#ifdef _WIN32
|
||||
WCHAR wbuf[PATH_MAX];
|
||||
WCHAR wout[PATH_MAX];
|
||||
MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, PATH_MAX);
|
||||
if (GetFullPathNameW(wbuf, PATH_MAX, wout, NULL)) {
|
||||
WideCharToMultiByte(CP_UTF8, 0, wout, -1, out, PATH_MAX, 0, 0);
|
||||
return;
|
||||
}
|
||||
#elif defined(HAVE_REALPATH)
|
||||
if (realpath(buf, out)) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
strncpy(out, buf, PATH_MAX);
|
||||
}
|
||||
|
||||
struct VFile* VDirFindFirst(struct VDir* dir, bool (*filter)(struct VFile*)) {
|
||||
dir->rewind(dir);
|
||||
struct VDirEntry* dirent = dir->listNext(dir);
|
||||
|
|
Loading…
Reference in New Issue