diff --git a/CMakeLists.txt b/CMakeLists.txt
index 42c975697..84c2a9cb6 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -58,7 +58,7 @@ if(NOT LIBMGBA_ONLY)
set(BUILD_SHARED ON CACHE BOOL "Build a shared library")
set(SKIP_LIBRARY OFF CACHE BOOL "Skip building the library (useful for only building libretro or OpenEmu cores)")
set(BUILD_GL ON CACHE BOOL "Build with OpenGL")
- set(BUILD_GLES2 OFF CACHE BOOL "Build with OpenGL|ES 2")
+ set(BUILD_GLES2 ON CACHE BOOL "Build with OpenGL|ES 2")
set(BUILD_GLES3 OFF CACHE BOOL "Build with OpenGL|ES 3")
set(USE_EPOXY ON CACHE STRING "Build with libepoxy")
set(DISABLE_DEPS OFF CACHE BOOL "Build without dependencies")
@@ -452,7 +452,7 @@ set(FEATURE_DEFINES)
set(FEATURE_FLAGS)
set(FEATURES)
set(ENABLES)
-if(CMAKE_SYSTEM_NAME MATCHES .*BSD)
+if(CMAKE_SYSTEM_NAME MATCHES ".*BSD|DragonFly")
set(LIBEDIT_LIBRARIES -ledit)
if (CMAKE_SYSTEM_NAME STREQUAL OpenBSD)
list(APPEND LIBEDIT_LIBRARIES -ltermcap)
@@ -467,9 +467,9 @@ if(BUILD_GL)
endif()
endif()
if(NOT BUILD_GL)
- set(OPENGLE_LIBRARY "" CACHE PATH "" FORCE)
+ set(OPENGL_LIBRARY "" CACHE PATH "" FORCE)
endif()
-if(BUILD_GLES2 AND NOT BUILD_RASPI)
+if(BUILD_GLES2 AND NOT BUILD_RASPI AND NOT CMAKE_SYSTEM_NAME MATCHES "^(Windows|Darwin|Linux|.*BSD|DragonFly|Haiku)$")
find_path(OPENGLES2_INCLUDE_DIR NAMES GLES2/gl2.h)
find_library(OPENGLES2_LIBRARY NAMES GLESv2 GLESv2_CM)
if(NOT OPENGLES2_INCLUDE_DIR OR NOT OPENGLES2_LIBRARY)
@@ -479,6 +479,16 @@ endif()
if(NOT BUILD_GLES2)
set(OPENGLES2_LIBRARY "" CACHE PATH "" FORCE)
endif()
+if(BUILD_GL)
+ list(APPEND OS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/opengl/gl.c)
+ list(APPEND DEPENDENCY_LIB ${OPENGL_LIBRARY})
+ include_directories(${OPENGL_INCLUDE_DIR})
+endif()
+if(BUILD_GLES2)
+ list(APPEND OS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/opengl/gles2.c)
+ list(APPEND DEPENDENCY_LIB ${OPENGLES2_LIBRARY})
+ include_directories(${OPENGLES2_INCLUDE_DIR})
+endif()
if(BUILD_GLES3)
find_path(OPENGLES3_INCLUDE_DIR NAMES GLES3/gl3.h)
find_library(OPENGLES3_LIBRARY NAMES GLESv3 GLESv2)
@@ -745,12 +755,18 @@ if (USE_LZMA)
endif()
if(USE_EPOXY)
+ list(APPEND OS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/opengl/gl.c ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/opengl/gles2.c)
add_definitions(-DBUILD_GL -DBUILD_GLES2)
list(APPEND FEATURES EPOXY)
include_directories(AFTER ${EPOXY_INCLUDE_DIRS})
link_directories(${EPOXY_LIBRARY_DIRS})
set(OPENGLES2_LIBRARY ${EPOXY_LIBRARIES})
+ list(APPEND DEPENDENCY_LIB ${EPOXY_LIBRARIES})
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libepoxy0")
+elseif(BUILD_GL)
+ set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libgl1|libgles2")
+elseif(BUILD_GLES2)
+ set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libgles2")
endif()
if(USE_SQLITE3)
diff --git a/README.md b/README.md
index a4f2c38f1..cd7669475 100644
--- a/README.md
+++ b/README.md
@@ -92,7 +92,7 @@ Other Unix-like platforms, such as OpenBSD, are known to work as well, but are u
### System requirements
-Requirements are minimal[[2]](#dscaveat). Any computer that can run Windows Vista or newer should be able to handle emulation. Support for OpenGL 1.1 or newer is also required.
+Requirements are minimal[[2]](#dscaveat). Any computer that can run Windows Vista or newer should be able to handle emulation. Support for OpenGL 1.1 or newer is also required, with OpenGL 3.0 or newer for shaders and advanced features.
Downloads
---------
diff --git a/include/mgba/core/core.h b/include/mgba/core/core.h
index 8e2c6b306..4881c0bc7 100644
--- a/include/mgba/core/core.h
+++ b/include/mgba/core/core.h
@@ -50,6 +50,7 @@ struct mCore {
struct mTiming* timing;
struct mDebugger* debugger;
struct mDebuggerSymbols* symbolTable;
+ struct mVideoLogger* videoLogger;
#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2
struct mDirectorySet dirs;
@@ -67,12 +68,14 @@ struct mCore {
void (*deinit)(struct mCore*);
enum mPlatform (*platform)(const struct mCore*);
+ bool (*supportsFeature)(const struct mCore*, enum mCoreFeature);
void (*setSync)(struct mCore*, struct mCoreSync*);
void (*loadConfig)(struct mCore*, const struct mCoreConfig*);
void (*desiredVideoDimensions)(struct mCore*, unsigned* width, unsigned* height);
void (*setVideoBuffer)(struct mCore*, color_t* buffer, size_t stride);
+ void (*setVideoGLTex)(struct mCore*, unsigned texid);
void (*getPixels)(struct mCore*, const void** buffer, size_t* stride);
void (*putPixels)(struct mCore*, const void* buffer, size_t stride);
diff --git a/include/mgba/core/interface.h b/include/mgba/core/interface.h
index 4f0ebded8..7614bc2ae 100644
--- a/include/mgba/core/interface.h
+++ b/include/mgba/core/interface.h
@@ -80,6 +80,10 @@ enum mColorFormat {
mCOLOR_ANY = -1
};
+enum mCoreFeature {
+ mCORE_FEATURE_OPENGL = 1,
+};
+
struct mCoreCallbacks {
void* context;
void (*videoFrameStarted)(void* context);
diff --git a/include/mgba/feature/thread-proxy.h b/include/mgba/feature/thread-proxy.h
index cb663d506..36f432b03 100644
--- a/include/mgba/feature/thread-proxy.h
+++ b/include/mgba/feature/thread-proxy.h
@@ -29,6 +29,7 @@ struct mVideoThreadProxy {
Condition toThreadCond;
Mutex mutex;
enum mVideoThreadProxyState threadState;
+ enum mVideoLoggerEvent event;
struct RingFIFO dirtyQueue;
};
diff --git a/include/mgba/feature/video-logger.h b/include/mgba/feature/video-logger.h
index f194433a2..d33e83155 100644
--- a/include/mgba/feature/video-logger.h
+++ b/include/mgba/feature/video-logger.h
@@ -27,6 +27,14 @@ enum mVideoLoggerDirtyType {
DIRTY_BUFFER,
};
+enum mVideoLoggerEvent {
+ LOGGER_EVENT_NONE = 0,
+ LOGGER_EVENT_INIT,
+ LOGGER_EVENT_DEINIT,
+ LOGGER_EVENT_RESET,
+ LOGGER_EVENT_GET_PIXELS,
+};
+
struct mVideoLoggerDirtyInfo {
enum mVideoLoggerDirtyType type;
uint32_t address;
@@ -38,6 +46,7 @@ struct VFile;
struct mVideoLogger {
bool (*writeData)(struct mVideoLogger* logger, const void* data, size_t length);
bool (*readData)(struct mVideoLogger* logger, void* data, size_t length, bool block);
+ void (*postEvent)(struct mVideoLogger* logger, enum mVideoLoggerEvent event);
void* dataContext;
bool block;
@@ -52,6 +61,7 @@ struct mVideoLogger {
void* context;
bool (*parsePacket)(struct mVideoLogger* logger, const struct mVideoLoggerDirtyInfo* packet);
+ void (*handleEvent)(struct mVideoLogger* logger, enum mVideoLoggerEvent event);
uint16_t* (*vramBlock)(struct mVideoLogger* logger, uint32_t address);
size_t vramSize;
@@ -64,6 +74,9 @@ struct mVideoLogger {
uint16_t* vram;
uint16_t* oam;
uint16_t* palette;
+
+ const void* pixelBuffer;
+ size_t pixelStride;
};
void mVideoLoggerRendererCreate(struct mVideoLogger* logger, bool readonly);
diff --git a/include/mgba/internal/gba/renderers/common.h b/include/mgba/internal/gba/renderers/common.h
new file mode 100644
index 000000000..80a618009
--- /dev/null
+++ b/include/mgba/internal/gba/renderers/common.h
@@ -0,0 +1,25 @@
+/* Copyright (c) 2013-2019 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 GBA_RENDERER_COMMON_H
+#define GBA_RENDERER_COMMON_H
+
+#include
+
+CXX_GUARD_START
+
+#include
+
+struct GBAVideoRendererSprite {
+ struct GBAObj obj;
+ int16_t y;
+ int16_t endY;
+};
+
+int GBAVideoRendererCleanOAM(struct GBAObj* oam, struct GBAVideoRendererSprite* sprites, int offsetY, int masterHeight, bool combinedObjSort);
+
+CXX_GUARD_END
+
+#endif
diff --git a/include/mgba/internal/gba/renderers/gl.h b/include/mgba/internal/gba/renderers/gl.h
new file mode 100644
index 000000000..209607f1c
--- /dev/null
+++ b/include/mgba/internal/gba/renderers/gl.h
@@ -0,0 +1,184 @@
+/* Copyright (c) 2013-2019 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 VIDEO_GL_H
+#define VIDEO_GL_H
+
+#include
+
+CXX_GUARD_START
+
+#include
+#include
+#include
+#include
+#include
+
+#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
+
+#ifdef USE_EPOXY
+#include
+#elif defined(BUILD_GL)
+#ifdef __APPLE__
+#include
+#else
+#define GL_GLEXT_PROTOTYPES
+#include
+#include
+#endif
+#else
+#include
+#endif
+
+struct GBAVideoGLAffine {
+ int16_t dx;
+ int16_t dmx;
+ int16_t dy;
+ int16_t dmy;
+ int32_t sx;
+ int32_t sy;
+};
+
+struct GBAVideoGLBackground {
+ GLuint fbo;
+ GLuint tex;
+ GLuint flags;
+
+ unsigned index;
+ int enabled;
+ unsigned priority;
+ uint32_t charBase;
+ int mosaic;
+ int multipalette;
+ uint32_t screenBase;
+ int overflow;
+ int size;
+ int target1;
+ int target2;
+ uint16_t x;
+ uint16_t y;
+ int32_t refx;
+ int32_t refy;
+
+ struct GBAVideoGLAffine affine[4];
+};
+
+enum {
+ GBA_GL_FBO_OBJ = 0,
+ GBA_GL_FBO_WINDOW = 1,
+ GBA_GL_FBO_OUTPUT = 2,
+ GBA_GL_FBO_MAX
+};
+
+enum {
+ GBA_GL_TEX_OBJ_COLOR = 0,
+ GBA_GL_TEX_OBJ_FLAGS = 1,
+ GBA_GL_TEX_WINDOW = 6,
+ GBA_GL_TEX_MAX
+};
+
+enum {
+ GBA_GL_VS_LOC = 0,
+ GBA_GL_VS_MAXPOS,
+
+ GBA_GL_BG_VRAM = 2,
+ GBA_GL_BG_PALETTE,
+ GBA_GL_BG_SCREENBASE,
+ GBA_GL_BG_CHARBASE,
+ GBA_GL_BG_SIZE,
+ GBA_GL_BG_OFFSET,
+ GBA_GL_BG_INFLAGS,
+ GBA_GL_BG_TRANSFORM,
+
+ GBA_GL_OBJ_VRAM = 2,
+ GBA_GL_OBJ_PALETTE,
+ GBA_GL_OBJ_CHARBASE,
+ GBA_GL_OBJ_STRIDE,
+ GBA_GL_OBJ_LOCALPALETTE,
+ GBA_GL_OBJ_INFLAGS,
+ GBA_GL_OBJ_TRANSFORM,
+ GBA_GL_OBJ_DIMS,
+ GBA_GL_OBJ_OBJWIN,
+
+ GBA_GL_FINALIZE_SCALE = 2,
+ GBA_GL_FINALIZE_LAYERS,
+ GBA_GL_FINALIZE_FLAGS,
+ GBA_GL_FINALIZE_WINDOW,
+ GBA_GL_FINALIZE_BACKDROP,
+ GBA_GL_FINALIZE_BACKDROPFLAGS,
+
+ GBA_GL_UNIFORM_MAX = 12
+};
+
+struct GBAVideoGLShader {
+ GLuint program;
+ GLuint vao;
+ GLuint uniforms[GBA_GL_UNIFORM_MAX];
+};
+
+struct GBAVideoGLRenderer {
+ struct GBAVideoRenderer d;
+
+ uint32_t* temporaryBuffer;
+
+ struct GBAVideoGLBackground bg[4];
+
+ int oamMax;
+ bool oamDirty;
+ struct GBAVideoRendererSprite sprites[128];
+
+ GLuint fbo[GBA_GL_FBO_MAX];
+ GLuint layers[GBA_GL_TEX_MAX];
+ GLuint vbo;
+
+ GLuint outputTex;
+
+#ifdef BUILD_GLES3
+ uint16_t shadowPalette[512];
+#endif
+ GLuint paletteTex;
+ bool paletteDirty;
+
+ GLuint vramTex;
+ unsigned vramDirty;
+
+ struct GBAVideoGLShader bgShader[6];
+ struct GBAVideoGLShader objShader[2];
+ struct GBAVideoGLShader finalizeShader;
+
+ GBARegisterDISPCNT dispcnt;
+
+ unsigned target1Obj;
+ unsigned target1Bd;
+ unsigned target2Obj;
+ unsigned target2Bd;
+ enum GBAVideoBlendEffect blendEffect;
+ uint16_t blda;
+ uint16_t bldb;
+ uint16_t bldy;
+
+ GBAMosaicControl mosaic;
+
+ struct GBAVideoGLWindowN {
+ struct GBAVideoWindowRegion h;
+ struct GBAVideoWindowRegion v;
+ GBAWindowControl control;
+ } winN[2];
+
+ GBAWindowControl winout;
+ GBAWindowControl objwin;
+
+ int firstAffine;
+
+ int scale;
+};
+
+void GBAVideoGLRendererCreate(struct GBAVideoGLRenderer* renderer);
+
+#endif
+
+CXX_GUARD_END
+
+#endif
\ No newline at end of file
diff --git a/include/mgba/internal/gba/renderers/video-software.h b/include/mgba/internal/gba/renderers/video-software.h
index 69222f606..0a705bf4e 100644
--- a/include/mgba/internal/gba/renderers/video-software.h
+++ b/include/mgba/internal/gba/renderers/video-software.h
@@ -13,14 +13,9 @@ CXX_GUARD_START
#include
#include
#include
+#include
#include
-struct GBAVideoSoftwareSprite {
- struct GBAObj obj;
- int y;
- int endY;
-};
-
struct GBAVideoSoftwareBackground {
unsigned index;
int enabled;
@@ -52,13 +47,6 @@ struct GBAVideoSoftwareBackground {
int32_t offsetY;
};
-enum BlendEffect {
- BLEND_NONE = 0,
- BLEND_ALPHA = 1,
- BLEND_BRIGHTEN = 2,
- BLEND_DARKEN = 3
-};
-
enum {
#ifdef COLOR_16_BIT
#ifdef COLOR_5_6_5
@@ -90,20 +78,6 @@ struct WindowRegion {
int start;
};
-DECL_BITFIELD(GBAWindowControl, uint8_t);
-DECL_BIT(GBAWindowControl, Bg0Enable, 0);
-DECL_BIT(GBAWindowControl, Bg1Enable, 1);
-DECL_BIT(GBAWindowControl, Bg2Enable, 2);
-DECL_BIT(GBAWindowControl, Bg3Enable, 3);
-DECL_BIT(GBAWindowControl, ObjEnable, 4);
-DECL_BIT(GBAWindowControl, BlendEnable, 5);
-
-DECL_BITFIELD(GBAMosaicControl, uint16_t);
-DECL_BITS(GBAMosaicControl, BgH, 0, 4);
-DECL_BITS(GBAMosaicControl, BgV, 4, 4);
-DECL_BITS(GBAMosaicControl, ObjH, 8, 4);
-DECL_BITS(GBAMosaicControl, ObjV, 12, 4);
-
struct WindowControl {
GBAWindowControl packed;
int8_t priority;
@@ -138,7 +112,7 @@ struct GBAVideoSoftwareRenderer {
unsigned target2Obj;
unsigned target2Bd;
bool blendDirty;
- enum BlendEffect blendEffect;
+ enum GBAVideoBlendEffect blendEffect;
color_t normalPalette[512];
color_t variantPalette[512];
color_t* objExtPalette;
@@ -151,8 +125,8 @@ struct GBAVideoSoftwareRenderer {
GBAMosaicControl mosaic;
struct WindowN {
- struct WindowRegion h;
- struct WindowRegion v;
+ struct GBAVideoWindowRegion h;
+ struct GBAVideoWindowRegion v;
struct WindowControl control;
} winN[2];
@@ -168,7 +142,7 @@ struct GBAVideoSoftwareRenderer {
int oamDirty;
int oamMax;
- struct GBAVideoSoftwareSprite sprites[128];
+ struct GBAVideoRendererSprite sprites[128];
int tileStride;
int bitmapStride;
bool combinedObjSort;
diff --git a/include/mgba/internal/gba/video.h b/include/mgba/internal/gba/video.h
index 208386b51..d79c5d5fe 100644
--- a/include/mgba/internal/gba/video.h
+++ b/include/mgba/internal/gba/video.h
@@ -35,19 +35,26 @@ enum {
VRAM_BLOCK_MASK = 0x3FFF
};
-enum ObjMode {
+enum GBAVideoObjMode {
OBJ_MODE_NORMAL = 0,
OBJ_MODE_SEMITRANSPARENT = 1,
OBJ_MODE_OBJWIN = 2,
OBJ_MODE_BITMAP = 3
};
-enum ObjShape {
+enum GBAVideoObjShape {
OBJ_SHAPE_SQUARE = 0,
OBJ_SHAPE_HORIZONTAL = 1,
OBJ_SHAPE_VERTICAL = 2
};
+enum GBAVideoBlendEffect {
+ BLEND_NONE = 0,
+ BLEND_ALPHA = 1,
+ BLEND_BRIGHTEN = 2,
+ BLEND_DARKEN = 3
+};
+
DECL_BITFIELD(GBAObjAttributesA, uint16_t);
DECL_BITS(GBAObjAttributesA, Y, 0, 8);
DECL_BIT(GBAObjAttributesA, Transformed, 8);
@@ -94,6 +101,11 @@ union GBAOAM {
uint16_t raw[512];
};
+struct GBAVideoWindowRegion {
+ uint8_t end;
+ uint8_t start;
+};
+
#define GBA_TEXT_MAP_TILE(MAP) ((MAP) & 0x03FF)
#define GBA_TEXT_MAP_HFLIP(MAP) ((MAP) & 0x0400)
#define GBA_TEXT_MAP_VFLIP(MAP) ((MAP) & 0x0800)
@@ -148,6 +160,20 @@ DECL_BIT(GBARegisterBLDCNT, Target2Bg3, 11);
DECL_BIT(GBARegisterBLDCNT, Target2Obj, 12);
DECL_BIT(GBARegisterBLDCNT, Target2Bd, 13);
+DECL_BITFIELD(GBAWindowControl, uint8_t);
+DECL_BIT(GBAWindowControl, Bg0Enable, 0);
+DECL_BIT(GBAWindowControl, Bg1Enable, 1);
+DECL_BIT(GBAWindowControl, Bg2Enable, 2);
+DECL_BIT(GBAWindowControl, Bg3Enable, 3);
+DECL_BIT(GBAWindowControl, ObjEnable, 4);
+DECL_BIT(GBAWindowControl, BlendEnable, 5);
+
+DECL_BITFIELD(GBAMosaicControl, uint16_t);
+DECL_BITS(GBAMosaicControl, BgH, 0, 4);
+DECL_BITS(GBAMosaicControl, BgV, 4, 4);
+DECL_BITS(GBAMosaicControl, ObjH, 8, 4);
+DECL_BITS(GBAMosaicControl, ObjV, 12, 4);
+
struct GBAVideoRenderer {
void (*init)(struct GBAVideoRenderer* renderer);
void (*reset)(struct GBAVideoRenderer* renderer);
diff --git a/src/ds/core.c b/src/ds/core.c
index a959ac6e3..1a0152933 100644
--- a/src/ds/core.c
+++ b/src/ds/core.c
@@ -143,6 +143,16 @@ static enum mPlatform _DSCorePlatform(const struct mCore* core) {
return PLATFORM_DS;
}
+static bool _DSCoreSupportsFeature(const struct mCore* core, enum mCoreFeature feature) {
+ UNUSED(core);
+ switch (feature) {
+ case mCORE_FEATURE_OPENGL:
+ return false;
+ default:
+ return false;
+ }
+}
+
static void _DSCoreSetSync(struct mCore* core, struct mCoreSync* sync) {
struct DS* ds = core->board;
ds->sync = sync;
@@ -628,6 +638,7 @@ struct mCore* DSCoreCreate(void) {
core->init = _DSCoreInit;
core->deinit = _DSCoreDeinit;
core->platform = _DSCorePlatform;
+ core->supportsFeature = _DSCoreSupportsFeature;
core->setSync = _DSCoreSetSync;
core->loadConfig = _DSCoreLoadConfig;
core->desiredVideoDimensions = _DSCoreDesiredVideoDimensions;
diff --git a/src/feature/thread-proxy.c b/src/feature/thread-proxy.c
index d00da8cd8..7a67dbedd 100644
--- a/src/feature/thread-proxy.c
+++ b/src/feature/thread-proxy.c
@@ -18,6 +18,7 @@ static THREAD_ENTRY _proxyThread(void* renderer);
static bool _writeData(struct mVideoLogger* logger, const void* data, size_t length);
static bool _readData(struct mVideoLogger* logger, void* data, size_t length, bool block);
+static void _postEvent(struct mVideoLogger* logger, enum mVideoLoggerEvent);
static void _lock(struct mVideoLogger* logger);
static void _unlock(struct mVideoLogger* logger);
@@ -38,6 +39,7 @@ void mVideoThreadProxyCreate(struct mVideoThreadProxy* renderer) {
renderer->d.writeData = _writeData;
renderer->d.readData = _readData;
+ renderer->d.postEvent = _postEvent;
}
void mVideoThreadProxyInit(struct mVideoLogger* logger) {
@@ -131,6 +133,14 @@ static bool _readData(struct mVideoLogger* logger, void* data, size_t length, bo
return read;
}
+static void _postEvent(struct mVideoLogger* logger, enum mVideoLoggerEvent event) {
+ struct mVideoThreadProxy* proxyRenderer = (struct mVideoThreadProxy*) logger;
+ MutexLock(&proxyRenderer->mutex);
+ proxyRenderer->event = event;
+ ConditionWake(&proxyRenderer->toThreadCond);
+ MutexUnlock(&proxyRenderer->mutex);
+}
+
static void _lock(struct mVideoLogger* logger) {
struct mVideoThreadProxy* proxyRenderer = (struct mVideoThreadProxy*) logger;
MutexLock(&proxyRenderer->mutex);
@@ -172,13 +182,18 @@ static THREAD_ENTRY _proxyThread(void* logger) {
break;
}
proxyRenderer->threadState = PROXY_THREAD_BUSY;
- MutexUnlock(&proxyRenderer->mutex);
- if (!mVideoLoggerRendererRun(&proxyRenderer->d, false)) {
- // FIFO was corrupted
- proxyRenderer->threadState = PROXY_THREAD_STOPPED;
- mLOG(GBA_VIDEO, ERROR, "Proxy thread queue got corrupted!");
+ if (proxyRenderer->event) {
+ proxyRenderer->d.handleEvent(&proxyRenderer->d, proxyRenderer->event);
+ proxyRenderer->event = 0;
+ } else {
+ MutexUnlock(&proxyRenderer->mutex);
+ if (!mVideoLoggerRendererRun(&proxyRenderer->d, false)) {
+ // FIFO was corrupted
+ proxyRenderer->threadState = PROXY_THREAD_STOPPED;
+ mLOG(GBA_VIDEO, ERROR, "Proxy thread queue got corrupted!");
+ }
+ MutexLock(&proxyRenderer->mutex);
}
- MutexLock(&proxyRenderer->mutex);
ConditionWake(&proxyRenderer->fromThreadCond);
if (proxyRenderer->threadState != PROXY_THREAD_STOPPED) {
proxyRenderer->threadState = PROXY_THREAD_IDLE;
diff --git a/src/gb/core.c b/src/gb/core.c
index 7fe089848..d0b592ff5 100644
--- a/src/gb/core.c
+++ b/src/gb/core.c
@@ -147,6 +147,14 @@ static enum mPlatform _GBCorePlatform(const struct mCore* core) {
return PLATFORM_GB;
}
+static bool _GBCoreSupportsFeature(const struct mCore* core, enum mCoreFeature feature) {
+ UNUSED(core);
+ switch (feature) {
+ default:
+ return false;
+ }
+}
+
static void _GBCoreSetSync(struct mCore* core, struct mCoreSync* sync) {
struct GB* gb = core->board;
gb->sync = sync;
@@ -242,6 +250,11 @@ static void _GBCoreSetVideoBuffer(struct mCore* core, color_t* buffer, size_t st
gbcore->renderer.outputBufferStride = stride;
}
+static void _GBCoreSetVideoGLTex(struct mCore* core, unsigned texid) {
+ UNUSED(core);
+ UNUSED(texid);
+}
+
static void _GBCoreGetPixels(struct mCore* core, const void** buffer, size_t* stride) {
struct GBCore* gbcore = (struct GBCore*) core;
gbcore->renderer.d.getPixels(&gbcore->renderer.d, stride, buffer);
@@ -906,10 +919,12 @@ struct mCore* GBCoreCreate(void) {
core->init = _GBCoreInit;
core->deinit = _GBCoreDeinit;
core->platform = _GBCorePlatform;
+ core->supportsFeature = _GBCoreSupportsFeature;
core->setSync = _GBCoreSetSync;
core->loadConfig = _GBCoreLoadConfig;
core->desiredVideoDimensions = _GBCoreDesiredVideoDimensions;
core->setVideoBuffer = _GBCoreSetVideoBuffer;
+ core->setVideoGLTex = _GBCoreSetVideoGLTex;
core->getPixels = _GBCoreGetPixels;
core->putPixels = _GBCorePutPixels;
core->getAudioChannel = _GBCoreGetAudioChannel;
diff --git a/src/gb/extra/proxy.c b/src/gb/extra/proxy.c
index c5230c88c..d8538124d 100644
--- a/src/gb/extra/proxy.c
+++ b/src/gb/extra/proxy.c
@@ -283,11 +283,13 @@ static void GBVideoProxyRendererGetPixels(struct GBVideoRenderer* renderer, size
proxyRenderer->logger->lock(proxyRenderer->logger);
// Insert an extra item into the queue to make sure it gets flushed
mVideoLoggerRendererFlush(proxyRenderer->logger);
- proxyRenderer->logger->wait(proxyRenderer->logger);
- }
- proxyRenderer->backend->getPixels(proxyRenderer->backend, stride, pixels);
- if (proxyRenderer->logger->block && proxyRenderer->logger->wait) {
+ proxyRenderer->logger->postEvent(proxyRenderer->logger, LOGGER_EVENT_GET_PIXELS);
+ mVideoLoggerRendererFlush(proxyRenderer->logger);
proxyRenderer->logger->unlock(proxyRenderer->logger);
+ *pixels = proxyRenderer->logger->pixelBuffer;
+ *stride = proxyRenderer->logger->pixelStride;
+ } else {
+ proxyRenderer->backend->getPixels(proxyRenderer->backend, stride, pixels);
}
}
diff --git a/src/gba/core.c b/src/gba/core.c
index 2918a21e6..50e6ab375 100644
--- a/src/gba/core.c
+++ b/src/gba/core.c
@@ -17,6 +17,9 @@
#ifndef DISABLE_THREADING
#include
#endif
+#ifdef BUILD_GLES2
+#include
+#endif
#include
#include
#include
@@ -127,6 +130,9 @@ struct mVideoLogContext;
struct GBACore {
struct mCore d;
struct GBAVideoSoftwareRenderer renderer;
+#ifdef BUILD_GLES2
+ struct GBAVideoGLRenderer glRenderer;
+#endif
struct GBAVideoProxyRenderer proxyRenderer;
struct mVideoLogContext* logContext;
struct mCoreCallbacks logCallbacks;
@@ -155,6 +161,7 @@ static bool _GBACoreInit(struct mCore* core) {
core->timing = &gba->timing;
core->debugger = NULL;
core->symbolTable = NULL;
+ core->videoLogger = NULL;
gbacore->overrides = NULL;
gbacore->debuggerPlatform = NULL;
gbacore->cheatDevice = NULL;
@@ -171,6 +178,11 @@ static bool _GBACoreInit(struct mCore* core) {
GBAVideoSoftwareRendererCreate(&gbacore->renderer);
gbacore->renderer.outputBuffer = NULL;
+#ifdef BUILD_GLES2
+ GBAVideoGLRendererCreate(&gbacore->glRenderer);
+ gbacore->glRenderer.outputTex = -1;
+#endif
+
#ifndef DISABLE_THREADING
mVideoThreadProxyCreate(&gbacore->threadProxy);
#endif
@@ -214,6 +226,20 @@ static enum mPlatform _GBACorePlatform(const struct mCore* core) {
return PLATFORM_GBA;
}
+static bool _GBACoreSupportsFeature(const struct mCore* core, enum mCoreFeature feature) {
+ UNUSED(core);
+ switch (feature) {
+ case mCORE_FEATURE_OPENGL:
+#ifdef BUILD_GLES2
+ return true;
+#else
+ return false;
+#endif
+ default:
+ return false;
+ }
+}
+
static void _GBACoreSetSync(struct mCore* core, struct mCoreSync* sync) {
struct GBA* gba = core->board;
gba->sync = sync;
@@ -258,12 +284,20 @@ static void _GBACoreLoadConfig(struct mCore* core, const struct mCoreConfig* con
#ifndef DISABLE_THREADING
mCoreConfigCopyValue(&core->config, config, "threadedVideo");
#endif
+ mCoreConfigCopyValue(&core->config, config, "hwaccelVideo");
+ mCoreConfigCopyValue(&core->config, config, "videoScale");
}
static void _GBACoreDesiredVideoDimensions(struct mCore* core, unsigned* width, unsigned* height) {
- UNUSED(core);
- *width = GBA_VIDEO_HORIZONTAL_PIXELS;
- *height = GBA_VIDEO_VERTICAL_PIXELS;
+#ifdef BUILD_GLES2
+ struct GBACore* gbacore = (struct GBACore*) core;
+ int scale = gbacore->glRenderer.scale;
+#else
+ int scale = 1;
+#endif
+
+ *width = GBA_VIDEO_HORIZONTAL_PIXELS * scale;
+ *height = GBA_VIDEO_VERTICAL_PIXELS * scale;
}
static void _GBACoreSetVideoBuffer(struct mCore* core, color_t* buffer, size_t stride) {
@@ -273,14 +307,24 @@ static void _GBACoreSetVideoBuffer(struct mCore* core, color_t* buffer, size_t s
memset(gbacore->renderer.scanlineDirty, 0xFFFFFFFF, sizeof(gbacore->renderer.scanlineDirty));
}
-static void _GBACoreGetPixels(struct mCore* core, const void** buffer, size_t* stride) {
+static void _GBACoreSetVideoGLTex(struct mCore* core, unsigned texid) {
+#ifdef BUILD_GLES2
struct GBACore* gbacore = (struct GBACore*) core;
- gbacore->renderer.d.getPixels(&gbacore->renderer.d, stride, buffer);
+ gbacore->glRenderer.outputTex = texid;
+#else
+ UNUSED(core);
+ UNUSED(texid);
+#endif
+}
+
+static void _GBACoreGetPixels(struct mCore* core, const void** buffer, size_t* stride) {
+ struct GBA* gba = core->board;
+ gba->video.renderer->getPixels(gba->video.renderer, stride, buffer);
}
static void _GBACorePutPixels(struct mCore* core, const void* buffer, size_t stride) {
- struct GBACore* gbacore = (struct GBACore*) core;
- gbacore->renderer.d.putPixels(&gbacore->renderer.d, stride, buffer);
+ struct GBA* gba = core->board;
+ gba->video.renderer->putPixels(gba->video.renderer, stride, buffer);
}
static struct blip_t* _GBACoreGetAudioChannel(struct mCore* core, int ch) {
@@ -319,7 +363,9 @@ static void _GBACoreSetAVStream(struct mCore* core, struct mAVStream* stream) {
struct GBA* gba = core->board;
gba->stream = stream;
if (stream && stream->videoDimensionsChanged) {
- stream->videoDimensionsChanged(stream, GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS);
+ unsigned width, height;
+ core->desiredVideoDimensions(core, &width, &height);
+ stream->videoDimensionsChanged(stream, width, height);
}
if (stream && stream->videoFrameRateChanged) {
stream->videoFrameRateChanged(stream, core->frameCycles(core), core->frequency(core));
@@ -398,16 +444,34 @@ static void _GBACoreChecksum(const struct mCore* core, void* data, enum mCoreChe
static void _GBACoreReset(struct mCore* core) {
struct GBACore* gbacore = (struct GBACore*) core;
struct GBA* gba = (struct GBA*) core->board;
- if (gbacore->renderer.outputBuffer) {
- struct GBAVideoRenderer* renderer = &gbacore->renderer.d;
-#ifndef DISABLE_THREADING
+ if (gbacore->renderer.outputBuffer
+#ifdef BUILD_GLES2
+ || gbacore->glRenderer.outputTex != (unsigned) -1
+#endif
+ ) {
+ struct GBAVideoRenderer* renderer;
+ if (gbacore->renderer.outputBuffer) {
+ renderer = &gbacore->renderer.d;
+ }
int fakeBool;
+#ifdef BUILD_GLES2
+ if (gbacore->glRenderer.outputTex != (unsigned) -1 && mCoreConfigGetIntValue(&core->config, "hwaccelVideo", &fakeBool) && fakeBool) {
+ renderer = &gbacore->glRenderer.d;
+ mCoreConfigGetIntValue(&core->config, "videoScale", &gbacore->glRenderer.scale);
+ }
+#endif
+#ifndef DISABLE_THREADING
if (mCoreConfigGetIntValue(&core->config, "threadedVideo", &fakeBool) && fakeBool) {
- gbacore->proxyRenderer.logger = &gbacore->threadProxy.d;
+ if (!core->videoLogger) {
+ core->videoLogger = &gbacore->threadProxy.d;
+ }
+ }
+#endif
+ if (core->videoLogger) {
+ gbacore->proxyRenderer.logger = core->videoLogger;
GBAVideoProxyRendererCreate(&gbacore->proxyRenderer, renderer);
renderer = &gbacore->proxyRenderer.d;
}
-#endif
GBAVideoAssociateRenderer(&gba->video, renderer);
}
@@ -946,10 +1010,12 @@ struct mCore* GBACoreCreate(void) {
core->init = _GBACoreInit;
core->deinit = _GBACoreDeinit;
core->platform = _GBACorePlatform;
+ core->supportsFeature = _GBACoreSupportsFeature;
core->setSync = _GBACoreSetSync;
core->loadConfig = _GBACoreLoadConfig;
core->desiredVideoDimensions = _GBACoreDesiredVideoDimensions;
core->setVideoBuffer = _GBACoreSetVideoBuffer;
+ core->setVideoGLTex = _GBACoreSetVideoGLTex;
core->getPixels = _GBACoreGetPixels;
core->putPixels = _GBACorePutPixels;
core->getAudioChannel = _GBACoreGetAudioChannel;
diff --git a/src/gba/extra/proxy.c b/src/gba/extra/proxy.c
index e42443f85..59c270224 100644
--- a/src/gba/extra/proxy.c
+++ b/src/gba/extra/proxy.c
@@ -21,6 +21,7 @@ static void GBAVideoProxyRendererFinishFrame(struct GBAVideoRenderer* renderer);
static void GBAVideoProxyRendererGetPixels(struct GBAVideoRenderer* renderer, size_t* stride, const void** pixels);
static void GBAVideoProxyRendererPutPixels(struct GBAVideoRenderer* renderer, size_t stride, const void* pixels);
+static void _handleEvent(struct mVideoLogger* logger, enum mVideoLoggerEvent event);
static bool _parsePacket(struct mVideoLogger* logger, const struct mVideoLoggerDirtyInfo* packet);
static uint16_t* _vramBlock(struct mVideoLogger* logger, uint32_t address);
@@ -45,6 +46,7 @@ void GBAVideoProxyRendererCreate(struct GBAVideoProxyRenderer* renderer, struct
renderer->logger->context = renderer;
renderer->logger->parsePacket = _parsePacket;
+ renderer->logger->handleEvent = _handleEvent;
renderer->logger->vramBlock = _vramBlock;
renderer->logger->paletteSize = SIZE_PALETTE_RAM;
renderer->logger->vramSize = SIZE_VRAM;
@@ -119,7 +121,11 @@ void GBAVideoProxyRendererInit(struct GBAVideoRenderer* renderer) {
_init(proxyRenderer);
_reset(proxyRenderer);
- proxyRenderer->backend->init(proxyRenderer->backend);
+ if (!proxyRenderer->logger->block) {
+ proxyRenderer->backend->init(proxyRenderer->backend);
+ } else {
+ proxyRenderer->logger->postEvent(proxyRenderer->logger, LOGGER_EVENT_INIT);
+ }
}
void GBAVideoProxyRendererReset(struct GBAVideoRenderer* renderer) {
@@ -127,17 +133,45 @@ void GBAVideoProxyRendererReset(struct GBAVideoRenderer* renderer) {
_reset(proxyRenderer);
- proxyRenderer->backend->reset(proxyRenderer->backend);
+ if (!proxyRenderer->logger->block) {
+ proxyRenderer->backend->reset(proxyRenderer->backend);
+ } else {
+ proxyRenderer->logger->postEvent(proxyRenderer->logger, LOGGER_EVENT_RESET);
+ }
}
void GBAVideoProxyRendererDeinit(struct GBAVideoRenderer* renderer) {
struct GBAVideoProxyRenderer* proxyRenderer = (struct GBAVideoProxyRenderer*) renderer;
- proxyRenderer->backend->deinit(proxyRenderer->backend);
+ if (!proxyRenderer->logger->block) {
+ proxyRenderer->backend->deinit(proxyRenderer->backend);
+ } else {
+ proxyRenderer->logger->postEvent(proxyRenderer->logger, LOGGER_EVENT_DEINIT);
+ }
mVideoLoggerRendererDeinit(proxyRenderer->logger);
}
+static void _handleEvent(struct mVideoLogger* logger, enum mVideoLoggerEvent event) {
+ struct GBAVideoProxyRenderer* proxyRenderer = logger->context;
+ switch (event) {
+ default:
+ break;
+ case LOGGER_EVENT_INIT:
+ proxyRenderer->backend->init(proxyRenderer->backend);
+ break;
+ case LOGGER_EVENT_DEINIT:
+ proxyRenderer->backend->deinit(proxyRenderer->backend);
+ break;
+ case LOGGER_EVENT_RESET:
+ proxyRenderer->backend->reset(proxyRenderer->backend);
+ break;
+ case LOGGER_EVENT_GET_PIXELS:
+ proxyRenderer->backend->getPixels(proxyRenderer->backend, &logger->pixelStride, &logger->pixelBuffer);
+ break;
+ }
+}
+
static bool _parsePacket(struct mVideoLogger* logger, const struct mVideoLoggerDirtyInfo* item) {
struct GBAVideoProxyRenderer* proxyRenderer = logger->context;
switch (item->type) {
@@ -165,6 +199,11 @@ static bool _parsePacket(struct mVideoLogger* logger, const struct mVideoLoggerD
}
break;
case DIRTY_SCANLINE:
+ proxyRenderer->backend->disableBG[0] = proxyRenderer->d.disableBG[0];
+ proxyRenderer->backend->disableBG[1] = proxyRenderer->d.disableBG[1];
+ proxyRenderer->backend->disableBG[2] = proxyRenderer->d.disableBG[2];
+ proxyRenderer->backend->disableBG[3] = proxyRenderer->d.disableBG[3];
+ proxyRenderer->backend->disableOBJ = proxyRenderer->d.disableOBJ;
if (item->address < GBA_VIDEO_VERTICAL_PIXELS) {
proxyRenderer->backend->drawScanline(proxyRenderer->backend, item->address);
}
@@ -287,10 +326,13 @@ static void GBAVideoProxyRendererGetPixels(struct GBAVideoRenderer* renderer, si
proxyRenderer->logger->lock(proxyRenderer->logger);
// Insert an extra item into the queue to make sure it gets flushed
mVideoLoggerRendererFlush(proxyRenderer->logger);
- }
- proxyRenderer->backend->getPixels(proxyRenderer->backend, stride, pixels);
- if (proxyRenderer->logger->block && proxyRenderer->logger->wait) {
+ proxyRenderer->logger->postEvent(proxyRenderer->logger, LOGGER_EVENT_GET_PIXELS);
+ mVideoLoggerRendererFlush(proxyRenderer->logger);
proxyRenderer->logger->unlock(proxyRenderer->logger);
+ *pixels = proxyRenderer->logger->pixelBuffer;
+ *stride = proxyRenderer->logger->pixelStride;
+ } else {
+ proxyRenderer->backend->getPixels(proxyRenderer->backend, stride, pixels);
}
}
diff --git a/src/gba/renderers/common.c b/src/gba/renderers/common.c
new file mode 100644
index 000000000..ceb51e42b
--- /dev/null
+++ b/src/gba/renderers/common.c
@@ -0,0 +1,43 @@
+/* Copyright (c) 2013-2019 Jeffrey Pfau
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include
+
+#include
+
+int GBAVideoRendererCleanOAM(struct GBAObj* oam, struct GBAVideoRendererSprite* sprites, int offsetY, int masterHeight, bool combinedObjSort) {
+ int i;
+ int p;
+ int oamMax = 0;
+ int maxP = 1;
+ if (combinedObjSort) {
+ maxP = 4;
+ }
+ for (p = 0; p < maxP; ++p) {
+ for (i = 0; i < 128; ++i) {
+ struct GBAObj obj;
+ LOAD_16LE(obj.a, 0, &oam[i].a);
+ LOAD_16LE(obj.b, 0, &oam[i].b);
+ LOAD_16LE(obj.c, 0, &oam[i].c);
+ if (combinedObjSort && GBAObjAttributesCGetPriority(obj.c) != p) {
+ continue;
+ }
+ if (GBAObjAttributesAIsTransformed(obj.a) || !GBAObjAttributesAIsDisable(obj.a)) {
+ int height = GBAVideoObjSizes[GBAObjAttributesAGetShape(obj.a) * 4 + GBAObjAttributesBGetSize(obj.b)][1];
+ if (GBAObjAttributesAIsTransformed(obj.a)) {
+ height <<= GBAObjAttributesAGetDoubleSize(obj.a);
+ }
+ if (GBAObjAttributesAGetY(obj.a) < masterHeight || GBAObjAttributesAGetY(obj.a) + height >= 256) {
+ int y = GBAObjAttributesAGetY(obj.a) + offsetY;
+ sprites[oamMax].y = y;
+ sprites[oamMax].endY = y + height;
+ sprites[oamMax].obj = obj;
+ ++oamMax;
+ }
+ }
+ }
+ }
+ return oamMax;
+}
diff --git a/src/gba/renderers/gl.c b/src/gba/renderers/gl.c
new file mode 100644
index 000000000..58248ca89
--- /dev/null
+++ b/src/gba/renderers/gl.c
@@ -0,0 +1,1265 @@
+/* Copyright (c) 2013-2019 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
+
+#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
+
+#include
+#include
+#include
+#include
+#include
+
+static void GBAVideoGLRendererInit(struct GBAVideoRenderer* renderer);
+static void GBAVideoGLRendererDeinit(struct GBAVideoRenderer* renderer);
+static void GBAVideoGLRendererReset(struct GBAVideoRenderer* renderer);
+static void GBAVideoGLRendererWriteVRAM(struct GBAVideoRenderer* renderer, uint32_t address);
+static void GBAVideoGLRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam);
+static void GBAVideoGLRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
+static uint16_t GBAVideoGLRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
+static void GBAVideoGLRendererDrawScanline(struct GBAVideoRenderer* renderer, int y);
+static void GBAVideoGLRendererFinishFrame(struct GBAVideoRenderer* renderer);
+static void GBAVideoGLRendererGetPixels(struct GBAVideoRenderer* renderer, size_t* stride, const void** pixels);
+static void GBAVideoGLRendererPutPixels(struct GBAVideoRenderer* renderer, size_t stride, const void* pixels);
+
+static void GBAVideoGLRendererUpdateDISPCNT(struct GBAVideoGLRenderer* renderer);
+static void GBAVideoGLRendererWriteBGCNT(struct GBAVideoGLBackground* bg, uint16_t value);
+static void GBAVideoGLRendererWriteBGX_LO(struct GBAVideoGLBackground* bg, uint16_t value);
+static void GBAVideoGLRendererWriteBGX_HI(struct GBAVideoGLBackground* bg, uint16_t value);
+static void GBAVideoGLRendererWriteBGY_LO(struct GBAVideoGLBackground* bg, uint16_t value);
+static void GBAVideoGLRendererWriteBGY_HI(struct GBAVideoGLBackground* bg, uint16_t value);
+static void GBAVideoGLRendererWriteBLDCNT(struct GBAVideoGLRenderer* renderer, uint16_t value);
+
+static void GBAVideoGLRendererDrawSprite(struct GBAVideoGLRenderer* renderer, struct GBAObj* sprite, int y, int spriteY);
+static void GBAVideoGLRendererDrawBackgroundMode0(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBackground* background, int y);
+static void GBAVideoGLRendererDrawBackgroundMode2(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBackground* background, int y);
+static void GBAVideoGLRendererDrawBackgroundMode3(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBackground* background, int y);
+static void GBAVideoGLRendererDrawBackgroundMode4(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBackground* background, int y);
+static void GBAVideoGLRendererDrawBackgroundMode5(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBackground* background, int y);
+static void GBAVideoGLRendererDrawWindow(struct GBAVideoGLRenderer* renderer, int y);
+
+static void _finalizeLayers(struct GBAVideoGLRenderer* renderer, int y);
+
+#define TEST_LAYER_ENABLED(X) !renderer->disableBG[X] && glRenderer->bg[X].enabled == 4
+
+struct GBAVideoGLUniform {
+ const char* name;
+ int type;
+};
+
+static const GLchar* const _gl3Header =
+ "#version 130\n";
+
+static const char* const _vertexShader =
+ "in vec2 position;\n"
+ "uniform ivec2 loc;\n"
+ "uniform ivec2 maxPos;\n"
+ "out vec2 texCoord;\n"
+
+ "void main() {\n"
+ " vec2 local = vec2(position.x, float(position.y * loc.x + loc.y) / abs(maxPos.y));\n"
+ " gl_Position = vec4((local * 2. - 1.) * sign(maxPos), 0., 1.);\n"
+ " texCoord = local * abs(maxPos);\n"
+ "}";
+
+static const char* const _renderTile16 =
+ "vec4 renderTile(int tile, int paletteId, ivec2 localCoord) {\n"
+ " int address = charBase + tile * 16 + (localCoord.x >> 2) + (localCoord.y << 1);\n"
+ " vec4 halfrow = texelFetch(vram, ivec2(address & 255, address >> 8), 0);\n"
+ " int entry = int(halfrow[3 - (localCoord.x & 3)] * 15.9);\n"
+ " vec4 color = texelFetch(palette, ivec2(entry, paletteId), 0);\n"
+ " if (entry == 0) {\n"
+ " discard;\n"
+ " }\n"
+ " color.a = 1;\n"
+ " return color;\n"
+ "}";
+
+static const char* const _renderTile256 =
+ "vec4 renderTile(int tile, int paletteId, ivec2 localCoord) {\n"
+ " int address = charBase + tile * 32 + (localCoord.x >> 1) + (localCoord.y << 2);\n"
+ " vec4 halfrow = texelFetch(vram, ivec2(address & 255, address >> 8), 0);\n"
+ " int entry = int(halfrow[3 - 2 * (localCoord.x & 1)] * 15.9);\n"
+ " int pal2 = int(halfrow[2 - 2 * (localCoord.x & 1)] * 15.9);\n"
+ " vec4 color = texelFetch(palette, ivec2(entry, pal2 + (paletteId & 16)), 0);\n"
+ " if ((pal2 | entry) == 0) {\n"
+ " discard;\n"
+ " }\n"
+ " color.a = 1.;\n"
+ " return color;\n"
+ "}";
+
+static const struct GBAVideoGLUniform _uniformsMode0[] = {
+ { "loc", GBA_GL_VS_LOC, },
+ { "maxPos", GBA_GL_VS_MAXPOS, },
+ { "vram", GBA_GL_BG_VRAM, },
+ { "palette", GBA_GL_BG_PALETTE, },
+ { "screenBase", GBA_GL_BG_SCREENBASE, },
+ { "charBase", GBA_GL_BG_CHARBASE, },
+ { "size", GBA_GL_BG_SIZE, },
+ { "offset", GBA_GL_BG_OFFSET, },
+ { "inflags", GBA_GL_BG_INFLAGS, },
+ { 0 }
+};
+
+static const char* const _renderMode0 =
+ "in vec2 texCoord;\n"
+ "uniform sampler2D vram;\n"
+ "uniform sampler2D palette;\n"
+ "uniform int screenBase;\n"
+ "uniform int charBase;\n"
+ "uniform int size;\n"
+ "uniform ivec2 offset;\n"
+ "uniform ivec4 inflags;\n"
+ "out vec4 color;\n"
+ "out vec4 flags;\n"
+ "const vec4 flagCoeff = vec4(32., 32., 16., 16.);\n"
+
+ "vec4 renderTile(int tile, int paletteId, ivec2 localCoord);\n"
+
+ "void main() {\n"
+ " ivec2 coord = ivec2(texCoord) + offset;\n"
+ " if ((size & 1) == 1) {\n"
+ " coord.y += coord.x & 256;\n"
+ " }\n"
+ " coord.x &= 255;\n"
+ " int mapAddress = screenBase + (coord.x >> 3) + (coord.y >> 3) * 32;\n"
+ " vec4 map = texelFetch(vram, ivec2(mapAddress & 255, mapAddress >> 8), 0);\n"
+ " int tileFlags = int(map.g * 15.9);\n"
+ " if ((tileFlags & 4) == 4) {\n"
+ " coord.x ^= 7;\n"
+ " }\n"
+ " if ((tileFlags & 8) == 8) {\n"
+ " coord.y ^= 7;\n"
+ " }\n"
+ " int tile = int(map.a * 15.9) + int(map.b * 15.9) * 16 + (tileFlags & 0x3) * 256;\n"
+ " color = renderTile(tile, int(map.r * 15.9), coord & 7);\n"
+ " flags = inflags / flagCoeff;\n"
+ "}";
+
+static const char* const _fetchTileOverflow =
+ "vec4 fetchTile(ivec2 coord) {\n"
+ " int sizeAdjusted = (0x8000 << size) - 1;\n"
+ " coord &= sizeAdjusted;\n"
+ " return renderTile(coord);\n"
+ "}";
+
+static const char* const _fetchTileNoOverflow =
+ "vec4 fetchTile(ivec2 coord) {\n"
+ " int sizeAdjusted = (0x8000 << size) - 1;\n"
+ " ivec2 outerCoord = coord & ~sizeAdjusted;\n"
+ " if ((outerCoord.x | outerCoord.y) != 0) {\n"
+ " discard;\n"
+ " }\n"
+ " return renderTile(coord);\n"
+ "}";
+
+static const struct GBAVideoGLUniform _uniformsMode2[] = {
+ { "loc", GBA_GL_VS_LOC, },
+ { "maxPos", GBA_GL_VS_MAXPOS, },
+ { "vram", GBA_GL_BG_VRAM, },
+ { "palette", GBA_GL_BG_PALETTE, },
+ { "screenBase", GBA_GL_BG_SCREENBASE, },
+ { "charBase", GBA_GL_BG_CHARBASE, },
+ { "size", GBA_GL_BG_SIZE, },
+ { "inflags", GBA_GL_BG_INFLAGS, },
+ { "offset", GBA_GL_BG_OFFSET, },
+ { "transform", GBA_GL_BG_TRANSFORM, },
+ { 0 }
+};
+
+static const char* const _renderMode2 =
+ "in vec2 texCoord;\n"
+ "uniform sampler2D vram;\n"
+ "uniform sampler2D palette;\n"
+ "uniform int screenBase;\n"
+ "uniform int charBase;\n"
+ "uniform int size;\n"
+ "uniform ivec4 inflags;\n"
+ "uniform ivec2[4] offset;\n"
+ "uniform ivec2[4] transform;\n"
+ "out vec4 color;\n"
+ "out vec4 flags;\n"
+ "const vec4 flagCoeff = vec4(32., 32., 16., 16.);\n"
+ "precision highp float;\n"
+ "precision highp int;\n"
+
+ "vec4 fetchTile(ivec2 coord);\n"
+
+ "vec4 renderTile(ivec2 coord) {\n"
+ " int map = (coord.x >> 11) + (((coord.y >> 7) & 0x7F0) << size);\n"
+ " int mapAddress = screenBase + (map >> 1);\n"
+ " vec4 twomaps = texelFetch(vram, ivec2(mapAddress & 255, mapAddress >> 8), 0);\n"
+ " int tile = int(twomaps[3 - 2 * (map & 1)] * 15.9) + int(twomaps[2 - 2 * (map & 1)] * 15.9) * 16;\n"
+ " int address = charBase + tile * 32 + ((coord.x >> 9) & 3) + ((coord.y >> 6) & 0x1C);\n"
+ " vec4 halfrow = texelFetch(vram, ivec2(address & 255, address >> 8), 0);\n"
+ " int entry = int(halfrow[3 - ((coord.x >> 7) & 2)] * 15.9);\n"
+ " int pal2 = int(halfrow[2 - ((coord.x >> 7) & 2)] * 15.9);\n"
+ " vec4 color = texelFetch(palette, ivec2(entry, pal2), 0);\n"
+ " if ((pal2 | entry) == 0) {\n"
+ " discard;\n"
+ " }\n"
+ " color.a = 1.;\n"
+ " return color;\n"
+ "}\n"
+
+ "vec2 interpolate(ivec2 arr[4], float x) {\n"
+ " float x1m = 1. - x;\n"
+ " return x1m * x1m * x1m * arr[0] +"
+ " 3 * x1m * x1m * x * arr[1] +"
+ " 3 * x1m * x * x * arr[2] +"
+ " x * x * x * arr[3];\n"
+ "}\n"
+
+ "void main() {\n"
+ " float y = fract(texCoord.y);\n"
+ " float lin = 0.5 - y / ceil(y) * 0.25;\n"
+ " vec2 mixedTransform = interpolate(transform, lin);\n"
+ " vec2 mixedOffset = interpolate(offset, lin);\n"
+ " color = fetchTile(ivec2(mixedTransform * texCoord.x + mixedOffset));\n"
+ " flags = inflags / flagCoeff;\n"
+ "}";
+
+static const struct GBAVideoGLUniform _uniformsObj[] = {
+ { "loc", GBA_GL_VS_LOC, },
+ { "maxPos", GBA_GL_VS_MAXPOS, },
+ { "vram", GBA_GL_OBJ_VRAM, },
+ { "palette", GBA_GL_OBJ_PALETTE, },
+ { "charBase", GBA_GL_OBJ_CHARBASE, },
+ { "stride", GBA_GL_OBJ_STRIDE, },
+ { "localPalette", GBA_GL_OBJ_LOCALPALETTE, },
+ { "inflags", GBA_GL_OBJ_INFLAGS, },
+ { "transform", GBA_GL_OBJ_TRANSFORM, },
+ { "dims", GBA_GL_OBJ_DIMS, },
+ { "objwin", GBA_GL_OBJ_OBJWIN, },
+ { 0 }
+};
+
+static const char* const _renderObj =
+ "in vec2 texCoord;\n"
+ "uniform sampler2D vram;\n"
+ "uniform sampler2D palette;\n"
+ "uniform int charBase;\n"
+ "uniform int stride;\n"
+ "uniform int localPalette;\n"
+ "uniform ivec4 inflags;\n"
+ "uniform mat2x2 transform;\n"
+ "uniform ivec4 dims;\n"
+ "uniform vec3 objwin;\n"
+ "out vec4 color;\n"
+ "out vec4 flags;\n"
+ "out vec2 window;\n"
+ "const vec4 flagCoeff = vec4(32., 32., 16., 16.);\n"
+
+ "vec4 renderTile(int tile, int paletteId, ivec2 localCoord);\n"
+
+ "void main() {\n"
+ " ivec2 coord = ivec2(transform * (texCoord - dims.zw / 2) + dims.xy / 2);\n"
+ " if ((coord & ~(dims.xy - 1)) != ivec2(0, 0)) {\n"
+ " discard;\n"
+ " }\n"
+ " vec4 pix = renderTile((coord.x >> 3) + (coord.y >> 3) * stride, 16 + localPalette, coord & 7);\n"
+ " if (objwin.x > 0) {\n"
+ " pix.a = 0;\n"
+ " }\n"
+ " color = pix;\n"
+ " flags = inflags / flagCoeff;\n"
+ " window = objwin.yz;\n"
+ "}";
+
+static const struct GBAVideoGLUniform _uniformsFinalize[] = {
+ { "loc", GBA_GL_VS_LOC, },
+ { "maxPos", GBA_GL_VS_MAXPOS, },
+ { "scale", GBA_GL_FINALIZE_SCALE, },
+ { "layers", GBA_GL_FINALIZE_LAYERS, },
+ { "flags", GBA_GL_FINALIZE_FLAGS, },
+ { "window", GBA_GL_FINALIZE_WINDOW, },
+ { "backdrop", GBA_GL_FINALIZE_BACKDROP, },
+ { "backdropFlags", GBA_GL_FINALIZE_BACKDROPFLAGS, },
+ { 0 }
+};
+
+static const char* const _finalize =
+ "in vec2 texCoord;\n"
+ "uniform int scale;\n"
+ "uniform sampler2D layers[5];\n"
+ "uniform sampler2D flags[5];\n"
+ "uniform sampler2D window;\n"
+ "uniform vec4 backdrop;\n"
+ "uniform vec4 backdropFlags;\n"
+ "const vec4 flagCoeff = vec4(32., 32., 16., 16.);\n"
+ "out vec4 color;\n"
+
+ "void composite(vec4 pixel, ivec4 flags, inout vec4 topPixel, inout ivec4 topFlags, inout vec4 bottomPixel, inout ivec4 bottomFlags) {\n"
+ " if (pixel.a == 0) {\n"
+ " return;\n"
+ " }\n"
+ " if (flags.x >= topFlags.x) {\n"
+ " if (flags.x >= bottomFlags.x) {\n"
+ " return;\n"
+ " }\n"
+ " bottomFlags = flags;\n"
+ " bottomPixel = pixel;\n"
+ " } else {\n"
+ " bottomFlags = topFlags;\n"
+ " topFlags = flags;\n"
+ " bottomPixel = topPixel;\n"
+ " topPixel = pixel;\n"
+ " }\n"
+ "}\n"
+
+ "void main() {\n"
+ " ivec2 windowFlags = ivec2(texelFetch(window, ivec2(texCoord * scale), 0).xy * 32);\n"
+ " int layerWindow = windowFlags.x | (windowFlags.y << 4);\n"
+ " vec4 topPixel = backdrop;\n"
+ " vec4 bottomPixel = backdrop;\n"
+ " ivec4 topFlags = ivec4(backdropFlags * flagCoeff);\n"
+ " ivec4 bottomFlags = ivec4(backdropFlags * flagCoeff);\n"
+ " if ((layerWindow & 1) == 0) {\n"
+ " vec4 pix = texelFetch(layers[0], ivec2(texCoord * scale), 0);\n"
+ " ivec4 inflags = ivec4(texelFetch(flags[0], ivec2(texCoord * scale), 0) * flagCoeff);\n"
+ " composite(pix, inflags, topPixel, topFlags, bottomPixel, bottomFlags);\n"
+ " }\n"
+ " if ((layerWindow & 2) == 0) {\n"
+ " vec4 pix = texelFetch(layers[1], ivec2(texCoord * scale), 0);\n"
+ " ivec4 inflags = ivec4(texelFetch(flags[1], ivec2(texCoord * scale), 0) * flagCoeff);\n"
+ " composite(pix, inflags, topPixel, topFlags, bottomPixel, bottomFlags);\n"
+ " }\n"
+ " if ((layerWindow & 4) == 0) {\n"
+ " vec4 pix = texelFetch(layers[2], ivec2(texCoord * scale), 0);\n"
+ " ivec4 inflags = ivec4(texelFetch(flags[2], ivec2(texCoord * scale), 0) * flagCoeff);\n"
+ " composite(pix, inflags, topPixel, topFlags, bottomPixel, bottomFlags);\n"
+ " }\n"
+ " if ((layerWindow & 8) == 0) {\n"
+ " vec4 pix = texelFetch(layers[3], ivec2(texCoord * scale), 0);\n"
+ " ivec4 inflags = ivec4(texelFetch(flags[3], ivec2(texCoord * scale), 0) * flagCoeff);\n"
+ " composite(pix, inflags, topPixel, topFlags, bottomPixel, bottomFlags);\n"
+ " }\n"
+ " if ((layerWindow & 32) != 0) {\n"
+ " topFlags.y &= ~1;\n"
+ " }\n"
+ " if ((layerWindow & 16) == 0) {\n"
+ " vec4 pix = texelFetch(layers[4], ivec2(texCoord * scale), 0);\n"
+ " ivec4 inflags = ivec4(texelFetch(flags[4], ivec2(texCoord * scale), 0) * flagCoeff);\n"
+ " composite(pix, inflags, topPixel, topFlags, bottomPixel, bottomFlags);\n"
+ " }\n"
+ " if ((topFlags.y & 13) == 5) {\n"
+ " if ((bottomFlags.y & 2) == 2) {\n"
+ " topPixel *= topFlags.z / 16.;\n"
+ " topPixel += bottomPixel * bottomFlags.w / 16.;\n"
+ " }\n"
+ " } else if ((topFlags.y & 13) == 9) {\n"
+ " topPixel += (1. - topPixel) * topFlags.z / 16.;\n"
+ " } else if ((topFlags.y & 13) == 13) {\n"
+ " topPixel -= topPixel * topFlags.z / 16.;\n"
+ " }\n"
+ " color = topPixel;\n"
+ "}";
+
+static const GLint _vertices[] = {
+ 0, 0,
+ 0, 1,
+ 1, 1,
+ 1, 0,
+};
+
+void GBAVideoGLRendererCreate(struct GBAVideoGLRenderer* renderer) {
+ renderer->d.init = GBAVideoGLRendererInit;
+ renderer->d.reset = GBAVideoGLRendererReset;
+ renderer->d.deinit = GBAVideoGLRendererDeinit;
+ renderer->d.writeVideoRegister = GBAVideoGLRendererWriteVideoRegister;
+ renderer->d.writeVRAM = GBAVideoGLRendererWriteVRAM;
+ renderer->d.writeOAM = GBAVideoGLRendererWriteOAM;
+ renderer->d.writePalette = GBAVideoGLRendererWritePalette;
+ renderer->d.drawScanline = GBAVideoGLRendererDrawScanline;
+ renderer->d.finishFrame = GBAVideoGLRendererFinishFrame;
+ renderer->d.getPixels = GBAVideoGLRendererGetPixels;
+ renderer->d.putPixels = GBAVideoGLRendererPutPixels;
+
+ renderer->d.disableBG[0] = false;
+ renderer->d.disableBG[1] = false;
+ renderer->d.disableBG[2] = false;
+ renderer->d.disableBG[3] = false;
+ renderer->d.disableOBJ = false;
+
+ renderer->scale = 1;
+}
+
+void _compileShader(struct GBAVideoGLRenderer* glRenderer, struct GBAVideoGLShader* shader, const char** shaderBuffer, int shaderBufferLines, GLuint vs, const struct GBAVideoGLUniform* uniforms, char* log) {
+ GLuint program = glCreateProgram();
+ shader->program = program;
+
+ GLuint fs = glCreateShader(GL_FRAGMENT_SHADER);
+ glAttachShader(program, vs);
+ glAttachShader(program, fs);
+ glShaderSource(fs, shaderBufferLines, shaderBuffer, 0);
+ glCompileShader(fs);
+ glGetShaderInfoLog(fs, 1024, 0, log);
+ if (log[0]) {
+ mLOG(GBA_VIDEO, ERROR, "Fragment shader compilation failure: %s", log);
+ }
+ glLinkProgram(program);
+ glGetProgramInfoLog(program, 1024, 0, log);
+ if (log[0]) {
+ mLOG(GBA_VIDEO, ERROR, "Program link failure: %s", log);
+ }
+ glDeleteShader(fs);
+#ifndef BUILD_GLES3
+ glBindFragDataLocation(program, 0, "color");
+ glBindFragDataLocation(program, 1, "flags");
+#endif
+
+ glGenVertexArrays(1, &shader->vao);
+ glBindVertexArray(shader->vao);
+ glBindBuffer(GL_ARRAY_BUFFER, glRenderer->vbo);
+ GLuint positionLocation = glGetAttribLocation(program, "position");
+ glVertexAttribPointer(positionLocation, 2, GL_INT, GL_FALSE, 0, NULL);
+
+ size_t i;
+ for (i = 0; uniforms[i].name; ++i) {
+ shader->uniforms[uniforms[i].type] = glGetUniformLocation(program, uniforms[i].name);
+ }
+}
+
+static void _initFramebufferTexture(GLuint tex, GLenum format, GLenum attachment, int scale) {
+ glBindTexture(GL_TEXTURE_2D, tex);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexImage2D(GL_TEXTURE_2D, 0, format, GBA_VIDEO_HORIZONTAL_PIXELS * scale, GBA_VIDEO_VERTICAL_PIXELS * scale, 0, format, GL_UNSIGNED_BYTE, 0);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, GL_TEXTURE_2D, tex, 0);
+}
+
+void GBAVideoGLRendererInit(struct GBAVideoRenderer* renderer) {
+ struct GBAVideoGLRenderer* glRenderer = (struct GBAVideoGLRenderer*) renderer;
+ glRenderer->temporaryBuffer = NULL;
+
+ glGenFramebuffers(GBA_GL_FBO_MAX, glRenderer->fbo);
+ glGenTextures(GBA_GL_TEX_MAX, glRenderer->layers);
+
+ glGenTextures(1, &glRenderer->paletteTex);
+ glBindTexture(GL_TEXTURE_2D, glRenderer->paletteTex);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+ glGenTextures(1, &glRenderer->vramTex);
+ glBindTexture(GL_TEXTURE_2D, glRenderer->vramTex);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA4, 256, 192, 0, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, 0);
+
+ glBindFramebuffer(GL_FRAMEBUFFER, glRenderer->fbo[GBA_GL_FBO_OBJ]);
+ _initFramebufferTexture(glRenderer->layers[GBA_GL_TEX_OBJ_COLOR], GL_RGBA, GL_COLOR_ATTACHMENT0, glRenderer->scale);
+ _initFramebufferTexture(glRenderer->layers[GBA_GL_TEX_OBJ_FLAGS], GL_RGBA, GL_COLOR_ATTACHMENT1, glRenderer->scale);
+
+ glBindFramebuffer(GL_FRAMEBUFFER, glRenderer->fbo[GBA_GL_FBO_WINDOW]);
+ _initFramebufferTexture(glRenderer->layers[GBA_GL_TEX_WINDOW], GL_RG, GL_COLOR_ATTACHMENT0, glRenderer->scale);
+
+ glBindFramebuffer(GL_FRAMEBUFFER, glRenderer->fbo[GBA_GL_FBO_OUTPUT]);
+ _initFramebufferTexture(glRenderer->outputTex, GL_RGB, GL_COLOR_ATTACHMENT0, glRenderer->scale);
+
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+
+ glGenBuffers(1, &glRenderer->vbo);
+ glBindBuffer(GL_ARRAY_BUFFER, glRenderer->vbo);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(_vertices), _vertices, GL_STATIC_DRAW);
+
+ int i;
+ for (i = 0; i < 4; ++i) {
+ struct GBAVideoGLBackground* bg = &glRenderer->bg[i];
+ bg->index = i;
+ bg->enabled = 0;
+ bg->priority = 0;
+ bg->charBase = 0;
+ bg->mosaic = 0;
+ bg->multipalette = 0;
+ bg->screenBase = 0;
+ bg->overflow = 0;
+ bg->size = 0;
+ bg->target1 = 0;
+ bg->target2 = 0;
+ bg->x = 0;
+ bg->y = 0;
+ bg->refx = 0;
+ bg->refy = 0;
+ bg->affine[0].dx = 256;
+ bg->affine[0].dmx = 0;
+ bg->affine[0].dy = 0;
+ bg->affine[0].dmy = 256;
+ bg->affine[0].sx = 0;
+ bg->affine[0].sy = 0;
+ glGenFramebuffers(1, &bg->fbo);
+ glGenTextures(1, &bg->tex);
+ glGenTextures(1, &bg->flags);
+ glBindFramebuffer(GL_FRAMEBUFFER, bg->fbo);
+ _initFramebufferTexture(bg->tex, GL_RGBA, GL_COLOR_ATTACHMENT0, glRenderer->scale);
+ _initFramebufferTexture(bg->flags, GL_RGBA, GL_COLOR_ATTACHMENT1, glRenderer->scale);
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ }
+
+ char log[1024];
+ const GLchar* shaderBuffer[8];
+ shaderBuffer[0] = _gl3Header;
+
+ GLuint vs = glCreateShader(GL_VERTEX_SHADER);
+ shaderBuffer[1] = _vertexShader;
+ glShaderSource(vs, 2, shaderBuffer, 0);
+ glCompileShader(vs);
+ glGetShaderInfoLog(vs, 1024, 0, log);
+ if (log[0]) {
+ mLOG(GBA_VIDEO, ERROR, "Vertex shader compilation failure: %s", log);
+ }
+
+ shaderBuffer[1] = _renderMode0;
+
+ shaderBuffer[2] = _renderTile16;
+ _compileShader(glRenderer, &glRenderer->bgShader[0], shaderBuffer, 3, vs, _uniformsMode0, log);
+
+ shaderBuffer[2] = _renderTile256;
+ _compileShader(glRenderer, &glRenderer->bgShader[1], shaderBuffer, 3, vs, _uniformsMode0, log);
+
+ shaderBuffer[1] = _renderMode2;
+
+ shaderBuffer[2] = _fetchTileOverflow;
+ _compileShader(glRenderer, &glRenderer->bgShader[2], shaderBuffer, 3, vs, _uniformsMode2, log);
+
+ shaderBuffer[2] = _fetchTileNoOverflow;
+ _compileShader(glRenderer, &glRenderer->bgShader[3], shaderBuffer, 3, vs, _uniformsMode2, log);
+
+ shaderBuffer[1] = _renderObj;
+
+ shaderBuffer[2] = _renderTile16;
+ _compileShader(glRenderer, &glRenderer->objShader[0], shaderBuffer, 3, vs, _uniformsObj, log);
+#ifndef BUILD_GLES3
+ glBindFragDataLocation(glRenderer->objShader[0].program, 2, "window");
+#endif
+
+ shaderBuffer[2] = _renderTile256;
+ _compileShader(glRenderer, &glRenderer->objShader[1], shaderBuffer, 3, vs, _uniformsObj, log);
+#ifndef BUILD_GLES3
+ glBindFragDataLocation(glRenderer->objShader[1].program, 2, "window");
+#endif
+
+ shaderBuffer[1] = _finalize;
+ _compileShader(glRenderer, &glRenderer->finalizeShader, shaderBuffer, 2, vs, _uniformsFinalize, log);
+
+ glBindVertexArray(0);
+ glDeleteShader(vs);
+
+ GBAVideoGLRendererReset(renderer);
+}
+
+void GBAVideoGLRendererDeinit(struct GBAVideoRenderer* renderer) {
+ struct GBAVideoGLRenderer* glRenderer = (struct GBAVideoGLRenderer*) renderer;
+ if (glRenderer->temporaryBuffer) {
+ mappedMemoryFree(glRenderer->temporaryBuffer, GBA_VIDEO_HORIZONTAL_PIXELS * GBA_VIDEO_VERTICAL_PIXELS * glRenderer->scale * glRenderer->scale);
+ }
+ glDeleteFramebuffers(GBA_GL_FBO_MAX, glRenderer->fbo);
+ glDeleteTextures(GBA_GL_TEX_MAX, glRenderer->layers);
+ glDeleteTextures(1, &glRenderer->paletteTex);
+ glDeleteTextures(1, &glRenderer->vramTex);
+}
+
+void GBAVideoGLRendererReset(struct GBAVideoRenderer* renderer) {
+ struct GBAVideoGLRenderer* glRenderer = (struct GBAVideoGLRenderer*) renderer;
+
+ glRenderer->paletteDirty = true;
+ glRenderer->vramDirty = 0xFFFFFF;
+ glRenderer->firstAffine = -1;
+}
+
+void GBAVideoGLRendererWriteVRAM(struct GBAVideoRenderer* renderer, uint32_t address) {
+ struct GBAVideoGLRenderer* glRenderer = (struct GBAVideoGLRenderer*) renderer;
+ glRenderer->vramDirty |= 1 << (address >> 12);
+}
+
+void GBAVideoGLRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam) {
+ UNUSED(oam);
+ struct GBAVideoGLRenderer* glRenderer = (struct GBAVideoGLRenderer*) renderer;
+ glRenderer->oamDirty = true;
+}
+
+void GBAVideoGLRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) {
+ struct GBAVideoGLRenderer* glRenderer = (struct GBAVideoGLRenderer*) renderer;
+#ifdef BUILD_GLES3
+ glRenderer->shadowPalette[address >> 1] = (value & 0x3F) | ((value & 0x7FE0) << 1);
+#else
+ UNUSED(address);
+ UNUSED(value);
+#endif
+ glRenderer->paletteDirty = true;
+}
+
+uint16_t GBAVideoGLRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) {
+ struct GBAVideoGLRenderer* glRenderer = (struct GBAVideoGLRenderer*) renderer;
+ if (renderer->cache) {
+ GBAVideoCacheWriteVideoRegister(renderer->cache, address, value);
+ }
+
+ switch (address) {
+ case REG_DISPCNT:
+ value &= 0xFFF7;
+ glRenderer->dispcnt = value;
+ GBAVideoGLRendererUpdateDISPCNT(glRenderer);
+ break;
+ case REG_BG0CNT:
+ value &= 0xDFFF;
+ GBAVideoGLRendererWriteBGCNT(&glRenderer->bg[0], value);
+ break;
+ case REG_BG1CNT:
+ value &= 0xDFFF;
+ GBAVideoGLRendererWriteBGCNT(&glRenderer->bg[1], value);
+ break;
+ case REG_BG2CNT:
+ value &= 0xFFFF;
+ GBAVideoGLRendererWriteBGCNT(&glRenderer->bg[2], value);
+ break;
+ case REG_BG3CNT:
+ value &= 0xFFFF;
+ GBAVideoGLRendererWriteBGCNT(&glRenderer->bg[3], value);
+ break;
+ case REG_BG0HOFS:
+ value &= 0x01FF;
+ glRenderer->bg[0].x = value;
+ break;
+ case REG_BG0VOFS:
+ value &= 0x01FF;
+ glRenderer->bg[0].y = value;
+ break;
+ case REG_BG1HOFS:
+ value &= 0x01FF;
+ glRenderer->bg[1].x = value;
+ break;
+ case REG_BG1VOFS:
+ value &= 0x01FF;
+ glRenderer->bg[1].y = value;
+ break;
+ case REG_BG2HOFS:
+ value &= 0x01FF;
+ glRenderer->bg[2].x = value;
+ break;
+ case REG_BG2VOFS:
+ value &= 0x01FF;
+ glRenderer->bg[2].y = value;
+ break;
+ case REG_BG3HOFS:
+ value &= 0x01FF;
+ glRenderer->bg[3].x = value;
+ break;
+ case REG_BG3VOFS:
+ value &= 0x01FF;
+ glRenderer->bg[3].y = value;
+ break;
+ case REG_BG2PA:
+ glRenderer->bg[2].affine[0].dx = value;
+ break;
+ case REG_BG2PB:
+ glRenderer->bg[2].affine[0].dmx = value;
+ break;
+ case REG_BG2PC:
+ glRenderer->bg[2].affine[0].dy = value;
+ break;
+ case REG_BG2PD:
+ glRenderer->bg[2].affine[0].dmy = value;
+ break;
+ case REG_BG2X_LO:
+ GBAVideoGLRendererWriteBGX_LO(&glRenderer->bg[2], value);
+ break;
+ case REG_BG2X_HI:
+ GBAVideoGLRendererWriteBGX_HI(&glRenderer->bg[2], value);
+ break;
+ case REG_BG2Y_LO:
+ GBAVideoGLRendererWriteBGY_LO(&glRenderer->bg[2], value);
+ break;
+ case REG_BG2Y_HI:
+ GBAVideoGLRendererWriteBGY_HI(&glRenderer->bg[2], value);
+ break;
+ case REG_BG3PA:
+ glRenderer->bg[3].affine[0].dx = value;
+ break;
+ case REG_BG3PB:
+ glRenderer->bg[3].affine[0].dmx = value;
+ break;
+ case REG_BG3PC:
+ glRenderer->bg[3].affine[0].dy = value;
+ break;
+ case REG_BG3PD:
+ glRenderer->bg[3].affine[0].dmy = value;
+ break;
+ case REG_BG3X_LO:
+ GBAVideoGLRendererWriteBGX_LO(&glRenderer->bg[3], value);
+ break;
+ case REG_BG3X_HI:
+ GBAVideoGLRendererWriteBGX_HI(&glRenderer->bg[3], value);
+ break;
+ case REG_BG3Y_LO:
+ GBAVideoGLRendererWriteBGY_LO(&glRenderer->bg[3], value);
+ break;
+ case REG_BG3Y_HI:
+ GBAVideoGLRendererWriteBGY_HI(&glRenderer->bg[3], value);
+ break;
+ case REG_BLDCNT:
+ GBAVideoGLRendererWriteBLDCNT(glRenderer, value);
+ value &= 0x3FFF;
+ break;
+ case REG_BLDALPHA:
+ glRenderer->blda = value & 0x1F;
+ if (glRenderer->blda > 0x10) {
+ glRenderer->blda = 0x10;
+ }
+ glRenderer->bldb = (value >> 8) & 0x1F;
+ if (glRenderer->bldb > 0x10) {
+ glRenderer->bldb = 0x10;
+ }
+ value &= 0x1F1F;
+ break;
+ case REG_BLDY:
+ value &= 0x1F;
+ if (value > 0x10) {
+ value = 0x10;
+ }
+ glRenderer->bldy = value;
+ break;
+ case REG_WIN0H:
+ glRenderer->winN[0].h.end = value;
+ glRenderer->winN[0].h.start = value >> 8;
+ if (glRenderer->winN[0].h.start > GBA_VIDEO_HORIZONTAL_PIXELS && glRenderer->winN[0].h.start > glRenderer->winN[0].h.end) {
+ glRenderer->winN[0].h.start = 0;
+ }
+ if (glRenderer->winN[0].h.end > GBA_VIDEO_HORIZONTAL_PIXELS) {
+ glRenderer->winN[0].h.end = GBA_VIDEO_HORIZONTAL_PIXELS;
+ if (glRenderer->winN[0].h.start > GBA_VIDEO_HORIZONTAL_PIXELS) {
+ glRenderer->winN[0].h.start = GBA_VIDEO_HORIZONTAL_PIXELS;
+ }
+ }
+ break;
+ case REG_WIN1H:
+ glRenderer->winN[1].h.end = value;
+ glRenderer->winN[1].h.start = value >> 8;
+ if (glRenderer->winN[1].h.start > GBA_VIDEO_HORIZONTAL_PIXELS && glRenderer->winN[1].h.start > glRenderer->winN[1].h.end) {
+ glRenderer->winN[1].h.start = 0;
+ }
+ if (glRenderer->winN[1].h.end > GBA_VIDEO_HORIZONTAL_PIXELS) {
+ glRenderer->winN[1].h.end = GBA_VIDEO_HORIZONTAL_PIXELS;
+ if (glRenderer->winN[1].h.start > GBA_VIDEO_HORIZONTAL_PIXELS) {
+ glRenderer->winN[1].h.start = GBA_VIDEO_HORIZONTAL_PIXELS;
+ }
+ }
+ break;
+ case REG_WIN0V:
+ glRenderer->winN[0].v.end = value;
+ glRenderer->winN[0].v.start = value >> 8;
+ if (glRenderer->winN[0].v.start > GBA_VIDEO_VERTICAL_PIXELS && glRenderer->winN[0].v.start > glRenderer->winN[0].v.end) {
+ glRenderer->winN[0].v.start = 0;
+ }
+ if (glRenderer->winN[0].v.end > GBA_VIDEO_VERTICAL_PIXELS) {
+ glRenderer->winN[0].v.end = GBA_VIDEO_VERTICAL_PIXELS;
+ if (glRenderer->winN[0].v.start > GBA_VIDEO_VERTICAL_PIXELS) {
+ glRenderer->winN[0].v.start = GBA_VIDEO_VERTICAL_PIXELS;
+ }
+ }
+ break;
+ case REG_WIN1V:
+ glRenderer->winN[1].v.end = value;
+ glRenderer->winN[1].v.start = value >> 8;
+ if (glRenderer->winN[1].v.start > GBA_VIDEO_VERTICAL_PIXELS && glRenderer->winN[1].v.start > glRenderer->winN[1].v.end) {
+ glRenderer->winN[1].v.start = 0;
+ }
+ if (glRenderer->winN[1].v.end > GBA_VIDEO_VERTICAL_PIXELS) {
+ glRenderer->winN[1].v.end = GBA_VIDEO_VERTICAL_PIXELS;
+ if (glRenderer->winN[1].v.start > GBA_VIDEO_VERTICAL_PIXELS) {
+ glRenderer->winN[1].v.start = GBA_VIDEO_VERTICAL_PIXELS;
+ }
+ }
+ break;
+ case REG_WININ:
+ value &= 0x3F3F;
+ glRenderer->winN[0].control = value;
+ glRenderer->winN[1].control = value >> 8;
+ break;
+ case REG_WINOUT:
+ value &= 0x3F3F;
+ glRenderer->winout = value;
+ glRenderer->objwin = value >> 8;
+ break;
+ case REG_MOSAIC:
+ glRenderer->mosaic = value;
+ break;
+ default:
+ break;
+ }
+ return value;
+}
+
+void GBAVideoGLRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) {
+ struct GBAVideoGLRenderer* glRenderer = (struct GBAVideoGLRenderer*) renderer;
+ if (glRenderer->paletteDirty) {
+ glBindTexture(GL_TEXTURE_2D, glRenderer->paletteTex);
+#ifdef BUILD_GLES3
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB565, 16, 32, 0, GL_RGBA, GL_UNSIGNED_SHORT_5_6_5, glRenderer->shadowPalette);
+#else
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB5_A1, 16, 32, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, glRenderer->d.palette);
+#endif
+ glRenderer->paletteDirty = false;
+ }
+ int i;
+ for (i = 0; i < 16; ++i) {
+ if (!(glRenderer->vramDirty & (1 << i))) {
+ continue;
+ }
+ // TODO: PBOs
+ glBindTexture(GL_TEXTURE_2D, glRenderer->vramTex);
+ glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 8 * i, 256, 8, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, &glRenderer->d.vramBG[2048 * i]);
+ }
+ for (i = 16; i < 24; ++i) {
+ if (!(glRenderer->vramDirty & (1 << i))) {
+ continue;
+ }
+ // TODO: PBOs
+ glBindTexture(GL_TEXTURE_2D, glRenderer->vramTex);
+ glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 8 * i, 256, 8, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, &glRenderer->d.vramOBJ[2048 * (i - 16)]);
+ }
+ glRenderer->vramDirty = 0;
+
+ if (y == 0) {
+ glDisable(GL_SCISSOR_TEST);
+ glClearColor(0, 0, 0, 0);
+ glBindFramebuffer(GL_FRAMEBUFFER, glRenderer->fbo[GBA_GL_FBO_OBJ]);
+ glDrawBuffers(2, (GLenum[]) { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 });
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ for (i = 0; i < 4; ++i) {
+ glBindFramebuffer(GL_FRAMEBUFFER, glRenderer->bg[i].fbo);
+ glDrawBuffers(2, (GLenum[]) { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 });
+ glClear(GL_COLOR_BUFFER_BIT);
+ }
+ glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 });
+ }
+ glEnable(GL_SCISSOR_TEST);
+
+ if (GBARegisterDISPCNTGetMode(glRenderer->dispcnt) != 0) {
+ if (glRenderer->firstAffine < 0) {
+ memcpy(&glRenderer->bg[2].affine[3], &glRenderer->bg[2].affine[0], sizeof(struct GBAVideoGLAffine));
+ memcpy(&glRenderer->bg[3].affine[3], &glRenderer->bg[3].affine[0], sizeof(struct GBAVideoGLAffine));
+ memcpy(&glRenderer->bg[2].affine[2], &glRenderer->bg[2].affine[0], sizeof(struct GBAVideoGLAffine));
+ memcpy(&glRenderer->bg[3].affine[2], &glRenderer->bg[3].affine[0], sizeof(struct GBAVideoGLAffine));
+ memcpy(&glRenderer->bg[2].affine[1], &glRenderer->bg[2].affine[0], sizeof(struct GBAVideoGLAffine));
+ memcpy(&glRenderer->bg[3].affine[1], &glRenderer->bg[3].affine[0], sizeof(struct GBAVideoGLAffine));
+ glRenderer->firstAffine = y;
+ } else if (y - glRenderer->firstAffine == 1) {
+ memcpy(&glRenderer->bg[2].affine[1], &glRenderer->bg[2].affine[0], sizeof(struct GBAVideoGLAffine));
+ memcpy(&glRenderer->bg[3].affine[1], &glRenderer->bg[3].affine[0], sizeof(struct GBAVideoGLAffine));
+ }
+ } else {
+ glRenderer->firstAffine = -1;
+ }
+
+ GBAVideoGLRendererDrawWindow(glRenderer, y);
+ if (GBARegisterDISPCNTIsObjEnable(glRenderer->dispcnt) && !glRenderer->d.disableOBJ) {
+ if (glRenderer->oamDirty) {
+ glRenderer->oamMax = GBAVideoRendererCleanOAM(glRenderer->d.oam->obj, glRenderer->sprites, 0, GBA_VIDEO_VERTICAL_PIXELS, false);
+ glRenderer->oamDirty = false;
+ }
+ int i;
+ for (i = glRenderer->oamMax; i--;) {
+ struct GBAVideoRendererSprite* sprite = &glRenderer->sprites[i];
+ if ((y < sprite->y && (sprite->endY - 256 < 0 || y >= sprite->endY - 256)) || y >= sprite->endY) {
+ continue;
+ }
+
+ GBAVideoGLRendererDrawSprite(glRenderer, &sprite->obj, y, sprite->y);
+ }
+ }
+
+ if (TEST_LAYER_ENABLED(0) && GBARegisterDISPCNTGetMode(glRenderer->dispcnt) < 2) {
+ GBAVideoGLRendererDrawBackgroundMode0(glRenderer, &glRenderer->bg[0], y);
+ }
+ if (TEST_LAYER_ENABLED(1) && GBARegisterDISPCNTGetMode(glRenderer->dispcnt) < 2) {
+ GBAVideoGLRendererDrawBackgroundMode0(glRenderer, &glRenderer->bg[1], y);
+ }
+ if (TEST_LAYER_ENABLED(2)) {
+ switch (GBARegisterDISPCNTGetMode(glRenderer->dispcnt)) {
+ case 0:
+ GBAVideoGLRendererDrawBackgroundMode0(glRenderer, &glRenderer->bg[2], y);
+ break;
+ case 1:
+ case 2:
+ GBAVideoGLRendererDrawBackgroundMode2(glRenderer, &glRenderer->bg[2], y);
+ break;
+ case 3:
+ //GBAVideoGLRendererDrawBackgroundMode3(glRenderer, &glRenderer->bg[2], y);
+ break;
+ case 4:
+ //GBAVideoGLRendererDrawBackgroundMode4(glRenderer, &glRenderer->bg[2], y);
+ break;
+ case 5:
+ //GBAVideoGLRendererDrawBackgroundMode5(glRenderer, &glRenderer->bg[2], y);
+ break;
+ }
+ }
+ if (TEST_LAYER_ENABLED(3)) {
+ switch (GBARegisterDISPCNTGetMode(glRenderer->dispcnt)) {
+ case 0:
+ GBAVideoGLRendererDrawBackgroundMode0(glRenderer, &glRenderer->bg[3], y);
+ break;
+ case 2:
+ GBAVideoGLRendererDrawBackgroundMode2(glRenderer, &glRenderer->bg[3], y);
+ break;
+ }
+ }
+ _finalizeLayers(glRenderer, y);
+
+ if (GBARegisterDISPCNTGetMode(glRenderer->dispcnt) != 0) {
+ memcpy(&glRenderer->bg[2].affine[3], &glRenderer->bg[2].affine[2], sizeof(struct GBAVideoGLAffine));
+ memcpy(&glRenderer->bg[3].affine[3], &glRenderer->bg[3].affine[2], sizeof(struct GBAVideoGLAffine));
+ memcpy(&glRenderer->bg[2].affine[2], &glRenderer->bg[2].affine[1], sizeof(struct GBAVideoGLAffine));
+ memcpy(&glRenderer->bg[3].affine[2], &glRenderer->bg[3].affine[1], sizeof(struct GBAVideoGLAffine));
+ memcpy(&glRenderer->bg[2].affine[1], &glRenderer->bg[2].affine[0], sizeof(struct GBAVideoGLAffine));
+ memcpy(&glRenderer->bg[3].affine[1], &glRenderer->bg[3].affine[0], sizeof(struct GBAVideoGLAffine));
+
+ glRenderer->bg[2].affine[0].sx += glRenderer->bg[2].affine[0].dmx;
+ glRenderer->bg[2].affine[0].sy += glRenderer->bg[2].affine[0].dmy;
+ glRenderer->bg[3].affine[0].sx += glRenderer->bg[3].affine[0].dmx;
+ glRenderer->bg[3].affine[0].sy += glRenderer->bg[3].affine[0].dmy;
+ }
+}
+
+void GBAVideoGLRendererFinishFrame(struct GBAVideoRenderer* renderer) {
+ struct GBAVideoGLRenderer* glRenderer = (struct GBAVideoGLRenderer*) renderer;
+ glRenderer->firstAffine = -1;
+ glRenderer->bg[2].affine[0].sx = glRenderer->bg[2].refx;
+ glRenderer->bg[2].affine[0].sy = glRenderer->bg[2].refy;
+ glRenderer->bg[3].affine[0].sx = glRenderer->bg[3].refx;
+ glRenderer->bg[3].affine[0].sy = glRenderer->bg[3].refy;
+ glFlush();
+}
+
+void GBAVideoGLRendererGetPixels(struct GBAVideoRenderer* renderer, size_t* stride, const void** pixels) {
+ struct GBAVideoGLRenderer* glRenderer = (struct GBAVideoGLRenderer*) renderer;
+ *stride = GBA_VIDEO_HORIZONTAL_PIXELS * glRenderer->scale;
+ if (!glRenderer->temporaryBuffer) {
+ glRenderer->temporaryBuffer = anonymousMemoryMap(GBA_VIDEO_HORIZONTAL_PIXELS * GBA_VIDEO_VERTICAL_PIXELS * glRenderer->scale * glRenderer->scale * BYTES_PER_PIXEL);
+ }
+ glFinish();
+ glBindFramebuffer(GL_FRAMEBUFFER, glRenderer->fbo[GBA_GL_FBO_OUTPUT]);
+ glPixelStorei(GL_PACK_ROW_LENGTH, GBA_VIDEO_HORIZONTAL_PIXELS * glRenderer->scale);
+ glPixelStorei(GL_PACK_ALIGNMENT, 1);
+ glReadPixels(0, 0, GBA_VIDEO_HORIZONTAL_PIXELS * glRenderer->scale, GBA_VIDEO_VERTICAL_PIXELS * glRenderer->scale, GL_RGBA, GL_UNSIGNED_BYTE, (void*) glRenderer->temporaryBuffer);
+ *pixels = glRenderer->temporaryBuffer;
+}
+
+void GBAVideoGLRendererPutPixels(struct GBAVideoRenderer* renderer, size_t stride, const void* pixels) {
+
+}
+
+static void _enableBg(struct GBAVideoGLRenderer* renderer, int bg, bool active) {
+ int wasActive = renderer->bg[bg].enabled;
+ if (!active) {
+ renderer->bg[bg].enabled = 0;
+ } else if (!wasActive && active) {
+ /*if (renderer->nextY == 0 || GBARegisterDISPCNTGetMode(renderer->dispcnt) > 2) {
+ // TODO: Investigate in more depth how switching background works in different modes
+ renderer->bg[bg].enabled = 4;
+ } else {
+ renderer->bg[bg].enabled = 1;
+ }*/
+ renderer->bg[bg].enabled = 4;
+ }
+}
+
+static void GBAVideoGLRendererUpdateDISPCNT(struct GBAVideoGLRenderer* renderer) {
+ _enableBg(renderer, 0, GBARegisterDISPCNTGetBg0Enable(renderer->dispcnt));
+ _enableBg(renderer, 1, GBARegisterDISPCNTGetBg1Enable(renderer->dispcnt));
+ _enableBg(renderer, 2, GBARegisterDISPCNTGetBg2Enable(renderer->dispcnt));
+ _enableBg(renderer, 3, GBARegisterDISPCNTGetBg3Enable(renderer->dispcnt));
+}
+
+static void GBAVideoGLRendererWriteBGCNT(struct GBAVideoGLBackground* bg, uint16_t value) {
+ bg->priority = GBARegisterBGCNTGetPriority(value);
+ bg->charBase = GBARegisterBGCNTGetCharBase(value) << 13;
+ bg->mosaic = GBARegisterBGCNTGetMosaic(value);
+ bg->multipalette = GBARegisterBGCNTGet256Color(value);
+ bg->screenBase = GBARegisterBGCNTGetScreenBase(value) << 10;
+ bg->overflow = GBARegisterBGCNTGetOverflow(value);
+ bg->size = GBARegisterBGCNTGetSize(value);
+}
+
+static void GBAVideoGLRendererWriteBGX_LO(struct GBAVideoGLBackground* bg, uint16_t value) {
+ bg->refx = (bg->refx & 0xFFFF0000) | value;
+ bg->affine[0].sx = bg->refx;
+}
+
+static void GBAVideoGLRendererWriteBGX_HI(struct GBAVideoGLBackground* bg, uint16_t value) {
+ bg->refx = (bg->refx & 0x0000FFFF) | (value << 16);
+ bg->refx <<= 4;
+ bg->refx >>= 4;
+ bg->affine[0].sx = bg->refx;
+}
+
+static void GBAVideoGLRendererWriteBGY_LO(struct GBAVideoGLBackground* bg, uint16_t value) {
+ bg->refy = (bg->refy & 0xFFFF0000) | value;
+ bg->affine[0].sy = bg->refy;
+}
+
+static void GBAVideoGLRendererWriteBGY_HI(struct GBAVideoGLBackground* bg, uint16_t value) {
+ bg->refy = (bg->refy & 0x0000FFFF) | (value << 16);
+ bg->refy <<= 4;
+ bg->refy >>= 4;
+ bg->affine[0].sy = bg->refy;
+}
+
+static void GBAVideoGLRendererWriteBLDCNT(struct GBAVideoGLRenderer* renderer, uint16_t value) {
+ renderer->bg[0].target1 = GBARegisterBLDCNTGetTarget1Bg0(value);
+ renderer->bg[1].target1 = GBARegisterBLDCNTGetTarget1Bg1(value);
+ renderer->bg[2].target1 = GBARegisterBLDCNTGetTarget1Bg2(value);
+ renderer->bg[3].target1 = GBARegisterBLDCNTGetTarget1Bg3(value);
+ renderer->bg[0].target2 = GBARegisterBLDCNTGetTarget2Bg0(value);
+ renderer->bg[1].target2 = GBARegisterBLDCNTGetTarget2Bg1(value);
+ renderer->bg[2].target2 = GBARegisterBLDCNTGetTarget2Bg2(value);
+ renderer->bg[3].target2 = GBARegisterBLDCNTGetTarget2Bg3(value);
+
+ renderer->blendEffect = GBARegisterBLDCNTGetEffect(value);
+ renderer->target1Obj = GBARegisterBLDCNTGetTarget1Obj(value);
+ renderer->target1Bd = GBARegisterBLDCNTGetTarget1Bd(value);
+ renderer->target2Obj = GBARegisterBLDCNTGetTarget2Obj(value);
+ renderer->target2Bd = GBARegisterBLDCNTGetTarget2Bd(value);
+}
+
+void _finalizeLayers(struct GBAVideoGLRenderer* renderer, int y) {
+ const GLuint* uniforms = renderer->finalizeShader.uniforms;
+ glBindFramebuffer(GL_FRAMEBUFFER, renderer->fbo[GBA_GL_FBO_OUTPUT]);
+ glViewport(0, 0, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, GBA_VIDEO_VERTICAL_PIXELS * renderer->scale);
+ glScissor(0, y * renderer->scale, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, renderer->scale);
+ glUseProgram(renderer->finalizeShader.program);
+ glBindVertexArray(renderer->finalizeShader.vao);
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, renderer->layers[GBA_GL_TEX_WINDOW]);
+ glActiveTexture(GL_TEXTURE0 + 1);
+ glBindTexture(GL_TEXTURE_2D, renderer->layers[GBA_GL_TEX_OBJ_COLOR]);
+ glActiveTexture(GL_TEXTURE0 + 2);
+ glBindTexture(GL_TEXTURE_2D, renderer->layers[GBA_GL_TEX_OBJ_FLAGS]);
+ glActiveTexture(GL_TEXTURE0 + 3);
+ glBindTexture(GL_TEXTURE_2D, renderer->bg[0].tex);
+ glActiveTexture(GL_TEXTURE0 + 4);
+ glBindTexture(GL_TEXTURE_2D, renderer->bg[0].flags);
+ glActiveTexture(GL_TEXTURE0 + 5);
+ glBindTexture(GL_TEXTURE_2D, renderer->bg[1].tex);
+ glActiveTexture(GL_TEXTURE0 + 6);
+ glBindTexture(GL_TEXTURE_2D, renderer->bg[1].flags);
+ glActiveTexture(GL_TEXTURE0 + 7);
+ glBindTexture(GL_TEXTURE_2D, renderer->bg[2].tex);
+ glActiveTexture(GL_TEXTURE0 + 8);
+ glBindTexture(GL_TEXTURE_2D, renderer->bg[2].flags);
+ glActiveTexture(GL_TEXTURE0 + 9);
+ glBindTexture(GL_TEXTURE_2D, renderer->bg[3].tex);
+ glActiveTexture(GL_TEXTURE0 + 10);
+ glBindTexture(GL_TEXTURE_2D, renderer->bg[3].flags);
+
+ uint32_t backdrop = M_RGB5_TO_RGB8(renderer->d.palette[0]);
+ glUniform2i(uniforms[GBA_GL_VS_LOC], 1, y);
+ glUniform2i(uniforms[GBA_GL_VS_MAXPOS], GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS);
+ glUniform1i(uniforms[GBA_GL_FINALIZE_SCALE], renderer->scale);
+ glUniform1iv(uniforms[GBA_GL_FINALIZE_LAYERS], 5, (GLint[]) { 3, 5, 7, 9, 1 });
+ glUniform1iv(uniforms[GBA_GL_FINALIZE_FLAGS], 5, (GLint[]) { 4, 6, 8, 10, 2 });
+ glUniform1i(uniforms[GBA_GL_FINALIZE_WINDOW], 0);
+ glUniform4f(uniforms[GBA_GL_FINALIZE_BACKDROP], ((backdrop >> 16) & 0xFF) / 256., ((backdrop >> 8) & 0xFF) / 256., (backdrop & 0xFF) / 256., 0.f);
+ glUniform4f(uniforms[GBA_GL_FINALIZE_BACKDROPFLAGS], 1, (renderer->target1Bd | (renderer->target2Bd * 2) | (renderer->blendEffect * 4)) / 32.f,
+ (renderer->blendEffect == BLEND_ALPHA ? renderer->blda : renderer->bldy) / 16.f, renderer->bldb / 16.f);
+ glEnableVertexAttribArray(0);
+ glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+}
+
+void GBAVideoGLRendererDrawSprite(struct GBAVideoGLRenderer* renderer, struct GBAObj* sprite, int y, int spriteY) {
+ int width = GBAVideoObjSizes[GBAObjAttributesAGetShape(sprite->a) * 4 + GBAObjAttributesBGetSize(sprite->b)][0];
+ int height = GBAVideoObjSizes[GBAObjAttributesAGetShape(sprite->a) * 4 + GBAObjAttributesBGetSize(sprite->b)][1];
+ int32_t x = (uint32_t) GBAObjAttributesBGetX(sprite->b) << 23;
+ x >>= 23;
+
+ int align = GBAObjAttributesAIs256Color(sprite->a) && !GBARegisterDISPCNTIsObjCharacterMapping(renderer->dispcnt);
+ unsigned charBase = (BASE_TILE >> 1) + (GBAObjAttributesCGetTile(sprite->c) & ~align) * 0x10;
+ int stride = GBARegisterDISPCNTIsObjCharacterMapping(renderer->dispcnt) ? (width >> 3) : (0x20 >> GBAObjAttributesAGet256Color(sprite->a));
+
+ if (spriteY + height >= 256) {
+ spriteY -= 256;
+ }
+
+ if (!GBAObjAttributesAIsTransformed(sprite->a) && GBAObjAttributesBIsVFlip(sprite->b)) {
+ spriteY = (y - height) + (y - spriteY) + 1;
+ }
+
+ int totalWidth = width;
+ int totalHeight = height;
+ if (GBAObjAttributesAIsTransformed(sprite->a) && GBAObjAttributesAIsDoubleSize(sprite->a)) {
+ totalWidth <<= 1;
+ totalHeight <<= 1;
+ }
+
+ enum GBAVideoBlendEffect blendEffect = GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT ? BLEND_ALPHA : renderer->blendEffect;
+
+ const struct GBAVideoGLShader* shader = &renderer->objShader[GBAObjAttributesAGet256Color(sprite->a)];
+ const GLuint* uniforms = shader->uniforms;
+ glBindFramebuffer(GL_FRAMEBUFFER, renderer->fbo[GBA_GL_FBO_OBJ]);
+ glViewport(x * renderer->scale, spriteY * renderer->scale, totalWidth * renderer->scale, totalHeight * renderer->scale);
+ glScissor(x * renderer->scale, y * renderer->scale, totalWidth * renderer->scale, renderer->scale);
+ glUseProgram(shader->program);
+ glBindVertexArray(shader->vao);
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, renderer->vramTex);
+ glActiveTexture(GL_TEXTURE0 + 1);
+ glBindTexture(GL_TEXTURE_2D, renderer->paletteTex);
+ glUniform2i(uniforms[GBA_GL_VS_LOC], 1, y - spriteY);
+ glUniform2i(uniforms[GBA_GL_VS_MAXPOS], (GBAObjAttributesBIsHFlip(sprite->b) && !GBAObjAttributesAIsTransformed(sprite->a)) ? -totalWidth : totalWidth, totalHeight);
+ glUniform1i(uniforms[GBA_GL_OBJ_VRAM], 0);
+ glUniform1i(uniforms[GBA_GL_OBJ_PALETTE], 1);
+ glUniform1i(uniforms[GBA_GL_OBJ_CHARBASE], charBase);
+ glUniform1i(uniforms[GBA_GL_OBJ_STRIDE], stride);
+ glUniform1i(uniforms[GBA_GL_OBJ_LOCALPALETTE], GBAObjAttributesCGetPalette(sprite->c));
+ glUniform4i(uniforms[GBA_GL_OBJ_INFLAGS], GBAObjAttributesCGetPriority(sprite->c) << 3,
+ (renderer->target1Obj || GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT) | (renderer->target2Obj * 2) | (blendEffect * 4),
+ blendEffect == BLEND_ALPHA ? renderer->blda : renderer->bldy, renderer->bldb);
+ if (GBAObjAttributesAIsTransformed(sprite->a)) {
+ struct GBAOAMMatrix mat;
+ LOAD_16(mat.a, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].a);
+ LOAD_16(mat.b, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].b);
+ LOAD_16(mat.c, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].c);
+ LOAD_16(mat.d, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].d);
+
+ glUniformMatrix2fv(uniforms[GBA_GL_OBJ_TRANSFORM], 1, GL_FALSE, (GLfloat[]) { mat.a / 256.f, mat.c / 256.f, mat.b / 256.f, mat.d / 256.f });
+ } else {
+ glUniformMatrix2fv(uniforms[GBA_GL_OBJ_TRANSFORM], 1, GL_FALSE, (GLfloat[]) { 1.f, 0, 0, 1.f });
+ }
+ glUniform4i(uniforms[GBA_GL_OBJ_DIMS], width, height, totalWidth, totalHeight);
+ if (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_OBJWIN) {
+ int window = ~renderer->objwin & 0xFF;
+ glUniform3f(uniforms[GBA_GL_OBJ_OBJWIN], 1, (window & 0xF) / 32.f, (window >> 4) / 32.f);
+ glDrawBuffers(3, (GLenum[]) { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2 });
+ } else {
+ glUniform3f(uniforms[GBA_GL_OBJ_OBJWIN], 0, 0, 0);
+ glDrawBuffers(2, (GLenum[]) { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 });
+ }
+ glEnableVertexAttribArray(0);
+ glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
+ glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 });
+}
+
+void GBAVideoGLRendererDrawBackgroundMode0(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBackground* background, int y) {
+ int inY = y + background->y;
+ int yBase = inY & 0xFF;
+ if (background->size == 2) {
+ yBase += inY & 0x100;
+ } else if (background->size == 3) {
+ yBase += (inY & 0x100) << 1;
+ }
+
+ const struct GBAVideoGLShader* shader = &renderer->bgShader[background->multipalette ? 1 : 0];
+ const GLuint* uniforms = shader->uniforms;
+ glBindFramebuffer(GL_FRAMEBUFFER, background->fbo);
+ glViewport(0, 0, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, GBA_VIDEO_VERTICAL_PIXELS * renderer->scale);
+ glScissor(0, y * renderer->scale, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, renderer->scale);
+ glUseProgram(shader->program);
+ glBindVertexArray(shader->vao);
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, renderer->vramTex);
+ glActiveTexture(GL_TEXTURE0 + 1);
+ glBindTexture(GL_TEXTURE_2D, renderer->paletteTex);
+ glUniform2i(uniforms[GBA_GL_VS_LOC], 1, y);
+ glUniform2i(uniforms[GBA_GL_VS_MAXPOS], GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS);
+ glUniform1i(uniforms[GBA_GL_BG_VRAM], 0);
+ glUniform1i(uniforms[GBA_GL_BG_PALETTE], 1);
+ glUniform1i(uniforms[GBA_GL_BG_SCREENBASE], background->screenBase);
+ glUniform1i(uniforms[GBA_GL_BG_CHARBASE], background->charBase);
+ glUniform1i(uniforms[GBA_GL_BG_SIZE], background->size);
+ glUniform2i(uniforms[GBA_GL_BG_OFFSET], background->x, yBase - y);
+ glUniform4i(uniforms[GBA_GL_BG_INFLAGS], (background->priority << 3) + (background->index << 1) + 1,
+ background->target1 | (background->target2 * 2) | (renderer->blendEffect * 4),
+ renderer->blendEffect == BLEND_ALPHA ? renderer->blda : renderer->bldy, renderer->bldb);
+ glDrawBuffers(2, (GLenum[]) { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 });
+ glEnableVertexAttribArray(0);
+ glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
+ glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 });
+}
+
+void GBAVideoGLRendererDrawBackgroundMode2(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBackground* background, int y) {
+ const struct GBAVideoGLShader* shader = &renderer->bgShader[background->overflow ? 2 : 3];
+ const GLuint* uniforms = shader->uniforms;
+ glBindFramebuffer(GL_FRAMEBUFFER, background->fbo);
+ glViewport(0, 0, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, GBA_VIDEO_VERTICAL_PIXELS * renderer->scale);
+ glScissor(0, y * renderer->scale, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, renderer->scale);
+ glUseProgram(shader->program);
+ glBindVertexArray(shader->vao);
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, renderer->vramTex);
+ glActiveTexture(GL_TEXTURE0 + 1);
+ glBindTexture(GL_TEXTURE_2D, renderer->paletteTex);
+ glUniform2i(uniforms[GBA_GL_VS_LOC], 1, y);
+ glUniform2i(uniforms[GBA_GL_VS_MAXPOS], GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS);
+ glUniform1i(uniforms[GBA_GL_BG_VRAM], 0);
+ glUniform1i(uniforms[GBA_GL_BG_PALETTE], 1);
+ glUniform1i(uniforms[GBA_GL_BG_SCREENBASE], background->screenBase);
+ glUniform1i(uniforms[GBA_GL_BG_CHARBASE], background->charBase);
+ glUniform1i(uniforms[GBA_GL_BG_SIZE], background->size);
+ glUniform4i(uniforms[GBA_GL_BG_INFLAGS], (background->priority << 3) + (background->index << 1) + 1,
+ background->target1 | (background->target2 * 2) | (renderer->blendEffect * 4),
+ renderer->blendEffect == BLEND_ALPHA ? renderer->blda : renderer->bldy, renderer->bldb);
+ if (renderer->scale > 1) {
+ glUniform2iv(uniforms[GBA_GL_BG_OFFSET], 4, (GLint[]) {
+ background->affine[0].sx, background->affine[0].sy,
+ background->affine[1].sx, background->affine[1].sy,
+ background->affine[2].sx, background->affine[2].sy,
+ background->affine[3].sx, background->affine[3].sy,
+ });
+ glUniform2iv(uniforms[GBA_GL_BG_TRANSFORM], 4, (GLint[]) {
+ background->affine[0].dx, background->affine[0].dy,
+ background->affine[1].dx, background->affine[1].dy,
+ background->affine[2].dx, background->affine[2].dy,
+ background->affine[3].dx, background->affine[3].dy,
+ });
+ } else {
+ glUniform2iv(uniforms[GBA_GL_BG_OFFSET], 4, (GLint[]) {
+ background->affine[0].sx, background->affine[0].sy,
+ background->affine[0].sx, background->affine[0].sy,
+ background->affine[0].sx, background->affine[0].sy,
+ background->affine[0].sx, background->affine[0].sy,
+ });
+ glUniform2iv(uniforms[GBA_GL_BG_TRANSFORM], 4, (GLint[]) {
+ background->affine[0].dx, background->affine[0].dy,
+ background->affine[0].dx, background->affine[0].dy,
+ background->affine[0].dx, background->affine[0].dy,
+ background->affine[0].dx, background->affine[0].dy,
+ });
+ }
+ glDrawBuffers(2, (GLenum[]) { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 });
+ glEnableVertexAttribArray(0);
+ glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
+ glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 });
+}
+
+static void _clearWindow(GBAWindowControl window, int start, int end, int y, int scale) {
+ glScissor(start, y, end - start, scale);
+ window = ~window & 0xFF;
+ glClearColor((window & 0xF) / 32.f, (window >> 4) / 32.f, 0, 0);
+ glClear(GL_COLOR_BUFFER_BIT);
+}
+
+void GBAVideoGLRendererDrawWindow(struct GBAVideoGLRenderer* renderer, int y) {
+ glBindFramebuffer(GL_FRAMEBUFFER, renderer->fbo[GBA_GL_FBO_WINDOW]);
+ int dispcnt = ((renderer->dispcnt >> 8) & 0x1F) | 0x20;
+ if (!(renderer->dispcnt & 0xE000)) {
+ _clearWindow(dispcnt, 0, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, y * renderer->scale, renderer->scale);
+ } else {
+ _clearWindow(renderer->winout & dispcnt, 0, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, y * renderer->scale, renderer->scale);
+ if (GBARegisterDISPCNTIsWin1Enable(renderer->dispcnt) && y >= renderer->winN[1].v.start && y < renderer->winN[1].v.end) {
+ _clearWindow(renderer->winN[1].control & dispcnt, renderer->winN[1].h.start * renderer->scale, renderer->winN[1].h.end * renderer->scale, y * renderer->scale, renderer->scale);
+ }
+ if (GBARegisterDISPCNTIsWin0Enable(renderer->dispcnt) && y >= renderer->winN[0].v.start && y < renderer->winN[0].v.end) {
+ _clearWindow(renderer->winN[0].control & dispcnt, renderer->winN[0].h.start * renderer->scale, renderer->winN[0].h.end * renderer->scale, y * renderer->scale, renderer->scale);
+ }
+ }
+}
+
+#endif
diff --git a/src/gba/renderers/video-software.c b/src/gba/renderers/video-software.c
index 1b9805bde..5c639840b 100644
--- a/src/gba/renderers/video-software.c
+++ b/src/gba/renderers/video-software.c
@@ -36,7 +36,7 @@ static void GBAVideoSoftwareRendererWriteBGY_LO(struct GBAVideoSoftwareBackgroun
static void GBAVideoSoftwareRendererWriteBGY_HI(struct GBAVideoSoftwareBackground* bg, uint16_t value);
static void GBAVideoSoftwareRendererWriteBLDCNT(struct GBAVideoSoftwareRenderer* renderer, uint16_t value);
-static void _cleanOAM(struct GBAVideoSoftwareRenderer* renderer);
+static void _drawScanline(struct GBAVideoSoftwareRenderer* renderer, int y);
static void _updatePalettes(struct GBAVideoSoftwareRenderer* renderer);
@@ -516,42 +516,6 @@ static void _breakWindowInner(struct GBAVideoSoftwareRenderer* softwareRenderer,
#endif
}
-static void _cleanOAM(struct GBAVideoSoftwareRenderer* renderer) {
- int i;
- int p;
- int oamMax = 0;
- int maxP = 1;
- if (renderer->combinedObjSort) {
- maxP = 4;
- }
- for (p = 0; p < maxP; ++p) {
- for (i = 0; i < 128; ++i) {
- struct GBAObj obj;
- LOAD_16(obj.a, 0, &renderer->d.oam->obj[i].a);
- LOAD_16(obj.b, 0, &renderer->d.oam->obj[i].b);
- LOAD_16(obj.c, 0, &renderer->d.oam->obj[i].c);
- if (renderer->combinedObjSort && GBAObjAttributesCGetPriority(obj.c) != p) {
- continue;
- }
- if (GBAObjAttributesAIsTransformed(obj.a) || !GBAObjAttributesAIsDisable(obj.a)) {
- int height = GBAVideoObjSizes[GBAObjAttributesAGetShape(obj.a) * 4 + GBAObjAttributesBGetSize(obj.b)][1];
- if (GBAObjAttributesAIsTransformed(obj.a)) {
- height <<= GBAObjAttributesAGetDoubleSize(obj.a);
- }
- if (GBAObjAttributesAGetY(obj.a) < renderer->masterHeight || GBAObjAttributesAGetY(obj.a) + height >= 256) {
- int y = GBAObjAttributesAGetY(obj.a) + renderer->objOffsetY;
- renderer->sprites[oamMax].y = y;
- renderer->sprites[oamMax].endY = y + height;
- renderer->sprites[oamMax].obj = obj;
- ++oamMax;
- }
- }
- }
- }
- renderer->oamMax = oamMax;
- renderer->oamDirty = 0;
-}
-
static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) {
struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
@@ -837,7 +801,7 @@ static void GBAVideoSoftwareRendererWriteBGY_HI(struct GBAVideoSoftwareBackgroun
}
static void GBAVideoSoftwareRendererWriteBLDCNT(struct GBAVideoSoftwareRenderer* renderer, uint16_t value) {
- enum BlendEffect oldEffect = renderer->blendEffect;
+ enum GBAVideoBlendEffect oldEffect = renderer->blendEffect;
renderer->bg[0].target1 = GBARegisterBLDCNTGetTarget1Bg0(value);
renderer->bg[1].target1 = GBARegisterBLDCNTGetTarget1Bg1(value);
@@ -967,14 +931,15 @@ int GBAVideoSoftwareRendererPreprocessSpriteLayer(struct GBAVideoSoftwareRendere
int spriteLayers = 0;
if (GBARegisterDISPCNTIsObjEnable(renderer->dispcnt) && !renderer->d.disableOBJ) {
if (renderer->oamDirty) {
- _cleanOAM(renderer);
+ renderer->oamMax = GBAVideoRendererCleanOAM(renderer->d.oam->obj, renderer->sprites, renderer->objOffsetY, renderer->masterHeight, renderer->combinedObjSort);
+ renderer->oamDirty = false;
}
renderer->spriteCyclesRemaining = GBARegisterDISPCNTIsHblankIntervalFree(renderer->dispcnt) ? OBJ_HBLANK_FREE_LENGTH : OBJ_LENGTH;
int mosaicV = GBAMosaicControlGetObjV(renderer->mosaic) + 1;
int mosaicY = y - (y % mosaicV);
int i;
for (i = 0; i < renderer->oamMax; ++i) {
- struct GBAVideoSoftwareSprite* sprite = &renderer->sprites[i];
+ struct GBAVideoRendererSprite* sprite = &renderer->sprites[i];
int localY = y;
renderer->end = 0;
if (GBAObjAttributesAIsMosaic(sprite->obj.a)) {
diff --git a/src/platform/opengl/gles2.c b/src/platform/opengl/gles2.c
index 94d0856ca..35e7ac9e0 100644
--- a/src/platform/opengl/gles2.c
+++ b/src/platform/opengl/gles2.c
@@ -84,7 +84,9 @@ static void mGLES2ContextInit(struct VideoBackend* v, WHandle handle) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
- glClearColor(0.f, 0.f, 0.f, 1.f);
+ glGenBuffers(1, &context->vbo);
+ glBindBuffer(GL_ARRAY_BUFFER, context->vbo);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(_vertices), _vertices, GL_STATIC_DRAW);
struct mGLES2Uniform* uniforms = malloc(sizeof(struct mGLES2Uniform) * 4);
uniforms[0].name = "gamma";
@@ -131,6 +133,15 @@ static void mGLES2ContextInit(struct VideoBackend* v, WHandle handle) {
uniforms[3].max.fvec3[2] = 1.0f;
mGLES2ShaderInit(&context->initialShader, _vertexShader, _fragmentShader, -1, -1, false, uniforms, 4);
mGLES2ShaderInit(&context->finalShader, 0, 0, 0, 0, false, 0, 0);
+
+ glBindVertexArray(context->initialShader.vao);
+ glBindBuffer(GL_ARRAY_BUFFER, context->vbo);
+ glVertexAttribPointer(context->initialShader.positionLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+ glBindVertexArray(context->finalShader.vao);
+ glBindBuffer(GL_ARRAY_BUFFER, context->vbo);
+ glVertexAttribPointer(context->finalShader.positionLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+ glBindVertexArray(0);
+
glDeleteFramebuffers(1, &context->finalShader.fbo);
glDeleteTextures(1, &context->finalShader.tex);
context->finalShader.fbo = 0;
@@ -159,6 +170,7 @@ static void mGLES2ContextSetDimensions(struct VideoBackend* v, unsigned width, u
static void mGLES2ContextDeinit(struct VideoBackend* v) {
struct mGLES2Context* context = (struct mGLES2Context*) v;
glDeleteTextures(1, &context->tex);
+ glDeleteBuffers(1, &context->vbo);
mGLES2ShaderDeinit(&context->initialShader);
mGLES2ShaderDeinit(&context->finalShader);
free(context->initialShader.uniforms);
@@ -178,14 +190,13 @@ static void mGLES2ContextResized(struct VideoBackend* v, unsigned w, unsigned h)
drawW -= drawW % v->width;
drawH -= drawH % v->height;
}
- glViewport(0, 0, w, h);
- glClearColor(0.f, 0.f, 0.f, 1.f);
- glClear(GL_COLOR_BUFFER_BIT);
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
glViewport((w - drawW) / 2, (h - drawH) / 2, drawW, drawH);
}
static void mGLES2ContextClear(struct VideoBackend* v) {
UNUSED(v);
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
glClearColor(0.f, 0.f, 0.f, 1.f);
glClear(GL_COLOR_BUFFER_BIT);
}
@@ -223,6 +234,7 @@ void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) {
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);
}
@@ -239,8 +251,7 @@ void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) {
glUseProgram(shader->program);
glUniform1i(shader->texLocation, 0);
glUniform2f(shader->texSizeLocation, context->d.width - padW, context->d.height - padH);
- glVertexAttribPointer(shader->positionLocation, 2, GL_FLOAT, GL_FALSE, 0, _vertices);
- glEnableVertexAttribArray(shader->positionLocation);
+ glBindVertexArray(shader->vao);
size_t u;
for (u = 0; u < shader->nUniforms; ++u) {
struct mGLES2Uniform* uniform = &shader->uniforms[u];
@@ -292,6 +303,7 @@ void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) {
break;
}
}
+ glEnableVertexAttribArray(shader->positionLocation);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
glBindTexture(GL_TEXTURE_2D, shader->tex);
}
@@ -420,6 +432,9 @@ void mGLES2ShaderInit(struct mGLES2Shader* shader, const char* vs, const char* f
for (i = 0; i < shader->nUniforms; ++i) {
shader->uniforms[i].location = glGetUniformLocation(shader->program, shader->uniforms[i].name);
}
+
+ glGenVertexArrays(1, &shader->vao);
+
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
@@ -428,6 +443,7 @@ void mGLES2ShaderDeinit(struct mGLES2Shader* shader) {
glDeleteShader(shader->fragmentShader);
glDeleteProgram(shader->program);
glDeleteFramebuffers(1, &shader->fbo);
+ glDeleteVertexArrays(1, &shader->vao);
}
void mGLES2ShaderAttach(struct mGLES2Context* context, struct mGLES2Shader* shaders, size_t nShaders) {
@@ -442,8 +458,15 @@ void mGLES2ShaderAttach(struct mGLES2Context* context, struct mGLES2Shader* shad
size_t i;
for (i = 0; i < nShaders; ++i) {
glBindFramebuffer(GL_FRAMEBUFFER, context->shaders[i].fbo);
+ glClearColor(0.f, 0.f, 0.f, 1.f);
glClear(GL_COLOR_BUFFER_BIT);
+
+ glBindVertexArray(context->shaders[i].vao);
+ glBindBuffer(GL_ARRAY_BUFFER, context->vbo);
+ glVertexAttribPointer(context->shaders[i].positionLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+ glEnableVertexAttribArray(context->shaders[i].positionLocation);
}
+ glBindVertexArray(0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
diff --git a/src/platform/opengl/gles2.h b/src/platform/opengl/gles2.h
index f6d050bc9..59f3b7996 100644
--- a/src/platform/opengl/gles2.h
+++ b/src/platform/opengl/gles2.h
@@ -62,6 +62,7 @@ struct mGLES2Shader {
bool blend;
GLuint tex;
GLuint fbo;
+ GLuint vao;
GLuint fragmentShader;
GLuint vertexShader;
GLuint program;
@@ -77,8 +78,7 @@ struct mGLES2Context {
struct VideoBackend d;
GLuint tex;
- GLuint texLocation;
- GLuint positionLocation;
+ GLuint vbo;
struct mGLES2Shader initialShader;
struct mGLES2Shader finalShader;
diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt
index 1d7bf36a8..53ee76478 100644
--- a/src/platform/qt/CMakeLists.txt
+++ b/src/platform/qt/CMakeLists.txt
@@ -48,17 +48,6 @@ if(APPLE)
endif()
endif()
-if(BUILD_GL)
- list(APPEND PLATFORM_SRC ${CMAKE_SOURCE_DIR}/src/platform/opengl/gl.c)
- if(NOT WIN32 OR USE_EPOXY)
- list(APPEND PLATFORM_SRC ${CMAKE_SOURCE_DIR}/src/platform/opengl/gles2.c)
- endif()
-endif()
-
-if(BUILD_GLES2)
- list(APPEND PLATFORM_SRC ${CMAKE_SOURCE_DIR}/src/platform/opengl/gles2.c)
-endif()
-
get_target_property(QT_TYPE Qt5::Core TYPE)
if(QT_TYPE STREQUAL STATIC_LIBRARY)
set(QT_STATIC ON)
@@ -119,6 +108,7 @@ set(SOURCE_FILES
utils.cpp
Window.cpp
VFileDevice.cpp
+ VideoProxy.cpp
VideoView.cpp
input/InputController.cpp
input/InputIndex.cpp
@@ -247,7 +237,7 @@ if(NOT DEFINED DATADIR)
set(DATADIR ${CMAKE_INSTALL_DATADIR}/${BINARY_NAME})
endif()
endif()
-if(BUILD_GL OR BUILD_GLES2)
+if(BUILD_GL OR BUILD_GLES2 OR BUILD_EPOXY)
install(DIRECTORY ${CMAKE_SOURCE_DIR}/res/shaders DESTINATION ${DATADIR} COMPONENT ${BINARY_NAME}-qt)
endif()
install(FILES ${CMAKE_SOURCE_DIR}/res/nointro.dat DESTINATION ${DATADIR} COMPONENT ${BINARY_NAME}-qt)
@@ -293,7 +283,7 @@ add_executable(${BINARY_NAME}-qt WIN32 MACOSX_BUNDLE main.cpp ${CMAKE_SOURCE_DIR
set_target_properties(${BINARY_NAME}-qt PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/res/info.plist.in COMPILE_DEFINITIONS "${FEATURE_DEFINES};${FUNCTION_DEFINES};${OS_DEFINES};${QT_DEFINES}" COMPILE_OPTIONS "${FEATURE_FLAGS}")
list(APPEND QT_LIBRARIES Qt5::Widgets)
-if(BUILD_GL OR BUILD_GLES2)
+if(BUILD_GL OR BUILD_GLES2 OR BUILD_EPOXY)
list(APPEND QT_LIBRARIES Qt5::OpenGL ${OPENGL_LIBRARY} ${OPENGLES2_LIBRARY})
endif()
if(QT_STATIC)
diff --git a/src/platform/qt/CoreController.cpp b/src/platform/qt/CoreController.cpp
index a60de2e39..582c15f61 100644
--- a/src/platform/qt/CoreController.cpp
+++ b/src/platform/qt/CoreController.cpp
@@ -40,16 +40,6 @@ CoreController::CoreController(mCore* core, QObject* parent)
m_threadContext.core = core;
m_threadContext.userData = this;
- QSize size(256, 512);
- m_buffers[0].resize(size.width() * size.height() * sizeof(color_t));
- m_buffers[1].resize(size.width() * size.height() * sizeof(color_t));
- m_buffers[0].fill(0xFF);
- m_buffers[1].fill(0xFF);
- m_activeBuffer = &m_buffers[0];
- m_completeBuffer = m_buffers[0];
-
- m_threadContext.core->setVideoBuffer(m_threadContext.core, reinterpret_cast(m_activeBuffer->data()), size.width());
-
m_resetActions.append([this]() {
if (m_autoload) {
mCoreLoadState(m_threadContext.core, 0, m_loadStateFlags);
@@ -91,8 +81,10 @@ CoreController::CoreController(mCore* core, QObject* parent)
controller->m_resetActions.clear();
- controller->m_activeBuffer = &controller->m_buffers[0];
- context->core->setVideoBuffer(context->core, reinterpret_cast(controller->m_activeBuffer->data()), controller->screenDimensions().width());
+ if (!controller->m_hwaccel) {
+ controller->m_activeBuffer = &controller->m_buffers[0];
+ context->core->setVideoBuffer(context->core, reinterpret_cast(controller->m_activeBuffer->data()), controller->screenDimensions().width());
+ }
controller->finishFrame();
};
@@ -211,6 +203,9 @@ CoreController::~CoreController() {
const color_t* CoreController::drawContext() {
QMutexLocker locker(&m_mutex);
+ if (m_hwaccel) {
+ return nullptr;
+ }
return reinterpret_cast(m_completeBuffer.constData());
}
@@ -345,6 +340,18 @@ void CoreController::setLogger(LogController* logger) {
}
void CoreController::start() {
+ if (!m_hwaccel) {
+ QSize size(1024, 2048);
+ m_buffers[0].resize(size.width() * size.height() * sizeof(color_t));
+ m_buffers[1].resize(size.width() * size.height() * sizeof(color_t));
+ m_buffers[0].fill(0xFF);
+ m_buffers[1].fill(0xFF);
+ m_activeBuffer = &m_buffers[0];
+ m_completeBuffer = m_buffers[0];
+
+ m_threadContext.core->setVideoBuffer(m_threadContext.core, reinterpret_cast(m_activeBuffer->data()), size.width());
+ }
+
if (!m_patched) {
mCoreAutoloadPatch(m_threadContext.core);
}
@@ -788,6 +795,16 @@ void CoreController::endVideoLog() {
m_vl = nullptr;
}
+void CoreController::setFramebufferHandle(int fb) {
+ Interrupter interrupter(this);
+ if (fb < 0) {
+ m_hwaccel = false;
+ } else {
+ m_threadContext.core->setVideoGLTex(m_threadContext.core, fb);
+ m_hwaccel = true;
+ }
+}
+
void CoreController::updateKeys() {
int activeKeys = m_inputController->updateAutofire() | m_inputController->pollEvents();
m_threadContext.core->setKeys(m_threadContext.core, activeKeys);
@@ -795,17 +812,18 @@ void CoreController::updateKeys() {
void CoreController::finishFrame() {
QMutexLocker locker(&m_mutex);
- memcpy(m_completeBuffer.data(), m_activeBuffer->constData(), m_activeBuffer->size());
+ if (!m_hwaccel) {
+ memcpy(m_completeBuffer.data(), m_activeBuffer->constData(), m_activeBuffer->size());
- // TODO: Generalize this to triple buffering?
- m_activeBuffer = &m_buffers[0];
- if (m_activeBuffer == m_completeBuffer) {
- m_activeBuffer = &m_buffers[1];
+ // TODO: Generalize this to triple buffering?
+ m_activeBuffer = &m_buffers[0];
+ if (m_activeBuffer == m_completeBuffer) {
+ m_activeBuffer = &m_buffers[1];
+ }
+ // Copy contents to avoid issues when doing frameskip
+ memcpy(m_activeBuffer->data(), m_completeBuffer.constData(), m_activeBuffer->size());
+ m_threadContext.core->setVideoBuffer(m_threadContext.core, reinterpret_cast(m_activeBuffer->data()), screenDimensions().width());
}
- // Copy contents to avoid issues when doing frameskip
- memcpy(m_activeBuffer->data(), m_completeBuffer.constData(), m_activeBuffer->size());
- m_threadContext.core->setVideoBuffer(m_threadContext.core, reinterpret_cast(m_activeBuffer->data()), screenDimensions().width());
-
for (auto& action : m_frameActions) {
action();
}
diff --git a/src/platform/qt/CoreController.h b/src/platform/qt/CoreController.h
index 6b22596b7..657688ce0 100644
--- a/src/platform/qt/CoreController.h
+++ b/src/platform/qt/CoreController.h
@@ -46,6 +46,10 @@ public:
static const bool VIDEO_SYNC = false;
static const bool AUDIO_SYNC = true;
+ enum class Feature {
+ OPENGL = mCORE_FEATURE_OPENGL,
+ };
+
class Interrupter {
public:
Interrupter(CoreController*, bool fromThread = false);
@@ -70,6 +74,8 @@ public:
mPlatform platform() const;
QSize screenDimensions() const;
QPair frameRate() const;
+ bool supportsFeature(Feature feature) const { return m_threadContext.core->supportsFeature(m_threadContext.core, static_cast(feature)); }
+ bool hardwareAccelerated() const { return m_hwaccel; }
void loadConfig(ConfigController*);
@@ -151,6 +157,8 @@ public slots:
void startVideoLog(const QString& path);
void endVideoLog();
+ void setFramebufferHandle(int fb);
+
signals:
void started();
void paused();
@@ -184,6 +192,7 @@ private:
QByteArray m_buffers[2];
QByteArray* m_activeBuffer;
QByteArray m_completeBuffer;
+ bool m_hwaccel = false;
std::unique_ptr m_cacheSet;
std::unique_ptr m_override;
diff --git a/src/platform/qt/Display.cpp b/src/platform/qt/Display.cpp
index 5e0de4b1c..b82f0d6b4 100644
--- a/src/platform/qt/Display.cpp
+++ b/src/platform/qt/Display.cpp
@@ -18,13 +18,15 @@ Display::Driver Display::s_driver = Display::Driver::QT;
Display* Display::create(QWidget* parent) {
#if defined(BUILD_GL) || defined(BUILD_GLES2) || defined(USE_EPOXY)
- QGLFormat format(QGLFormat(QGL::Rgba | QGL::DoubleBuffer));
+ QSurfaceFormat format;
format.setSwapInterval(1);
+ format.setSwapBehavior(QSurfaceFormat::TripleBuffer);
#endif
switch (s_driver) {
#if defined(BUILD_GL) || defined(BUILD_GLES2) || defined(USE_EPOXY)
case Driver::OPENGL:
+ format.setVersion(3, 0);
return new DisplayGL(format, parent);
#endif
#ifdef BUILD_GL
diff --git a/src/platform/qt/Display.h b/src/platform/qt/Display.h
index e16f3a026..fe52ba371 100644
--- a/src/platform/qt/Display.h
+++ b/src/platform/qt/Display.h
@@ -19,6 +19,7 @@ struct VideoShader;
namespace QGBA {
class CoreController;
+class VideoProxy;
class Display : public QWidget {
Q_OBJECT
@@ -47,6 +48,8 @@ public:
virtual bool isDrawing() const = 0;
virtual bool supportsShaders() const = 0;
virtual VideoShader* shaders() = 0;
+ virtual VideoProxy* videoProxy() { return nullptr; }
+ virtual int framebufferHandle() { return -1; }
QSize viewportSize();
diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp
index de9c3193f..7da5fae79 100644
--- a/src/platform/qt/DisplayGL.cpp
+++ b/src/platform/qt/DisplayGL.cpp
@@ -10,15 +10,18 @@
#include "CoreController.h"
#include
+#include
+#include
#include
#include
+#include
#include
#include
#ifdef BUILD_GL
#include "platform/opengl/gl.h"
#endif
-#if !defined(_WIN32) || defined(USE_EPOXY)
+#ifdef BUILD_GLES2
#include "platform/opengl/gles2.h"
#ifdef _WIN32
#include
@@ -27,22 +30,27 @@
using namespace QGBA;
-DisplayGL::DisplayGL(const QGLFormat& format, QWidget* parent)
+DisplayGL::DisplayGL(const QSurfaceFormat& format, QWidget* parent)
: Display(parent)
, m_gl(nullptr)
{
// This can spontaneously re-enter into this->resizeEvent before creation is done, so we
// need to make sure it's initialized to nullptr before we assign the new object to it
- m_gl = new EmptyGLWidget(format, this);
- m_painter = new PainterGL(format.majorVersion() < 2 ? 1 : m_gl->format().majorVersion(), m_gl);
- m_gl->setMouseTracking(true);
- m_gl->setAttribute(Qt::WA_TransparentForMouseEvents); // This doesn't seem to work?
+ m_gl = new QOpenGLContext;
+ m_gl->setFormat(format);
+ m_gl->create();
+ setAttribute(Qt::WA_NativeWindow);
+ m_painter = new PainterGL(&m_videoProxy, windowHandle(), m_gl);
setUpdatesEnabled(false); // Prevent paint events, which can cause race conditions
+
+ connect(&m_videoProxy, &VideoProxy::dataAvailable, &m_videoProxy, &VideoProxy::processData);
+ connect(&m_videoProxy, &VideoProxy::eventPosted, &m_videoProxy, &VideoProxy::handleEvent);
}
DisplayGL::~DisplayGL() {
stopDrawing();
delete m_painter;
+ delete m_gl;
}
bool DisplayGL::supportsShaders() const {
@@ -71,12 +79,12 @@ void DisplayGL::startDrawing(std::shared_ptr controller) {
m_painter->setMessagePainter(messagePainter());
m_context = controller;
m_painter->resize(size());
- m_gl->move(0, 0);
m_drawThread = new QThread(this);
m_drawThread->setObjectName("Painter Thread");
- m_gl->context()->doneCurrent();
- m_gl->context()->moveToThread(m_drawThread);
+ m_gl->doneCurrent();
+ m_gl->moveToThread(m_drawThread);
m_painter->moveToThread(m_drawThread);
+ m_videoProxy.moveToThread(m_drawThread);
connect(m_drawThread, &QThread::started, m_painter, &PainterGL::start);
m_drawThread->start();
@@ -99,6 +107,11 @@ void DisplayGL::stopDrawing() {
QMetaObject::invokeMethod(m_painter, "stop", Qt::BlockingQueuedConnection);
m_drawThread->exit();
m_drawThread = nullptr;
+
+ m_gl->makeCurrent(windowHandle());
+#if defined(_WIN32) && defined(USE_EPOXY)
+ epoxy_handle_external_wglMakeCurrent();
+#endif
}
m_context.reset();
}
@@ -180,33 +193,45 @@ void DisplayGL::resizeEvent(QResizeEvent* event) {
}
void DisplayGL::resizePainter() {
- if (m_gl) {
- m_gl->resize(size());
- }
if (m_drawThread) {
QMetaObject::invokeMethod(m_painter, "resize", Qt::BlockingQueuedConnection, Q_ARG(QSize, size()));
}
}
-PainterGL::PainterGL(int majorVersion, QGLWidget* parent)
+VideoProxy* DisplayGL::videoProxy() {
+ if (supportsShaders()) {
+ return &m_videoProxy;
+ }
+ return nullptr;
+}
+
+int DisplayGL::framebufferHandle() {
+ return m_painter->glTex();
+}
+
+PainterGL::PainterGL(VideoProxy* proxy, QWindow* surface, QOpenGLContext* parent)
: m_gl(parent)
+ , m_surface(surface)
+ , m_videoProxy(proxy)
{
#ifdef BUILD_GL
mGLContext* glBackend;
#endif
-#if !defined(_WIN32) || defined(USE_EPOXY)
+#ifdef BUILD_GLES2
mGLES2Context* gl2Backend;
#endif
- m_gl->makeCurrent();
+ m_gl->makeCurrent(m_surface);
+ m_window = new QOpenGLPaintDevice;
#if defined(_WIN32) && defined(USE_EPOXY)
epoxy_handle_external_wglMakeCurrent();
#endif
+ int majorVersion = m_gl->format().majorVersion();
QStringList extensions = QString(reinterpret_cast(glGetString(GL_EXTENSIONS))).split(' ');
-#if !defined(_WIN32) || defined(USE_EPOXY)
- if (extensions.contains("GL_ARB_framebuffer_object") && majorVersion >= 2) {
+#ifdef BUILD_GLES2
+ if ((majorVersion == 2 && extensions.contains("GL_ARB_framebuffer_object")) || majorVersion > 2) {
gl2Backend = static_cast(malloc(sizeof(mGLES2Context)));
mGLES2ContextCreate(gl2Backend);
m_backend = &gl2Backend->d;
@@ -224,14 +249,14 @@ PainterGL::PainterGL(int majorVersion, QGLWidget* parent)
#endif
m_backend->swap = [](VideoBackend* v) {
PainterGL* painter = static_cast(v->user);
- if (!painter->m_gl->isVisible()) {
+ if (!painter->m_gl->isValid()) {
return;
}
- painter->m_gl->swapBuffers();
+ painter->m_gl->swapBuffers(painter->m_gl->surface());
};
- m_backend->init(m_backend, reinterpret_cast(m_gl->winId()));
-#if !defined(_WIN32) || defined(USE_EPOXY)
+ m_backend->init(m_backend, 0);
+#ifdef BUILD_GLES2
if (m_supportsShaders) {
m_shader.preprocessShader = static_cast(&reinterpret_cast(m_backend)->initialShader);
}
@@ -243,7 +268,7 @@ PainterGL::PainterGL(int majorVersion, QGLWidget* parent)
m_backend->lockAspectRatio = false;
for (int i = 0; i < 2; ++i) {
- m_free.append(new uint32_t[256 * 384]);
+ m_free.append(new uint32_t[256 * 512]);
}
}
@@ -254,11 +279,11 @@ PainterGL::~PainterGL() {
for (auto item : m_free) {
delete[] item;
}
- m_gl->makeCurrent();
+ m_gl->makeCurrent(m_surface);
#if defined(_WIN32) && defined(USE_EPOXY)
epoxy_handle_external_wglMakeCurrent();
#endif
-#if !defined(_WIN32) || defined(USE_EPOXY)
+#ifdef BUILD_GLES2
if (m_shader.passes) {
mGLES2ShaderFree(&m_shader);
}
@@ -267,6 +292,7 @@ PainterGL::~PainterGL() {
m_gl->doneCurrent();
free(m_backend);
m_backend = nullptr;
+ delete m_window;
}
void PainterGL::setContext(std::shared_ptr context) {
@@ -279,13 +305,18 @@ void PainterGL::resizeContext() {
return;
}
- m_gl->makeCurrent();
+ if (!m_active) {
+ m_gl->makeCurrent(m_surface);
#if defined(_WIN32) && defined(USE_EPOXY)
- epoxy_handle_external_wglMakeCurrent();
+ epoxy_handle_external_wglMakeCurrent();
#endif
+ }
+
QSize size = m_context->screenDimensions();
m_backend->setDimensions(m_backend, size.width(), size.height());
- m_gl->doneCurrent();
+ if (!m_active) {
+ m_gl->doneCurrent();
+ }
}
void PainterGL::setMessagePainter(MessagePainter* messagePainter) {
@@ -301,16 +332,12 @@ void PainterGL::resize(const QSize& size) {
void PainterGL::lockAspectRatio(bool lock) {
m_backend->lockAspectRatio = lock;
- if (m_started && !m_active) {
- forceDraw();
- }
+ resize(m_size);
}
void PainterGL::lockIntegerScaling(bool lock) {
m_backend->lockIntegerScaling = lock;
- if (m_started && !m_active) {
- forceDraw();
- }
+ resize(m_size);
}
void PainterGL::filter(bool filter) {
@@ -321,18 +348,17 @@ void PainterGL::filter(bool filter) {
}
void PainterGL::start() {
- m_gl->makeCurrent();
+ m_gl->makeCurrent(m_surface);
#if defined(_WIN32) && defined(USE_EPOXY)
epoxy_handle_external_wglMakeCurrent();
#endif
-#if !defined(_WIN32) || defined(USE_EPOXY)
+#ifdef BUILD_GLES2
if (m_supportsShaders && m_shader.passes) {
mGLES2ShaderAttach(reinterpret_cast(m_backend), static_cast(m_shader.passes), m_shader.nPasses);
}
#endif
- m_gl->doneCurrent();
m_active = true;
m_started = true;
}
@@ -345,13 +371,13 @@ void PainterGL::draw() {
if (mCoreSyncWaitFrameStart(&m_context->thread()->impl->sync) || !m_queue.isEmpty()) {
dequeue();
mCoreSyncWaitFrameEnd(&m_context->thread()->impl->sync);
- m_painter.begin(m_gl->context()->device());
+ m_painter.begin(m_window);
performDraw();
m_painter.end();
m_backend->swap(m_backend);
if (!m_delayTimer.isValid()) {
m_delayTimer.start();
- } else {
+ } else if (m_gl->format().swapInterval()) {
while (m_delayTimer.elapsed() < 15) {
QThread::usleep(100);
}
@@ -366,7 +392,7 @@ void PainterGL::draw() {
}
void PainterGL::forceDraw() {
- m_painter.begin(m_gl->context()->device());
+ m_painter.begin(m_window);
performDraw();
m_painter.end();
m_backend->swap(m_backend);
@@ -375,17 +401,14 @@ void PainterGL::forceDraw() {
void PainterGL::stop() {
m_active = false;
m_started = false;
- m_gl->makeCurrent();
-#if defined(_WIN32) && defined(USE_EPOXY)
- epoxy_handle_external_wglMakeCurrent();
-#endif
dequeueAll();
m_backend->clear(m_backend);
m_backend->swap(m_backend);
m_gl->doneCurrent();
- m_gl->context()->moveToThread(m_gl->thread());
+ m_gl->moveToThread(m_surface->thread());
m_context.reset();
moveToThread(m_gl->thread());
+ m_videoProxy->moveToThread(m_gl->thread());
}
void PainterGL::pause() {
@@ -398,11 +421,7 @@ void PainterGL::unpause() {
void PainterGL::performDraw() {
m_painter.beginNativePainting();
-#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
- float r = m_gl->devicePixelRatioF();
-#else
- float r = m_gl->devicePixelRatio();
-#endif
+ float r = m_surface->devicePixelRatio();
m_backend->resized(m_backend, m_size.width() * r, m_size.height() * r);
m_backend->drawFrame(m_backend);
m_painter.endNativePainting();
@@ -413,14 +432,16 @@ void PainterGL::performDraw() {
void PainterGL::enqueue(const uint32_t* backing) {
m_mutex.lock();
- uint32_t* buffer;
- if (m_free.isEmpty()) {
- buffer = m_queue.dequeue();
- } else {
- buffer = m_free.takeLast();
+ uint32_t* buffer = nullptr;
+ if (backing) {
+ if (m_free.isEmpty()) {
+ buffer = m_queue.dequeue();
+ } else {
+ buffer = m_free.takeLast();
+ }
+ QSize size = m_context->screenDimensions();
+ memcpy(buffer, backing, size.width() * size.height() * BYTES_PER_PIXEL);
}
- QSize size = m_context->screenDimensions();
- memcpy(buffer, backing, size.width() * size.height() * BYTES_PER_PIXEL);
m_queue.enqueue(buffer);
m_mutex.unlock();
}
@@ -432,8 +453,10 @@ void PainterGL::dequeue() {
return;
}
uint32_t* buffer = m_queue.dequeue();
- m_backend->postFrame(m_backend, buffer);
- m_free.append(buffer);
+ if (buffer) {
+ m_backend->postFrame(m_backend, buffer);
+ m_free.append(buffer);
+ }
m_mutex.unlock();
}
@@ -442,7 +465,9 @@ void PainterGL::dequeueAll() {
m_mutex.lock();
while (!m_queue.isEmpty()) {
buffer = m_queue.dequeue();
- m_free.append(buffer);
+ if (buffer) {
+ m_free.append(buffer);
+ }
}
if (buffer) {
m_backend->postFrame(m_backend, buffer);
@@ -454,11 +479,13 @@ void PainterGL::setShaders(struct VDir* dir) {
if (!supportsShaders()) {
return;
}
-#if !defined(_WIN32) || defined(USE_EPOXY)
- m_gl->makeCurrent();
+#ifdef BUILD_GLES2
+ if (!m_active) {
+ m_gl->makeCurrent(m_surface);
#if defined(_WIN32) && defined(USE_EPOXY)
- epoxy_handle_external_wglMakeCurrent();
+ epoxy_handle_external_wglMakeCurrent();
#endif
+ }
if (m_shader.passes) {
mGLES2ShaderDetach(reinterpret_cast(m_backend));
mGLES2ShaderFree(&m_shader);
@@ -467,7 +494,9 @@ void PainterGL::setShaders(struct VDir* dir) {
if (m_started) {
mGLES2ShaderAttach(reinterpret_cast(m_backend), static_cast(m_shader.passes), m_shader.nPasses);
}
- m_gl->doneCurrent();
+ if (!m_active) {
+ m_gl->doneCurrent();
+ }
#endif
}
@@ -475,16 +504,20 @@ void PainterGL::clearShaders() {
if (!supportsShaders()) {
return;
}
-#if !defined(_WIN32) || defined(USE_EPOXY)
- m_gl->makeCurrent();
+#ifdef BUILD_GLES2
+ if (!m_active) {
+ m_gl->makeCurrent(m_surface);
#if defined(_WIN32) && defined(USE_EPOXY)
- epoxy_handle_external_wglMakeCurrent();
+ epoxy_handle_external_wglMakeCurrent();
#endif
+ }
if (m_shader.passes) {
mGLES2ShaderDetach(reinterpret_cast(m_backend));
mGLES2ShaderFree(&m_shader);
}
- m_gl->doneCurrent();
+ if (!m_active) {
+ m_gl->doneCurrent();
+ }
#endif
}
@@ -492,4 +525,19 @@ VideoShader* PainterGL::shaders() {
return &m_shader;
}
+int PainterGL::glTex() {
+#ifdef BUILD_GLES2
+ if (supportsShaders()) {
+ mGLES2Context* gl2Backend = reinterpret_cast(m_backend);
+ return gl2Backend->tex;
+ }
+#endif
+#ifdef BUILD_GL
+ mGLContext* glBackend = reinterpret_cast(m_backend);
+ return glBackend->tex;
+#else
+ return -1;
+#endif
+}
+
#endif
diff --git a/src/platform/qt/DisplayGL.h b/src/platform/qt/DisplayGL.h
index fbf44a547..2dbd1f2f0 100644
--- a/src/platform/qt/DisplayGL.h
+++ b/src/platform/qt/DisplayGL.h
@@ -17,38 +17,33 @@
#endif
#include
-#include
+#include
#include
#include
+#include
#include
#include
+#include "VideoProxy.h"
+
#include "platform/video-backend.h"
namespace QGBA {
-class EmptyGLWidget : public QGLWidget {
-public:
- EmptyGLWidget(const QGLFormat& format, QWidget* parent) : QGLWidget(format, parent) { setAutoBufferSwap(false); }
-
-protected:
- void paintEvent(QPaintEvent* event) override { event->ignore(); }
- void resizeEvent(QResizeEvent*) override {}
- void mouseMoveEvent(QMouseEvent* event) override { event->ignore(); }
-};
-
class PainterGL;
class DisplayGL : public Display {
Q_OBJECT
public:
- DisplayGL(const QGLFormat& format, QWidget* parent = nullptr);
+ DisplayGL(const QSurfaceFormat& format, QWidget* parent = nullptr);
~DisplayGL();
void startDrawing(std::shared_ptr) override;
bool isDrawing() const override { return m_isDrawing; }
bool supportsShaders() const override;
VideoShader* shaders() override;
+ VideoProxy* videoProxy() override;
+ int framebufferHandle() override;
public slots:
void stopDrawing() override;
@@ -71,17 +66,18 @@ private:
void resizePainter();
bool m_isDrawing = false;
- QGLWidget* m_gl;
+ QOpenGLContext* m_gl;
PainterGL* m_painter;
QThread* m_drawThread = nullptr;
std::shared_ptr m_context;
+ VideoProxy m_videoProxy;
};
class PainterGL : public QObject {
Q_OBJECT
public:
- PainterGL(int majorVersion, QGLWidget* parent);
+ PainterGL(VideoProxy* proxy, QWindow* surface, QOpenGLContext* parent);
~PainterGL();
void setContext(std::shared_ptr);
@@ -107,6 +103,8 @@ public slots:
void clearShaders();
VideoShader* shaders();
+ int glTex();
+
private:
void performDraw();
void dequeue();
@@ -116,7 +114,9 @@ private:
QQueue m_queue;
QPainter m_painter;
QMutex m_mutex;
- QGLWidget* m_gl;
+ QWindow* m_surface;
+ QPaintDevice* m_window;
+ QOpenGLContext* m_gl;
bool m_active = false;
bool m_started = false;
std::shared_ptr m_context = nullptr;
@@ -126,6 +126,7 @@ private:
QSize m_size;
MessagePainter* m_messagePainter = nullptr;
QElapsedTimer m_delayTimer;
+ VideoProxy* m_videoProxy;
};
}
diff --git a/src/platform/qt/SettingsView.cpp b/src/platform/qt/SettingsView.cpp
index dceaff928..4b5133b60 100644
--- a/src/platform/qt/SettingsView.cpp
+++ b/src/platform/qt/SettingsView.cpp
@@ -395,6 +395,7 @@ void SettingsView::updateConfig() {
saveSetting("logToStdout", m_ui.logToStdout);
saveSetting("logFile", m_ui.logFile);
saveSetting("useDiscordPresence", m_ui.useDiscordPresence);
+ saveSetting("audioHle", m_ui.audioHle);
if (m_ui.fastForwardUnbounded->isChecked()) {
saveSetting("fastForwardRatio", "-1");
@@ -459,6 +460,14 @@ void SettingsView::updateConfig() {
emit languageChanged();
}
+ int videoScale = m_controller->getOption("videoScale", 1).toInt();
+ int hwaccelVideo = m_controller->getOption("hwaccelVideo").toInt();
+ if (videoScale != m_ui.videoScale->value() || hwaccelVideo != m_ui.hwaccelVideo->currentIndex()) {
+ emit videoRendererChanged();
+ }
+ saveSetting("videoScale", m_ui.videoScale);
+ saveSetting("hwaccelVideo", m_ui.hwaccelVideo->currentIndex());
+
m_logModel.save(m_controller);
m_logModel.logger()->setLogFile(m_ui.logFile->text());
m_logModel.logger()->logToFile(m_ui.logToFile->isChecked());
@@ -540,6 +549,8 @@ void SettingsView::reloadConfig() {
loadSetting("logToStdout", m_ui.logToStdout);
loadSetting("logFile", m_ui.logFile);
loadSetting("useDiscordPresence", m_ui.useDiscordPresence);
+ loadSetting("audioHle", m_ui.audioHle);
+ loadSetting("videoScale", m_ui.videoScale, 1);
m_ui.libraryStyle->setCurrentIndex(loadSetting("libraryStyle").toInt());
@@ -603,6 +614,9 @@ void SettingsView::reloadConfig() {
m_ui.cgbModel->setCurrentIndex(index >= 0 ? index : 0);
}
#endif
+
+ int hwaccelVideo = m_controller->getOption("hwaccelVideo", 0).toInt();
+ m_ui.hwaccelVideo->setCurrentIndex(hwaccelVideo);
}
void SettingsView::saveSetting(const char* key, const QAbstractButton* field) {
@@ -659,9 +673,9 @@ void SettingsView::loadSetting(const char* key, QSlider* field, int defaultVal)
field->setValue(option.isNull() ? defaultVal : option.toInt());
}
-void SettingsView::loadSetting(const char* key, QSpinBox* field) {
+void SettingsView::loadSetting(const char* key, QSpinBox* field, int defaultVal) {
QString option = loadSetting(key);
- field->setValue(option.toInt());
+ field->setValue(option.isNull() ? defaultVal : option.toInt());
}
QString SettingsView::loadSetting(const char* key) {
diff --git a/src/platform/qt/SettingsView.h b/src/platform/qt/SettingsView.h
index 497908cb2..e2b343fd7 100644
--- a/src/platform/qt/SettingsView.h
+++ b/src/platform/qt/SettingsView.h
@@ -40,6 +40,7 @@ signals:
void displayDriverChanged();
void cameraDriverChanged();
void cameraChanged(const QByteArray&);
+ void videoRendererChanged();
void pathsChanged();
void languageChanged();
void libraryCleared();
@@ -76,7 +77,7 @@ private:
void loadSetting(const char* key, QDoubleSpinBox*);
void loadSetting(const char* key, QLineEdit*);
void loadSetting(const char* key, QSlider*, int defaultVal = 0);
- void loadSetting(const char* key, QSpinBox*);
+ void loadSetting(const char* key, QSpinBox*, int defaultVal = 0);
QString loadSetting(const char* key);
};
diff --git a/src/platform/qt/SettingsView.ui b/src/platform/qt/SettingsView.ui
index 3a687b629..84c8a4e38 100644
--- a/src/platform/qt/SettingsView.ui
+++ b/src/platform/qt/SettingsView.ui
@@ -6,8 +6,8 @@
0
0
- 588
- 488
+ 790
+ 686
@@ -33,14 +33,14 @@
-
-
+
0
0
- 140
+ 200
16777215
@@ -62,6 +62,11 @@
Emulation
+ -
+
+ Enhancements
+
+
-
BIOS
@@ -844,6 +849,70 @@
+
+
+ -
+
+
+ Video renderer:
+
+
+
+ -
+
+
-
+
+ Software
+
+
+ -
+
+ OpenGL
+
+
+
+
+ -
+
+
+ OpenGL enhancements
+
+
+
-
+
+
+ High-resolution scale:
+
+
+
+ -
+
+
+ ×
+
+
+ 1
+
+
+ 13
+
+
+
+
+
+
+ -
+
+
+ false
+
+
+ XQ GBA audio (experimental)
+
+
+
+
+
@@ -1301,12 +1370,12 @@
-
-
- 77
-
0
+
+ 77
+
-
diff --git a/src/platform/qt/VideoProxy.cpp b/src/platform/qt/VideoProxy.cpp
new file mode 100644
index 000000000..a6c673207
--- /dev/null
+++ b/src/platform/qt/VideoProxy.cpp
@@ -0,0 +1,106 @@
+/* Copyright (c) 2013-2018 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 "VideoProxy.h"
+
+#include "CoreController.h"
+
+using namespace QGBA;
+
+VideoProxy::VideoProxy() {
+ mVideoLoggerRendererCreate(&m_logger.d, false);
+ m_logger.d.block = true;
+
+ m_logger.d.init = &cbind<&VideoProxy::init>;
+ m_logger.d.reset = &cbind<&VideoProxy::reset>;
+ m_logger.d.deinit = &cbind<&VideoProxy::deinit>;
+ m_logger.d.lock = &cbind<&VideoProxy::lock>;
+ m_logger.d.unlock = &cbind<&VideoProxy::unlock>;
+ m_logger.d.wait = &cbind<&VideoProxy::wait>;
+ m_logger.d.wake = &callback::func<&VideoProxy::wake>;
+
+ m_logger.d.writeData = &callback::func<&VideoProxy::writeData>;
+ m_logger.d.readData = &callback::func<&VideoProxy::readData>;
+ m_logger.d.postEvent = &callback::func<&VideoProxy::postEvent>;
+}
+
+void VideoProxy::attach(CoreController* controller) {
+ CoreController::Interrupter interrupter(controller);
+ controller->thread()->core->videoLogger = &m_logger.d;
+}
+
+void VideoProxy::processData() {
+ mVideoLoggerRendererRun(&m_logger.d, false);
+ m_fromThreadCond.wakeAll();
+}
+
+void VideoProxy::init() {
+ RingFIFOInit(&m_dirtyQueue, 0x80000);
+}
+
+void VideoProxy::reset() {
+ RingFIFOClear(&m_dirtyQueue);
+}
+
+void VideoProxy::deinit() {
+ RingFIFODeinit(&m_dirtyQueue);
+}
+
+bool VideoProxy::writeData(const void* data, size_t length) {
+ while (!RingFIFOWrite(&m_dirtyQueue, data, length)) {
+ emit dataAvailable();
+ m_mutex.lock();
+ m_toThreadCond.wakeAll();
+ m_fromThreadCond.wait(&m_mutex);
+ m_mutex.unlock();
+ }
+ emit dataAvailable();
+ return true;
+}
+
+bool VideoProxy::readData(void* data, size_t length, bool block) {
+ bool read = false;
+ while (true) {
+ read = RingFIFORead(&m_dirtyQueue, data, length);
+ if (!block || read) {
+ break;
+ }
+ m_mutex.lock();
+ m_fromThreadCond.wakeAll();
+ m_toThreadCond.wait(&m_mutex);
+ m_mutex.unlock();
+ }
+ return read;
+}
+
+void VideoProxy::postEvent(enum mVideoLoggerEvent event) {
+ emit eventPosted(event);
+}
+
+void VideoProxy::handleEvent(int event) {
+ m_logger.d.handleEvent(&m_logger.d, static_cast(event));
+}
+
+void VideoProxy::lock() {
+ m_mutex.lock();
+}
+
+void VideoProxy::unlock() {
+ m_mutex.unlock();
+}
+
+void VideoProxy::wait() {
+ while (RingFIFOSize(&m_dirtyQueue)) {
+ emit dataAvailable();
+ m_toThreadCond.wakeAll();
+ m_fromThreadCond.wait(&m_mutex, 1);
+ }
+}
+
+void VideoProxy::wake(int y) {
+ if ((y & 15) == 15) {
+ m_toThreadCond.wakeAll();
+ }
+}
diff --git a/src/platform/qt/VideoProxy.h b/src/platform/qt/VideoProxy.h
new file mode 100644
index 000000000..7f778a605
--- /dev/null
+++ b/src/platform/qt/VideoProxy.h
@@ -0,0 +1,71 @@
+/* Copyright (c) 2013-2018 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/. */
+#pragma once
+
+#include
+#include
+#include
+
+#include
+#include
+
+namespace QGBA {
+
+class CoreController;
+
+class VideoProxy : public QObject {
+Q_OBJECT
+
+public:
+ VideoProxy();
+
+ void attach(CoreController*);
+
+signals:
+ void dataAvailable();
+ void eventPosted(int);
+
+public slots:
+ void processData();
+ void handleEvent(int);
+
+private:
+ void init();
+ void reset();
+ void deinit();
+
+ bool writeData(const void* data, size_t length);
+ bool readData(void* data, size_t length, bool block);
+ void postEvent(enum mVideoLoggerEvent event);
+
+ void lock();
+ void unlock();
+ void wait();
+ void wake(int y);
+
+ template struct callback {
+ using type = T (VideoProxy::*)(A...);
+
+ template static T func(mVideoLogger* logger, A... args) {
+ VideoProxy* proxy = reinterpret_cast(logger)->p;
+ return (proxy->*F)(args...);
+ }
+ };
+
+ template static void cbind(mVideoLogger* logger) { callback::func(logger); }
+
+ struct Logger {
+ mVideoLogger d;
+ VideoProxy* p;
+ } m_logger = {{}, this};
+
+ RingFIFO m_dirtyQueue;
+ QMutex m_mutex;
+ QWaitCondition m_toThreadCond;
+ QWaitCondition m_fromThreadCond;
+};
+
+}
\ No newline at end of file
diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp
index fafd009a9..72ac72dad 100644
--- a/src/platform/qt/Window.cpp
+++ b/src/platform/qt/Window.cpp
@@ -52,6 +52,7 @@
#include "ShaderSelector.h"
#include "ShortcutController.h"
#include "TileView.h"
+#include "VideoProxy.h"
#include "VideoView.h"
#ifdef USE_DISCORD_RPC
@@ -508,6 +509,7 @@ void Window::openSettingsWindow() {
connect(settingsWindow, &SettingsView::audioDriverChanged, this, &Window::reloadAudioDriver);
connect(settingsWindow, &SettingsView::cameraDriverChanged, this, &Window::mustRestart);
connect(settingsWindow, &SettingsView::cameraChanged, &m_inputController, &InputController::setCamera);
+ connect(settingsWindow, &SettingsView::videoRendererChanged, this, &Window::mustRestart);
connect(settingsWindow, &SettingsView::languageChanged, this, &Window::mustRestart);
connect(settingsWindow, &SettingsView::pathsChanged, this, &Window::reloadConfig);
#ifdef USE_SQLITE3
@@ -780,9 +782,6 @@ void Window::gameStarted() {
if (m_savedScale > 0) {
resizeFrame(size * m_savedScale);
}
- if (!m_display) {
- reloadDisplayDriver();
- }
attachWidget(m_display.get());
setMouseTracking(true);
m_display->setMinimumSize(size);
@@ -917,6 +916,10 @@ void Window::unimplementedBiosCall(int call) {
void Window::reloadDisplayDriver() {
if (m_controller) {
+ if (m_controller->hardwareAccelerated()) {
+ mustRestart();
+ return;
+ }
m_display->stopDrawing();
detachWidget(m_display.get());
}
@@ -1749,6 +1752,21 @@ void Window::setController(CoreController* controller, const QString& fname) {
appendMRU(fname);
}
+ if (!m_display) {
+ reloadDisplayDriver();
+ }
+
+ if (m_config->getOption("hwaccelVideo").toInt() && m_display->supportsShaders() && controller->supportsFeature(CoreController::Feature::OPENGL)) {
+ if (m_display->videoProxy()) {
+ m_display->videoProxy()->attach(controller);
+ }
+
+ int fb = m_display->framebufferHandle();
+ if (fb >= 0) {
+ controller->setFramebufferHandle(fb);
+ }
+ }
+
m_controller = std::shared_ptr(controller);
m_inputController.recalibrateAxes();
m_controller->setInputController(&m_inputController);
diff --git a/src/platform/qt/main.cpp b/src/platform/qt/main.cpp
index 9c3fa5072..e2659eae3 100644
--- a/src/platform/qt/main.cpp
+++ b/src/platform/qt/main.cpp
@@ -17,6 +17,10 @@
#include
#include
+#ifdef BUILD_GLES2
+#include
+#endif
+
#ifdef QT_STATIC
#include
#ifdef Q_OS_WIN
@@ -65,6 +69,12 @@ int main(int argc, char* argv[]) {
QApplication::setApplicationName(projectName);
QApplication::setApplicationVersion(projectVersion);
+#ifdef BUILD_GLES2
+ QSurfaceFormat format;
+ format.setVersion(3, 0);
+ QSurfaceFormat::setDefaultFormat(format);
+#endif
+
GBAApp application(argc, argv, &configController);
#ifndef Q_OS_MAC
diff --git a/src/platform/sdl/CMakeLists.txt b/src/platform/sdl/CMakeLists.txt
index fbfaece3c..7a74924ec 100644
--- a/src/platform/sdl/CMakeLists.txt
+++ b/src/platform/sdl/CMakeLists.txt
@@ -77,12 +77,11 @@ if(BUILD_PANDORA)
else()
if(BUILD_GL)
list(APPEND MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/gl-sdl.c)
- list(APPEND PLATFORM_SRC ${CMAKE_SOURCE_DIR}/src/platform/opengl/gl.c ${CMAKE_SOURCE_DIR}/src/platform/sdl/gl-common.c)
- include_directories(${OPENGL_INCLUDE_DIR})
+ list(APPEND PLATFORM_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/gl-common.c)
endif()
if(BUILD_GLES2)
list(APPEND MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/gles2-sdl.c)
- list(APPEND PLATFORM_SRC ${CMAKE_SOURCE_DIR}/src/platform/opengl/gles2.c ${CMAKE_SOURCE_DIR}/src/platform/sdl/gl-common.c)
+ list(APPEND PLATFORM_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/gl-common.c)
include_directories(${OPENGLES2_INCLUDE_DIR})
endif()
if(NOT BUILD_GL AND NOT BUILD_GLES2)
diff --git a/src/platform/sdl/gles2-sdl.c b/src/platform/sdl/gles2-sdl.c
index 6cc046769..675a1139c 100644
--- a/src/platform/sdl/gles2-sdl.c
+++ b/src/platform/sdl/gles2-sdl.c
@@ -35,7 +35,9 @@ bool mSDLGLES2Init(struct mSDLRenderer* renderer) {
#endif
size_t size = renderer->width * renderer->height * BYTES_PER_PIXEL;
-#ifndef __APPLE__
+#ifdef _WIN32
+ renderer->outputBuffer = _aligned_malloc(size, 16);
+#elif !defined(__APPLE__)
renderer->outputBuffer = memalign(16, size);
#else
posix_memalign((void**) &renderer->outputBuffer, 16, size);
diff --git a/src/platform/sdl/main.c b/src/platform/sdl/main.c
index 933b72991..13ea14fd3 100644
--- a/src/platform/sdl/main.c
+++ b/src/platform/sdl/main.c
@@ -90,6 +90,12 @@ int main(int argc, char** argv) {
freeArguments(&args);
return 1;
}
+
+ if (!renderer.core->init(renderer.core)) {
+ freeArguments(&args);
+ return 1;
+ }
+
renderer.core->desiredVideoDimensions(renderer.core, &renderer.width, &renderer.height);
#ifdef BUILD_GL
mSDLGLCreate(&renderer);
@@ -106,11 +112,6 @@ int main(int argc, char** argv) {
opts.width = renderer.width * renderer.ratio;
opts.height = renderer.height * renderer.ratio;
- if (!renderer.core->init(renderer.core)) {
- freeArguments(&args);
- return 1;
- }
-
struct mCheatDevice* device = NULL;
if (args.cheatsFile && (device = renderer.core->cheatDevice(renderer.core))) {
struct VFile* vf = VFileOpen(args.cheatsFile, O_RDONLY);
diff --git a/tools/sanitize-deb.sh b/tools/sanitize-deb.sh
index d400f0489..df00f11dd 100755
--- a/tools/sanitize-deb.sh
+++ b/tools/sanitize-deb.sh
@@ -31,6 +31,7 @@ while [ $# -gt 0 ]; do
rmdep libav
rmdep libedit
rmdep libelf
+ rmdep libgl
rmdep libpng
rmdep libzip
rmdep libmagickwand
@@ -45,6 +46,7 @@ while [ $# -gt 0 ]; do
rmdep libav
rmdep libedit
rmdep libelf
+ rmdep libgl
rmdep libpng
rmdep qt
rmdep libzip