all repos — mgba @ 03c17cdc5637d3609b08caaa2c2c416fee9f9cbc

mGBA Game Boy Advance Emulator

Merge branch 'feature/hwrender'
Vicki Pfau vi@endrift.com
Fri, 17 May 2019 17:11:43 -0700
commit

03c17cdc5637d3609b08caaa2c2c416fee9f9cbc

parent

bb37a60765fafdce9db1aeb0f70f951c29522777

M CMakeLists.txtCMakeLists.txt

@@ -57,7 +57,7 @@ set(BUILD_STATIC OFF CACHE BOOL "Build a static library")

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")

@@ -447,7 +447,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)

@@ -462,9 +462,9 @@ set(BUILD_GL OFF CACHE BOOL "OpenGL not found" FORCE)

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)

@@ -473,6 +473,16 @@ endif()

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)

@@ -727,12 +737,18 @@ list(APPEND FEATURES 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)
M README.mdREADME.md

@@ -85,7 +85,7 @@ Other Unix-like platforms, such as OpenBSD, are known to work as well, but are untested and not fully supported.

### System requirements -Requirements are minimal. 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. 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 ---------
M include/mgba/core/core.hinclude/mgba/core/core.h

@@ -47,6 +47,7 @@ void* board;

struct mTiming* timing; struct mDebugger* debugger; struct mDebuggerSymbols* symbolTable; + struct mVideoLogger* videoLogger; #if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2 struct mDirectorySet dirs;

@@ -63,12 +64,14 @@ bool (*init)(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);
M include/mgba/core/interface.hinclude/mgba/core/interface.h

@@ -80,6 +80,10 @@

mCOLOR_ANY = -1 }; +enum mCoreFeature { + mCORE_FEATURE_OPENGL = 1, +}; + struct mCoreCallbacks { void* context; void (*videoFrameStarted)(void* context);
M include/mgba/feature/thread-proxy.hinclude/mgba/feature/thread-proxy.h

@@ -29,6 +29,7 @@ Condition fromThreadCond;

Condition toThreadCond; Mutex mutex; enum mVideoThreadProxyState threadState; + enum mVideoLoggerEvent event; struct RingFIFO dirtyQueue; };
M include/mgba/feature/video-logger.hinclude/mgba/feature/video-logger.h

@@ -27,6 +27,14 @@ DIRTY_RANGE,

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 @@ void (*wake)(struct mVideoLogger*, int y);

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 @@

uint16_t* vram; uint16_t* oam; uint16_t* palette; + + const void* pixelBuffer; + size_t pixelStride; }; void mVideoLoggerRendererCreate(struct mVideoLogger* logger, bool readonly);
A 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 <mgba-util/common.h> + +CXX_GUARD_START + +#include <mgba/internal/gba/video.h> + +struct GBAVideoRendererSprite { + struct GBAObj obj; + int16_t y; + int16_t endY; +}; + +int GBAVideoRendererCleanOAM(struct GBAObj* oam, struct GBAVideoRendererSprite* sprites, int offsetY); + +CXX_GUARD_END + +#endif
A 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 <mgba-util/common.h> + +CXX_GUARD_START + +#include <mgba/core/core.h> +#include <mgba/gba/interface.h> +#include <mgba/internal/gba/io.h> +#include <mgba/internal/gba/renderers/common.h> +#include <mgba/internal/gba/video.h> + +#if defined(BUILD_GLES2) || defined(BUILD_GLES3) + +#ifdef USE_EPOXY +#include <epoxy/gl.h> +#elif defined(BUILD_GL) +#ifdef __APPLE__ +#include <OpenGL/gl3.h> +#else +#define GL_GLEXT_PROTOTYPES +#include <GL/gl.h> +#include <GL/glext.h> +#endif +#else +#include <GLES3/gl3.h> +#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
M include/mgba/internal/gba/renderers/video-software.hinclude/mgba/internal/gba/renderers/video-software.h

@@ -13,13 +13,8 @@

#include <mgba/core/core.h> #include <mgba/gba/interface.h> #include <mgba/internal/gba/io.h> +#include <mgba/internal/gba/renderers/common.h> #include <mgba/internal/gba/video.h> - -struct GBAVideoSoftwareSprite { - struct GBAObj obj; - int y; - int endY; -}; struct GBAVideoSoftwareBackground { unsigned index;

@@ -49,13 +44,6 @@ int32_t offsetX;

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

@@ -82,25 +70,6 @@ #define FLAG_ORDER_MASK 0xF8000000

#define IS_WRITABLE(PIXEL) ((PIXEL) & 0xFE000000) -struct WindowRegion { - uint8_t end; - uint8_t 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;

@@ -133,7 +102,7 @@ unsigned target1Bd;

unsigned target2Obj; unsigned target2Bd; bool blendDirty; - enum BlendEffect blendEffect; + enum GBAVideoBlendEffect blendEffect; color_t normalPalette[512]; color_t variantPalette[512];

@@ -144,8 +113,8 @@

GBAMosaicControl mosaic; struct WindowN { - struct WindowRegion h; - struct WindowRegion v; + struct GBAVideoWindowRegion h; + struct GBAVideoWindowRegion v; struct WindowControl control; } winN[2];

@@ -161,7 +130,7 @@ struct GBAVideoSoftwareBackground bg[4];

int oamDirty; int oamMax; - struct GBAVideoSoftwareSprite sprites[128]; + struct GBAVideoRendererSprite sprites[128]; int16_t objOffsetX; int16_t objOffsetY;
M include/mgba/internal/gba/video.hinclude/mgba/internal/gba/video.h

@@ -32,16 +32,23 @@

BASE_TILE = 0x00010000 }; -enum ObjMode { +enum GBAVideoObjMode { OBJ_MODE_NORMAL = 0, OBJ_MODE_SEMITRANSPARENT = 1, OBJ_MODE_OBJWIN = 2 }; -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);

@@ -90,6 +97,11 @@

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)

@@ -143,6 +155,20 @@ DECL_BIT(GBARegisterBLDCNT, Target2Bg2, 10);

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);
M src/feature/thread-proxy.csrc/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 @@ renderer->d.wake = _wake;

renderer->d.writeData = _writeData; renderer->d.readData = _readData; + renderer->d.postEvent = _postEvent; } void mVideoThreadProxyInit(struct mVideoLogger* logger) {

@@ -131,6 +133,14 @@ }

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 @@ if (proxyRenderer->threadState == PROXY_THREAD_STOPPED) {

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;
M src/gb/core.csrc/gb/core.c

@@ -138,6 +138,14 @@ UNUSED(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;

@@ -231,6 +239,11 @@ static void _GBCoreSetVideoBuffer(struct mCore* core, color_t* buffer, size_t stride) {

struct GBCore* gbcore = (struct GBCore*) core; gbcore->renderer.outputBuffer = buffer; 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) {

@@ -883,10 +896,12 @@ core->symbolTable = NULL;

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;
M src/gb/extra/proxy.csrc/gb/extra/proxy.c

@@ -283,11 +283,13 @@ if (proxyRenderer->logger->block && proxyRenderer->logger->wait) {

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); } }
M src/gba/core.csrc/gba/core.c

@@ -17,6 +17,9 @@ #include <mgba/internal/gba/overrides.h>

#ifndef DISABLE_THREADING #include <mgba/feature/thread-proxy.h> #endif +#ifdef BUILD_GLES2 +#include <mgba/internal/gba/renderers/gl.h> +#endif #include <mgba/internal/gba/renderers/proxy.h> #include <mgba/internal/gba/renderers/video-software.h> #include <mgba/internal/gba/savedata.h>

@@ -123,6 +126,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;

@@ -151,6 +157,7 @@ core->board = gba;

core->timing = &gba->timing; core->debugger = NULL; core->symbolTable = NULL; + core->videoLogger = NULL; gbacore->overrides = NULL; gbacore->debuggerPlatform = NULL; gbacore->cheatDevice = NULL;

@@ -167,6 +174,11 @@

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

@@ -206,6 +218,20 @@ UNUSED(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;

@@ -250,12 +276,20 @@

#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) {

@@ -265,14 +299,24 @@ gbacore->renderer.outputBufferStride = stride;

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) {

@@ -311,7 +355,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); } }

@@ -387,16 +433,34 @@

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); }

@@ -924,10 +988,12 @@ core->debugger = NULL;

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;
M src/gba/extra/proxy.csrc/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 @@ renderer->d.disableOBJ = false;

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;

@@ -105,7 +107,11 @@

_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) {

@@ -113,17 +119,45 @@ struct GBAVideoProxyRenderer* proxyRenderer = (struct GBAVideoProxyRenderer*) 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) {

@@ -151,6 +185,11 @@ logger->readData(logger, NULL, 0x1000, true);

} 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); }

@@ -269,10 +308,13 @@ if (proxyRenderer->logger->block && proxyRenderer->logger->wait) {

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); } }
A src/gba/renderers/common.c

@@ -0,0 +1,33 @@

+/* 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 <mgba/internal/gba/renderers/common.h> + +#include <mgba/gba/interface.h> + +int GBAVideoRendererCleanOAM(struct GBAObj* oam, struct GBAVideoRendererSprite* sprites, int offsetY) { + int i; + int oamMax = 0; + 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 (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) < GBA_VIDEO_VERTICAL_PIXELS || GBAObjAttributesAGetY(obj.a) + height >= VIDEO_VERTICAL_TOTAL_PIXELS) { + int y = GBAObjAttributesAGetY(obj.a) + offsetY; + sprites[oamMax].y = y; + sprites[oamMax].endY = y + height; + sprites[oamMax].obj = obj; + ++oamMax; + } + } + } + return oamMax; +}
A src/gba/renderers/gl.c

@@ -0,0 +1,1257 @@

+/* 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 <mgba/internal/gba/renderers/gl.h> + +#if defined(BUILD_GLES2) || defined(BUILD_GLES3) + +#include <mgba/core/cache-set.h> +#include <mgba/internal/arm/macros.h> +#include <mgba/internal/gba/io.h> +#include <mgba/internal/gba/renderers/cache-set.h> +#include <mgba-util/memory.h> + +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 < 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.vram[2048 * i]); + } + 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); + 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
M src/gba/renderers/video-software.csrc/gba/renderers/video-software.c

@@ -36,7 +36,6 @@ static void GBAVideoSoftwareRendererWriteBGY_LO(struct GBAVideoSoftwareBackground* bg, uint16_t value);

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);

@@ -498,32 +497,6 @@ }

#endif } -static void _cleanOAM(struct GBAVideoSoftwareRenderer* renderer) { - int i; - int oamMax = 0; - 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 (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) < GBA_VIDEO_VERTICAL_PIXELS || GBAObjAttributesAGetY(obj.a) + height >= VIDEO_VERTICAL_TOTAL_PIXELS) { - 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;

@@ -787,7 +760,7 @@ bg->sy = bg->refy;

} 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);

@@ -821,14 +794,15 @@ int w;

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->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)) {
M src/platform/opengl/gles2.csrc/platform/opengl/gles2.c

@@ -84,7 +84,9 @@ glBindTexture(GL_TEXTURE_2D, context->tex);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - 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 @@ uniforms[3].max.fvec3[1] = 1.0f;

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 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 @@ if (v->lockIntegerScaling) {

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 @@ glEnable(GL_BLEND);

glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } else { glDisable(GL_BLEND); + glClearColor(0.f, 0.f, 0.f, 1.f); glClear(GL_COLOR_BUFFER_BIT); }

@@ -239,8 +251,7 @@ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, shader->filter ? GL_LINEAR : GL_NEAREST);

glUseProgram(shader->program); glUniform1i(shader->texLocation, 0); glUniform2f(shader->texSizeLocation, context->d.width - 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 @@ glUniformMatrix4fv(uniform->location, 1, GL_FALSE, uniform->value.fmat4x4);

break; } } + glEnableVertexAttribArray(shader->positionLocation); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); glBindTexture(GL_TEXTURE_2D, shader->tex); }

@@ -420,6 +432,9 @@ size_t i;

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 @@ glDeleteTextures(1, &shader->tex);

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 @@ context->nShaders = nShaders;

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); }
M src/platform/opengl/gles2.hsrc/platform/opengl/gles2.h

@@ -62,6 +62,7 @@ bool filter;

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;
M src/platform/qt/CMakeLists.txtsrc/platform/qt/CMakeLists.txt

@@ -47,17 +47,6 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")

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)

@@ -121,6 +110,7 @@ TileView.cpp

utils.cpp Window.cpp VFileDevice.cpp + VideoProxy.cpp VideoView.cpp) set(UI_FILES

@@ -244,7 +234,7 @@ else()

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)

@@ -290,7 +280,7 @@ add_executable(${BINARY_NAME}-qt WIN32 MACOSX_BUNDLE main.cpp ${CMAKE_SOURCE_DIR}/res/mgba.icns ${SOURCE_FILES} ${PLATFORM_SRC} ${UI_SRC} ${AUDIO_SRC} ${RESOURCES})

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)
M src/platform/qt/CoreController.cppsrc/platform/qt/CoreController.cpp

@@ -40,16 +40,6 @@ {

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<color_t*>(m_activeBuffer->data()), size.width()); - m_resetActions.append([this]() { if (m_autoload) { mCoreLoadState(m_threadContext.core, 0, m_loadStateFlags);

@@ -91,8 +81,10 @@ }

controller->m_resetActions.clear(); - controller->m_activeBuffer = &controller->m_buffers[0]; - context->core->setVideoBuffer(context->core, reinterpret_cast<color_t*>(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<color_t*>(controller->m_activeBuffer->data()), controller->screenDimensions().width()); + } controller->finishFrame(); };

@@ -211,6 +203,9 @@ }

const color_t* CoreController::drawContext() { QMutexLocker locker(&m_mutex); + if (m_hwaccel) { + return nullptr; + } return reinterpret_cast<const color_t*>(m_completeBuffer.constData()); }

@@ -340,6 +335,18 @@ connect(this, &CoreController::logPosted, m_log, &LogController::postLog);

} 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<color_t*>(m_activeBuffer->data()), size.width()); + } + if (!m_patched) { mCoreAutoloadPatch(m_threadContext.core); }

@@ -800,6 +807,16 @@ }

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_activeKeys | updateAutofire() | m_inputController->pollEvents(); m_threadContext.core->setKeys(m_threadContext.core, activeKeys);

@@ -823,17 +840,18 @@ }

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<color_t*>(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<color_t*>(m_activeBuffer->data()), screenDimensions().width()); - for (auto& action : m_frameActions) { action(); }
M src/platform/qt/CoreController.hsrc/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);

@@ -69,6 +73,8 @@ bool hasStarted();

mPlatform platform() const; QSize screenDimensions() const; + bool supportsFeature(Feature feature) const { return m_threadContext.core->supportsFeature(m_threadContext.core, static_cast<mCoreFeature>(feature)); } + bool hardwareAccelerated() const { return m_hwaccel; } void loadConfig(ConfigController*);

@@ -154,6 +160,8 @@

void startVideoLog(const QString& path); void endVideoLog(); + void setFramebufferHandle(int fb); + signals: void started(); void paused();

@@ -188,6 +196,7 @@

QByteArray m_buffers[2]; QByteArray* m_activeBuffer; QByteArray m_completeBuffer; + bool m_hwaccel = false; std::unique_ptr<mCacheSet> m_cacheSet; std::unique_ptr<Override> m_override;
M src/platform/qt/Display.cppsrc/platform/qt/Display.cpp

@@ -18,13 +18,15 @@ #endif

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
M src/platform/qt/Display.hsrc/platform/qt/Display.h

@@ -19,6 +19,7 @@

namespace QGBA { class CoreController; +class VideoProxy; class Display : public QWidget { Q_OBJECT

@@ -47,6 +48,8 @@ virtual void startDrawing(std::shared_ptr<CoreController>) = 0;

virtual bool isDrawing() const = 0; virtual bool supportsShaders() const = 0; virtual VideoShader* shaders() = 0; + virtual VideoProxy* videoProxy() { return nullptr; } + virtual int framebufferHandle() { return -1; } signals: void showCursor();
M src/platform/qt/DisplayGL.cppsrc/platform/qt/DisplayGL.cpp

@@ -10,15 +10,18 @@

#include "CoreController.h" #include <QApplication> +#include <QOpenGLContext> +#include <QOpenGLPaintDevice> #include <QResizeEvent> #include <QTimer> +#include <QWindow> #include <mgba/core/core.h> #include <mgba-util/math.h> #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 <epoxy/wgl.h>

@@ -27,22 +30,27 @@ #endif

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 {

@@ -68,12 +76,12 @@ m_painter->setContext(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();

@@ -95,6 +103,11 @@ CoreController::Interrupter interrupter(m_context);

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(); }

@@ -176,33 +189,45 @@ resizePainter();

} 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<const char*>(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<mGLES2Context*>(malloc(sizeof(mGLES2Context))); mGLES2ContextCreate(gl2Backend); m_backend = &gl2Backend->d;

@@ -220,14 +245,14 @@ }

#endif m_backend->swap = [](VideoBackend* v) { PainterGL* painter = static_cast<PainterGL*>(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<WHandle>(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<void*>(&reinterpret_cast<mGLES2Context*>(m_backend)->initialShader); }

@@ -239,7 +264,7 @@ m_backend->filter = false;

m_backend->lockAspectRatio = false; for (int i = 0; i < 2; ++i) { - m_free.append(new uint32_t[256 * 256]); + m_free.append(new uint32_t[1024 * 2048]); } }

@@ -250,11 +275,11 @@ }

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); }

@@ -263,6 +288,7 @@ m_backend->deinit(m_backend);

m_gl->doneCurrent(); free(m_backend); m_backend = nullptr; + delete m_window; } void PainterGL::setContext(std::shared_ptr<CoreController> context) {

@@ -275,13 +301,18 @@ if (!m_context) {

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) {

@@ -297,16 +328,12 @@ }

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) {

@@ -317,18 +344,17 @@ }

} 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<mGLES2Context*>(m_backend), static_cast<mGLES2Shader*>(m_shader.passes), m_shader.nPasses); } #endif - m_gl->doneCurrent(); m_active = true; m_started = true; }

@@ -341,13 +367,13 @@

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); }

@@ -362,7 +388,7 @@ }

} void PainterGL::forceDraw() { - m_painter.begin(m_gl->context()->device()); + m_painter.begin(m_window); performDraw(); m_painter.end(); m_backend->swap(m_backend);

@@ -371,17 +397,14 @@

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() {

@@ -394,11 +417,7 @@ }

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();

@@ -409,14 +428,16 @@ }

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(); }

@@ -428,8 +449,10 @@ m_mutex.unlock();

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(); }

@@ -438,7 +461,9 @@ uint32_t* buffer = 0;

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);

@@ -450,11 +475,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<mGLES2Context*>(m_backend)); mGLES2ShaderFree(&m_shader);

@@ -463,7 +490,9 @@ mGLES2ShaderLoad(&m_shader, dir);

if (m_started) { mGLES2ShaderAttach(reinterpret_cast<mGLES2Context*>(m_backend), static_cast<mGLES2Shader*>(m_shader.passes), m_shader.nPasses); } - m_gl->doneCurrent(); + if (!m_active) { + m_gl->doneCurrent(); + } #endif }

@@ -471,21 +500,40 @@ 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<mGLES2Context*>(m_backend)); mGLES2ShaderFree(&m_shader); } - m_gl->doneCurrent(); + if (!m_active) { + m_gl->doneCurrent(); + } #endif } VideoShader* PainterGL::shaders() { return &m_shader; +} + +int PainterGL::glTex() { +#ifdef BUILD_GLES2 + if (supportsShaders()) { + mGLES2Context* gl2Backend = reinterpret_cast<mGLES2Context*>(m_backend); + return gl2Backend->tex; + } +#endif +#ifdef BUILD_GL + mGLContext* glBackend = reinterpret_cast<mGLContext*>(m_backend); + return glBackend->tex; +#else + return -1; +#endif } #endif
M src/platform/qt/DisplayGL.hsrc/platform/qt/DisplayGL.h

@@ -17,38 +17,33 @@ #endif

#endif #include <QElapsedTimer> -#include <QGLWidget> +#include <QOpenGLContext> #include <QList> #include <QMouseEvent> +#include <QPainter> #include <QQueue> #include <QThread> +#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<CoreController>) 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<CoreController> 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<CoreController>);

@@ -107,6 +103,8 @@ void setShaders(struct VDir*);

void clearShaders(); VideoShader* shaders(); + int glTex(); + private: void performDraw(); void dequeue();

@@ -116,7 +114,9 @@ QList<uint32_t*> m_free;

QQueue<uint32_t*> 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<CoreController> m_context = nullptr;

@@ -126,6 +126,7 @@ VideoBackend* m_backend = nullptr;

QSize m_size; MessagePainter* m_messagePainter = nullptr; QElapsedTimer m_delayTimer; + VideoProxy* m_videoProxy; }; }
M src/platform/qt/SettingsView.cppsrc/platform/qt/SettingsView.cpp

@@ -401,6 +401,7 @@ saveSetting("logToFile", m_ui.logToFile);

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");

@@ -464,6 +465,14 @@ if (language != m_controller->getQtOption("language").toLocale() && !(language.bcp47Name() == QLocale::system().bcp47Name() && m_controller->getQtOption("language").isNull())) {

m_controller->setQtOption("language", language.bcp47Name()); 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());

@@ -541,6 +550,8 @@ loadSetting("logToFile", m_ui.logToFile);

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());

@@ -604,6 +615,9 @@ int index = s_gbModelList.indexOf(model);

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) {

@@ -660,9 +674,9 @@ QString option = loadSetting(key);

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) {
M src/platform/qt/SettingsView.hsrc/platform/qt/SettingsView.h

@@ -40,6 +40,7 @@ void audioDriverChanged();

void displayDriverChanged(); void cameraDriverChanged(); void cameraChanged(const QByteArray&); + void videoRendererChanged(); void pathsChanged(); void languageChanged(); void libraryCleared();

@@ -76,7 +77,7 @@ void loadSetting(const char* key, QComboBox*);

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); };
M src/platform/qt/SettingsView.uisrc/platform/qt/SettingsView.ui

@@ -6,8 +6,8 @@ <property name="geometry">

<rect> <x>0</x> <y>0</y> - <width>588</width> - <height>488</height> + <width>790</width> + <height>686</height> </rect> </property> <property name="sizePolicy">

@@ -33,14 +33,14 @@ </item>

<item row="1" column="0"> <widget class="QListWidget" name="tabs"> <property name="sizePolicy"> - <sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding"> + <sizepolicy hsizetype="Fixed" vsizetype="Expanding"> <horstretch>0</horstretch> <verstretch>0</verstretch> </sizepolicy> </property> <property name="maximumSize"> <size> - <width>140</width> + <width>200</width> <height>16777215</height> </size> </property>

@@ -60,6 +60,11 @@ </item>

<item> <property name="text"> <string>Emulation</string> + </property> + </item> + <item> + <property name="text"> + <string>Enhancements</string> </property> </item> <item>

@@ -844,6 +849,70 @@ </widget>

</item> </layout> </widget> + <widget class="QWidget" name="enhancements"> + <layout class="QFormLayout" name="formLayout_6"> + <item row="0" column="0"> + <widget class="QLabel" name="label_36"> + <property name="text"> + <string>Video renderer:</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QComboBox" name="hwaccelVideo"> + <item> + <property name="text"> + <string>Software</string> + </property> + </item> + <item> + <property name="text"> + <string>OpenGL</string> + </property> + </item> + </widget> + </item> + <item row="2" column="0" colspan="2"> + <widget class="QGroupBox" name="oglEnahance"> + <property name="title"> + <string>OpenGL enhancements</string> + </property> + <layout class="QFormLayout" name="formLayout_7"> + <item row="0" column="0"> + <widget class="QLabel" name="label_37"> + <property name="text"> + <string>High-resolution scale:</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QSpinBox" name="videoScale"> + <property name="suffix"> + <string>×</string> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>13</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="1" column="1"> + <widget class="QCheckBox" name="audioHle"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>XQ GBA audio (experimental)</string> + </property> + </widget> + </item> + </layout> + </widget> <widget class="QWidget" name="bios"> <layout class="QFormLayout" name="formLayout_5"> <item row="0" column="0">

@@ -1221,11 +1290,11 @@ <widget class="QWidget" name="logging">

<layout class="QVBoxLayout" name="verticalLayout"> <item> <widget class="QTableView" name="loggingView"> - <attribute name="horizontalHeaderDefaultSectionSize"> - <number>77</number> - </attribute> <attribute name="horizontalHeaderMinimumSectionSize"> <number>0</number> + </attribute> + <attribute name="horizontalHeaderDefaultSectionSize"> + <number>77</number> </attribute> </widget> </item>
A 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<void, int>::func<&VideoProxy::wake>; + + m_logger.d.writeData = &callback<bool, const void*, size_t>::func<&VideoProxy::writeData>; + m_logger.d.readData = &callback<bool, void*, size_t, bool>::func<&VideoProxy::readData>; + m_logger.d.postEvent = &callback<void, enum mVideoLoggerEvent>::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<enum mVideoLoggerEvent>(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(); + } +}
A 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 <QMutex> +#include <QObject> +#include <QWaitCondition> + +#include <mgba/feature/video-logger.h> +#include <mgba-util/ring-fifo.h> + +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<typename T, typename... A> struct callback { + using type = T (VideoProxy::*)(A...); + + template<type F> static T func(mVideoLogger* logger, A... args) { + VideoProxy* proxy = reinterpret_cast<Logger*>(logger)->p; + return (proxy->*F)(args...); + } + }; + + template<void (VideoProxy::*F)()> static void cbind(mVideoLogger* logger) { callback<void>::func<F>(logger); } + + struct Logger { + mVideoLogger d; + VideoProxy* p; + } m_logger = {{}, this}; + + RingFIFO m_dirtyQueue; + QMutex m_mutex; + QWaitCondition m_toThreadCond; + QWaitCondition m_fromThreadCond; +}; + +}
M src/platform/qt/Window.cppsrc/platform/qt/Window.cpp

@@ -52,6 +52,7 @@ #include "SettingsView.h"

#include "ShaderSelector.h" #include "ShortcutController.h" #include "TileView.h" +#include "VideoProxy.h" #include "VideoView.h" #ifdef USE_DISCORD_RPC

@@ -459,6 +460,7 @@ connect(settingsWindow, &SettingsView::displayDriverChanged, this, &Window::reloadDisplayDriver);

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

@@ -716,9 +718,6 @@ m_config->updateOption("lockAspectRatio");

if (m_savedScale > 0) { resizeFrame(size * m_savedScale); } - if (!m_display) { - reloadDisplayDriver(); - } attachWidget(m_display.get()); m_display->setMinimumSize(size); setFocus();

@@ -853,6 +852,10 @@ }

void Window::reloadDisplayDriver() { if (m_controller) { + if (m_controller->hardwareAccelerated()) { + mustRestart(); + return; + } m_display->stopDrawing(); detachWidget(m_display.get()); }

@@ -1711,6 +1714,21 @@ }

if (!fname.isEmpty()) { setWindowFilePath(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<CoreController>(controller);
M src/platform/qt/main.cppsrc/platform/qt/main.cpp

@@ -17,6 +17,10 @@

#include <QLibraryInfo> #include <QTranslator> +#ifdef BUILD_GLES2 +#include <QSurfaceFormat> +#endif + #ifdef QT_STATIC #include <QtPlugin> #ifdef Q_OS_WIN

@@ -64,6 +68,12 @@ }

QApplication::setApplicationName(projectName); QApplication::setApplicationVersion(projectVersion); + +#ifdef BUILD_GLES2 + QSurfaceFormat format; + format.setVersion(3, 0); + QSurfaceFormat::setDefaultFormat(format); +#endif GBAApp application(argc, argv, &configController);
M src/platform/sdl/CMakeLists.txtsrc/platform/sdl/CMakeLists.txt

@@ -77,12 +77,11 @@ list(APPEND MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/pandora-sdl.c)

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)
M src/platform/sdl/gles2-sdl.csrc/platform/sdl/gles2-sdl.c

@@ -35,7 +35,9 @@ mSDLGLCommonInit(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);
M src/platform/sdl/main.csrc/platform/sdl/main.c

@@ -90,6 +90,12 @@ printf("Could not run game. Are you sure the file exists and is a compatible game?\n");

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);

@@ -105,11 +111,6 @@ renderer.ratio = 1;

} 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))) {
M tools/sanitize-deb.shtools/sanitize-deb.sh

@@ -31,6 +31,7 @@ PKG=$BINARY-qt

rmdep libav rmdep libedit rmdep libelf + rmdep libgl rmdep libpng rmdep libzip rmdep libmagickwand

@@ -45,6 +46,7 @@ PKG=$BINARY-sdl

rmdep libav rmdep libedit rmdep libelf + rmdep libgl rmdep libpng rmdep qt rmdep libzip